From a329baaa5495c51aafe2f2214f15b5992e17c4a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 23 Dec 2025 03:17:16 -0800 Subject: [PATCH 001/110] =?UTF-8?q?=F0=9F=91=B7=20Update=20secrets=20check?= =?UTF-8?q?=20(#14592)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pre-commit.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index e628ce541d..b397912e67 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -7,7 +7,8 @@ on: - synchronize env: - IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + # Forks and Dependabot don't have access to secrets + HAS_SECRETS: ${{ secrets.PRE_COMMIT != '' }} jobs: pre-commit: @@ -19,7 +20,7 @@ jobs: run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v5 name: Checkout PR for own repo - if: env.IS_FORK == 'false' + if: env.HAS_SECRETS == 'true' with: # To be able to commit it needs to fetch the head of the branch, not the # merge commit @@ -31,7 +32,7 @@ jobs: # pre-commit lite ci needs the default checkout configs to work - uses: actions/checkout@v5 name: Checkout PR for fork - if: env.IS_FORK == 'true' + if: env.HAS_SECRETS == 'false' with: # To be able to commit it needs the head branch of the PR, the remote one ref: ${{ github.event.pull_request.head.sha }} @@ -56,7 +57,7 @@ jobs: run: uvx prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure continue-on-error: true - name: Commit and push changes - if: env.IS_FORK == 'false' + if: env.HAS_SECRETS == 'true' run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" @@ -68,7 +69,7 @@ jobs: git push fi - uses: pre-commit-ci/lite-action@v1.1.0 - if: env.IS_FORK == 'true' + if: env.HAS_SECRETS == 'false' with: msg: ๐ŸŽจ Auto format - name: Error out on pre-commit errors From e55f223b4695b80ad164a150489b3c3a26a35e4d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 23 Dec 2025 11:17:37 +0000 Subject: [PATCH 002/110] =?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 a08ac1cd83..09e6341aa9 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* ๐Ÿ‘ท Update secrets check. PR [#14592](https://github.com/fastapi/fastapi/pull/14592) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Run CodSpeed tests in parallel to other tests to speed up CI. PR [#14586](https://github.com/fastapi/fastapi/pull/14586) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Update scripts and pre-commit to autofix files. PR [#14585](https://github.com/fastapi/fastapi/pull/14585) by [@tiangolo](https://github.com/tiangolo). From 7203e860b3adacc09dfd2171ea1b3ed1a9589fa9 Mon Sep 17 00:00:00 2001 From: Nils-Hero Lindemann Date: Wed, 24 Dec 2025 11:28:19 +0100 Subject: [PATCH 003/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20de=20(update-outdated)=20(#14581)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Sync with #14575 (Drop support for Pydantic v1) * Add a word and fix a typo Found while syncing. --- .../path-operation-advanced-configuration.md | 36 +----------------- docs/de/docs/advanced/settings.md | 38 ------------------- ...migrate-from-pydantic-v1-to-pydantic-v2.md | 14 ++++--- .../docs/how-to/separate-openapi-schemas.md | 4 +- docs/de/docs/tutorial/body-updates.md | 16 -------- docs/de/docs/tutorial/body.md | 8 ---- docs/de/docs/tutorial/extra-models.md | 29 +++++--------- .../tutorial/query-params-str-validations.md | 14 ------- docs/de/docs/tutorial/response-model.md | 14 ------- docs/de/docs/tutorial/schema-extra-example.md | 24 +----------- 10 files changed, 21 insertions(+), 176 deletions(-) diff --git a/docs/de/docs/advanced/path-operation-advanced-configuration.md b/docs/de/docs/advanced/path-operation-advanced-configuration.md index e722526002..c7ac1cf61b 100644 --- a/docs/de/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/de/docs/advanced/path-operation-advanced-configuration.md @@ -48,7 +48,7 @@ Sie kรถnnen die verwendeten Zeilen aus dem Docstring einer *Pfadoperation-Funkti Das Hinzufรผgen eines `\f` (ein maskiertes โ€žForm Feedโ€œ-Zeichen) fรผhrt dazu, dass **FastAPI** die fรผr OpenAPI verwendete Ausgabe an dieser Stelle abschneidet. -Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (z. B. Sphinx) kรถnnen den Rest verwenden. +Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (wie z. B. Sphinx) kรถnnen den Rest verwenden. {* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} @@ -153,48 +153,16 @@ 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: -//// tab | Pydantic v2 - {* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} - -//// - -/// info | Info - -In Pydantic Version 1 hieรŸ die Methode zum Abrufen des JSON-Schemas fรผr ein Modell `Item.schema()`, in Pydantic Version 2 heiรŸt die Methode `Item.model_json_schema()`. - -/// - 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. -Dann verwenden wir den Request direkt und extrahieren den Body als `bytes`. Das bedeutet, dass FastAPI nicht einmal versucht, den Request-Payload als JSON zu parsen. +Dann verwenden wir den Request direkt und extrahieren den Body als `bytes`. Das bedeutet, dass FastAPI nicht einmal versucht, die Request-Payload als JSON zu parsen. Und dann parsen wir in unserem Code diesen YAML-Inhalt direkt und verwenden dann wieder dasselbe Pydantic-Modell, um den YAML-Inhalt zu validieren: -//// tab | Pydantic v2 - {* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} - -//// - -/// info | Info - -In Pydantic Version 1 war die Methode zum Parsen und Validieren eines Objekts `Item.parse_obj()`, in Pydantic Version 2 heiรŸt die Methode `Item.model_validate()`. - -/// - /// tip | Tipp Hier verwenden wir dasselbe Pydantic-Modell wieder. diff --git a/docs/de/docs/advanced/settings.md b/docs/de/docs/advanced/settings.md index ebacf76f42..ea4540e10e 100644 --- a/docs/de/docs/advanced/settings.md +++ b/docs/de/docs/advanced/settings.md @@ -60,24 +60,8 @@ 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()`. -//// tab | Pydantic v2 - {* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} -//// - -//// tab | Pydantic v1 - -/// info | Info - -In Pydantic v1 wรผrden Sie `BaseSettings` direkt von `pydantic` statt von `pydantic_settings` importieren. - -/// - -{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *} - -//// - /// tip | Tipp Fรผr ein schnelles Copy-and-paste verwenden Sie nicht dieses Beispiel, sondern das letzte unten. @@ -215,8 +199,6 @@ APP_NAME="ChimichangApp" Und dann aktualisieren Sie Ihre `config.py` mit: -//// tab | Pydantic v2 - {* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip | Tipp @@ -225,26 +207,6 @@ Das Attribut `model_config` wird nur fรผr die Pydantic-Konfiguration verwendet. /// -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} - -/// tip | Tipp - -Die Klasse `Config` wird nur fรผr die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter Pydantic Model Config. - -/// - -//// - -/// info | Info - -In Pydantic Version 1 erfolgte die Konfiguration in einer internen Klasse `Config`, in Pydantic Version 2 erfolgt sie in einem Attribut `model_config`. Dieses Attribut akzeptiert ein `dict`. Um automatische Codevervollstรคndigung und Inline-Fehlerberichte zu erhalten, kรถnnen Sie `SettingsConfigDict` importieren und verwenden, um dieses `dict` zu definieren. - -/// - Hier definieren wir die Konfiguration `env_file` innerhalb Ihrer Pydantic-`Settings`-Klasse und setzen den Wert auf den Dateinamen mit der dotenv-Datei, die wir verwenden mรถchten. ### Die `Settings` nur einmal laden mittels `lru_cache` { #creating-the-settings-only-once-with-lru-cache } diff --git a/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md index 7f60492ee9..a8eff3b2b0 100644 --- a/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md +++ b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -2,21 +2,23 @@ Wenn Sie eine รคltere FastAPI-App haben, nutzen Sie mรถglicherweise Pydantic Version 1. -FastAPI unterstรผtzt seit Version 0.100.0 sowohl Pydantic v1 als auch v2. +FastAPI Version 0.100.0 unterstรผtzte sowohl Pydantic v1 als auch v2. Es verwendete, was auch immer Sie installiert hatten. -Wenn Sie Pydantic v2 installiert hatten, wurde dieses verwendet. Wenn stattdessen Pydantic v1 installiert war, wurde jenes verwendet. +FastAPI Version 0.119.0 fรผhrte eine teilweise Unterstรผtzung fรผr Pydantic v1 innerhalb von Pydantic v2 (als `pydantic.v1`) ein, um die Migration zu v2 zu erleichtern. -Pydantic v1 ist jetzt deprecatet und die Unterstรผtzung dafรผr wird in den nรคchsten Versionen von FastAPI entfernt, Sie sollten also zu **Pydantic v2 migrieren**. Auf diese Weise erhalten Sie die neuesten Features, Verbesserungen und Fixes. +FastAPI 0.126.0 entfernte die Unterstรผtzung fรผr Pydantic v1, wรคhrend `pydantic.v1` noch eine Weile unterstรผtzt wurde. /// warning | Achtung -AuรŸerdem hat das Pydantic-Team die Unterstรผtzung fรผr Pydantic v1 in den neuesten Python-Versionen eingestellt, beginnend mit **Python 3.14**. +Das Pydantic-Team hat die Unterstรผtzung fรผr Pydantic v1 in den neuesten Python-Versionen eingestellt, beginnend mit **Python 3.14**. + +Dies schlieรŸt `pydantic.v1` ein, das unter Python 3.14 und hรถher nicht mehr unterstรผtzt wird. Wenn Sie die neuesten Features von Python nutzen mรถchten, mรผssen Sie sicherstellen, dass Sie Pydantic v2 verwenden. /// -Wenn Sie eine รคltere FastAPI-App mit Pydantic v1 haben, zeige ich Ihnen hier, wie Sie sie zu Pydantic v2 migrieren, und die **neuen Features in FastAPI 0.119.0**, die Ihnen bei einer schrittweisen Migration helfen. +Wenn Sie eine รคltere FastAPI-App mit Pydantic v1 haben, zeige ich Ihnen hier, wie Sie sie zu Pydantic v2 migrieren, und die **Features in FastAPI 0.119.0**, die Ihnen bei einer schrittweisen Migration helfen. ## Offizieller Leitfaden { #official-guide } @@ -44,7 +46,7 @@ Danach kรถnnen Sie die Tests ausfรผhren und prรผfen, ob alles funktioniert. Fall ## Pydantic v1 in v2 { #pydantic-v1-in-v2 } -Pydantic v2 enthรคlt alles aus Pydantic v1 als Untermodul `pydantic.v1`. +Pydantic v2 enthรคlt alles aus Pydantic v1 als Untermodul `pydantic.v1`. Dies wird aber in Versionen oberhalb von Python 3.13 nicht mehr unterstรผtzt. Das bedeutet, Sie kรถnnen die neueste Version von Pydantic v2 installieren und die alten Pydanticโ€‘v1โ€‘Komponenten aus diesem Untermodul importieren und verwenden, als hรคtten Sie das alte Pydantic v1 installiert. diff --git a/docs/de/docs/how-to/separate-openapi-schemas.md b/docs/de/docs/how-to/separate-openapi-schemas.md index 31653590b5..16f9c8a144 100644 --- a/docs/de/docs/how-to/separate-openapi-schemas.md +++ b/docs/de/docs/how-to/separate-openapi-schemas.md @@ -1,6 +1,6 @@ # Separate OpenAPI-Schemas fรผr Eingabe und Ausgabe oder nicht { #separate-openapi-schemas-for-input-and-output-or-not } -Bei Verwendung von **Pydantic v2** ist die generierte OpenAPI etwas genauer und **korrekter** als zuvor. ๐Ÿ˜Ž +Seit der Verรถffentlichung von **Pydantic v2** ist die generierte OpenAPI etwas genauer und **korrekter** als zuvor. ๐Ÿ˜Ž Tatsรคchlich gibt es in einigen Fรคllen sogar **zwei JSON-Schemas** in OpenAPI fรผr dasselbe Pydantic-Modell, fรผr Eingabe und Ausgabe, je nachdem, ob sie **Defaultwerte** haben. @@ -100,5 +100,3 @@ Und jetzt wird es ein einziges Schema fรผr die Eingabe und Ausgabe des Modells g
- -Dies ist das gleiche Verhalten wie in Pydantic v1. ๐Ÿค“ diff --git a/docs/de/docs/tutorial/body-updates.md b/docs/de/docs/tutorial/body-updates.md index aa62199feb..d260998e91 100644 --- a/docs/de/docs/tutorial/body-updates.md +++ b/docs/de/docs/tutorial/body-updates.md @@ -50,14 +50,6 @@ Wenn Sie Teil-Aktualisierungen entgegennehmen, ist der `exclude_unset`-Parameter Wie in `item.model_dump(exclude_unset=True)`. -/// info | Info - -In Pydantic v1 hieรŸ diese Methode `.dict()`, in Pydantic v2 wurde sie deprecatet (aber immer noch unterstรผtzt) und in `.model_dump()` umbenannt. - -Die Beispiele hier verwenden `.dict()` fรผr die Kompatibilitรคt mit Pydantic v1, Sie sollten jedoch stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden kรถnnen. - -/// - Das wird ein `dict` erstellen, mit nur den Daten, die gesetzt wurden, als das `item`-Modell erstellt wurde, Defaultwerte ausgeschlossen. Sie kรถnnen das verwenden, um ein `dict` zu erstellen, das nur die (im Request) gesendeten Daten enthรคlt, ohne Defaultwerte: @@ -68,14 +60,6 @@ Sie kรถnnen das verwenden, um ein `dict` zu erstellen, das nur die (im deprecatet (aber immer noch unterstรผtzt) und in `.model_copy()` umbenannt. - -Die Beispiele hier verwenden `.copy()` fรผr die Kompatibilitรคt mit Pydantic v1, Sie sollten jedoch stattdessen `.model_copy()` verwenden, wenn Sie Pydantic v2 verwenden kรถnnen. - -/// - Wie in `stored_item_model.model_copy(update=update_data)`: {* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} diff --git a/docs/de/docs/tutorial/body.md b/docs/de/docs/tutorial/body.md index 0ad95b0386..cdf3122f2f 100644 --- a/docs/de/docs/tutorial/body.md +++ b/docs/de/docs/tutorial/body.md @@ -127,14 +127,6 @@ Innerhalb der Funktion kรถnnen Sie alle Attribute des Modellobjekts direkt verwe {* ../../docs_src/body/tutorial002_py310.py *} -/// info | Info - -In Pydantic v1 hieรŸ die Methode `.dict()`, sie wurde in Pydantic v2 deprecatet (aber weiterhin unterstรผtzt) und in `.model_dump()` umbenannt. - -Die Beispiele hier verwenden `.dict()` zur Kompatibilitรคt mit Pydantic v1, aber Sie sollten stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 nutzen kรถnnen. - -/// - ## Requestbody- + Pfad-Parameter { #request-body-path-parameters } Sie kรถnnen Pfad-Parameter und den Requestbody gleichzeitig deklarieren. diff --git a/docs/de/docs/tutorial/extra-models.md b/docs/de/docs/tutorial/extra-models.md index 967e8535b8..889fdb9a3b 100644 --- a/docs/de/docs/tutorial/extra-models.md +++ b/docs/de/docs/tutorial/extra-models.md @@ -22,21 +22,13 @@ Hier ist eine allgemeine Idee, wie die Modelle mit ihren Passwortfeldern aussehe {* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} -/// info | Info +### รœber `**user_in.model_dump()` { #about-user-in-model-dump } -In Pydantic v1 hieรŸ die Methode `.dict()`, in Pydantic v2 wurde sie deprecatet (aber weiterhin unterstรผtzt) und in `.model_dump()` umbenannt. - -Die Beispiele hier verwenden `.dict()` fรผr die Kompatibilitรคt mit Pydantic v1, aber Sie sollten `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden kรถnnen. - -/// - -### รœber `**user_in.dict()` { #about-user-in-dict } - -#### Die `.dict()`-Methode von Pydantic { #pydantics-dict } +#### Pydantics `.model_dump()` { #pydantics-model-dump } `user_in` ist ein Pydantic-Modell der Klasse `UserIn`. -Pydantic-Modelle haben eine `.dict()`-Methode, die ein `dict` mit den Daten des Modells zurรผckgibt. +Pydantic-Modelle haben eine `.model_dump()`-Methode, die ein `dict` mit den Daten des Modells zurรผckgibt. Wenn wir also ein Pydantic-Objekt `user_in` erstellen, etwa so: @@ -47,7 +39,7 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com und dann aufrufen: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() ``` haben wir jetzt ein `dict` mit den Daten in der Variablen `user_dict` (es ist ein `dict` statt eines Pydantic-Modellobjekts). @@ -103,20 +95,20 @@ UserInDB( #### Ein Pydantic-Modell aus dem Inhalt eines anderen { #a-pydantic-model-from-the-contents-of-another } -Da wir im obigen Beispiel `user_dict` von `user_in.dict()` bekommen haben, wรคre dieser Code: +Da wir im obigen Beispiel `user_dict` von `user_in.model_dump()` bekommen haben, wรคre dieser Code: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() UserInDB(**user_dict) ``` gleichwertig zu: ```Python -UserInDB(**user_in.dict()) +UserInDB(**user_in.model_dump()) ``` -... weil `user_in.dict()` ein `dict` ist, und dann lassen wir Python es โ€žentpackenโ€œ, indem wir es an `UserInDB` mit vorangestelltem `**` รผbergeben. +... weil `user_in.model_dump()` ein `dict` ist, und dann lassen wir Python es โ€žentpackenโ€œ, indem wir es an `UserInDB` mit vorangestelltem `**` รผbergeben. Auf diese Weise erhalten wir ein Pydantic-Modell aus den Daten eines anderen Pydantic-Modells. @@ -125,7 +117,7 @@ Auf diese Weise erhalten wir ein Pydantic-Modell aus den Daten eines anderen Pyd Und dann fรผgen wir das zusรคtzliche Schlรผsselwort-Argument `hashed_password=hashed_password` hinzu, wie in: ```Python -UserInDB(**user_in.dict(), hashed_password=hashed_password) +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) ``` ... was so ist wie: @@ -180,7 +172,6 @@ Wenn Sie eine deprecatet (aber immer noch unterstรผtzt) und in `.model_dump()` umbenannt. - -Die Beispiele hier verwenden `.dict()` fรผr die Kompatibilitรคt mit Pydantic v1, Sie sollten jedoch stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden kรถnnen. - -/// - -/// info | Info - -FastAPI verwendet `.dict()` von Pydantic Modellen, mit dessen `exclude_unset`-Parameter, um das zu erreichen. - -/// - -/// info | Info - Sie kรถnnen auch: * `response_model_exclude_defaults=True` diff --git a/docs/de/docs/tutorial/schema-extra-example.md b/docs/de/docs/tutorial/schema-extra-example.md index e2ffed292e..07fe8c5d92 100644 --- a/docs/de/docs/tutorial/schema-extra-example.md +++ b/docs/de/docs/tutorial/schema-extra-example.md @@ -8,36 +8,14 @@ Hier sind mehrere Mรถglichkeiten, das zu tun. Sie kรถnnen `examples` (โ€žBeispieleโ€œ) fรผr ein Pydantic-Modell deklarieren, welche dem generierten JSON-Schema hinzugefรผgt werden. -//// tab | Pydantic v2 - {* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *} - -//// - Diese zusรคtzlichen Informationen werden unverรคndert zum fรผr dieses Modell ausgegebenen **JSON-Schema** hinzugefรผgt und in der API-Dokumentation verwendet. -//// tab | Pydantic v2 - -In Pydantic Version 2 wรผrden Sie das Attribut `model_config` verwenden, das ein `dict` akzeptiert, wie beschrieben in Pydantic-Dokumentation: Configuration. +Sie kรถnnen das Attribut `model_config` verwenden, das ein `dict` akzeptiert, wie beschrieben in Pydantic-Dokumentation: Configuration. Sie kรถnnen `json_schema_extra` setzen, mit einem `dict`, das alle zusรคtzlichen Daten enthรคlt, die im generierten JSON-Schema angezeigt werden sollen, einschlieรŸlich `examples`. -//// - -//// tab | Pydantic v1 - -In Pydantic Version 1 wรผrden Sie eine interne Klasse `Config` und `schema_extra` verwenden, wie beschrieben in Pydantic-Dokumentation: Schema customization. - -Sie kรถnnen `schema_extra` setzen, mit einem `dict`, das alle zusรคtzlichen Daten enthรคlt, die im generierten JSON-Schema angezeigt werden sollen, einschlieรŸlich `examples`. - -//// - /// tip | Tipp Mit derselben Technik kรถnnen Sie das JSON-Schema erweitern und Ihre eigenen benutzerdefinierten Zusatzinformationen hinzufรผgen. From 2b212ddd7604891d42e2ffbafe764c334230bdb7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 24 Dec 2025 10:28:45 +0000 Subject: [PATCH 004/110] =?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 09e6341aa9..30d5c44039 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 de (update-outdated). PR [#14581](https://github.com/fastapi/fastapi/pull/14581) by [@nilslindemann](https://github.com/nilslindemann). + ### Internal * ๐Ÿ‘ท Update secrets check. PR [#14592](https://github.com/fastapi/fastapi/pull/14592) by [@tiangolo](https://github.com/tiangolo). From c264467efec71548d4feeecbfb29da78cceb1be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 25 Dec 2025 03:01:37 -0800 Subject: [PATCH 005/110] =?UTF-8?q?=F0=9F=93=9D=20Add=20documentary=20to?= =?UTF-8?q?=20website=20(#14600)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++++ docs/en/docs/img/fastapi-documentary.jpg | Bin 0 -> 191709 bytes docs/en/docs/index.md | 6 ++++++ 3 files changed, 12 insertions(+) create mode 100644 docs/en/docs/img/fastapi-documentary.jpg diff --git a/README.md b/README.md index a42cedae69..1057b86942 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,12 @@ The key features are: --- +## FastAPI mini documentary + +There's a FastAPI mini documentary released at the end of 2025, you can watch it online: + +FastAPI Mini Documentary + ## **Typer**, the FastAPI of CLIs diff --git a/docs/en/docs/img/fastapi-documentary.jpg b/docs/en/docs/img/fastapi-documentary.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3ddbfdb389794fd3b2d87e086bb6e73145ed3d1e GIT binary patch literal 191709 zcmeFad0bQ1);E3%7E5#tyQd=(AsJhtMsag1J;O& z5Ya}(2ni4^t=3Srt*sL{v{WX&mJTXfXKU+to!_=l%Wh-jBUECOJ80 zpS{;!>$|^eIGbmupPi+;$m0^?C?1cZcf+{3d$@P( z=-#tyx6X7QpMHJ&`1J1WJ7Ca2U;kJ8_wFqkDt_7hR*?!8?#f9HV<;3%+_?|o`Pu{b`R6ppafJgqL*#CHZCuf1M zgNv(MM>wIf3&rO-Iq{vH1OjJgIC}y7PC0uDy84C22)ijKckrJ@hw0aDb?HB9=K(KO z-Jb)(Gd@`C>eju7cTb;yz=0xhkYq^2(8ys?v7=>iW8xF!>a?-rULXHP`joe)PMfaL zX3n0IHTT1L^Rt&MU6!-_;}wSd0^_>E^&37h6_;47rDf%|ZC~uNSAMyB&)%;N9y(lA zedOq|-L?y_wN7m!9yGuk8)y$1^+SwYd0=W z7#H8!*~z&Bj*G|t5MEB6&H}$sVb>UChsm?L`G@IU=uvC8?mXbyKV0>vSH=f*Zruk+ z&ISC1Ln9;m-wkZ>|EG~T2G%mJCd%E32b1UINsXi)JT-`=fjTP8BOoDH{4i|}rIgYC z)i0GkPeYj&hLl~Ade;4yk5wrXIoDFc7>g}**+}_notvomzx$;K^Psw=R?|;J{HXu( zvG!OKEAQR;Txp%O^We3)>i_c5x=oDs2%xF6XBT;h|DK!iiHxF^|J|>_eRGVF{SGA; z>-xUc-@Ej`e6;j8**cC4K zc&P&~b>O8Aywri0I`C2lUh2S09eAk&FLmIh4!qQXmpbrL2VUyHOC5Nr121*pr4GE* zftNb)QU_k@z)Kx?sRJ){;H3_{)Pa{e@KOg}>cC4Kc&P&~b>ROQ9VqEgpR3@>=+-au zG+kH2o29Et^%Lacxuysczgd}RkiQyIY!Ff8wf>!KW#v8*Ph@Rc;L$5}tJ%4Dz4ne| z#t0*2RNs3>?Rr?Z`^lD5>-JBjpL}=yseNO@jWyLPbxq=*J6HDVTp7|!_S)ooC6n)& zg>@;X7nH;Qd3N=e{W9IwNxecipPm8rL$T)uUp zqI<{O;w`^#NguxJTKu3e!=l0k8aT?EWmG@jb!`lMBfmeYe=x^=wPDfv1p7mUlcvmb zb#+1CY8c*xk9q2%J33&bG{V77Am$+`Cl-I`$FbF1%^OvwgW zSep)2ww}5iobKeY5YIM9g1B82B(T zGw2!gXFYn!MrukXmCb|`9tnC0@PvDA$;B&|RoA67iJHU{21OVa*2Nu2IY)kpa2wR4 zr)=aT3-im|9;a57oZ5WwyvEVoYnl>G@i~zEj*(w!9xVDw3oRwydQ^Ah-8SQ#yXE~K zSC##I8#Sf-s8(n3%-j2Nwhu#4p+VARJ-U*8g*QJcvfxae|iOW-hGKk{>Fnk z`=f<(C=l+5lH2t4kJi-dddgnw@MKGUZohkrSydeJ&6!Z7#D zqTl1-1;()1p8bppidgg3?@uKZDCC&WZ#|=Y9N(+POnGdJ02$!qeQQ~mvHtz&F_+-F zExPEDC(g%p*nR04^$)X9nY#bp<7d>vJ2U#8+478<2qP1FM_SB$165*W)K*n~tZL+> z`(~HRt+mL*dju-RgnqGcor#%usI6Oa84U+i&Dic4wn3e(U<*os{}TG_w`C8|;%4;y z?)a*be?0&B{u5cR=pz?JIQBags<#+pSfUzfkd;T_! zqFA_PL&^IMFjIJ!hYK@S;SV}6WPG1-zuyK#m!JO_85(zH)&m1fANgU~0x(FhG_YII zTdj`&%L`ug!=ELM3#UBRx~)+@So2rQZI~I`edd93_nEt-k1!4~H$9-GC8(DKHNT5; z@dvqz_s;^|71nXnGwGv+i=JdtQ|arS-?|M7iIa~#-+LwQL*!@$MEQ+}^?(0%ONCQUS;AMQ`8N?&=}8Y21Zuot))nD# z9V+0$vpx2Xv`X&c5#CMa(AS{IaSb;R;8K3R`OuIwTaWVUytM=no>)iDsOP45mmPVKGkGixBYp!@5T7c$0W;=X60E5E8qs zl^tw`2QS@Q7H6ZlKh6SR;uEb~Uf3dpa^;dMQUM4{bz@*9e7&*iN?gN}CYS_EKY3C= zIp{VlnXTW;u3KJNZSQnQV{Kk7>gXyPOwU5ZpJyiDXoYXF(S4Kpn{T6PokV5tO6o)A zgSbT~^5`p9JD9=XZ1UVWUOm-CoTa%&(v%U`!-#&FIQwK}0^5k(>NH%fVU}QSDQ^G1 zsmt0Tkz+w+8hAzx{w=RmI2+WU{ppVHA*q=ZXVfWUk6oCReBj4W5X9M~4Tise(@DdR z693$*-a!3!%kLRg6#9%hipn*zbCgukB0ZVT|M&A z1@-MX4R6czB~Rxac}D%+Fycgq$A7JP`hb31XQHP6boRyV=z89pH}X>MqWecnuK&5{ zg!eP*=GxSno@U|vF3%|D>zK8j9Sx1+6^$NA?xwsD+p0S&sS_>T>*wghf}W*Lj(%_* z4w8%bBVTlOzJYol?sY5m(FLaU0_gp9>^&aEp^#3ANFR@u`0L$?bd*Fo{{0?Y*mD19 z*#)IZ7=Cp5)A<8IH01XLl*U|5jc+f~Wo(qRGXO>Tn#4|>fNOsTNs9me@uExli_Z9O zmWA}XH;?(eX4fGYCth@Qri9n;r*j}j(&C5{OjpxBDQR>3Eyo3q>o4m1bF-u5I_|=!qQ?LhQhPhwGcT}JN%N+{9{`^q%A0BN{ zA*KbdKurxbh8sYOqNt|j);ce8%r-aE#6>oA2fqj*urJhmZ^;E*W6X znR&*aABsAzeb9xqg+y-DFbfl2Y(*msU|!}H^TOK@Gff~9?fBYnx^B_;#K2V3@^w91 z?58hlQZT23P<0vmeGB@I_APFlrUPwkZ=@+T!4#1(aK&$BXmY6W;yZiIE>FSoi4oyd zsLz%f+MS4Zf-Pys8oaxR~ zt~+n-`WpwJ1Wy;3&ODtrh5&QT`_Q5OC}*NN70;+kO9=peB)Q|aT2Hxqd`}@l-q8z@*Zup~I z`c2&p>g&1a44*+DcE0Ha`9D9MCxbq_E!57IdOmS{9dQhrVy;afm<3k=68)2Ik!eX* zE3HVp_s$|lnI9B`V!@YlWaVm6ESv%Muy`R65HTTG=>S59kk%j23ksn>C_}db$$Ow% z-(xROkT3!meUT@u1boY{aaX}0$ykY=GJS)U1mL(Qk&WZtWO*bg9lV>UI2rPSW^6JM z2Ilz$??@ytg4~RV3y0k17l$AdOcMYQ8EIl))-b|{1xt_W5jN5>8HtWby7Nt~KOA3l z;v1M5>m%sMqbeNY0vhLzIb3r6%QLrc!jyvbzzX3whjePhUodfCZ1+KFpu0@(2mTbi z=u%U^M`zRa;_`I*2J=mM@3prc+*=N|JcvO0YpLWLyKs(HZVM0qk}q_f7Pu&*zUH#bQ4I2ICx!wEpxU=p z_)9JRB7e1FV#(~6ro%1r7M5!IkGy%hbYm~*6CSt!s`a4KBD!z6eGQ}|9&w249lydyYj!)6QHhr$!>ZtK@9)Q*5XD6u<^5pkYM?LK>>L zf5p)B6_@$j{}u!4k7gnmeF_B5u(ffft(nwk88MGLK2W=BpPYQWZCZKL22c8}KR*9G zVduy_->n;wulG2;<(IdPuUhe>>and+;WT&P{T{bUG8WBR6qH3%zvAI?a>nLFsr#*l zM=KwwuD|uin&X=jq^>&`eKj&DV(|Sjzb7V2tI6S2E1%RoP?wv9*HhspDV|60`m#=T zm+TnN-qp*m1|4+tR2H{6==dD-l>Ae_++7UFm3C!%=Az$!Lkv@W>SvaI$;j%@ps71J zkz3U@Nu_y5za?|C1Lh{tF*ikpdUnpp@h4kw`tYN=&LoU|vSsRT^_?Hx6OY{W>7q+N zo`Ff*X-cO)hvV!lC$@Z41KDo%OTv@QAlMOepHcU(k2Q(<7@ko--Fu6u5WQ&nPm755 zq`|_$_kDWe3z+G6P!3DW$L&2WHBPO6idwMc@k5xeg;nEg{4$+ikLb>}wq-JRx$Sm> ziCTU=V$tnupsDnN9ME2(nx!=~uk-150lZJaa72$hps54cRI(|l)_LS)PEsYP<#?^v z5SrKH^sDRzpqmc4Tr7~dx4Q)~kWvd67&(p6{#RJ-Hvz#LnSnRJ0hU}J$C?qMN9?^< zQW+ab-I>ms-2-T6Au&{(hB2qnPNC01nIe8a?j+EB)~+{7K7rGT<>2X0Va%K*m)1Df zIt!~g11{hUxZgQ7i~+=fqr=%9d2r2+pVmCcLYqV=@_Xb}#)`-9i(Mmc5qU(A?)G8_ zHNt^0e1bU(31lI%@C&YwW8p4S+*;PmwW>O)=Ym3xH>DCr<$~UyS_PLN^FuOVV8-7cfKZl*OAFGU z)=RusZ~zP;@qJu}fJ8WCn<(RbFxas|uWdzLt;HZa)~!}@%n&>P&nGP`u0`kxgr0VY zM<@m+4!$Sp7B1l6S)%`8;TlUbvK>D{I?hs2uV9+z`6b63Q+IK=&J2eorqBlC`lW$F z`9G43ynP&m2fbT#$q4Mx2HgBxbV-rZpcBY+!c;)>ychSaBi?WZEFiE<{oPRDU99VW z%kg#ihVL$!&`*G2zu+hYTKFRa2NVCgH@#P$o=i-Y7z3g?AM%n4%(dOW@jF2DLO@FTd^hi~}$Ft%ut6X&0Jxh)Zi zR13r>LSV%sp@1<3?5MA%3&W3b15f1w0+)URvOAph?ALHdfkw3i2iWls2>=I#?TzX| z=%N!6oX`;l=kTL2Xml9=4k6J?`Jkmke69cLTpR+ND{2ws0($?N=a1$aVwovOz!`B5 z+4A6RR)#In3G@unuT?2QvRz=n#M#56$JnDS=MH8=C#Yf*LHKyan6*8KCz!UU1iHHx zS|}uU8(t3MATwSSK!qP)!}WONExalfmBSBog*o?QW|l3*`nB`f+0~8??fhWcmUXE4 zzmLJ?KC}!dL|Oebh{+&dP+ll|eEY5A=uUV(fB+3J3%ntCTQdzgMTL$!U#tw!8<+#a|=eMHjo*o#Xld! z$_nY@hJHxXL}uXwEeihDdFB%D6XcS$2ltjpj0*n6+IjKO~Z$^K-ifjzHY)NVkamU=j$ zfd|g`Ym#Xjq5V!ZJxebZ>Uv0~F_+JvsjFg$p7AsoS`d^-KCzH<;nys>DV<6e!|XT0 zr4wqg&(p{~;UWkjvi}a!@Mgzpp+n#!LT5fyeg)Ugy&9fe5~%BA3Qs=9y^~I;XT!Z3 zxqlN)a07NM_`yk{1TK&+4%3Edcz4KI%?4;x1BS9P5$@;$F~^AY5^67uUKmV+AnQPa z%m)+&)03Luh(diw83@G=R^IXfWSH0@^!74SXzdE8+i_Z+)hwv#3Al|yD%82xUqn&W zU)%`S79ykbRaZeM?P_QW%n0t%CQp}B@2Grcm6|AR zX+up&ty6M9WP^t1A!%3z`3|8;TV~RlT{c#XJ{g`iSn4@s)UQ{kmH%yDh=1m)ZE@vI zdUp#fxm^wEkYK^^T1|6JZ?&zwvO4m&m@w2X}Dn{+g zoN5Ve-keO^UDB~XSrG=|AkD8JZlX#sL0T>79CdY+HvA^cn(E-jwF&SJI*GEW2L+Ro zi2-h@lpc~O{^z zgye`APp(%rq^3jbWWJk36sT*xg1048WQKH!%#Se$QE|SmJKm4vZxX;JX=OsZzygiP zV33Hm!yxKlw&9S`ZX|LP!byyZBOEWm zj*+_v@J=QN+{g*;6t--1*%qB=hN}gU>Mnn!&<9d!>Rd_{+>O661#l-e zS#my^APs~~5O7pJl-44-s=Xow6iSgClDcOuyE^TfVjng7WXBI}rmgSYIXwDgue4Rl zN-C_~bhlOk0sZ^6WP1B4U0}+(sZnF!zA~yNtRUWG&GH64tur}8Sireqw$v?osH9tRU>>e;DuXb}AWx^n zS}{pBk`${LY}L8AFrQ2sS~b}_5G@tH#7cPvG^5Tz^x)s$curzea|*qvu%36f?-5EOy)Ia;Mm0MpXS0z~FfU_QJd>`dVq z%*(Fey8*VsP&nlKSMP3+EiTrt0O$}bHi#@>=4vt|RsfU=U>*z+u)5X2iJ5U~1-~*t z%|ae(7U~JxmG92t!v?GzfE zeXXPs14Y0FuX8Lc{6YXa{%X5@NkuS4yRlY-kRqxAj8)3vT)0|Dt3U&3rCdy47Zy(j z9TPzl%+qoea97YqzRq3M4CfVtCWp%4a<_D_MP({|e5SdBTXusiQtjz$wWMc(hL$Nb z{P1MSL%^55#)edY6c=sjCT*#%Yu+8JWVw|JX-JN2NT%X`&Fo<_6~AZabxaFZ4;j_C zf5o;4n@>rma~Lex2&4Q1Akgk8nH}s5l4VN#{i%&96YadPu}imIYbuMCcAs)XUGaKs zeQ7$iBg$=Erq>nK=knOTVlA@ZpMU^^{sOyw4|$vnleEFWn8Dq(0Rc~{ADm7)k&=^!l3%1O@bwm)hU2t7z8j5R|DdZMFNVdQtyQ5Z$e5j ztjc1BX;86bDQh^jNh|)CQ#>A7^3c?vW+etLW{91f0!WikMk6$ZL(V}=gY9n>ejX7; z+EP$$8ZUsw8z)AD;52A88 zE(RGBdMYlfK=1>6Ymf>gcgoZnp4fvnx_bnOuOv%|_>n23E9>)(bI2;?4MtVVuZRL| z^DQ=rKgOT&1>TeeivLg$QTd3+9*AD80YdEI)E!)bF7}+fI%&sv4J$6Zm<4uhg~eEj z@hTMUfat+?#5$Bs3rj$1*Vl-Q4G9uwK(O8wfu6Tok;Moy0xNAS23LYsI5#q!kSR4o z+yv<@MzQ=)FFhCpuE&$16#$u5HiMv4yT|jmv7GeD(#xITZO#R z&9qMIo^Oaj?;1UH^qIk1CZ0C%S@5R8#r(t)2umdpm&IBOy4Vfjau46W;^q?JW|MHZ zdYMGMjEYpsyove%p|HTZ3bG{njP3?7`L=cq8dL&`2J}b7V=N;V7x7^FZ~#j){E3Bu z{Szj^p$A#XK|KkS7*S*J7YBZEWCC~vsuGz!0%F)3A34XSSPSg3R0RZsuv@?`atWde zsuP1OfF)3gtVGrY-A5D32Or1JV!jLRoR7B52fVSStO#ANgp|_SkZ3Wk367<;qdZEb zT5Y84=q}*eGSk}5hr*RO?;vTmFt3&uQ@ljF6z7nP3&)m&htVJt7^a27Xxq)n%r9hx zQ?oUC3|J8hMbJVVgjX9DW6Ex@l~tgO7-tjD`5#D2N96Z0*qQ(7C1tIHt7C7ilSD0$ z)k$%F@P+bq(ZG_k%nS=SS`8t5+|TTLi@~zPVQjW4nDPdRwTB1ty@Xr$+x8b+OluX8 z(+$=t7)v}ZWKEoSzz&hSEU^N;P;#)+o*=>Z;5PWk_{ofDfFUtGV}#!F*$QDE(qt<# zq5xz-t(uI?gn>hl04xT~mkgs@I+di-qk=GT5riR#^(b)#M?}FnPM=w70LEgNS6&q_ z{ut(Udn$O`_4VKcm6~RivTqokfp6d~#b-l^Le70%wJq?Ps&e$N8h-ck^wP{_Hhs6W z8LA;6wSGDIAdP#4HXv8PJq)EDcbOrNcbs&)Rp$*NWkl&{sg=P9L(5Qysy zK|3SZf)T7dKH0e;!8|FIKBx)&1YIWK#v-lEuj`W3@6Z(#ax92aJwG)QwnAizzCjeus^jfWY0@I<1%L@on&1Jn(02F~ChMWw0* zM$p1h(A0>T;R!x7fY@H)gUKf7uz!$rUmBlfppF-{@F z-qAxzh7gYfi$vx-=2(qH1?~^=?bO=FCI|yTIL8lW?ApI!C{D|WJ1(Q``{gZ^K7KOl zd%PATVy=sU2O(g+f}a_+j7^N!5aR$qV<1|^_=FCR3J2xz_vIoBAw=X7kT1Ry#=?X$ zlrmpX@C^bGsX%hKObwAQn1B}ZEzUOrK`_Xb(q(WVKa=YPzQc@xME?H*6aegE9WN2= z1aI=97h|ptG~?nG?ah|^ZdUNnNO8tnze*~CebMu1b-3Y_K@EHvCjqZGR}UQ!!0=nk z5IzeafK?|wY-O#OL2JgnVi{8~<#2_VUt4gUWqshIgX}^7>_+SZf|k@QsDghF7J|Es z#Di}Te*{KS2Q#G*iyKK#foJ1!R0IJbhyc29X9F99<)tDwq0XgZq~&n5_D)qucNK5)BL0-0c z-9Y&#*=et+gQ=v-pvFX1uwTBL7}yx?2$W-D09+lhtC@mM-6^ke>e3*t-Y6A_O7BC= znCA~5V*e}NRWMxLy$EsxK4OixQEdZ+AlPp~WpyU9GjfKEhCsNAkoe26;1gE@xB^dv zF<8I^!VbvPl5h*lP1r?@Tbro>(f~{>iKcMW!5L#w4a@?M`hq@&V_kQZ2{{jewK5Qt zGrbg4gv4xdDMfJ00DD7A&O@7X(SWf9pz#Hu#b>MP`x;&3v4BH|$xXpqs0C_NuL$j& zt}W|pZ*X`50&5?!c#IrlboymLhp9+ay&K2_^~lJ$R)ho*D1jN`Lxic;59f~D4sS`K zHk{tWH3))6R4*Onq)T=ARn93l%&CiGm0)EnekkWz83@&;_f zf%cJ!u7KQ8_S`E&pC~;H8vZ;<VWzAgiRjqTdqB7}j zM}_kYrD^T9@cIoo$|@MUAyh_pItRXmoPQ2v+5?2eu=1Y81wg|(*%e4s)Me@V%&F)E zrBw18;XOFMvg;k&I9*?lL0Y>#$01Z>X}+$T#0X(!)x1)ltPrIBR!I{D@o1ne`4#a& z0X7*GUtAjqd_)q;NdDmrh#cnNEXSm+HfbCl0jQwD7f}ICn$ofv z7ve>sc7h>-4OyZ^RA4^qM8cs~&X5@6#c(xQ2S76XZjx^C5REyA7&#*#DzBEWPL48V zEBNZ!7U#`l8H7=9rG!a=bwoBlX$!7Z);0%Gx|WL*$M8;#y+7fV*eAcLzk_u*ibau3fl?9a2s#va&6=MHaZlb3^D; zUQ$U@utXx@aQ*JE+Ng+X-O?TcLoIO?NAnPt0mi|CaytrUbfc;OA;h6fJP}q|5Q~5= z&VvX7m?;yn62O`SYX^I2Avi<`CMfJ+TBVn|2%=!Bt(c-yQ+!y|_WcV~wR0Z6=wL)cdHUS2H8ljmZ`YP1;qn zluyjlt^5Jt9VP9*R~o(q5=twU$x||+#3iIAPL^tz;+Em-pQ}Dvx)z}w<{|?Wc!I)7 zRP`FY0T3H)nA1E(RGk#TCTU-$mu-Col~Nhi*il_U$@c|~Enjby+o`J%EFk$Y1bKLN zv9p}`1C(4_riR`%Xp;jd8o0HJFSE%;SBlJUuT)ixt_-Sm+Lv2^%6o z2m(JaMd-EwNXyx!7-A_oVK)N^b>EGk9I08$9E4ka5T zdO*w-&<^dp3tIGYE9AgMBti{O^O>^MwlXUf09|l$s^6FrEw;Nrco&pQFN3>mPVS>^ zB)Kh~Ok|>-PUSiX|M5*hGoF(o8B_#KeEikSTHImP<=7J^1Ns{?8{JYu3MtXPr?m=+B#WPRt}hvo;wJH3 z>aW!FNoAj>XY>^}*3}cxDb{&wbBfZp9}AgyhdQs^#rw^if4S2-<)!I2rQfd!T7B1b z)AS`{zq)VE&<&VB{(gBvhW4>?oq5Wrf>ck{eBGRqAI%-+&&i1z{!4vMpl(Oeo03B& z_Co z+A;r6cLn_!H1O}W&Vtb7s`wP`UbD~>K4>ipr&$SxxnfZlBXSOiKGo1A6;ym>*xmAR zrI4YDYP_ZxoVi)UAA1Er2QbUr0aK8~N)I92y;<;EyST9;K|sQ-xUY5h>h3ctx8Q0H|~ltHa(m57UmDVv6KC(=2{9_%14oeOh*7u zX95gh6^l7T{5S8&aN5U-+xcfkQj7AOrv(!&t?HxIc1TZ`G7!nx;fwCfG(-G?1lT-?A(uKWi-xp!bat&@bK%C{j63=yQ^B$UTRQ}9E&8i}hrlle zy`Q^unZ$ix^t~`)(9+$biz<^X18wEsD+Eg-OHOS2#-!+!T^n{}Q)>2GlE#?mOfFYuC^#Dv#3_YMsycmdqRI7UZk_kSFalpNhPe zbf(Cd8&leof5+@P`&!J!t+!pv%+rP|_}7I+d5zm$`)CKuT&b5jg;fV_uPMs;q<{C} zjRR`^$M2G0iz%U z;y(kkh6M1yUQ9d~j)%ZxuJsMvNE{skQy8Mg;u6OD!BR#})tN#f=m|q``Wk_Ifmf=8 zAQSert0T%3Qrcj%6BicBCfHIi#yN;|i%Cg$7M1a&^q&xZi4&iOqV%4YvbD()M_s^S znxPSX%Q$8UJu*8>U>$LI7{ps4JN1n1ut4%4gw^n*95g&(G%>p z!~5axBWMNAX#cH>(EMZ=VMhbzzpI4q#IP1l?6LLdM96t9Yu$LO&j;I?H}Vl>JyU(s zo=_1q$MA_C229phQawSGE#u)v%E1+`2;4lies7N`{PkGPy(A7R5jR-L;?x59Tw|@@ z#Tbb}c&ET%BB3gRhFU;AJCGLu8lQj|5f52sS`8m4>uNjfe&aY~CpyO9P8MKRFypq# zpmGB4fpnMXVI5)`))intih(LzP!Qrd%!4)%Ntr|)9)t^FfW<&u1d*^}X7mk;Q7+LJ zr2SDTSlXp5P&erNpz6IDf~mh|uFNk_7%ATxeeL^AJGV{UB|Y}`p0LnYtBVJk-V+~K zkWBR|uht$IlR5X7EaYCbY1^IpCRF+UeU5o=bTXB&H0G+-dSbFc_RA6dxUSv%TSI)^ zewgPq?Uji)#!ZRO=)Jin{^p)P#qRpGF=tht$ z%DY2Catp>y{?d7|EY$OFP4>-Wyyj1dU%oG4t~Fywk#?fDt6AG0+-`lgn@Z?r5) zTADBF=ylphD=hO>$J0C>y}@R9q;{za>0jkk5!vust^e3-YU}G0D<^CCS++uR`VJ#6 zXmwCb(nNTSwv*KYiU?|*B*mN+laA*5$kj5o-pSWTYlqTHipl@W*NMvWt#nR&rEmEu z$m7kkLb{twa|Q&7oC9Fugb=TR*@t+rP;6&;T__ZpX4m?%4ks`WQuG5^UBWaN7z96* zltN;qfDodiwVEy)pc3XPY>JbBrADkz29=L2V9L=jx*aIlCLbo&<&UhdAV-bH3M{r-Y0xEA)9IsGL) zAi_%NBetKb*Ma;P9L7#hZY4n&ImrS#NGsuDtwCFPD&LqzRv3^T1Q=mn9QkVkjKR19 z@eB1#E`@GTmaD(-u zNTuCpQrNA3=5F&j`r)e|Bu6C-^;tg5EQl6=Q{QZyC3USYiXzX0ER3YLgnwwx*yN*M zGuGQSZb)<{Z%JdrZv8K{f-27=e`u>ei0i<&D?bGnXI{Ku(@|UgSnu7&DP_v|+(wquXZjbuYL;A+XEAjDn z&2LxB7bn0DO-l>vH|^N9$xRyfQTD^Mue%2f9yoA^)z9zphu_^QH|*+j;(^+8VqDml zAEiwlcJ|~7{vBst^7!v}q``g+@APc8E?PS?`(*z1_cq-+IcoZ=v!zQGibpR^6B>P} zEk6XV&l=@1DA_6SN9`;{PT6)J=MucO_(m6a;&G9_k?lwQ$O_{>*^Pz z%|F^WTU{}##Po>_5|bFVvr;MhV2vr0=P8`Z(Wc}@=wCzf_bsA6qhAZ2l{GW^x z3R>ou4uNPbW*GqR9aR>H1{7IOU<@%}g8aTlW0@7A3SfC%>R|Oj#3Lap%xj`IoZ(=D5S!vT!mw7M|?6BreajCn!8q4nsP&jJc)V=LZ_ z3dz0*y0pVPqGnBa8+Lgh#k9cYo=1ADUBw&Y9D&ZXMP`*Ais&hSJyr(epjvf-Or6Rg zz{CN|{tmVjJP?mt45WkrCqpN(X{3q(m>e=P<>e$kYyiu{hFqoB@NSz$13FcvT)pgj z_O`Us-o4`+Z`Vwz88$a?qok&PR(Wy=AL=9np$fYgpS4R={&B!2bh(K2b4M~^2bH)9 zqDdckgiGE(PycdJH=x6B>lcg`?>A6uy)SLu9=h`nLE7zE^_#4lvLw7&rTt5qH(u&~ zzkKYdTfdmH^HV!kQ6mSK=1T8NT)r+yaS5-YLJFe=y~bxf2|8G{C37x+)8Ih;TFZhH ze9Nm_U4A@yGez`+IOnI-LFfN6yZFRx8ui_Z((7wa1XkSGo%_L0_q=-j>c8Od!uc(5VKq0Qm_yP)vwNh3KRJbmQfqxtD5gv(- zEm4EKDmd28Hr48rUDHY`{qlq5m0cURbFMY3drXBoswk#a!40+e>v2&I6$pWCMxaiU z{Dr)AR1nA|K}AaLfkYJgVg%h1Vi*oU7Qm;}z@&--5RC=G{_4W~9IM{5XlgJB-n zsbw&59`Q~mW3qy>jVtyFs`Z!I1aXzVPy^(LPeDyagoHOzSN43+kgG05q~dj!%hmO) ztIcI{h}}VG5$nfDmDggFDp}cZq2pfMa)a3MT-bs8(QyM2EGAJcp}f}`Z2PV>0+*5f zU*V8Qrl8v#)^SlB7T;K$gAtGpAYXb>kdQeIkfH?+f&6hSt>qw|fTj1(Mez72CwR4K zf^CjS2!ob_$T_7A%OUOqeCRF4D{8Jt9{gcnO5 z1IRC|YYo{|@)LvyNvIUz8)JtkeWr+v#b-|X_o zX9pMaKK*3sp!t$Szq!YT^pxy}f~=(aT3&)FG)_caixclx|0&`}o9ma=#Fx8XsY$T& zVDFe6hS&qPo?p973zx#4#Z@Ajstr#WUp-%PJ*{s4i6b#m4_EJpjoY0<&Yn%Py}NeP z?jfOT$NWN5M{iI)=Thp`b+@)XmHhDbAJYQYZYrV`VWaBI4{Cfj_3fW}xY57QkykT+ zaj|{x?naGvpD(dK;{CHO_~7|WH{B~o=O3l1OB+6?E?rSZ+`G`&ESWg#pA89pmihXh zPL)(2e3Gf^V_W47W%7gUU(fyrf3+aw;)a}WdF3^c7Ylm7UF+0YaOqKbxPS2lPkkgnCA5yIqB5)e?rzsy>V0ZL*qZeEk8_kY zek+6i@Oq=b>!ayj-OA|0$H$ii5BmpQmo(Z|Zy_8o_HuDvEx!(&2`1=fkzmX*uik{D z46DcDAsvlXG0%D2SR_~B1dj&^+G&E(u0n;IL__ou@)lQPPZ1=6xEi|OFzo@Li3po~ zWejE)Fyw}ODpm$b&(^fE4$4;~T!U_g+= zM(&;9Dj}N8ZJCNOUpIh8lC;1MfeF9(MUaU>NCNxt^m41x6TAgeHk6@2KOS0PQz?*= zr3CWIF1B&{u0Yn+bec}VZ_cZ4&Z~9u2x+JZE$2a&3YaSae8%ZV(^GjfStV#`G0FyL z-VrPNxSu0_h@BMz^))ij3zGY`t&hWwk#RFbhgo?zfwcb7!BkrwMk0Rghh^%IpcF*| zd_I|N1A0fm;orRz<8FN)?sL0Z5yrdb4wAKtB(X^xm&L6%{? zG(N0q%m-4Q%xVdygK<&Auqq6skCxfv09_I2JwPu+K}j?03E-E5&mM|xCc+FO#SEa+ zdW3)Kl5kDx{?%SjanFl81C2)y7bHd>Sa+=E^_mcRQ@v^$?j7*wH`7ewM|-NM2uMWN zjPy1;|C~CuCVW_ApP;w)hh~JEACdfUQE!MhM|`AytwCq*pswp!BycI&m(-i5 zucAgo@^2khKd#+0FmP*dQRyb#?igw3VN=$RTsBP6Nwa@qlxeLp{+F76Dndh&%al9d6qp4qh4NB8(`(x59MQY&9b@OQI*xzRDzoNJyF|QkOQq+I=9ka_^ z)eV2kRM<)RM-4x^j28HpHGQOlZJ~MF;wxTBxOdj4SQw=F>y&=uG2f|Mt7YmhKbRNz z+1>QmpksYAJ@yQlwe9Pyi{*t&z5E*@uWPSKmm5TM4siuza9+@!0j;#IW+6a@N#lJ8 zy>MS1Cg!2$rczX12q8;(WzZe?Fwy9~;04IT6uT1uB^wWrY8gbMLHVkDH$FdbQ(ch_nmDGwuoMF0#?7cR3%bkNp?rNvgrH)}k@3`R(IK}1aKF_Lbm*ZE7T%PK?5`QZu7W9K9| z>UeT8mNR}_U~pa-W_z%ON=g(O2X$mt6WF+c@E+)Z0~ye%pN!JP7tE>$84-?(yf{Mp zA{0ez;Q!x-Q8(_vOa=liB=SI_uW$f6%HRyi+_K$nGbSrM|wBxKIRk<{;Bw2P+1Chvna#9A6Y9y-MI4LXmJO zM{5D4QI|;2kvxYAWmO{YJy^L#pqL3kTm!xjtOm5(u4G6w!B8e&?M7mij-Vxnzoqf# z=mM$hfR)|@Ob5T&8y-Ku+4{}b(+4i`7MkB)EBod` zQP3L$##i?+Y`a~wqH4gb;md=ioo_!lTeolHzJv>+zIO-AT%4_NPU!jGFKJ(ggaqyH z)q|Ry@*pAXJ08#}?){OJJwp2;=70k;m#k=)IF z>eWS6=KI$;bJpd?oE7JepIZ8!TchWpwR>mE5_Wvm&8-nT-QZ5a|7D`*>pt)E!L-;DvOmYO6BkofKlsJi@W8^^{+);!vMr62@ zF{h9z1SAzR7$zX_-b>b@qE(mCX5~@!nC7%|CIs&RSX#`C$uJjovl&V+RsR()qvEvU z!%)YGkL>ECu)~mk6+j%}Fr?NGFeF^+7L-=LEXnAyIWrxi#L|wym$l~6Gm?h%p97D& z^p`Zvf^M=^itS2)$emfb%4E+;`~n4x(M}TdAkrpzloqPXfGUm!r@(;_EfCE@3y02R z_myS{d({^})RTCaB(){LbS`FTyTM%?iH{e7Q|n_d%w)GkSA?Gb7-2!QxO(88KMu5m zb}$RVy1lJt4ANBKP-C)$y^MQ@%U|r|JHxsKI*7$?T)5K?==ofzlX%fFC|rCoWU-av zV;F+OQVC-64KMJ1AZ!8;WwDJH!t+0biW=cz2b2s!Rv40x$Yj=ROHe}z)K#cj@Hs~7aH9? z{x-EL&UesnjmtA4D-L;7mx`uCu#62cI|(}f9vGeExjk5 z9NN2KuSZAki<@^3S9ZBrw%Y%#f!5ay2M_Y&61Kg!H{+dAmo(BI6>5NJ7nO(D%a^v! zH#@I7dab^q|0|wj4;nt9KbPq0^`=+7-d!XAx_9021wOm{^U9k))qNKB%}}dT@Ha8b z`($p~x$c^3%uMB%`^Fc%y<)Aj=ZsNBnbTJaw^nFVap$qH+dJ%`kapR%S4qc+Dw%?p|{zLr-6 zi0KWajECqM8uC&|8*%kTpsMCOV8$4Ia*crx2#rp77DOl^@5IJS;Som^nfm+`vAu#wMWHhud&baMH@WtxhaF_O)Y)5ZYTSNEc!-g80L@fUsU3dk*vg zx}`ito=jwv))%S}fEhzxDHQkyT96%p8G^O2CCd;$FZhckK619;yd9K=dh{um>}l4! zRR(31FOx4fQJXWBiV8VA54hY41CH%wjnm$M?G|zywD{~G>(OJzA8*r=b2hROMYgn( zr0a5_S%z$<<(=Y;U%Fy}td8?2?| zx4DM9_PKZ1o{8gU9DLzBaxOyM8e2}5h83Im+sh4;<=8&z4o@nWz-WU0+k@wR8dfTWT^tiiM1qcQMClN zi9>)R-D7j!EuI2aZt~T(P*NMjc=T=zQEtyV?8We?EZG?+6hs8i`WWq$dbxZ?6qzFk z12DA#uvo!Es_^_E{?RQvMIPNJV9U{O@fRU!ftxpt?e$Yiy?~lNQU3Pq8g<#;VZ%yc zMKyT@gmhmoX(D?+mcr&CDHi%V@Qy@y8imqp536_T;~*ZYm&Nzs7xb?6;}@hVf70^K z(|5dS_X%$|TYJwGe{zJsX_(n{jO6Zv`K5#TdAp-E{3)oxU<-ES&|Wqud+g$muU zvIn1(_b5>5O)~$q%)A{=hE?-y^t8OgiXStNe|EBZ*wzs7d0|1&!B^)?Zugt4@-IA_ z_Py&(^#}ES@r&dF;>2 zD~7#pDhoS%(H0(In7ul)`^wz4<0AgZ-VnP-zgnDEH_;{Jljy4lkLX{|sN@YD>K4>< zW+Z)V=f=qI(*}&Sb=%aKwCCc|gNI{grBp?~E(vSi@M%bZyoA42An&fHGDfV+5Wl`P zvOIjX$KdtV<8;Aa<@B@&*6$np`n(@KQohJBci44dwld^||E5dz8~Rm-OzF7pT1o=X zvi3u_PseXH=l?Tv?$z=y7KgR?x{lsx8J z(?orKx8$0*^nxtkfSTxJX_;xAh(BjY)V*z&GqWsVQ*DJ?C$0;-W1ezEo*q+Q$UN=N zMhPIV4%?eCRt?2cKvMU>bFyNOLHKN*63hyoLx;nXyLcW$tVlr;lc9})XfKd05(AKW zIG!s3x@L|^E>n48aZ>`j_Xt8RHs%0n2+@+H8SJOTpexuO z5d!0d<&R`GUKl67+WJ}Ny`0&YMKv)j2IowfsLvrPD#Q&+!QnAk>RWf{MIC?#dhD*h`33)+1EB2M|olqKU z9d)e9-sH15e!wHDX)`^>Kx?i{Z-Wk%dx~?cF>(H&p$xTJslfSCDfqo7{lLHH z(eeZ5-%)#dZ@9Q*IsKD=|Fz1+@KDdp*HXmit_BXXIM@0wxbt55hpO(S{tJG)vk@@( zy;A2#wxMaSmH#STky!tJ`EJ#FX4elJK1}<_79QWx?PS=Hv9ao{&AMRmc9+a;KD+wK zV_$`bB!g(%dh3faTi}}4Z-9qn@!3#}9gt02n1~?1E9T)b?2C_1f-QH2umJ1K6Sj-| z7hu2%EfjypsDLw#obU=_665pZ14U42NW5qGDApN4&HSM36iOACAS+(UzSvkWw-f+} z0mpDM+=D~_eHjlCd(zG>;khyr5ml2b!Gds$HSq2*n6L+)YxWU_G<23D;`&PtseE9^ zTO2LyG18f-)nQ_@lStyv%fB%z$jv#aGNimV4gA%-2Ac=p5B6CeS6&m+Uy@_2_lYI< zXU)D%!4%235WG`lD`Q~mROpL02`j@5_k)o1V8SgMUJw%pd9d$JD2LW#`q*;aFphLG zc=(w7Cl}+nd|mG-yaI;$SeQ%cD4D=6(a6Qo-@(WS5xi&ACyILpQ}yYh#@sb8k32Ew7&LEB-)3 z@luopG19Qiw^P$)il8|Y)|y@qI&^+bWJSk$*;J7Cs=|p;RWS`9I ziu;nnduKm5Ie!$dDskEh&!FbAiGqGItIOXi{60?6F3l(nqPEq4wfc^rdPMX)BYxk1 zR9I@7{6<6K*ZU+Nzq{;KjZ`X5yKu5R_=n|@+xpL3-1U$(UGwFj%pW2ijr%0zm!HST z3%`%wX50G@zGdD%*&B=dEZu1v{aQ}EZSUOB-A@tqj$z#sd{Zi zNvH8U7HsRf)a`U3m3Hc*t*W@7f^f|?q07{3sII~o5By}h;B*3%x@ zKW?T>PEBqwtxGj+xuKpr>4JK|=L3fSG0}eJ+hOOHP4M+G#6LLNyut8UnU^c|B=cuC z{XaVR>phnpKYLPJ()2;p*aPubd(KLVn>*j!m_Nqe_=d$PQJo08dex*YJ@Uu>k}R2N zXutG~fL{~7ErD(Nd4sMdpX_^esKP(FQNJy^(OfH7n)yYJ&6>Vi7hIj$xFE=ql^GQ) zTWb3UY_uN5Fy9Vx)MZ6D;N~E)BkclzU|f7HA)eSYC?6NIbtaE8l8OR2!4af|#FC@r z3ad|fsFQ3ND$?f8Q2AevSPf+a*c#&~v(B-Yd9sIn0;|1owyscR;~4<>@JM+Vh_dQ| zI7^JgZs5>!#=O`7A(V_y437;;3TEeZk20Gk_aP^BvJoE(H<8raXIvwQ+Uf2E9gvKjjt1Cy#LZF^NI^VqAv`9N)rZ(qNk%uBsI^s{S^u+bKe>Sfa)FcFc z5Y=ou=AXMhFJtF>L&msE-!+v^H%&PvxgNXLHY?k5W6kaI9)Hg%Jbv(vqvzMC-}EY7 z-`ls_vAgDR?{1ww=?~syLsL#;96W5ECV%IL@MRa*eIK7Q?dMyo?T0@uFny6HDVrO5 zbKLn#?`ErUtieCdrTNoOBfTKYpREe3=NBC2ZTm+TVV_r$$|_|8qz2*af>Bf6o%`vd z4a*g;Y~8!NX6lLe=B`Z+OAqZ6GJCan<%S=}$oKkB>F=e~t8b(SP8pE2DmQA5?VrZC zXJ+Py#;$GHW7~bhudqBP;po{0?}l7`^+ydJp`MZDCx-MleQa_beTe?`v@QN<`Q}+k zu>rhg>Zu2|Pu(W&(cfS1wPDuSrizelU%2~ZObLh0ElU01Z-GFG)@)e-{9O|({R?b# zusNtPOBFt@#4OwMXzZS=ma*?e#l9K<&l%7F$WHy546nfy4yDh5j=;7zE%DV-NUG1e zl1#$Cew>#^9ws4)D<;khMXuS*reK7a61c#Ho$Q-V0GZ2j>0Ox}#aeALA&JL>hUT6kjClOcr84KI_hLko6iOf&(AoPPwGdwzg##3Si zViSUBN~hUQL1}E3xua`+sh1m+5qGu}FG(6bL+P__TYsr%WyGe&CWaO>D_GlD1nD#} z`VLmukQsTu{$t#&6pkg~&=^__sDKUyHxBNds{(e&Mho0OP31!hj|5_!PtFFOj*gJ^b%o?&k+|3h8z%(j zudj%7ixvDI?%o8dsjKZ54`C1xR5U0cS^+@M0u65U4>#k); z%y7;=`|SODrr+~Crq+UZ>D0^|>IjuOR2ZmhRcpeWLKB#2Ep`aYeM@^7B0qLuhA3nz zDa$?4Hgs@b!m1p8S4MtSaa`d($GT?`F~Yg`UWR`olb;RwxxMD$jlolGr?sbNNBzl~ z_2t{9@MG*<5z)bhVaXkT(RM%bv6C1c{KjF=V25L+rLe0o^x*71#y1H{mTDi?E zG-Su=DO=rVZ~bYp!&CdL>pxSTJ4Q(+9GNXE8#=vNULL8NbDy|N-um!lWYy{OW)y+>ep~N%)yhV{#Eks;pipmUb5CEC zda#AWFq)lNm>DtEompV?Cr2=usx{s)cj*f5spk8#Pp?pPyPc+)ny~tRJt)Z9U{&q0 zJ=xJ~$jB&jb>FgAk4%FDGC6d&!ZUk~f;M)4)!`^oIqjQ0V6(f?FSWs+&G61t*eBkp zE1NHCO|82UE0o7vdTyWW#X0@lbmp`6>dPF~(o2P*%Ds1jVo%G+GkU`2Ta-+?AX|E$ z(m`(*PK;%{)*a-=*c5~Z@}DF-Kx?1*=WM+y-a8nvdBrN_do{vCXLY`8S$J$isM36c zSJ)F}gJdO8$o|F0-X{co@X$LdXAkWr$XIH!0cfN|1zMyVAe9Wf3*ZREWtV93LHvrK z*5F$C#0LPIa41f50FfXCyB>Qi^ngnk5|5A;RcKRtd7-2Sc07#Yv#qU@?FV}A-}GhJ ztojJn+Od_)sYUw@1$Sp7ni3x&%kAW1h2;W4sUT|{USOET; zMvib%j+WQh2dJS0U@~=qZi@RXO%_A}K&wG>toVw_0L&HXNdCcvjn^ZZ8tg_vKILdw zZjiG0~dgFrAhgV3VPx6mQyc zA(PX%vVl5t9J4}rqoeH3Ye5XRXoqZg&jNn*X-d@G^#_s{asKKSC%4o%b$YKRFRU7B zKJ?4V<{q}Bap6Noe1+?Z{La+_2l6{t%AURM7M!&VvHx+|J%{*4ihESPVjQ0&E-cy= z8o|CBcgZsRH<>EcG|4)|UVGu@3Ul|w>Cv~tMXja|b6QBQHs@=z7e3}VC<_HTu2aRg zeBagzN%5AUr%DXn1im&&oxW3qCr@u%EX|HLUmNgSC%e#Pg=xcS_lWK1bhX>|r|ECm z-n1#?ltYK$NljE-^YLT14LWId$+-cZ?&EVU1wCi>nxAjHX%KwQacT5y+5=00!QI7= z#SfAKCK%7xo9e)49I`Q13eUUL9qf)bSv1GoFX;95?<`e2yQ;4m|GM`+%h#21Va2*d z771jF^l;uX)Ag#!`P6+iJnAEvNBN#kTQ9_6Xa(w6NkgC@xku-6M0LFsYs@3yzGzJ2 zFDc16V4$q!W`p9lez&@&gdvJh;TTy}h6MnY6ZnZiEkM0R2++kF9&L1-_&~H0!g*d6 zNKMlKG{m2X|HA%2E{bv#rVGFjqmSz(Anyl@68lv{ zFTfhQ3o7D`jzMsI(Z(Ls9Va6b;UWw~h+?mUEBNYBK;ghqLt-R=$fz@e7nHMs#(e&K zzfd7*Kh#MT^K;(-MxvBk%rdse98D{z7G!S*wXJ9*!@_h`jCida2xuD~hsc7f1*Ekq z3*^kmtG5Bq63;02ZB5@nZUT@&G=(|gcOQB)K(@tTPmT{3M~Fm1h!^C{K(@pvvw+A0 zM#wtmfl!6u&%z6=A!-;F?_mv%wM2t)Y772<7{b#8Jnbe7H5reHDhA-kq&2t;px5Im27Ma7 zBnBF?gvdK1a2ylSh|1+SNe$!%axB5LU|hyi$tw6UXvwk49}PrCXHP$P&~Gr^-AT2l zt^Q)UE=|Zm@f0eL4$Hjct=ecr>P!{z;0!@F$&H|75RWlSE10soavY7an8DPY%@bBS zQz-@h-}F4Yrn@k|qpn~YpP@JY>+l(ylH0rdMTH8#+Lf<|Js-7fzBJPne8UC%r23s) z!6*EcN|}GDUPsJ4{`RQwjLMK$vGojjHs7}uQY9JO!!`h+bQpo z*d4AcEgSO&gPB44r5mP^GjtAWOPI|9N}S|;C_O`TiJTo}5yHDMyT^`MEr@0Zd2=#E zeo#8cQ)!_$$E@brWl3l;Tlc)kFDa8{eUwKvilY`eM~JP<-x-pw9CdD~@Lt8%m$glE zXU4ZZreE{_>xbAWg740-;?A!Ab6xkQ(sLQob^KztBG+$!{H1ngd{UMm-o45tx>YLi zOyo_Z36eVJGaQ3%t+-}9PDskl<(R2*s%?Zi3*0S_IItb4OP;wUM(o#Tq0k%|!Sa(5Fy89E>egkpz?cQJz09qXbl$3(aTp^=q&9wkq#b9 zl;*5Cn~F8f*jR|CG?D{^=h>vJp?@MK>xx# zU*tv}!`wxc3a~F(0!spB2(TQcDtzZxZ{up`n6e-K6&0dKc^ANU5Z-@L9Kz8Ga_M^v zq)=Opd4P)kfo1}7AXu9M>M$tq7pYVuE)bekM!L)c3fF0NjrI?;0!s?LH+6cxt<`FA zSg_fUQP_Rcvq6|9E&T41DR*$Ha2-jJ`b$}r+4)QEPNk;O*{-^z7}>=V74z!`L5@N> z^MGP?p2L|5{0FH<|0+vTR-BT}yIoUq-&17Rm}RPSK(XqJT$Qn6)^zh>FpiV5gw50A zGUk{of^xM&^Z9F4Mn|Va^dyBZHEF+`gC0sRhs7BPk2)x#$~7j+-CpW^*j>fnR$Wz&NaMm+wstc>tJpx__= ze)h66CF@ih1);+Ali5=QS|WjtU5Y=I-sx#1AjREv_~wFnL&f&`ZBZO@MOsbI3L0m& zM!bVZpPXSgil7fb>nx2}Qi^m>)L%=dVLYl>pz8#8XoQ+E2)*M#SgZM5zPJM?=$6JL zTRp&6%`veZ4cZ~V9#FX;bqtpru7YnEL!gxii=qD^)=&-V?ST4jibcK{B5;`Nyapo) zq(#NF43Lx0!VesLR#k$;c&$Jr+&^f_jov^*80GfsVETfF31NkkLE*gUWG@Ks9vLxo z?Zpy`{uT*kcS#74C(AN`9}AiF>Tb8AY!CZM7eZSC_#UG5L4?;8lM6~v z=PW~_0FrMHM57S@lBB!*6RtpVP)43Rhg%s2{e>tUfS&-Y{{7?AQ14c`)^rZ_8B6Rk z%Bes;+7Gx4NWft!fVVZ4E-q?yyauO@jp{^1o;A9%j~;{&(O?z91R33Epe?DX!eizF zeTsH8(&P5msV7u(U;pX(A2_RsVfwc^1EEUX{xgFKmC_&*IurfLgjUc%b`ZIgViw_}0528u;;~ zK%=_9aftE3!JPyRH8KAKeg;Y~QJZu(2E@OoQ-Jbes0Q$Oq*tf|k)w!-(kY0r>X^~b zA_8gvqF~XWm4~w);9Mnm;ADitP_;Q821F>TNX$4BEkI396o&*&jdb4}w!mnr8*4)P zPceSsBb`?g3;JVOx(XWfh9<5R^`4$W5g{uIT68QIkiaqV7`F?D?28WrpJm6Ec6bNs9R4TjC1kTVZO7OwC%6nIE8^)gM@M+qA5n;vt>4X5f_6>O6h z3l5xPmS{yAvy3DC1O*nP6FD;fWCwx!s=SvhMn)2IW|b2?n#E{+pjf%gwDNdX#8ei< z#IF39W3Pw^9GAl0aUfqlDcFS<6gJIjwYk*$X9y{rDVfA?V2w)(HDB;$AN|UM395F7 zGt6!m-tfAeO0BEB)|%(F0zaGRAa6lowo9sL!z$&;=hnA#S~ggQZ)8bD6Ko<*br%aB z#!ZjPTAN+Z9rlLZipbd^ZOs*7 z?$7aBuALk2{wnj1J9+$&jO@naEfR7G)dw@tq-KeF)$0{uu*7XkAuc z25Q!plEb=r`@Vz;A#DnC0uvs5tqBkjonN5QEHl7e8W0cr;Nu7w;Y6kHA+pKTu4ES= z->SDfOb&LKyvGj$OK|jSv{4$uQkZ9ubwu=xfX=bP&z=$roi0$|iJO2Dv53ye?|ttW zn#RJE`ZZRj#)30?jpftWHvyY5OAXCE&^KV-?4bn>q^kshwAJY1;E9f>9Fa0+LH9X( z5>%B&b-0NemZ>}DXk(qP=(4f*6GP!x;;k+$nZIKw2FQQLQKZZ5LF+6Y@C@D_1nx}YB!~qEkcW2$3IYQ!7JxJp*e#%g5PjJ-(MC{RJV76=r0Sw9 z2SS5}6btl%*oAW72tWo1!c?T$BAYBQnm{_gWmObhwvB zJFt{p`7-j&*)J}iHTCiTwJE>-6K62moozdZz7qP{YYVedS$Ujsosny)H^tpfj{Wq7`me7KE_L_ zx1?J0g(dFck&jI6zu=f&S?DQt9+#fcP5QH+yQzcqu&pLYVd63)Xx^ajE612$A+U`NB@iTjmqg&;-9L^URFDf)(W@Xntn2_?e*5$ z-?AN|3U)=@e-dDPA-&@j@ACGp^KVV_Pd;DBKdtk)HKmXh*Q%QI^Mv?6*+)xB`R0*} zcKlM~wfS+ESoip;XOI6-HID?gIwrkXmHYd%zzbUWvBQ%I zZfw`64g9vX#w*S~tVt8arQNm~@{ZJM;+EgpXZqO9F6=tBX)rM_pIWtE?)}rZW?8|M z(}k4M`ZIy2vsT*(nH~3K0eK-g-!s#N>)m|soN!%wIZL>E+~82tm8_ZZWc}YwgWvAr zM8tgIK|LMVM%~wLBlLCPO)=tC#GA}8b?)|n#wJpa(1$%bsS{e}9HQrESe7PwHSLe6 z=IX3M`f#;rg05AuB)G*8&_|;Kk5Z3Nkr?QAG3vM@qdEg%>Jw%mLT*HZBK~+_;D!Ie z+J;g%ki|>)u!64Z|%^gdaH44*6-D1Mp#r0`{7XdE`w*>ZfVg!@p&7v_y z$DA8nq3ItMf*}$-#_nZYm0w$ma^7PANz^rZ4DJdRK^s#qz$ykXjtyHT z3+OiyyXGOd#$4qOl=TneU`#0Miv6lM>t9ojk%M3ehx7nYh{@DUxcV6f(KNP$2DGpi ztoaXm9yL^8hW!jtTz^stM8t2Uu<6e>lPDTpF|a18MO$e=EZDcDVWE zEG1OlMV?I;%81WENysb_@*>qBj}g(v;71fEl7q(9f$U~<=i%{z+WWC*RULMMv5X*m zQ$MDnv;D&Zf%pem5t@mEm5Nnf5LxO3GR-ByVcs7XA3qc&?-Ey!HD9&wnCM;2R-z)F zHPDqyG6VSyzyhGOf;9zQg=vGtt!_t3ueTvr*QR0@vTl*eTR^0)2WPwpS5Tu z1z4AVpD+LA^RfokyO3!n&wXdMO#kfVz{y7?yZPdx$|P2q!l1=T^m-NN?qwT};mJzj z`ts25m_*l_#NjG8%eOU)EDjp&sIeZrB4023tcA?4QKk#G-cA+y@4jl5Catcde&=3d zDO*ALwyMzo%{2XV;i>$8w{Oy0@ZHeyTbG4?9#!AOU(%~x{Yzql@QPpO%G{&v8MLA; zcfwTNhadjS;kdSor9f^s{v9o&EkWL-|6RkE&8wPkTYY-ReJeG~Xrr(wJJFsSFhG_T(8jE9-*m1pCv8^2O(#+7bz*}<9VXz--@rR^zYuw#Bw7Pn)#u0-i9O$s=|znZqn;cBWD-!+2Wa490909z&mR0CB7={XuQ6B*T%QP=xN z@8|St)(ETRin5RYMSg8U2FOU~d-EZ_ubPA06XvslS zD@ee_tO#3MFK~3=It+-;1)G)7PsZ*|tc#28vZ_|SvMkiA()1y!6nFMMr1dMN`{g~kP?GOQ&j@2X^}3p zjtkMo5xp1~jy~>BEk?c_1j_$6v}xSl78ZpXlXW4WHFmyWP%;wvhE&v-54 z_!{DtpmyW-CpEI?+)VS`?v4S*x!n8BJMvvS~mBeaxI})nSoZ(5}Zt zL*w#A6ksAUlfd!E?p+Q|S%If+Psw2V1w-<;p6Y<|uwc6=_5$bhSKR!ff-7V{L3%g8 z&hDs1`SZ{6@8#DtO0J*V(4fj}Ru%?UnB0CogMT$8Hg{ldqK{?&4asA6Z&l&h1ZVyq zOxKkWuh;dz5grrP@P4o4WDQH_wl$bo3jF4hdEq&{hSzMbN%myl+ZIiavX-}Ivb+T` z_T7g!_s4~}MGl*BYq|qP5wjBanuhlZn-?~I`9 z(yxkM@m?MBHr%u7GW8KHYOZ(SCw0=vOBZ)q$3O4)?5X){a(wI4#WTL-KXudX+<0W> zt0j@Ni_<(V{o4HX%vRAx2IafGOyw!k*LH22R^RnEPUNo9v#~2!`ID)SCFhrQsr3WO zAD`1lztkt&b9EL->}gk(!+;rf@nGHt`p1)@tabm= zhS0YnM%PGpfBYZ)VoFYMsmrs_Sx_hEAZ8$f&z8^uM)_fCNQeQw85eiNA2UJaASk(r zmkS@M(_|5j;1C%80}38>ANnehWud9OjWI(5%GyC5m6#g?P*QRq^5|H#eG|FFLH@~S ziFHChsAG?{W-Jzv&HmqL&8{klg{Tt`kh2)=#4219T}uU>4vgKWgw<5>fu%su3^t&6 zBSC}!`SM;EK$LApz`1x6ueZ_x5fmiTdIPM9zDXDBDr8K)-4o#Tx~TF(g?<{W9d&Jg zs4Z(0-bo)hi9t45X^>a*9oWmG);v}SaY7V)b5!lq6crqOLjyCToK>X!LIg-a0=4b` zKi2}q^2eSPzy6zt7}}lUa>FFRVUVo=vH(Vg@sEQ=x(ZO`psI4QAn6HCpUEYedclKp3IOiDxorVqLYoU~l1aNyYRFnemF%GzaodFaBv>;JV2hx6E zHt`%2y!K8peDWY|NTYGYZtpphpnQf0H~}cm2{!?$ugd(zxch zJ?tr5kH*078oQ=BJ96*36|&Ho0E!`++^$16Hd3 zx@vPv!_1XhmqkDLO`APj|J||X&nL}}HI?d+?h77F>oA!9Ma$MtO*3<2A2YHJ&nQ|| zJe!oM+Z?pvzj4N15BSO-u3lt-w;t>n0l=FU%`@Hz{e&;>zDc z6^?n;n{#t}s)hN}j2vzUxP|#mojqLZC=SU;eEj&$|Z`?`GJt>Tfdj=D69VD zMTPdc^rrd>*{#QG6Y7>7$eH;z$+a%!dFh%QW8U(q2KC9yxRy`%d-b;-G^@|b;#}NO zquX(>PeDK4$vi=(YEhn(clM9_LU`J%$StfqrhJd2X7~_|^t4x)7_T5LqP5k;K@W@gT2n`Kc-I|GEH``|5>&sFJ7y=MlwElsdP_j4nx2S`)duY#-2Qv&FO}MW%jVA zN$j{X=r>0mE{4U55LBP)=7Sov0I2BDU`L_-gL9yX%r~qeg6|tCj#hvQwrb5Ay?>r* zFD8gsOe88GvlF^H6S_Ib5yGQV2F-JfJw~}X2eof@Rcd=hFvTbhxxGnP+d{D)bXHx$jRKjq)P1~BRCiy>kEqMD%7u%>4kALG z#J+biErg65DAH-*Ke1&w=t@<6lm77YkB9Q|A*Z?Js-S@k8$?GYq{4ew2J;_yYiOn@ z`hJ0^k~@MJBp_X2R)M&!ZkG7JE)_IFK=TZT!XW*TehwiG1CR_zNhUu2@1BwT{0N;< z%}8JsjFCl{IgHlBXhl16e#uCDEz!H3S7U(+!y`q&(u>pSj(QP+(dGjQY?(QzFAzHZ zEDd@SzGDq6qi0~dU|^=CNyHF39L4y5ven3F5SR%D$NWc$fTGJdQmghk6Q}6|FdO(U7EZ65eNe> zq0RSI+KecsJ)9+hxV)TY3TvyUyiJoe=$cb&QB49hMmc z{J!3-=0w8;D#d@gAmm1ksYCoVdvY4Tz@pwl5+R=7J#&-)jp37%`u~jhJ$t#c-JKJ! z9OLe6H!a?D&5?g2wJxr!ucgbn#aGX#Gek}gkd@FUXKWa^SKIwa@{vu+p`9Co58Gw9 zJf9ZtUbyIsi>F`MbPFFnpVS~UTxu@famwlvmsfWu<+4bBW!Xa7#qv3ARm-$9*{cT) z7SehSGkNPb%--VYFckM-{q^PB_>L5C7)c#B`)$+x3uhQ*k{FAnx_dcC zH|1z=dd(e-IG$7!z`fJDIIO?H@_F#^z_DWz*{jA0TbqC4Z|j@aSh(Rp%DlA>`>xM? z{>MV+Y5c67NC_{`=$C*SM*7FZJabyI#kPIx^Fu9v=e$@G-tx!oy5Dm)&FkRKp0adf zCb|35vv=Fhj{jX&YtlAL+PPu&=LS*NuQzrqI52<0bXm~4-^ucQ`G0*sd;O4f^)0g> z%!WigH(KoEKibuCkCP(y#8M_*v5`2@W>V=jv#hJr3sjr!&Av^R6^4!ydol@TY8oBD6C9H?F7A8N)g^O~`XKSky`B_qlA zfkk>U^+&4K^j9rRM#FQL7GI8H{wiHN9^@HK13}VDpP;a(b#D=kca7LXuJhq;eWJ{m z=j-rpd7-lYd`tD@RMEJLNBg7MWwZwU5~GYu&qC#CJFnExQZv4fe;GP|==e8j<1am< zQM~bSW1*C(NJt@K9#OcI+zYu_D^zj0RiS`gkIACm`m4u?|M4&?A8ud_Fp0EKx zqi19AB8v0)jf*I(BOY6~Iq4wwi#9(5E7S*Dx5Gu1HxBuVV z#u{=8L67?%2^l>s!4MvQXnCZ87wAqr*$$_ih+Pp-qfi7PWDw?Iq-wE#TQ|ah7Va`| z(yOkpmX<)<54w^26!i1=QB@?1)d-5*V*iZ`hyo;-%xtxUL^%4M=KZmGfiM~4r<%$# z>aEtCMgzm|o%EXA5}Z&N4`zoDM&$#?`J4P0`yS45`kNRU^(JZL5;#eSpn-e@3PGS7 za0wAc1AKd7V*6N&dBf$Pqo9D~IKwm=r_M~C1D6N0j(-J8z^JGrRPnJx(1CYdY{9I#0H2sI9BUFcooSdscORqWEMy{wRfJd zcL-)4{0h4eU9d-y;Es}B0)rHivr5gS#{N7~$vvtZdM85lA?Wj^)qW(1GD4!(px!b_ z@8LrAIMS$uA!Hc^wSGbt7b?F20azd?4uZom?~e$NW2%9^pB*QLeCjGPx-d|#x<4RP zb$FnDMJqo4^+|;5tAVIbzKCf2EH&)J(5IA`BMj?{CIP&Qps(efuRnez{E&Beq$kaA zXwfsvu!YV$j$Px02F(wQaDH=r?RM$)=Y7oA!f`R;Eb_4#Ns)I<4%NDcb+~mdD(crFtXwzr z^@hmP1zBstUe_IbXx_N`DYIhh7kBckm2uk(qhinbkN6xrmbOrv^1wF7EJSCWs-BnI z3Yj52x+U&gZ+_h_JH%`P(md1sr2l`B<{#xh6In61LYwxsu9@v!DIu&yE;kwtW7hy`S-m z&hV?}4tMhgCT>d=P~HxoTNbou^)2hv^YsJiGtQM?-MQ>s{El@-dR6zqyf}89+*a|- zVnK?-&v!OeCEUDqx8j$$=&qq-Czm$HJnr+hxxA~t{wvqZl-IRnKZnnU2gzRv;@<5o+w8OxhuKbLH=xZLsVZPOq3|HFyY zmL2@TJ&dt9w>Zbt_rbb;(VxK%(AGju;|Zb>bW@C*se4PU}sQIggKX{6&+hIGMrGlMk$}+ za8feMzEHrEKH%=@u_EGma?BP$TaW67_In^bTMT;!^K0zIB9f3ttEr%CA-Q89gg11> z3N@4kqAnt%c{2z(L73Qsq`{E_1lxG`!9Ijs1EPI1+Ks5sG0@Wcf=EP0&d2DYA(4}? zdkBFhvbH+ZXf#U$F!*^(O<%V542+#7H6AgC^t12K^ z)fAX97x6zcW01S(pUjt0sr#`RgCq!qW6@n2`v6}Rn#-}AN6oU6!)hU9VE|MgH)6sD z5RGFC5+KRxXI=_R;T#W&64x(4586gCAxU5FsPQtUJvlU3;ctwpf8aD?g6#vz9fm;s zjwhHzDCB|4^rPSfVo4(N8hl+D5EIGFE%>4(tM1E@pH{AqaIT)ZgPh?SWLK{};<(sg?HifZTXFb-yxA)shl@=rUxp8^<$IJI%_%QmKH0KAV&8<_66Y1% zSj(Q}vr_fSrCFSzJo)9X?WOlE`zR2a6U+1d@5#{*~)ynX!GGVQvB=c1HSjy00qu zv82zu*7QO54`1~6|NGFv@&WG@P{rRk?`^HRx7jCUc4eY_HKj4~Xn*17))Pvll>M0k z=jHR}DRwV&k8@Xq3&K71j$fR=gcIOb=Jl_V#gO8(!_@25LUG{4GHQ2=NAF;9B9&BL zbDD{IUoPbxR32scUl7DHc>hJ}Z`qQ5?>~P=?)dbNQ%%#NJ~Cn{?)sooNX{M)ngP9fs zL-HTj>TX;OmuL7hwd6pkY!#!fntS2^{k)SEw;Ud%#w#K7`!90$mu1+W>EFiv=1KSw z-sFv4t?p~C>mBcEiC~CzIe{ypS)y1t7m;~c1zufS0Il6cu$K`H8Vn#5&<%z3NQ8BS z7L3E(7@KNl0Yj50w2ZkwM9HT2L2G2jK$m8ytFJ?p(}ya}G!jT4AZrTK*-9w(QG#Qw zLf9>%t_B1ju+cH@0^41kzyt6&N(@HVb1#B-CH-pa?m!)M&7N1 zTt9OG6U?Fab@;JZfK3azMOD?(Ng>v$(t%^lBfNcSdXQzMlMp(y#VyPUxwm+=?ldLt zQH`bwVZ1(oy@o=mZu11v5e;${N^QluI2u0s?jB^1VOTFhMZ?EJC1}mr=!hRn49#iK z&(QWzTZZIjdC*?aFxw=U8GLqM*MbT13-7=j$BsIxe<8)Q4;9i7LPf2ViDYBCy zw}>%a^0RDp$}7R;wN>(G`C%XsqN*~Nv*T%kyzJ#c))|f`9`xx;yK0^(NdCI!wNGi; z_Tvq+ge76KOxQ*}Y+JpfGmnw_&eM!1n@=}2PZ4h6EdTlsw%Ij#g4CCDibHPW<{o$F z9TM!xkGH#E%Ii#Gde*ux%FfYZRtu?-GOOy86`r|~e(RHUQwBcWV))Zse^OmsugAI( zN49olMA$e9-=~K?-3hvqd~!BGK6w#kz)VV&(Ywr3!kXrGWW3~#gFG1RLv3HD%^}w< zZl1x|nm4)iK{)lz!;}JQ?gK^hV(Gu5bzZeK?GExh?h|loqcWrI`y)%--g!!bXj5Jl zIUbw3wx723Kz_;YVWSAP|4w1gDgU)Cbxs-g3_~~j+zTQ5zWmuTynoMELc4bkXF7J) zHJB6~@f2J8EUKN*;=q#i^l9DswN2E%=Rp$vfK{DcRmd{)TBTN_s-1V03cz!T^2a6) zS^Qt5E!U^Wj$iA)b#;kf)UMMr18XuXzr5rZ=J#6A+Zr|5(dpuBnaTG~isfd)WoELt z(~cR8$e!r=lae}9lev0@+v|!Kh~?uw$+y<0M;Z*d{Z-k1&Z%nB&EjWk*BlT0cgVkF zUh9WE=KH{UIpx_wQv-t*{RyH(e0J*D>a9l_CSR|UorN5f=@EAt*fUEHCGK-~a*?TUhuQ))9`A`cjxsOoUQ`&;}zdN^wCM_3yVI71cbUb%0mJ z7;W{Oj7iF^5*p$u!Qo~>#h)4ka5(D}Q$T6Rf3Mc@5i;Zu_y(ppW>CX6Ml>QQ_NoV) z1e7%$E27)hC_3&&=nL&f&|ya(sQdd%W#)lVogdKAim*5sKoY5fKtEX!W5M?Tzkn(S zrGIj(AQ*w$fFpn+yeACg%7)RII)sHo2iH_-C%x2DG!Axqd5*j99ex}HKW16;Hw5(0 zK-zmjhk6?R7oAGPa7XJZA&7xF`nX2{dJ_@}H28|1@qD1Ks~@Atj%KzOHA#_xE;)?` zLtg=J&3b=#{tlF5KEoI^Rv-QV5D|yn7|s|FjV2n6di)@C_U$CuIzgu(eI|+CXNNn^{3UDV}O|s>O(2jr(l-~E+_ozGUg2V%;|C$^5w@3qWEMf0YJ8|1br2u*C z|J5=w6D7r~WeCFT_g*PjU7E%&_!J+Yxg6#VkvCygLl-re5ItL9GsBRGBg7PyWd-PT z!rU7|k}HZ?5Q#6yZcswONh09@8JbA8;=`jOqPPWA9)#*^siBt$#Dw zasDs2W)(QD6&=y0QnaoFw7Q!&TrW&W30uJhYFm%oJ7W8jNZCZb*--p(CYSLz*xjx= z9zyk$jG#8rGPahmltVMNZW9z=wmU9fC@s6qZsVCZeEy6*{&Rlc>ZIUTjA@@x%bors z9Wv~h^e6jwO4B0qzgD!&D~-(ctf5rhZ;reoc8p*GK6Lc1WenASJ>2xQBBCI~(ZrBL zx9n^7v2hLdd2I-P6x!}jCz2u=@ms3e&f0V}<@EgQU@i|j5L?Xs#Pr3pB~nc6uM5Z&hMKXX?Uh7CF`8OW&WFsVBOT^`Y8*R zzgRwO=+|r>wN)G{Nbz(xPmFI}xu(ONmFqC2&)s^f$YPF8I%n@<>Fydvm#C23x9`4S z3wMi|Ser8Q%~SSKV~ReH(!Nld1jH}08;id)Y@>wav*3<<_eewA;2G(z$4#*uvlM#1 z$6JrCO%AEQe`k7DI%iSCytrSJI@S*mvr)ewCTxucv6t3E3+HymXGkYph{ml8hmyg#nT<_R?lCC_QV>QAEpk6F$S-89C z&^f?Kxa*V&@(`~cz3k)sTk@YACtsH(a#Jb#H&6ZK$ez%D`176!F?6<_Hq-z-q=Nn~ zh;x^)3z=1iC`P%0TtTHEnxfzMB*d%#6gPVS>Epb~$IpcCm|S3f!YRDak-4CA*|W7=orYYLo&o^R^tTr5UcJBZ2=yUSVx_(dQq4F z-NHynK4E0iFkg0ygMo)6*YdJB!q>%gQ26aY-D(+&KNVVl^7En6N)Jo{8|&owUcptM zOGVCat89d=cmj}ajFJ-&;t-Y$Km$76DC7X#yzjw128Uuk033BwuYW}C_en|!H>M0D zIuOy=ifKOyiU10s-hmqwxc#_;i{=)RiK>Am05^d`Hvk?x2>n*mLg}3fZ6O>>1&k5s z^T5O(#VfR;**-`HjCp9z9|y3G4w(kog7`g8cAu^jR69(dke(|0v?L06jaU{R{>7uf z7kIOpb{lwj(`f8CDB6oJrn9P;ra7*97SQ0y=tMd&xM~_nUR5EC0`?YqUl(43Lr(Sn z0BAO#BM$gQLbOjZJOrdKDYx+$(N9OHZ7?V&rv-41PZ*lNZEzTL)*1L6^tTOFW`wDo zP*q?-%p%g|b9=O<7bI@9dpy$1K{I2gvns>5ig z*~AH#vtY!rk9}(5;HQ63VL)0i*3e<%e`vPKFIXRfYO+QZznu|wCS|QL{aM#In)!`T z&-WbvS?%5WO#@Bb|BRRo@s2Z%wI!x{nG7d0QEZuPslyq=lQXP~=1N$eb?jQQxZi5T zZN;sC-QrH|uPb<4l|yBjH}cAkZD^Pswpl)*Gp1#_Q*e6~-(W)=sD_-jI`bS}L6?3f z<$h58?5McPs)De3D%pu!q~DaIIH8-;vt?6qXRGPlfjK^L$#mJ1)b;`6Lw8Mk=}TCu zQY!_NZU8!C=nB1Z>Ychtjcwvv%gXB)iYGg!iu51Q8DVMK)=dHJ4S`t-UF(JoL9*i^8QebL8gyUB6Y8f z$#4<)PBE`ea5Dc6BBCy`<%8wz8MBfEdsMFsGn8b5bc^A)-ihu?D1N2jR~yq0U;CjZ z#_q&3D<_w(7ptd5U2LyX9-HXd^&%{&WzmX>jD2ONohdOV<#q4A*5$4Em2Vu{@!NOJ zG5&m2wf=cL*@0=+ zH=YffdqL=#j2S_8#c2!D(9RkOQo%Yr3+)^xh-;pU;fcmcfmG=NvM}9>?a{pgE;UXe;$m4Bvz`oDoD#v|M*FvY6Oc{ z0FC_vtN=H%ZmnS=@+J$o>u6`|wBB&Gp-T_oEr6C4u=CNtUossQtL~|^Y@OWFrc3v+ z`5qFx@*tAy8I-Jbv5J;M6EabY29SkNlq;^p5^8%Osv!XH?xXT<2opc%I6-xFj)^VB z(3s?k>8_}w84Y!pC;{29UxR#V-5qmkKj58y;J_i=C}aKhz)&J`3XotO^8iKjy;lGC zKIL$3LaD$CD&k1OK#+o|Wf!k-qs3)Tj?3*sV&Z`^u0qJ_(SE&L7zbecDJ||Wf-VKV z+uA4Jzso?MRWN|`7FPkkyD343$@oO*hHz;dEDLnB->cQ!Xg3;kFnS$`XmZ*!gl>3A zz!}lfxe2%pfVT^e0}gp*N zoOzH1`JF_u3_<>De;!Nd{IX27jd?a;t|Zja94YG}fc3=;w0K65Ay-!oXvTDFv{ zFauvwt}364#=-O{ROtS!QK5HtsYS&*<#09JF9SD!zBdTZ*?PIEoziU~=!_Kr0+-SI z@O2PqXcMV&1(0l(4FAJXE=g${GHH5kXC^(at55@@;3JKx|2YnU!zK_gXKm#1iik{DWK5- zI`o%6k*8M|u6%XeuI15#h|Nz1GD~*$U-|4@_=>v?5kI6C>+p`>k#xFy8EzNzvnZ_+ zN>!m>LHM4!g0OBX<$#ImNysve%qEN_e;Jy!LT~rpp(41pg7d1)ysI}%wa<|)o`p+_ z$z_2_!eie5*-;y_+90lO{DZf3Ygv6DYHuzX+2TIwF}toN&_OlgSr-zXZOJyDy=GI) z zzy`;g=9I!!{~Fx9S7)fD>5%>Qrbtdi-miv5LwOR_ws}q~re)-A6g-&v+asO+uH#!= zh2xuh4ll}L@$?+$FZy?S!8QGRJ-VIAJNwJ?0~&8UG)-K{aB+{FH6)X4SmyLuP6)qn zZqGppOE)s_y9Uxr*5$ltRwr`><=Pv4y8QZDhtQy1B^moTL7p#JP67H49)};~em&eU zMPXI7&n&hxBeJwmk$yV&)BB3OEjyCd1|_VB&Hr}?gW)Zk zzR7R0cj$`2_*?Xx&=t4*2T2?3YZD~n6{OXt?+wV##+-;YbFVhINX2IB%34%+Er<+_+%4W9|TAo|j5IFCguEhbTMT*9U6?eaS6r%gWny(8a8^kxUGqRpKC7Cm%6~h4G1*cPI#nE3cO^sz z7JX=9b=s`icLIe6p{E>L`PH!UGG;@MjS49&V6@vnN@_Xt+(Hv+(CrAsBmU+g_&U7c zWjd?Z{$!A!p|H*IK|$>4R7l-IpI4`mXTyd+m^Qr*L6<^Rfu7TroheR(O^^pYJc2Ib z#a?{}c0rIT4(}`VKHLSn1p&vep~5&G>_@v|T044H*o`FE#cM$;CJutcTXh)8G(d@f z7G(G?-h15D{1<)%gSZOv^*wSUa3Q_x6&^%*4nTrGMbEt&s6zM^N}rr4xf2Sw0Z>_Wq#bR^HmK02z#gb7CDPhf;%@jMze z9~;a-*PsI+ULw~A!A7${wh*>!kXIgX9*i=3(0wS-hgV&?9V=OC)5n>R9lgl@f>r`Z z9+*QsT>GRC7$PpnID=TZ9nUBYR41rfSxqb9OhSJ{Kpj)Uhe9`qmf(Zn58>^y3J9`; z(j-xkM^}X)5n>5yD+s9vaWJo=XG2cFIEcGNeKZKH@x?13dsw}g;K(Ge+&(Z8;{*wC zJab5!7^fEPu(Xh92T|w&i}oa;v^^!r@@mZjmM&K1V7;mJjS7$gmym2nYgD5YfCh+_ zP4t8h1a_rF0S1{yB%}0b!rFz|<-)K78D#Ji61MO(D8P+&38BP*Yy-0tsVLCxbX2oZ z4-VutNL=zTVTEBK4)p#Yh|(jgI`e=qst>D0!1RZaz@cE?(PjYyEdXmh%Z2MkFU=WF zqzC#+R9^}M&lq@*Vcy{HnM1U0d(cD9RKt;xK9Y|GISt9?V%o;L<;I{yJ(nn4ETFd9 z`bxnu&xTiD4oaMKs~7K8L?rz6bDiZE{j|mLCgQJB!**BY{_GISj^Aszy+#nFq)wIv zx2!Q{O{5%Plt_K*>wlB_v1yD9-aVC}>SvYF4qE$-V-;jkQ54(0^ubU=ptHlY%JhC- zqNjI{UHi`5jlye12^aUTpBvYr`jRez{7IG>y9vy@s);Gt96^(%h=YYbsHF zAvm`E9@gJGz+bxvB1I@?;0yHisSV zvYneRc}Ezx&!5TAy{*ju^==a*?dV%YW9wr6?MSmrw3W`Oy}_;nvd#tPQbN~mRmA(H zDKn24r6dhB`7N2qy^!-oe#_Q{Up_AC?YZFjwqd{L+ooOST@Cv`J-4W!_xNy@{~zD4 zx+G(s)b{b1=NPxQE;hZSQea@6zDHQ(*GUi39_Jmt^$B}Q=xHw9y{$1QDBzmo<^g}n zuLhzC{+x>Qi=U(q^yEaEX3cr`ubMA6wKVVZUVf0t3Sb=W^-YO5Hak}s==9PqDR_Ky zP+jC!~WICRlzOY=?)jWJvb(x zZLsS;`LOq4kM5TxI?`WuvY(1{a-nV?UpFf3GJRTT{q5w`#y9TK76l4QS);h6{5WTB zxyabcebKeXmrnDJyr55eH{v~V?9oo8`MICSza9y43!mq8&?BR5gQSkj+;ce;0v4r5 zt2Wl%9Pt3zc(lqX$cbfFFu^HE;UZ-9Y5gt+NpcuA^lsi^NGk}kLw!wdib1tKAEiXK zwb2H2L*IA+A}JowZZoL;x$W8$GAnXLJ;GR{j49X$mN{AOMF!oyLN&Ko~dTt0IZ}cy0kfB+wmjh8n^w6kV7gABlewEQ%4J za#^*uE`7?(sgrA=x>I;INW$d|S0jG*8Pzi-s*`6vn&mMjBV8@vVn8OL&^7Mer9*?b zzfVBAQaMvj1cAodk;o@IMuKS)D2XVKraJX#g^W?M_o!G@`#0ZbRU?Nb>N#R85!Y(b zA>L0-qF;n1R43AbWD;~TF%U7Tpor!N!6G6PaC9u0qbGp4i7O^mL7gEjDmI&8y8cEO zSKHHw!)4kILf07;%Z_4#H+#Imi;aY#S~93#grf4waYTp)3iI^>&jgf*(fE!W2|{#B z#_+U1jQ*hOEMX57Q%IpukCX^s0=EyhLm^e=Lg9TN1GkEFYS}S}Mi8U1($!flMAGA^ z1K0=AgLNdxmPdVhOQ5X;Bub(3{X~D2aojXJ9F&ucrrWfN*5Nbw0u?};C?gG>1<9!4 zcL2Ezy7WTkV+=Tzp%b1 zVNb0LYJT(^^t=v`vlClpuVJ_!qWpBGPut(Q)TPe)&lWFL+1_73`M3?zMk-bHZ*aHg zGpe91a72W(R62Ek7ALbVQ*uXOINK>aPQl_<738MSqpG3essH5$x7N#@9B*O#HCC=N z<|xBGFSKI1@btJl^{a+!1$0lE!L)5jfx^Alw9nCQ%N$LU&wHzSFAJ)iC;cj&9K4-o z>=t3ZL#DqHxM}m;s}mLk^SSN^7RXlWnf{w+=c-8Yc-Av~$|>$57$RN)kItLU8{Fiu z@S>M8FeU$uNv!{BVbkePJW^I}6P}=pPaeG1ykdXfE{ZZX;DvE_;XwUum(rxDhc&0> zbSA9sS|4&z=tupY*S5m++KAWIrv;AkWO2;NCRrAl4z{89-Qj83-&1Z351m=FJoxrR z)2mVEe_l9r!0=&7Nq!1<>aRd{-hFarDWCdvdT&fgzR=*06mH1(m65KWC-n&jcJC_n zps{`s@Y^igOb<}nEnudZ_FNajrOm$Sn&-bHLq4twIHmoTO4c9>4j z_uxgDWD#qzqi!(6Vn?Hb@#)r0tZ?IHGgndc?+@>38M;4k*4QR>o#P*ojy9XW@q>Jr zHoMvD7O`XfD{lo3)nD7%9;8)yH2$kkHg5idJ@@rzle#vXi(YxwF?OZ-zyBrMxVdTH z(=9889G_NiSaIUe#+FbJJ{5eBMHAk;wmZlFNVBMW!VZbiXihz>(31lX&PzZeJ9B_ex7j#^vm@eC9MCxCX5 z!iGHtgr5*A^hXP1LI}ASbU-G_-cGt13zRn1sK4~ zAq1_q9$c~Z5210)PC&^GIpCuu*@9WHyFjUo$=uH{1H?jb3V;ocy(vDBr)eN`gJP^| zty_e;X?~!MgP;NU8ZHv(slS40&O!ie1bO3`R0wup)o0!7Uk}0baTO7lj=tj`6n;%WsKcl&e>HrglbcEm;=(3P?1kG?|PF7Fo*q#|1 zh5`s24eukDG?F8u(m}(Fd(Vlok^qz=9Mn8U!4jp^js2|=I}_>&LP;qox|v`{y&(o) zJ4zBquw{mduow_Eo$>6#eM6n45v0OrLjnT~ggAn31+W>SmBnzP2=JThG(BvFazpGE zK#ByEtQy1(sh|ur;2Qi0Hy)t=5kJud91Z_1%)JYx}@ce+3I1`t+CIL**Z zOWrD}hCCYe{Ai(BgSysm(ZsKR3(}3>}hIouSjatyyghx*&YQUcoMSYXOBg zn&z0DP9!^f7$bauCQ1ut<|zmm7FYTKS z^x$qh1<7MBa9sg{DK?3zi{93+To=A@hkLrutA45$D1DPlZX4z=FNKU>zZoyLRZez!1Si&S^10|O{$GNyy2a<4f4uqQZdCNBSmJ>$}p@S0-T!iV)K zfvljyw%fctz>6E8UA<+|uidivcKD9z?h&@l5Co`FSVj9-c6__R{z8~}Z9_~(n{&ID zMS4Kjm(t7;#)S~tm88o@8S$&H>~^%8U(>{xV-A`38v{T0JU`sx^jynE_;6NRG}q%~ zbF_kySG-7HGMy3mP3z#Ed0g4&Hm0Vo`&_TE$5+ap$*)hI+%Y7|S{7H3v?lE9i{nV) z8-$*pRdNC%=kn6SLNdM|IOW}+s~bgfG&$a1?D}t;uj>wlzV@2AQ?PZ0;_)2PMtAKs zM#-DZK5MRA`Y5ZXPkeYF;!FBJDSu1|}2z0tIq z(sFI}iawz#C(*&ed04rGuL@cf5-9v8ce}{gey}#8KIB`*Dh{h``I)0N)nqYOKepbZ zwm8VU$0|z}H!|mi;aSJ0!zbo#yn9JVI^X)~W5>5mXIX_a?<{Lc+Lowu?|Klpwyd?i zmC;jK2XM- zF**Ns{p$EzY+#3XFiz#QI1hUC=q!rA#Yw16AfY$x&>K798_y);=_Uyh_-Zl5=pZFV z3=2_bnZT8uP)*UANp(f<<7e2}a18x8w4g(@p_#>R5Fds091I-}J3YPthK9{+RCNUX?Zg|9XrVI*SPS5QQIG&e^$`#Op%WpB2?*K+l#-_*3^;F8DBbv3 zU@#DFpaM~vEeY!Jp(-v|7Q^m`BsSRl8nlOJO|l=7n5Z@_YU>B-a!_%ahvNQ7TFyx? zp*E_aln}?S~Qh z1@0WE`PE2?5V%6X4<`iLi(R0^%P`2L)@oW@xgM38;*a|<+joLw0y&7~9ukNE&Pb#o zr3Bv!g!*=HPC!?pY9L6aJ)O>DCARvt5fZ0nF<=#u)ejwHpwOZJ46I5dUR(ooL?BAw z&|qzWjKH7{au|s_jR}G-24qerTtQ3*KBU%1-^69LHM1_@>e!lr93w$RtN)LOu{DA zB`9d^AQcxBD{B>(A}SWF*1heto$q_j=SdLTneT6|ce&m_AV9*if9`Xi`)uT?34({& zgEDr+{eU#1GPo{lxsdftSte^RYjK6yVIHuElJvXMp?QI!B={$|sKIzDbZ#O(hS10# zmI7F5uxe8wafo)9HGsAYZxRhQBsX#9N}))M`ZE|aV*TOsVE_J(CJ#QEIu+UnV9PBX z?RFS3J;Y*xXbb$L4A*6>qyo$q+~GPX1BL7L0~*%25@Ir8o0=vgF!S?OVV=`st0jAr zl-FJJA|5T#?GN{y{JuB;A89qQZjnYxx-tsVeIw;tzkeFhv0s?VYiqL=*!z_kh09Z! zR$09T-V>AjWadtmezGq?L7P5QuwR2M^Wv8;mk0mco)YjF= zLj3m3k=d+Wc+q(uXSz~6kmB_;s<+BVGf8A~dSj)-lBCeJ`zwWCc=p#Nt{74MLYY5P zJB@xi;Y5%X<6uc;V(2g)&)Z8Bl>c(;htD1_*w)m0WxV{tykAdktTJ22ckRyY*j2jb zhX5D>7JGl#v-0H!uFTq~buA3I{HWIcrN#c^rrv^fJEbzf_gYHWiF?Oxv^yPF$WxkT z*z8!HP+gP~+5NqZjP4cMeZS26*czXt*t5DK-DwD)qqub#LWQlMa<#NuK`W`i*Iq?JAW`g#mkyhb#~WKImxF zWv^Z_z20McVpCv%OZKJb0WR>U#@Wby+VO!|Ly;#RikNwU83Hgcebe@&tQR zh&&SpNP-pmCybg0djwrS?ZL{-lFy-7o;b{7Tev()-Ne9;!rH-qN5fPHYXdC?93B{^ z+hR#SSUC-Kz_IRzWhX+`2@97CKT{?StxO=vfw8m7m4oT=0jM&`3k(GpL!m$@ zD!?tF!$2|wfwi|0`~~+8RTD!4MbK=hZXcL_3u(kLeqA8HFdz+=IbEu&;*WNMKK&7o zgYz+W7I>-mxKv2shQ0tk;QOci!NK_gOQ)u3SmjfYBmC*}_jlhTg{c|#G{01pup+R~Rmtc<|vNP@78 zIE6XXR6>)OBAkvuP!;WfhPt}s_^IMX%2TBbFVJ^X!H6pbrb+LMxS^PZcV7gqis1+N zAe68hjmExrjbKW^sH1S!WDPq2!%~Vig@cZb!Ge%i#@jP9xReH=44aIBQNRb5;6C_4 zo*2#+641j-j2e_SE@cVO-Y$F6}BMEONvxQLNN z?+Ber>ryaMbUCs2=qkZ~z;%F~(!e^!3_4UICS1R8Ih2H=Q3AK#X)0Hm1bN@U6o@IK zwl89$+6LL+bx;8WQ{1VKgAFrrv6*z+a=uVtRKGLD#-_JC{=G#%C#@L4TzdJ%B|p{F z#9M!ylSGWU$Z<(4j2WYpN@vIgW`3)~!-Y52ad&&J?#oyD9_Gn|&6@mOdm?haVT+`m z(nEne&rIfZeJ-2Om+NsSB0Khr<~x-qZ1;ZaZPvGc+Gw5%B6$A&rYf-#Ewh^SoZmFO z$>p`m7E;I_OXgklUi=<=8Pj3!<(Sy@9d|BK#Leu-zqk_|>iE09VDw-&5zIaGA{Fl4MpR-=OJ$$yAz1Y#TXo7U$0b`!O zaMTnrd*8tlih%QvlkwLek$B!~WFuDEbV#qXAWyn0q{on7{I3;QDBx8o$s zhuNjsR*s+Y;OCZD&zkFxio?b_TW))Q@00;!r+G0|7PoFzUUAROk~$aKy>Y(dC~=GGTF&k9^6xS>vH!keVvpsja}zznWH%Go4mB5CzFM`_rTMv^MTzv+ z%6rd4Hotix-BEHQ?!;PW7>QXJ_{(j}l~?RGUP~O=cc8^T`sX7en@TNblJQEfFo92A<2S zHV$1Osdx{R31^fl5@D^3lLMBkOnMK@*ZvHTn-kX>R2Vf(v4duausC*?qMu>XiVE;Z zg#yfiCFQOtw&B0&g#{$EglY?cS(&urujkc3 z+`$e2powu=ispEM>54<1DI{Zk!B>G29_RoM3s^6#sZ5mq*ehEU+M&Z!0gg-8iF>h= z7^p<7ECuB1aQ;EF!tV$jnDB{YNA!q^C-mmq{8)5n~rUjC(0I))oR%b!^eE?&~$KjFD8y&nIgXRVAHQsiZ zR7Q;{q9iM#T|t9$8b)Q#>yM4f$W5285#}fQf(z)!wqVz)r8W0HFfElH66R~4&7;IkO)e^gS;x?e;82VW| zg9Pvho&&#UDu-dSz%};agDnZ4RIh(p&)!~Px1fbFF7bP3mpdyL`S?~H`bFL15Ev-5 zwq3$yIm>J|*;mGg*WFf!xx2mS%l^GKLKHvK@&5G=?TVY{Sj-E}znWCcj5r=r+`FGO z^IG)tkWtS5Igo^Za7CmwJK~^h$M}oh6K3)bCDIS11nK&;5jGWb-0am0%!FZ`M>D(j zU9nMps<9szvDHD=BVhF~$J%Cu&5<@*bQZ{y+v;Pt`bm{_Zs`-ac^`Hci?}|CrE}ig zpWyPri7k;m1C0d_pFoS6u``@!1e%{~>Ej|5fK z2zjkm%dG+nGK1EtE=T^HRaKbxNC`%w*SvtmY0xxrRaiskfvOFnns!smsKp;4ehH&d2XCz!K;)uT8Ss2bRD#Nds@v=*G$Pb`-3eqKy;G04dJT4|j{5R$vyX05^_D zii;EQ0BY!9&4WVlIH<{q15LsT3s}%FSFM5fUqhJXOObJKiA!p^L<7-+iP$(0+7_c$ zC8K4xTh3~?wBJq63~?2{L_?YbfKo*O_`mcJhB_AvpuXT>5X?pSiim6_{Wzw99x_|n z(cXn<3%Euhn*MZ(P-=sTrr_aVkjx)itcl5JT@FKr+K%;j5p)7YR92Xv&{`fEuM))yYRYXw35kpm#;TN4L>~-*e4FAg@NpXZR549B; zNFW-)Vo;9~SRy+(%^}{*Uw@{PUR|ZsmDZmWrV=PHjh!mCCEAbmVYpV{rNY23$_3Lq zNrZdz2{ebJH9(P$3c7=vtRIv&HZ*pYMy%6FEds|Iw4MEfjgWnmmGP!_I~wk>I) zZSm$goi&|vo~_+prwXtL$y{LcJYq!TrHZ}398&CGd9_b%6&c*#S;0;|mlV*mx!k&M zZ|IEcGjo(qdpAFR9I^b=`dJfQ-aK`gyt<81IrYQWK7rBeYQ5w&%_$K4I%k(<7U3U9&G1KVS`$hB8!nxnZYK8>UQMiC&8a)GzGGh44nEMN+ zSKI>|>{|QZyw*SY7nezH1(>Z* zN8e^H)|UUee`ZRP=jM%j(n??KP09=|n4gqAsx!ntx8C9KqTQFjjoJC&+a;EaaWe~l zir7~6xnI9{%*<=D?5pu9!G#w;4xQjTtIOiYprxIcwUfOA;;+ftyb7%MzofBiWkul& zM_OERO{Q;W3oqwqgq9ZW2SbBR0bshLkyW;&{CA9%@6rCxp=A34YGP z23-nRKN)6$qC{030-XY?o`{SgLbQT7Iy&w7uny6p2vBM=6L5(?1bcRXwPolFYrqKt za)`v15c=0iD(Y0r;EX&5sWtQuK|`dbWF2EQPmA;P06=hOlnHSk*=C#SWC!zDVOWXy zI@DBy13~}|X5`ocYwdK{t`uV+AG!E z)>l=gveOX*=w5`dbm#5MyVcI6@ffZTl`VU>#}^1khU6>mU|8k9i`J{DN>anWza3}< z*OtUUCa1-~JVF|{7OA|~VogH?aW> zA71>yFT+Ct6yD%*IFVBalk#s~p^cbc%c5)zJau>)jN%z$I=EqOjKT8+-vG>+-ynT} zb-{o(`K=fuqRt&a8xK~mN|DXt=D?%XJ0D4A%v0kNC0um@vYH#A{p?qfSSf6|G*j;Krxg<4?a1 z{;l0;#3!${O549DYZf_1nBTt6+%DTB+aBVp^snd0q{)leHfj8IkdF2DRC|X*ccuPe zGxJlm?q!(}!8LYDmd(x42v2mWBhEEwlGM`@#vEGT^6{t#5y=A4!{|%-vuwY}T@@-! z>8`kJHcaC=WxiD2UVHDc!=$5K+jTdR{B3z9{*kx5yGtv^7&Ba2juhYbwrI6YVCQ?9 zR(9oWmgGhC{;lwkv0IZv-k0?L1@5LtF0Hu8PE8n>){&jFX@T8+<%!jEGQxs;YEtGV zFEtNNczEVjwXd4FxhBo#=vmpT1(kJI%tyJ6{(4MLac6no#hClvYt{-=z1@A6PY^`^ z4{h7=QPx-Iea^kHp}t)c(iKe+2qqT$5P5K>mJ6xYFTk4 zf9`Ju*&9onI0s9rmh9mDZ2!e1`OAR%{9&8Jp9OlyEsoiIXw;dfM#sGOrHp@(UEcGC z9cV6W>6rTH?7qI4yFYGyHE-{>oU@y&(h_#G2`36Z59NEV`fBtV4q#mxt}6>3#OZ>C(FjKFFk*9%8zq?V()kSIzFyi_x%}{&iof^ ziZUYO7ELNSSsc0YQ+eV2K5yGO(Y+NmKG7!@U-hjnzrYmNp1heELraPf-b3o;2%5b? z9NuckpQJ*`D8JZx2y9f#gwl{{JX3up5@bhNx|yWgVq-!x>{o~buASVil!6(#XfcS8 z1V#e1evo>ky?-Z00{Xr{^F}3FkXC-y8+F-4C@Vj&)-!u!Lh21@L;Mi3(iOxPxlVRL z@>5`K0!IU*0`WW&|3j|;ya<|MODK>B^0zr)+8Iup<)$03pAJokg*YKLV>fT20j-)pK%@RV6M1*WIN=W=XR`tp(g>d0IjV*Mi~ANniLii`*#)Q zzNSpZ&*j4MTU>g2c#bW;~hnYmkx0w=7~V#X@bkn z7=yl+RNKBA`kKK$nAxc0Llza}H|Qe*5Uh5iq<_lifX*izn8UFZFUOz0@Z1Iyjtxgk zy}H@se>YP9Kr|}5QrI(f^&t^6;}BXWkY5J95rbEPuW_WJ$gn$O*u9#xRx#}3ugT4z z4WVm*_Y&fD0NDnMgO(E0AxP#GMuu^#V38xZUP-cEZhJ5SL(&$=c&R*xxv#THOQTdktUO&v2x4oiG zd)C_8^`D)AuM4-N_cyTRv#$QpKEM0XFCk+l-0WEN_}Iv4&t&($d(v?4L~OFZ$Af^s zxsxXZB<0D(ob-uK$$ZN&U%Rj}qN6%k3%~7y@!7kDI zyv(cWtrX_zi&@tw~X4zCHtKl=9g01SR9&W0L&Y6oOU5R{o>h)JmL+MKb^8G=@#xd&p0cz2tM5(d z-*_L|g8Hp)T^74KnN=`h_v}X(*~Z@_UaGwC@d4+`^CzSCXL(w#y!QQ9CYqO?H@{^^ z-P_e{s`Zb*k@X;QSK^D#$YI`PiNj)+AMa++dbLXwnaXuqAw25ugzzl%xPw6CL^CPC zY#Xr@d(GggCX$FH3tE1)`oW)+Q2&M^xPqo2eOzooM~|a~9aPjsQ1mA<@qx4;5+XKA zBtZZii%blH32u$J7R0M~3;23~!?(bWz}B;1=}>8?0wO&Dwi}?5U?&7Sc*Ic|CxEs! z%k=L-hcb?uSOUr{WwwzBN}FvGOW%ZyOlbizZGakbYJh`5`pm;qYwya0Nr-R9w^C4m z!jz#WEg8`u+Vl5hodm)OBDMi_k#)}5WOdZsXu|CuXslWFI=es zi2?n=i{Rsl!52AP{HNj6`Cvs2)x{A7oOkfJPG4=I7j6u$2qb!$^Ra=>;I99gcoC9c&2wCG?a03@suCH_eJuj7Wq{QO8Sck)f`|(1Q*9 z9O!F?rUgw7xCI7~2Op&bofxnt;Kie>q0_q=_z4Ycvk@!^JxRaQK{E&ILQ~HMn_?Pk5%u9=+S2F|t5#&R_$7^a(88EL zOk5qPZ2juwImf6ZYhNv+BmH^s6KEa7YA-wxBwSu8Yb-X`T1IF0WfaEG+`V}F{Fo|6 zdP-bRnRmH5_d4A$w zmv-b`p!MZ;<}G$>FwtaP^;#aO$xf(lj2srqaoum3b#-E={c+j@dHwjR3(Qd3vP)3w z;dxb5oZ%<_Mt(j2PIOYZsZVR%+KB8=Z&bvNtS{sFE!rTP?kuiTE%5qsM8tO=luz|q z?0xG~ZT(q(p|QDX0xLZwa)Ie3z5bixA|7ark z{}M6-2r}|}YNr$g=21MBN8G zVZG&|E)=B~(FOJzR@Pr4%7X|cOcR4U1o3hRi~xUBAw7oTKn#O0P-EDdj~&twptTc$ zg9Zv{G5SjaCJ|v(Z;$jUy8fYngQ2e1+gCU^{ZRm=gg2=OHU-Rj35JO3)D6|)8wr3= zi6kZuULfUJDBxkl2l79BnL+1aA)ujwi2vtTjs6h1Ao5#4gae@uDF(=Fj8?uzO~SF( z2E4UT{L>jhLw|A*Xh|q!mWwETjI^|*Xgs7Cf&Pbq1)~0-Y6IaB4F3X8$Lsbd5)J_g zr0a3wRuQa}Mm!5*E(&w8&7|S@RD;!x9`i)tG+=riG$##(PcVt#hP6>Xc?x#Z%r*8r z(@S4x+B6&qymupIactgZ)q^O(of^MOT9b*JA&+E_&AhTO4>(9g7;TvMtVq+cs-YL&Q%3Qenm{50R%IEXbKGSUvb(!)sCSv9i&EdH%CezuGW;wBMs*>*I zSGIgJO1S)L&kJt&7u8Wwm--I;L)r@iXDz(sCnkCA!XGwimOh#m#lL9Nr=4fM*}G-j z?IVWNS$Lz?3>Wx$#xAuUGaR%422AkGT)?t^EB-A#-7G`oqxoWXlUg z(>|CW`)X}No9@h_FWI9n8LjNvKW*~L_jh~59!@CzzOGGpGluc-^4j6^tcUZL#<~5QYu!P=pTS7ladjj2D zZV13B06J9N>INX)bC5gRCi2wcF!&TG9BQ79;);k6P5==!BATFe0AU5-T+X2Y9C$qJ zbO>x2z#G%r6$IL;O#mr{ry?t3r(V+`4Y3d+O@L*?X(`~-WGFI825JXk?=()&$^gw%2e~JSB+|K80!2F0lC*!ZXGE@lEYW~*>S&O5 zoSzi2Mr^yg9OgwqUg!jxlZo7Nuxl66>2^pEiInlM5^h+WVaMqjiaUi%$; zt!cZ>wZQTZ+g};=Qz0Da;UC!+zP^b8*N_wCapvLoO!&9*4}Xf zm#vkeU$h|?%Z!|h9`*Ge9uXaWjWf!_&dJeZ|Hp~t9S@JL3cYY+pJfh*(NcYs?_2eF zqAX$ujr$8D!(zC69o$wsT&j@!oo-nC_^JRR$G1mtG=^U0z_cN|2Bz5^k;{FCL9_F;S~2AiAg_Ob6;s- z7&WXH6G%JJD-1xhg>mFGaZHVW8<{;W34!7S5b<%^1~oHKZ>jLnhkPDbl>{as0d%4c zWg7|(5C#SY(_wV9t?038mgp3v>CQ`P0j0fRB&QJ5UW&GPkF(9Ibmq}6l=nXg2IT`# z8sU{}831`u-5w%;{IALTZ{3XsK_jq?-i~1NFWDL?*Z+U=aYU;bcgg zbZerR7_wdM0HVRsfU6HpKmf;e3QYLI{_*%9AP^NXXMlG|<2EX}jj$cRApUTe?Gi&v zMK8(CcsUrc9ZNjt&2}9ZaeTyq>lX664wc`I=}9z_r*dxa_EuEHuxB>(9Kb1v4d(S&INqq78G1YM44Oz(|=Zb*jn^j5odHVwkSv{SXLS?E8E{VnFS*!O; zzg`wrwA0(uy&~OMJf-4jM%#W}U(M>vz5AN2RU0@8$ByT>3OXXLjBSzBjM>}x&7|7! z%Ua>x@C&|Gj`_ZoKaENeG~3U;``ekwc3I6~x$J@i-{tl&N29h~TV0$Ae|)~xWs^nW zPg$|@ulN(VkFBBS#MGH11-4fv7paW;CBmz!bf%x$sJm?5xU;s$ zO05=NIX&z3HOnQx#Wnk_=$@aJ`+?W73(`|G&y}+)%72tRd}O4FQZ8_7=y{{E_^oHd zC}rQ5tV513YFih7{o3xCAZ4G}s6IsOd4Ri|?LFLTg!Iy8hoY9cC)J~Nt+=(}k8iR! zXtnHQ>2vycV`a5t5u>Ok$jc<>#L1r~d$0cbVQ=MHm%a=$+f?haUsw8`)0m%s%1*vL zW~u3Al}SuA@AwGI^<%p3ik9ZAS~RDP?(djcJGG3KHNQ~%PsRO1b8_2SBNBY%B$`h& z0SE;SQW}_50M$1b4ul4#B+CT`&0&^{zbLKEmh_qfs!ZaFK!5ck&2YXb&AwFbAIyZ& zUTcVyP5fxU77!g`#TZD$Xd+~v=m%I}y^m*M%YYEe+A%pQPr>4Xd!l?~WKm;UX z;1vqMV*^>EUg6k>N+pmeNj`>!OIaCaphRH*hr#+JKDgMQV}=K&XCWc3breWll5JODNl88%^Z873)(4N2wXqs3gy#)w123VoS4{U`8Jh%{)M_C`_ z6ufotEJ3hR4$bGAN+HvQ4F2Gz1O^4eAo(`s3>yc$Qcf>Ht$o<+^(z^ghOzim}1 zCU$yzTuh@~_T$%zgW4;=*sXcx!P1iL$%_N#-=FY!|LNq|u97F`xJ~g_Z$9c$ z1|-MRwdLKvsxRH;E^J!3YswX0kL~P*MG>LDPLPIWEeUa-SX|&In5^{6xKySZrsRhh zZ~0fu54_!+uPPTNyM7dSY;&Ily=mh=*A&U^^TN5&ZKLYry6EGVEl{!d9toK7-JBiF z6$3}t&PiHWR-rz8=*mHzqd5_9bwDg4-O|jaW+?KN#miSecdPofE3({CfF%}={s;h$UK}K|G$Ln zRDkSI0@2}WknSOksXrCq$oGUm zjG-P1a;2~f1=TPK>%@Dh@)RfmgYwrOAzcP4im~4hmXt$Ptr4I&bWDMED6vrD$c3Wz zvOMG#a+q_9?xHDV&JscnWqGhx(+_Jn{X*_M5e-T{ae-^Q-1T;q&e3E zxEw~vjdNuM7w(0Nv${=xOr#Ioei>!IHYt)`5%lM* zYCdIwM+zk8OV`!Cu8iYvRn#YvQ?ZSpr7h$YKyQPDsVwo&;ddWLBtpz*Rml)ObTln{aM!H|y`kRd_N z7q%@yjKBzGI-;R9q_)6mQghu6@QAXIijfJ&Vj#qN0|K)Akm65tTmKW^_Drp4Q{|NT z^LGl|c#?`8{^7Glh~3H}>3^q`p!4#d(+Kqhn-W-%m=9Vpyn^@zM?NTR8|=Z*SMt03V&9UjSVwN^mVal(AUjj|zu9 z(Cv3~9RWpA&S+u~gTw@h z6~L%y98cO<%;ZU$a9%l|3nMJ)p$vsNX>WjVjy_%zkOc-75Q31xqQQaGL3$x+b>o5U z`H}*-Ffj+~?Rau&nR1PhtrUK-A08xxPow3a5WG~eUJaZ)9m191G!TmdylMs+N}3KG zY2nNh!4`7MG>{VP_4K>U=o5mvgRaRxrLS_Y2?<)^xcu8S+j}CdG@+KC2=}u^iL)!T z&e_j}vUzi~haJlE=n%kcpV^k9qc1-h5teV!x$gY?vUR>HGiC5em$`o{tQ_4kGl`LL z`$S)RaIMJzAJW^8JC^NCkyo8z6j!$x)vphcM;*}4>7qdK70&8uX;a2Jjo6}G6}-UV(95$fA9$q{Uw2iG zdGXB8`=g?&QAZT}Rz+q`jZMln*r2uw7^q^ ziPjQnh?5-=CU`{}7)j9J5u&db1$6@$20X)%@`sR2V3wc;;+qpm;Xu9%d}01~X^=OWYE)D{x`p0mP%K(B1fLAdoE(MzXp8~W z|5sEtM6*C$fNP+&#Q}Y|(f<;Z4H7H-Gj4!L6e0i$>SEyq8~hp~5mV|%98^_e#_~HF z4bVcE=ky;8f^Kj(uq%Kd6hIhy*eB!k-tKMap5bSPoQbe0jFTfx-U{7WsH*}{3xXY} zc6jtN;G)46s9y$?0#Uudoe`q|PZgp%7NAnb0kZX2<}{c{9Yb5!57Y0+DuNx7|0*T3 zJ|)``h9~fI}XUHjs*`NZ*ZULEm9dwBXBSR|#9zqM_3IK{peHU?4m}+!Jc#`Oz zFy^=medV^b>u<^`Wj3YO0kY|nE>%>Rwiq4TKkLKJfVr-VzO?ohZTUPc(%C;?u}#H6 zD;I~lO1to_oMEiHNx~4?%(Jt!V7<`X!u#RV)yO7ogce4&5e#bywEJ3ziZS_D$eu_vE^k^@S(r&=}?a?3ivFFm||``>#v$h`Ldp%l&rMVX~GJ@u`SJ1whCzFKmvI72))=n}{n0{zCTbn-PRn6bT zt=m+&?yD+ib%+DTuRk=?tjX)jCvHzy7eBBnRecfW*rR+E)Z#f^krlQor}KB;);^($ z=_B?L*~aN*qFZK`*Oz)lWt!NqTP`}D{OKg$Z(?o5C#kv9t2cV`q`J;r_udjx4iVR8 zc!os)9tZq*2$tYxqXGd!L;L)grR0&M5~2aOjEMXIngS&=P?9oHE(l3B$=3rPajQYZkACDM*=Slv8I$0Z*pIIX}zlV<1>gVJWmrX_wc28BeRkP~*?$Dq|a�ZbiUJW7`0+*<~!)iZPu94je0j^;+eFO@s<#RN9u^MCtOA~ zRv{$Vo(RzJaT6BOeljQzx-$;O3H8n_$!C!i9te#w7VY)yFzYIdiGy=$h4JZGq*?O3 zYFa=?^A-u$0GxxSAx@7RZ=1Wo!X6ox7Vg&%vJJ%bhjMpvOa^Rl8U(esFk^~(bR!%e z<3D{BQhYK4LCTWX4hhnbDgo6qr{UthLmgq?|LJ#vJ8+FDVhTPE0c-t|Hm)%zffN0@>`sI!)PY@0Aad$tR7Mc z&_W3fr+)~j?)4))iVXfZ~iSlYB!VfRJfyszQhIM^KtUa~?KA0X@R;fkhgO zz1D$ZR4ZciJa&<7`2qtOz+Ct0^Cv`G4 z)I3cx!7yDC%u~0g@EbZ34u*I_%?kv({cy)5GJ*8Ahzj`v@nAu22fPIm2C)IKmgQh5 z5aZ>LdF3YX+8(oT`KUwAC!Ff=Gj#ALdZdXc?71 z;;X2gW8+Nna)82Bfqdxz&Bfyz69p5qpa60M~VYuC&Et{P?JcH90#flUihp zZn|)0%KrGubz!}2fYBN^hN_|3amP}NaEmQb)fbARR(IMj+ao_^+q>VkCt`pzCD)?8 zK^@*S%iNeIDTmIu(6F4>2*a9!9sUkYKw5=R;I!8+;F6`dUbm3-%yk~?!V-?r_O4ee zR;-LXH{r3u;#s}qplgS>@5TJv8@}}IWVu~gy0N&w-~CDExhn?Gi| z*V(03|Cn;%Ygx{#?{&^;UE>y?susM_POctf)4DU_74OGAF^}$_UUs*-Y10;Elu?Az zE3&uP_pzkyD6B*!NUBg!;Do#mD#{TfL!2->UY|^d+&C^A3pPkmAA!rPOca3;psCjM z_|%HvKZXSAfKfOh7bTHFXv+nO)CLhFt1xqjrh!QpG}b^OPLzdJFkH!nM;Qi?z|<4W z&4mLDMp;_91~RpY)PaCUK^a0TrCN$8pOi5=5Q39b)da=qIanhO719vv^WbVr0=q$Y z5Wry>vCte4-2)Cjjua2JQR%PH{GNAKjbX@5^w4~Pk z2{igG=58(F8{yDQnO9f(BpDUw!z~&ZogV3NTkjHF&TP&Qf=lGj!N{orxBe}`<$-sy z?5#tN;=mUgnhj3HvQIJ0(88%!y6*6{j9r9!g4tq_!PFt>Jd~zSX$2M<0}K2=GEc+t zO6h$W6Jv9skcD7~K5C{|$Ea?;&xf_N_?OKw<}&GBgLvC@q0S z?f`gT6n&)t73~Z{DMN3unD7UfW5kbuSvhFrq;R8}{Ps?5-^ zAdTZalVVGEai-9WUX~ed6lzjrWdxH$VTLh`glqSLsg7i9m)QEpwmz5x;fUisasJhi zahIAFyZ4oIr1j7mlT7c~uVOmq%Pq0-rdnU#C}O(9Pi}VfPo*A^x)(y=QfKUpt(6PCIE$O<1bKp;ZsUHnUs4R-J!cKJodUG}?Dx{p0%= znc)V~=Q~)A^RvTTS!i*EdbG-QLqQ*10KGl3p?E^g7Yf zdEEt4Z{Iz3o@zGkoIS4KtVZFIZMSQLN{}6^ed|UF8q5t1IR4&Vb931)3SaM8XnBW+??zrCeGZ zu*58P=owhSIcg^N4=vX$RTT)*FLgPa4PXA9a=_}>eQtYV20rzxSl2wRqQ>LamV!m? zJ6oa_8^eq`UfZe^`jOwSm+`*oQu{TzJBbP$BUYU&>&v~|TU_xtW?gJ%cGpx{^g~xs z!zT}>ImoYNJGk%%F&VxVTp0C)figAm-I7;Fdv0->fHKzPz& zoJbIk-osp>u{;F~zQS2}`S5*zDSlT#Gy>Q)YR-y3K`=s-pMV94Z|$&WQnxk)fl-at zDYC#KKpJ3f+d=)=V4XbX00K%Qw(I9Q=~*<%DF&D%uxoe)xh9NGo$W&{)Q($y5Tl52 z?SkS<7|B|%HQylJ5!-I*=96yy{}1JbSdDr%(NErT%flKcPCvx)0b5c60Xnjj(hEw0 zwDbgA)No3Wodw-ehBDB9`?C(5aN^dY!6ni@UJgo3AlC`kz6V6O^jB>-2t|}_fDXil zPJ!reK!bLQf&OJTb+Ba*bcFOaC$~|b`-^w?-;2F1RHqK|xMH}i0iEs&;QXUEMpu^bj()6hdX-iHncq%`CVzqCzdU{Nul->a;9_kM8N5IXr z#F*(NZTK3}i7~%k`Mw#A5E)q4n%5b0}b$H%TV&0%U#N^QTDalJ|Cch^K6> zV5Epk#dXnff+#BwQHA7!;sAGU^u39DlQ|WWfS6iE+^!wf>aRS-;?3Q=#wst$cBg!A z0QVmEmej&eTv7g9G=gh!w3e4p#jR-P(WREr{!NkolBzJKZkA-=^fb1e%=jbTwzdR_ z=Cf?QgiO_|!}1u~GIiOg;K?itGo$Kdk7wlHYHXP19Usvl?w<70;=2;rKTq5VZXWlr zDtqnKM>8Et{%Eb+pR=h&qEbq|+xF%JJT-1N)9{M4_Ow22cyPFw4*4xW1@TVHX=%sU zE>{H$09~1#^^?wu_O0wXkrGrVkFwG2qo3(Gm(d>`JghVW%1gPB2w=v@rHpy7HtZi0Cdq&W zO0dZm2r^)o{b3eRPRLX^s8Co~8l+Gfg>+o-!^px>tC(u_VV-FWPP`nGFZbdzofAtl zvJzWh_kgd6c;pMkNq}rf(B0OmgfN#0;JkvCkliZqFd0gDK=I^Si2Oxh2_T`Gr@-oP z;Lx!68KCF+Q|p}w0SdhhIREa_GOQ}tElJ&g(XxxO00~eXCrla_vOdLDFENi4j}8if%4%}GF@XaSiP#wmgM$5r{4xkk z1fZ}FHh2vg+o?}Ng-btFxS;xD=wJsR2bYY|mkzBPV6Z($Wzq*u2UzWJ{usDEd_XTF zOp29~gbD(R2#o*ci%=q}j^r)288D7$0q^|Vx_*I?hSFsXwDMhM4B;WT{*-b9K8gWy zb#U|`sDhIOb{FEQaM=*wDcl4sBeax&A$5N!4O_npE*ytnC;?8Q&Eg4zIPfk|y8-Gk z2sVRq3%Nho0s|$7M92X%)PEqh;xz(e3HG8FlG2Eb4^6+!B|}ErgVG(XXF5`HE#q%59{a8L!oHQWWnO&xbRZtVd|X>j87HBmn@O|zqjTp> zYL7g;1WfhB98BO~o0LleVWOpVy|z`DI8p)B$3i56UWU+R2Qq`e=s`&&_V#zTW%{~t z*e=yH53xF;>`1DMCTK!dX=KrNtWHshRh}^4qiNAjnQ=_*$lkofTC2|UOI&o3$sa1x z*TpQ>J>xiJg47A|p`%kAkc8eP#8TpbzF(JZA)$H{ZO7f8fh)!c^mN{`SM z#u%8h4g*A)z7oGfcOsKRXcl~%Ba0CXBJL^HaCtUQqE*E(vH$ao|N_ zvj`I~TN1lOv;mzZ<#zq9lSQ2IkNCVPN!R0lx%)n{xG+yiKcgT<43zR4I<(|C&eZB(y6 zLRhIa`4_ANzj9=cj4$(;&mtr<2Uu9W+BrWLGL}V98iqJL6!u6$F8qPYs@p` zB{W=deEm1@NU#lVFg(!Eb(YD3oL-o+#HolU*AAW|cnW5BwiQC_{A1M$54E19L)~2n{`g!`xwfYm+tLH^6)tb@US}OW8Hzyv@7i6^f`vj*#?RnmI z;4o-2fM`I=K`M1^!*wGVUsABfxtq3@301a$RizrgkZ_5eI_w#DK=&ZFL$1hj(7>D> zT@T0lRaH`?pr^BZx5KVuooeq;pDLl$;wbkInK$oQA|0zwX)uK^bU2?8Q57x zB6gcw>(DleQ)U!mQld0=vuW}!<~-;<{^jdUYjS_T&ue41*wnPKop$ur@BaAwPTQja zHb*8bIKRipOI-JY?8S75RnQ}U0cuW&NRT%@L z{;`#B>Nz7~rx*8euu9I4&H1sT@!3k{m43g;UAZlJ$<=p@9VK^<8E11F?`1p|HLQ8c zTIpT5ZySvE*JzpwP!3NRqzgbgEk;-saFgtLqp=JOatUCi z7~`Kz6JhCEI)P;*XmXz7ph!evRTv`pP!k6a67l9;V*f(-@K1XlfVqtHeIn^9A6Fc_&eup*QKK$*Z;3a3mC z(S4(HWeNL(_~s#SLg=VdXMm{iPS_s)K(=ncJY!aSSIPfP9*I{RP-zgtp`nNLJz(}g zE%9#r#wDCFn9hGDB|uQ&1lJm=Qs~q zRPdr<0CrsZzIx5{FsPQ7qfniAQofar&jB!2G2aKVHMoDrS@ zJ=d{hnVtl=Se5}JN_N-UvCbsgjA;(p`(bz3*4$AJ9Ru7s=4t0a%RT>9M1y3MuwrkB zeL5@dAgk?(D&8bTVt-B>2h!HAq}rH(N{+0tmN}L^utKKY9W}Cz0jb1tX)h2#A=4wR z1G|zmgmD`)XcbH^yVT0>Y`6Ig1i6ntbEnuM3pwpQj{WWCs=>yF8uuO$?_9XP~4V~FF($}+pPhO ztY`$In8`Q@3Cn5+brVxcXFJR0K!hv(YO8WXv(I(tB6bUA&uLnmSU_ut&R35BOWPn# z2`SBe4>amlK7LOC{QR*^41zVIw_vT*E9i9k$QrKSvLMt>E_!jR^6spge&nCY#ge18 zk6jMF>gma;RX9#MD=gUilVmAxr?#)stwnSqvuhXKN6OtQnB9Nti|R4fI&lJea)^p# zA%CG^AU(vaQjD9!1Qp^rNasPWqJNUXi)JSuIN#psz6Ps?1 zfimRg95t9dffI{LS%XW5xBkyxxRb%UGVw%(4q`-3dQ|xHXQ|>3*oE5=L@}gg{S{B~ zG!3UZh+_JDqqm>t^aqM&fTE_q8+aB5BLW=B97|d$3^lwO-X)g?jvjFiA+Evjfx%Zu zLBa?TWl;3!I00^p9ra&S^gOP05X#-4T-dOC4Ll>bRYG3jHH^+Lh3NuNPX!00FwosN zFmZ(AQK2YuXhWn)xS5p6W$EaUAvYEJHo!b&z(`=sgn+`iCNi*@86;6dzl}r!u9&=< z1WyRbKt(+vbOIN$S{XR`5`Zlm3m7op7CPiZDjtrC6Gnd^2_9xXZ(XKy)#VL453ZW= zIP6jb&ven_YoWjsxf^D+wRq&62pf&wvNOq%AWsHOr(a&=J{jyE%LrDN=hbP<_hQlXPcK`e4!bJOn14$&CVixNp+}3mTZnaE?wC=srMepz*}g1`gzh=M6&_Ft z!S65%T^9a`m0l8~jjEL2?r83?r~`oPWUY=YdQke!RHOMORXdp`>z5p1x-zfGWlII| zfmMqdrzb7k_qmJQea`q!&h34?oNA3=d|u?_F1IHgPm`L~P5XTELGhdd`9pfgWObF5`sP6^yop55ivlI?da|!WukJN(C;4kTCJH4N-9XQxWi{C_04vQMZK_T4Xze^ox=(-UV8`EJP>NUo>bs3r5EPyMxglurB`{+eE(+ zFAn8?8c&Ad52=ByOOeN!}>Qt#1Edtb%vii^6O8(`0}CyvcF5r%v@M7U|f*7ux&>{xI^TPt zIzbB=RHhrG;CVN|hyff(LwtEFIhXJa1-V~%&lH9y7bSW!nv1r2y*F>8#TV6nB92kO z% zm+@11N5sl$=^A#fvT~z0r!)TBm?_PY0L@gF^iGG1F(WF*R!#?p@%Tywdg25H2EuMGY3Bb_9hcSR9dH zk?Kqk!zF-10$5YqDk@E4b);UZcG?Xps3}@5Q6XL$Aqh8WYYkRAb+)xet8H2c;8fbq zwzQ=cd$YCeba&?b&-1SD`vTa`*>m>(owI+x#xD)YcUkLQ&wAc__j2u`8HfHbKDea7 zc5Bi-^-Wjq&71N-)dRhcRi_NjsH=T=|38dvgB#}l-1AfB56w6K__s^9f*w6};q0;{ z;eUwvy(@c7{o+E!PySyn>bx1+bSr4j-&3WxT_uk6`44*5{n&J6=itevLl@pj8NKwo zHLr~Sw{gcSyDW3_X0AQ3`m9C!-Q4CeCk749q9eOcpJ`ga<&g#!K#baY)Gch`K?E;0A(I?Dq;S)8<*gM7PgRjS8?>E~tXS+7hze$*i6sT&-!G zKk%U99rzMIO#2uK)pwVmq7a?TYp6~4fD=?aykn6oS}RKSVGWHPKEs;m-o7{?(GxD<&)X9awf^c|41N`P29EX9!NK|I{OLsA3c3@VMVOIl8V5OllEWH{28om`V zs>nIx;EdoMPuc$T2?mUjIe&|+QBjH)hL3wz_t5?#tXSdbEW!>!byLV4N#Rj2QnCx&tJlalPKzZb1)+Kf-*>;_vJ>H{B@OwDaMmPN&Fu#^K@rzXxwS#RJSaWTG1Y zlQke63ad6LFUL)H)Hs)h?GP+)JsMIRq2*O}`ZnWcn1f=9($QTmij&cKxZ7-0&=}=q zC0|4%0N3`O@ejuay>q4~`TEsWf2q9GJ}dFD9??5!MA{tx_0lhA+`p|@KW@cW_0Q>c zo>}Lp0XeSNk(m*2)Y)hcxBHrwL+!#!AihAzvhXtVMtQL`7uD<3;*URf>_}1g zU&>=tf;NtZgNp4)&tNP-rZ@wND0=u(kLRo3IqBWSO5jxK8AY)v7s!MTT0XJ?qyd-K z+uYQ_ZbCcP^UIE;hFq9MD7M3i39+aXGGC>bn%de$qi=%ni_Nm8wbqpzc4z{^9kaW) zN_^%=&zj>80s5lYqA@4V+@QQzRAaMU)Q4UHX^aP4gg4X$z^n${KH!i=hY&PDSglMR zvOBkFG~tML0KlQlI5-Q{QwgP9zJQrD!8F}|5e>Cx!5JV2t_E^Xle7#_ATh!&i_6Fb zsJD*#${s?ced-eDJ{lH?5aBUdaoN!U%>hlLZ2sSz`_cYp>w?sq+Ucd9mnKZev?Ra^ zumTP^uTBaa&a~i+?)%xyatYGhOu2m7~uxwHc88g)& ztsFn$IAUoKT!;)mCmzXx2H@dM_ri%dZBT%f6b?}YFAwYyRQLQUTJk~kqCrf2OQ;eQ z)sbO#3=XY_aX6UOXxa#dy=beL%A*^SULeAP$y1C##QHRY=Ful;6rOXtN1uQ%I3JG;lB@!4OGYTnX!7Jl#F- z4C4j@O@@YdnBYztS&pjAMLdh-rj*BOnmY#R4d|}{f*7AAgv_Ftc>`+ziCoT3F@;4^ zsox^f(p|oc2J5o9bgNn5?mtn2NbHm~={JCLn|6 za#ol{tYMkZ@hbGQ&M4Bb_QY%BlBpjD;sLV#>}Y~|+7?NL0Vu;XP5vG{vu5oHjMCaT z{D!L}K<%_m2^b}Z({vVLLc7m?E_Nl_#$0XAY&O~BZcVQ;ycZJj^#ZWaCHI$~+)-dZ z{oB8`oroz5`gY=cgBcN@WI$l$iF}zXG7}TCd6_lLY(a(w@_eh}z?-ZToDe;eeo@Kf z$s#gDSSz(HA*f1#=82)29T588Rd%P?tt(wg`O7^!ly|`+g4BZX2QZmxos9Vn2ANhagMw7_yniXszGYeeThp|9H0K-(- zua4b1Cg}kPncUV;iJBACZxCKO@;PI=A*OgVf_%2;QdtrDWb|Ln0Fd*<9Ef2NF0hw( zM_0=?07uAOnwt|XyQv&&FP!SpJZX!1`-$vTdSLA& zy-!`k^ClQ_sna9QW#Y5;>BJ!}h3RPtm7(abU5g3d+&8B)-b}wO3${0{rFvfIGWR+n z{Ihms?O=^ogKGfJo%~t2RVnq6jVYiVqUn^sA51y46FhX zb-sfvLh+C1oK^r6^p#|vWn*b$S>puEv`6MW?~BvI`RP*2Ttgc-ZQuy&LdX+ln-t!< zF|-CS$3)|>-CRJ3gv};~uMaI(#lc;c_%wMXP$7;Wxd?*7?u*s)*v=Bc(Fl+h=sFpC z&!RqPzZn?9aUf4j z$*sYNV>>~(4X62+LHwPp@)kAc

F^9*`A$pM;f~n&LF{A$<{T5%ER3WlFsS?xM*8 zEgKgX+UX<%B;4N1axPS&^YtkN;g@>bmIcKuWHI?1ztk59j*t5^jN055oGA-WT zU-dFdTj07EkKBDWC3>*Azcg;VfBb-Ia81mbZ!=$S{lk_^XGM=+-qz-6d8_%Ji!UGj z$kKS1B@GQ|7T{FolPw(&` zxjslvf|((j7Fe_d*XE@X?dZr7>q6Z(>PgS7z&W*CM|!#ZZxG)r48Z%AVWFNXovPvrBrlq{r7l3Q$J7|?Vf35#}6}4=Rc*}yH~mA z{-FE27ZyKvU#IVDSLR*u%l|0U8t`>Y?ew08_{BjovFNzrW<<*=Y@%t?#7EJ`+q8Tp zAeGoKCF=6=6(&$JN94sCQ+6lCyHeIcJ-Do0+o(AQZ z-(fL_SSDsLaT*y;h47aY*AOX$2AO6!LlNIZ?tLsIn1~0nFa>A|Q(Q2zF?x4EP3aUg zcf3U^gjyE*eNtQA994dpB4f?E)f=@L4wg7Z=z6H2rtBM*L1Lc5;Xw-}zWg3#@ z1SZi`7l*Bmz-&zsZ8o&CxiF2v*{n7c_k%q4vpfC;K=t!&qq#yn>{>;o5vF*CUi=*t z%TN@aeM(uz!hNj`CtzkLz+Gme>KZZ!Y(n5E9&F7#pcQ+@Qu$r zWy9|R0iW!6D=ilH&0P|yXmyR8^az)f|IG&RGHr(BasL8>u(+ZnU>^)Exn1fGYaqA$ z%?Jbq*fF;R^zc;b1jnh#3|%nc!xUdN?Q4Ri(`%sw&J0;SM}Q71vDhHkDhW<0mH=Ll zJmvVJ%KSt6_p@u}Y5Jc(g*5GZWO(*?Z+)Y9JaV2gSbUHbKUznvm61Z^qSeK5B*-2Q zv4lxjkf=;I3YA51?(-%&{PH>j+cwhlqJ~;%6=KxSK(5yyr70rD6Wb!b`vIyykAX5l zIbp@5x&n(DYT$S@>F5jm{}2BIbPY%_|C4&0kSUy4L+oR~04uyAF>By& zM4z2ew+=}5gY2 zC2qwoF$7JQ;Ttiyzd3yHnV@D7mtC>pH)u2TvM(dvXKzeUYzO7CW}# zc5kUt7y178!DVN-T8*zLk%##ozBCrnTg$3u&FMPzJhmjir&KfhY9&Q=!DF&cJ)flvzLxcp zw%O|Uo{akRia{!{fFk3w$U}y02er>9spadrv{bN>gObsEK*Ox}G15X*fqxNGp(P?h z2}M<8g#oR$awBh}hwr&=ku3^o*;GCbC3J0H!)y~!!+A;r$qf+9^i^IzrE}DhWMQf; z>g+BgFi@-|OnD-d!XW!2eF%**hRrpmjMXGNU+Ix9(2HWKz|Q@Wv{xTDXs`Q zWp-DFn3G<7pDicS2(vp$>Y;cuDaH*K3o=gM5B623xGGgod+r%j12{S79v3_4GCzs&I^`s)FsgBCUeRNKw|$7 zcM{qLaNyBxyqdY@)B+BopEB=dAC^>>8y>zp^ZU=4a=t zm0^FFnK|8e=|I}Mx{Z6!oN0E?y8GU%2P|aSME?BfObZkd;HnuJ>qLK8TnWF=3P>TU zBLfDfVDoI4aEof8ks9J8Wjc;L`COX{_?5cZfZL})5M5#d$Q7`MAe_bpV-3i5QE9NI z{{Z?%x?lhxJwt9FiZc^{1N9NV=QJ!Vp>t$}2xcQhch{APlm{-@3JZb7=;QBfRQ12y z?5jpwu&?<3#**#H%U%HOr%xWd*+9Zbkuy6KLTNJcimCxA;)w^5)5oX+JXd^T8bHj+ z1_BW|TGxAX$w~x7sR-Mg)LaOZVB>Ok=2d=NK)|cHToBy;S0E%S_Q50GuV0IScH|7 zP!z*WX@~5QHy|mCW2pGr7hP;q)eNxR+8u*{XUdhOA825SB>uoQM=ppdA+t8KRyo!! z`2e7J4bFb%8#+Vh@sRk_-s+115(mk+yK5+WmJ#MkCDs$~S?*<`RPd<=B9O%=ZUiNKl-y`JT*Vyo!VXw-Uh|_l7s@ zb0`ai#w<;b)zyWo`*YZ>K$otRS?ng6#bOTnHk2hZ6b&^^$ySBfcB{CCv!b#rBGQLTRI||X6ZWpGOoUd@NFq`^8&r>xd7xzhMg$!xjL2&4Vdsrbtc_;`#p}<0mX+r+LAI%}b;v;{dUN&PW+q)DD=`0m+=4 z*@xs;@VOz2AC3a4yu^}?h_0$GgX-{Nb_6uUL&y00pZIb ztcKcbmaa4C#j2L7?+UKkwHafSo`D9f>j6b65EsZ~iv-FL_AKK#KxP^Qo7}U(@&rg- z9+5NVnZXVF3x>&T50&6$1iP|}JacMY007v_%kU$Ij|IPn2L+o32hPN$c%2EN$H)Zq z0c31*;z_7Bv@mI1Ghd1dh)cgh^|_PeSK;gc6tNAA=J}$UXcEc`gQj5h{32S#YobES zSulIplhh1mP%2a-4PfhE3MvfXqH79(Wfswj8;)k6t0N<$c?hh4wXq1J!K4M1(fKps z$B`s3fT2*fcx{+l?jm{0w>E0>va1Q5kf^Q!>E#@xvOY4my~A+Pk+LG&l3o2)hHKvsXZDgR4DR3+tiZ>iF8k12gl)L7)E%N-cC9fmJjJ%{52ud)U90#Rjs0* zx}GAwMPtHN&5=OHO&Z8aDdy~vSPqP_(kXE|JN}1K2bmgofDabO*qdP_!#}XW;BpKu z5`^*bO`QDlTFDPdVQKw~dd|H#lQqx1C7OIDM=oBOQvK<}*$2ZF zZxqHXGM))H9z$YW#rM^Bwkqpm&|+C z1xUiQp}M$DagG(B*?s^;pnaQJgeSIsCfSs+!pbT_iZL}XZ^F8pWuIO7JHZ<0z zSaWBq!lpXn=4nMShyJ?1#UJ-atAXm8jgOlrE$VI-7Z{&0h9$~wXwEIej%f3-=;Cqr z!@wdsdKk;d#MCpVDm{T33=+_dlw}$X#A9ef9W_*QSXf8gk)o%eo#P3y`Ag*?qL`cq zh?ce*~A`#r^3mKyxxZqI3V+}X|&Ig3V>NuN+93NGXW)cP+p3EotQ?9 z5B$C;&St$321|}^E?OYXROue$N9f~Y>D{f zksqZ`UpVxfZij+tQbB-8A8Oo-Eg$=OGu?rY!|`l;y5?eXuHuoM8`}d9mgQT+?148l z?(2IR)rZqv(~FMFniVtV0g0Bxf0VOk_O1M{mi+jbNuQ%%o_)l)W8a!bKHYNsOj?L; z?zK?(z#U398W<5kWj49{VDchLOXg0>Q>`c+Z`R}{clx%Vadj{(SI{1V$asdqJ`L9e zip_*}Up=(gHAz1vxG5;N;_&#g_tgF{r;3@y7S2~LR-Hf^h+Ra=C~RMB3t&rx7!!NL zG!$BvkXsbQD0@~F4UiIKjiO*!-a@p@)=)OK0ogwBDRv6KftP^a@G#!PHcjtOE4b+k zrO07m6Lt))Y74j(v&Bf9(1}V@*}{hgv9bkSrTXi}O7^PdFDio-9z%}edz)Cg-qF2F z?WcAQE~ISyC|l5n?~w4cX49!(b!il`*ULaoX)Z4 z{L10$h~9H<4h}{c9uOAL4M*dRFKf*FU*Qvp18$!RD`7#v1)!6K)ZDVT`cr1oU%mk2)w?@ygMC&5vZ2 znO=F|3-zVbRVTIM#>~-~)=EUT$ZzJtriDI(r~))_NV&5KsY49B3wG~{vQ9DfOtVB} zoLx6VJ5}e%$_MuU zv?iwHp@UhkAAhLKt*?J8vAD3o6#F*K_6oKvS>O=u$I$;ooKd>U(~ra_jxC}Uc6%~a zao*1HLMG#tsM$5`xtQwA;Cy#>)ngq?O~L#2Z=Uh^4=s;(Dc_xVX^QOU_}lFr&NpwU zAOA=5fL!_6QSk%$(eV#`xAd0hXePhQ+H4>RS6hzl3?9FG2x9zgU0niA&GjIDGmnGPbXMc6`l>otTUg-1tP z*NS0Dx7^h*t!UY3;lnx)Wo0ssi6JwK@^Vl*RMcS8?KKwMP?kI9HubDkhM+vO;tl1w z{OyfXpcL+zU>~cWBCp(PEqbykXmy-;N={6rX9DQWw)_PS^~&i}AWCkMPLM>9d<0GBsW&ZFq&up z{=(ozfKQkwa;z(uYZohmZ7~1j$a{LgtHBfq5-C87-7)c+qC0*nxEVnYnYCZT>f_)& z?cWrH0j5rY@rX zwklL0M34xr1en>eg#D=^H%6Zsr9j_Ez`+wp`SC%CM*17*0rmwIs<(#N!KPS{hErtT zRko=4v07uN|D?;Wn%paEwI5`j*}Ft>AsU7NUsrNt0P;xnNW*jnQw`(J$g?-jA6hg8 zcL<~|xVa=UEHAnDy>#_~w@iwulM8oF1HqbHSeaV3^ki%FZr4)X_T-luY&~g{3k>OD zYs;6^{n25WFni`-Y9Bl}lOYN zFc#sY@<9-=)w4N7aI23@0+aqOI2lXjJ!~`sNvC}&PuYUbiQ?48Wxa0}{or_GiasU$ z&6X!lIH#4|Ne|acw8^I|I`OmSOku+GQ~uiu zSNFa+%561zVJX>1V-ZNwKe@;2me`5OJn*4W`>QmFV$&Czi z#Wep1$TS6c1;dU62=NvRsGJyOdIV1J!LvaZm@H7>MVBSucP1rv1k|q~d4x%h)M^p{ zCRu`nFyjZKVgOY*3gH5U+bV=&=^M&8y^>DA_+57n<3_N<5$jJ460A;ZlQ!BEW1XV! zTSlpizHdQR*p0ji?u<*O-Ds&bXMc-R^y%cD*4V@r)Dc}QZn)P{{c3(1$0`Hrj~X%^ zXh{)es!vz%^;gSFS?z-Dw4Er`6~}$6^LwuMnjbeQe>Vc?s*p_U!JV-YVygu21@)0s zUW9VN{mOHzXS4s85{@VFYMi5ycJ(oaLB@7o3@}*Qij+GnaxJH;X9B2GTOoR zfm&5Ez!$qyA=4Ow%`gu5N0GcTTokK~-79r!q22&%DKSC{=N!K2x3Y{%;zzXIpRhLk z_*UC}7wanGFFTO_;1hE1HWXZ!XkTx>alSNKr_I(bfW}ecz z^5e<9e@jnX6zP7nbYpXI)o;%y7w&wxwKD3(^uCkH4bSHFmN!pp8gc}`_DXO@B2*rr}?U+c}srtOnhW}(T03gtgPYGTov1I z*jB^1CaTv#x{!K65MhT%K+qeU< zfXen@2B!l3MyMHf=8jTxrhh&hfQsPp<{Yxek;aq38dC*RH3UD&SZ8rnu3y_#$COyf zP3>57@XDSv+cO;z4qs_iuKp2cX|*vg8^(^p#3`tK2Pc4izPzF|=!t!Xjwq9#di(Pk zuLX^E%tC;WPKgU3ZppE?RrUlvqN zAOm;}!pA?EH&a&7J(XDD6cfQ!lfmy83$XFA3cn5rk;t##rs2MM=YWw~%8=k+0yvJ% z5m@XXYa~yznEu3>_Ms$2^AE)ykY{5QBFs42uQ@A6DyOvehXCu>*c_D}AT5{;3HCQ2 zEw2pzGe{FY{}pKC!OFFO@%>opme!^5v#8Zxz>Ldgxc( zi)`>GCZyOTK2esZ7dmY4Bd*bwM_L2-&fS))5|>Ue$o7&$md;NIU z>sOt7%5y3W>54AT^Rou(`<&?sh?`I)ZFI~CkbRk@no6CuBo_gB66a1BIlz#1wI_9l z%z{-yjB->*{ZY+}^zL9-|03L?=(*{N(){MvJY(+K`^0(Yij;x|_1U1F%p;O6V{}{R zKI8GCcGuE58*R(>y>Q9XlD4I-#8UHYp5>K@{+7tLybj;{f=rKxLa)oalH}O)*za3@ z8*uvU!aolDX>Izk7iMUG9_ZNf?vj;r4wz=FWR@qMO3T6#=%?X}s*gfQR1gRk)&YE_ zRR0ho2Fsp{uPrgW9kWsa^88(vMUUnLXf*~{Q8PO>h!qR>Ve&%;IyNs*Wrx3kyrMfF zRR+zCr1hXyQ|oM8mVF5*B2A@!DEDB{0GxrS0cV$h)9q64+1Ar&7p48fn6P+V$E4hN z2YMeIx$&k*|9X5EKw8dbp$^f;$+@^3A2z& z4rQ;sjj-Ii-UVA5gT_0{8*o@5V8+lfvYa(bAkbwP4Nzjg=V5jtCSo}!COq5>{q&!m zl6}s~IuQvIC?=mh!AR5pA{f%coyXG-`3g`VlefzGH{7}3OU$IpVr*DEl%U;G`FoN$ zR2vhhACR1P=@R0G+$(nSE{sk_oc+j`ffj+x?@U# z`lVf_+Eovw7B(iT6AG(5;=Yat_qNtpPEN|5{q6mdn&-_u`oC+`|3@Tu>?xjU`#{>? zF|9siQ=j>;^y3%zeU^B9?6%`}m89?zC+gfPhb&M#6*57wT0F`Z0Mck|H+1VM%4JJiceST4kfvGHb5yC0*Ud<|XY}ePz|Z z*X{0k?kaGDs&J(zPPG8gC!Qb%zCtz%t7u@jvW9N#3HY$>TRW= zv3;5}f1osOp|Pakp(>mdW_tMw@QkO-JAzZ@5cm6w=ClTD~h%Qy}lu1B9FeG@P zyfl_hAIE}PO{6k%P8aT?Tny`w0i6UKMVNv4Lo^NMICcgVj6mg|Xvb zp8V?{dRHbw5a{ty6g3M$}b*z*1oi|K>upNY1hKN%NIH9TK7Wdt-h1RpF4Mjt(%sA zp#6=$>eYXHH|%~`^bT*d`5O+L`0Xy;f7F#c_?Nd{Z`)(Ox+^j1r@bjlW=B^Mpnn`v3Z#Z)#Ql{icOq~S-r)TF?G}G(o?4oO1T1H9nWxGW zFv?Oru`apib9u=4iyi9{n~QX(ca$x?d?a*F)J*?H+Z!L*A9eMy1q-hjS0##j-pa5o zU9^97NWr50D?+B;*V5A2iz?@x{^o6_{$+COJ+bzl=j8*I$dmWx9bBLI*29y}w7>sT z@?Yl9eXFo*ZtHAmf+||S;XMFWmhwRe#By%^!l6A{iQp2#J2;?G4mlDrW+_Ah1~(-r zteuhl6cv_Y9fd>=Sm4d5y~!LHMf`RSKQN{{3A8sEn^dmeN3iDxq1^(hH!g{j8SIBP zYUb#rb|gOXb1XZX>Tn}Q+o0!QleH>g1}{G=dZh2++`A|(>K7-{Xm`P7ul4=5C(5RE z`9?j{F(o{7UeIg`f3z&vVnb+& z=w7WhEjBT1_E?!%mDJMHyWUuUnziB9jdmOCT?%t4;Zn9>lReLHhm@z&q)Ms0{`+UUi{oO^cy+HFZG7CaHc#v^zj{tFzWRU`b|EA-5XfjA%mEcv4t z?q4#T2pkU0lj&;gN~DA_&n1hmU4dT+{c*nWhpSR?R7$SkTdjLe(SY;wQv2<@agpFSuR~l_QIB4<5F90+hUvez%fh1ro?sk^k&{Aej&9} zb6=dN5+@RQxhvaHiv^_vab zx(!R(BTbE#4rx!unZ$L8wzsyQxi70QKflCOSC~Cm`hCm9I(fgXcYI4rS?i!N*AOx> zx3|u%O)dP`=D)usrMd9vT@ul^?}Z(C^0%Wb9Wy>!BmH4rhyF_Tl|R=`zq)W&%nJ`^ z9!OkxwJUx4hwlNh@_0uDS3}5$9FKu&V&LmpneYlUh=TL7Z>z%+5MxMjSPHD8I^_s* zL5e7>v6S9#`BG0@_c> zKZt>r%P%5u)?(lzu&Db6 z1^i?b37F^4dRYMA@Cgz{eKJ}71vsrZ9ttwWQzpBMw=7X15Wtiqn5^^^GD4nKxG$*2 zXr?8E;EPxBzn}oQVE|EurekF$nWEHFtK3Pe<4!POcnUlNrt)@o*i;ZW;w=Nk2sABg z{^fPFVmD9>8NG(pQ7-4dEZ8uK^-!~_5wwSXJwkQ%;v(1=4Xu)H3Wf}3-W9=sLimT9 zWcEU4-=Rl2iF5xNh$0-2B#K9=|MEyfNSDKlt8L7j1;Rta_H-u?WZ!neyoS5=IyLBq z$XhB506$^^a&h$;+#?ie{-W%3f>DsLlYt*B7Eq9|E>hH9BI(_S1V0JEA|4cy!re22 zLv~+OznxV(bK6SP z@4Ea>rIh^kuVK5Mnz=!D|Ljf2XIxIOcx2}vKQ`mjy$cM>zRPK^zH*+)7pD_oes<7G zlkZkz50XeXp^QJaC3qYZ-7Su)5CxDMn&~o6kSzQg0~ND|nM+;@Q6g(NNC^FmZR5F! z78sS65d!6y=uQQ6V{lxE2A2*noW*!Ji;@^U+cPr|Scu$)?vvTbk9&anIA*7|&^ZY) zPTQLL51Ev6s!W@reNhiYQdAaVmsCItFNctu)_<;#81wYt`pKSg-)VLz$2w}WER_~f zh>RMy2FZImE?oBh4A{z($!Tg+zkOlJVoqp#$ zJu6zyb^7jhRA-?noNtLz@`ZMLXMAo!^QEw$IK+G{ExC91`q=)lv9D#l*;d`2@@i_A z;b+5}9Wi|~3w8(PKi;8lPrI41I!*G<_X(!wB&FLn0uMV1wgq+ErBmJg(aj|v-^gEa zb4jDkukLlntNEp?a^IL?TK<@O%Ib$zulK()ec6tsKgNHe%|5X2k->QrmU_mn+5FYa zj^}1CxAfijY0IS9{!JR69a;HGjZ{7wv9quQ`egf~KU#)AnpR@FVFQWsPfCeMcDZd_ zd;G-qwEg#;ZqV-%AAeE3PjRlL^f+vj0HTo0hI>Nd63_}-ei!-@HLEg0A`h%gG)UnJ z3EE;Ev*NU4-r-HnuUyX`fB7#K{hrxpKX~n)HRA`ePqh7I_V)uXuYSDtA=TvCRSQ4y z6q~z^Z|_2$sc0tci}XT5G5i_Gt5c(FC#Bd;G)pn9<{3ti-Ugk>ATbXbtYWl2^p&cjle#mIpTGV_ z_9M<)p0aVzpG`O31vnk;DBk~^1eVO8^|)5*$&4I{S@egF4JvMPqgQ0pZF=`wO7}qyB->o zaoKhEs0s6H!^Q+l&qnnw2}@fW>M2W`YnP07%}0R^g<0e@H=y-AJ1xpdb_$^NYsdd> z%p8fA|5ME5aFZ8XL45B#dUtAJFJqA;Zh$+)kIPD+5)yp2vDM=l>vXccZup6aFAAV6 zXufQB#RRjX8!VJ@y#8NI!3dH{WEE>YvK((%7^s+KIZ+@B@d&aH4tovq_e}dN!%b|u zaLRF)VodV9kWN04+J8c2k%?NDG>Llq-ah-sQ?hYKKg>~hYBRF~s<9ZWmHrXPV!kJ2 ze$IlBd5NMNRie(^>ANGpHKp~;fMaiSgkekwiEkemmVKi9Nph;ZSA}rwl+56 z<(L#rQQiKjE%zBF=g*9NJ#36g)Y{+ZTGZd55XstR^{yz4n&*)!BIotK_ioh5{&}Xe z<37>IPMwvPxTAXNZ2nvC4pVP>GtnN^n^|kpAK36?-e-x=Zn|f5|Eo_mpHAGh>26)$ zz4N*%(?S$IF9%C^_twRab_|#ny`~in8dZ|c*c9b<_NbV%fEYcvlTtn?w{Fn14xqi; zEaNB2o^keNd)LfO*b=q+5%l30CAmH$;aH?iGfq=vQTc|e!WI2;6ybNP&$!YWEhtws zKlb7Gns-SmZA6;I)>Wrzmf7^<_~MrM0o(p>a~7^ywITDvy_5m7 zT}6~p5DYc!ifkfDGAP6~>SBa*cyp@F4jLHYQG0!JPZpI%NQYLEUrM z6R5ZyxT#~H^d9+!?0|y`WMp)o%uztHKvHAmJw+(>)e&x=FB4C@T6I*uShrFtN_B%h z4pJS(Aiv~;s3(P{V>vM}g~DczkHYO}_CmMsg8kl!d|wvio3yFLUuD^5@yp(ye7p7Y ztt~w$np1DLc`H5+vo9qmp)u!h3LLESLp49h5=6U%H#kOEXXix~Kdt1dKD z{#rS@Uv;b0DnQ@!lssXFF18#ub*IUWltv!JkpMUR3lmU#fteiX(;>9Epjz6eCfKFjciaW(~SXvtWzNI=q zzWWJki*aIHTi5NdwD=_rp93AoRGCigFPypG;Ga5YA>^~DXS?l0eBSAnmP)ygCMEZ1 zM{!C)UtLI;;xWsvvl+WA)eX1P?T_xhy795HCC3z!M=L)I`@OMkT-!9eXj*Xlf}7Ra zeYY=8&Yd>%quC)v54-+i`n+jTNA0G2HWb#ItKVLhUXhsdx5Sg1?tWxOY<|0T?kA?t zJck?ho>mMt&O*Y;dGuzAMqf^uwG)dj&n?;|Ya91sq7=k8Ie*TwJcXgKpd@>5iKdx2 zBtpWm+S3}SNSX;G>X0!TT-ncfxaXMRBSVI3>a=ySuRUk_quXS^Z+}UewyNf>d|PeI zk!ih;ty`17&k#SUT>+C-vwOE8UOm!2&JwVIie|RI%6wGGP%+Pe5N2%igjQ5_8c;6G z!0J$)M;4vTr~k0rJcuBEpr_cZHNcpc)rUlX3JZ!v@4iL1$xTD}NK>mpp%}qVLx{;$ z9*IK_-?Ne?!({Z_{Nk@PS&CV|80Pz8Qp<*nkJq~c_e6YaFPIkKT$OFI$6oKPdpTLwuwBd* zzNtC9O{@R)_!nO1gBm=9BkVr0x<=3-Y>!^<z3yvdF;s{#qeEHBme8)qKc&J|*V=3~O zu4?CIU<7=Q3L*{S&WrGd5@t}C1@gj)^YG`TQ0ZSV_J4^w+DXW$H`0ioFLM0Y#C^g} z0&t)#IL0D5l?!6Z_ge>o5~uW^(ro=kCfHf6srw#cz2-lNYS)1O8wQ%A1 zs=GJ$po&9O)FJJcHu;yzamS|iw_h;&XXoDnDl~lYaO>@&<~RCYbmhAHbGd`n~6W6aAm$0e30SDBQBRRx>Fj41^lXvJf{kJ{uIw@6pr@JZSW3woa)*H$eJ zQcPQC^h`E2PFhvaY^jTeDtQwB={q z!nEVLVQvM=RNt*Iok=X7{)6q3BzZaltv!uaNrhTC0> zB=s4<%v>1H4qv!i_-jE;zX z@Pzh-jB1wzuW)qlox?N{%ER7-()_^974V@*MAr?fIfy>&uj*pWfVi$=O#P0geVG#_ z4a$ikhKB-oq9vu+ZJDbfK!&t^ZVxA@1@MrUwF;hOmJsAWk79dFDn<8=@La!*^>)P- z5lYb2ksof0!gec1S8_eqH%t4Z_QCEH>zKf6O=r&c6y6|wjrPpP{5q#PU7>uC!a@B44llZt3I+b}i(fe1B zlQ8y9^!(pMP7-(hFltFt3wmiu?r!$(K*E?o=k+>o0=${Lc%TuS_m@1EmjIFi_JXI} zz&0nAe?@ssoj$+HEq%{|vv8o!2&xdA$S5rP1g9od$;oyqCjuHoj*RzG7dOuwJ5AwJ z`^2>k1%sJjm#$gk50C;9jUvy~IP5eljHzr2!m`s})LW$iGyAIj6SuZh>-`po-dM7- z^@Qn6cysZsI8|SJT*0%}wk(aiA-hA?e83)HHzO`1NRwoX8tuPig==X^?yQ^2um^L` z8orR%Syy!(HGYuRA|E(@v8S%M)Xu+n|;ZapIXn%ne|oNu6czj*XhqpH$K~yc(CLThi~QAkKgl_ zdt&&l-17Ek53Q_q<>^Zh?LBY5>AI%pkZO{D-ZcMtF;8iC{;_xW$2P_8nt7HplKj7Y z^g~;<>&x70&Zvba^2pRju{U-K{Ch^NJJm0QMdt&vNUDvNZWjT@XSz^InW3@YG>L;FYZc&$| zaZWVlPzs537ceQ<;h77W8qq{nQhy1wu?}~1%^gyT_wg9 z>6OB)TRIf4LczuB-L10>*E{2l8H33+$)Y-H&RETnC{3;cOX(McMY;|8X?-M1xglyn zb)kv#2qPWLMN1<9JS78ls!|jcFi0TWy4+2P4!M8tE^Gb{y5hZsO z8@-$6XL=8Cz^&*`3$?rW})7gC)<@NWxE=f)s%rWo(0I)lB%wJ#h#{?hhx0ZFW=E! zAdM{Sg3dbPEV;>`0;D*v7|D1>q0}6kd!k-T|@nbl9uCxH}awZxU&M0+PZ} z5{9NpUK`4=aTVw(yE*|nON8d&mrg6#G-={}jC0d+d(4%yPg~O@Z;Y4jJN~fa+Su4r zhSD$cP#PD_b|l&-HS4&#pw+aFZnXl}Q_x6eS*^e~mSJ!}sbc2bwP!znFi+XZgm)wR1;FzHwe& z{iM3g^Ww|-znu~vQFAH3*mNp-UrW|+bjKb%e|pD@Vb90C(y?)Zs=DAFR9?uezap=T zdB4y6&|T`Qb3UFgpI3IH;O+D%S@h}KEg!!UabwB&yNpMs{$Tq=vt702Bj1kf6TSO) z_1V5X+xn0qqv>VKTT8MF#yDbgg31j$3o3M#?NcM0nj6>7SBd7TvbWbYG^J#$s(vw1 zK3Ow$;PBtIk>(e+1e{oB`{b(q{p-?mXKDu$k1yK#vhC9B;zs+*+Du&?i|L7mVkQ=> zKU5@utu{Y2+KIHT43Ri$s?Hy#nC-D&(uBuT4VtF*sjP9yPQ@FG6y2N{QAI-to+u>w zP!`JpL7zhPARrkDonSlJx+nxa0pH)E$gstJ^RCNE}kOu3C}NfG(WdZwA#1* z-fekTt0qLx+4jecZ&kZQ`S;c?R#$%CqA9(1a@>MTF}9Uuc~9cfCM9USxJ*qIu$(09 zh=&>!Wow&B(WYwpw2n}6Hak~bTxmfKc=056L{m{sS%u+xPtg(O1YgaiTzS7^U)h9x zW-W@Oe&Lw_&jK+8uU0I83LPnV+b{AalKh4hAY-J01*^!B6ei+WW#Y==nQ zq%EjaxH|dqhwHo}bX-KjMiVg|BBUICcv&k#PdJMvTlW5#2Bz5W+J;OWGe=e7q341w z1+eDT^dXE^4}*tc5ImHrNOI?;^Z2rF4mlkOEr!1+VcKhv7as4)O9|8P~=J4Qmbpm(=G2G^aABJWj%tKs-d>w@6x76wK*`VZ@bXA_%3U`M5mhbb^% zF4STgjAF#I(>Rcf3Otk(wO>GM5II>TUJ>+r?9t|nB}*M0zuo=*(lm4)OUQs-o)Wt7 zFE(+mkng<;6p^H^bh8(>l*2B{M4_OWv zpKa|;PSm8g$$Ct2zSWu+)!P%a?Ng_iR>mP@Sd_o9+4MtOWqtEfd9dTgsfMo$KiHi3 zV9STf%hM8{n3i8v)c!_BW7>oz*IPq3w+C*q`8y})Tk{QCSi+{6_8d%{8#XDZDE8HY zxhn^p%L)v4D=w_gnxgEwrJJ2@DXX2*@qIKuk%`rXO)6Xbo6c)V0ZqQ97w4W+bd8Vy`gze+zb_B8KlIbws1t`jZPOn< zxvIFv>?y2Jl79ngJs1SBGSY8~1i@ypj40{|pO7cK;b`nEV~VmI=IsM@V{#OO2Ag;@ z;ny3KfyUZIqoWRD<;OY)Q?4E;+l>>)aLntYMGn)l&iL~?GD0FKoZ5zDY?cQ%h!tIG zF2e4KYop3E7S94HZbbCZSE9gQvtunyS z1A7Qn2l8`AD9~T-z0oMO>XEVv(6ZDFy~1o_kq5Id>RhojvB#96mQ6<)#*kTvK0=b^ zgpzBv22}bYV!0i4bQw8h8QY8)jWAf^cA^Eggd+?m!|>62uL4-%wrGY4cIZVM?kdZK z_sKd6P+}lZ!;@Zo5iLBuRS~(=>lCj3v=@H_$-?pf6Ue+1LYa>|E&!R!!~eqBc+2oa zPCPQRBJyJb<0y|y-e;!ZpyAH*UswR>%-)g{Y)EJiU>f%@NEMzO7xG$j*&GE@00vDI zSEEo24S(d_()r~*$iQZvdQNiquMl=mG^J2727&C*j$OS^(m8GBKmPo9@kzJt?BkaU zD1{k|i`w5PtwsL!_ZQ(g-A=er;7WOJZ8*AeNknbhOIMHOY16M(bv+U5tZ2DtymC)$ zgmY<<;+x(;#Wx12V`*Aje1|5r5KLFU(0HXcY41a+8LQ4$<~skb34dD~u|Z32Yx(7S zjDLy=d300r6q)#?$-Q-|S&?pCxh%ry<8ema$nSq-|F+07$$1ig}8Wa z&a9}Cg2M%l=4G031qT}R-}SYJ*{&(CJX0`F^{)Em@#bi8)PsY{{ZrLjbhzcj15K{P z1En)&=ij&gans%dW#;9Fj z9L2{uieQ67y0zIoRg%BGAuOuXZ#%`1&tgO~Rpt&rR0~)q+~|?2c^jK=oa>D5(Nb># z_jjs7L01trk^!5fVt(wB zpZAadzU85>a{U8-Ge09)`_R|j%~a-7k5G&h7?H|UBu(Ukjdx3|T9L1X3T6AzN+Deb zC69raSxPH3Y%HRkn7z)z(#WqD7Y=NfkCFtRp$;Oi#TCkl5TwuLofC)8>jN(%0SDM}`Zmd6btR=SN{i1of)1p3lCBryb`yU4seaj&>xDI9Y!sNCZ?tx7+>C>^yLwz^UARXZx3Dp6>b%^@ia|M*<%>Y~>=C)#GXW8}d~`So1?q@YzVN1WEZ{_p>G z_Ms=&e*2e%=F59~OJ>*aed5~6f=l8u7Bcc0a_W zD18XpECG?t?s>PeW2!_=FY1;nzR`5Hy1~tK#&6*lN#SS(dmTB z3M1-*(iqTn60agg3nWscga$BIAYqFLM29}-m9#vIK%*EumZP$@NH(uaC7MMU1Rj}B z!lICDSAHIP?H_}>H3&*aH!DOVlp+5K4Zo@&qz!eVrk(DK zH~IY2vNMJ9t-tH(9v5c+M7mP>wCwZ~_2nfQ#+&ETjW68lf3~J8WTtGIq~4Li;)=`h zOg;vdr*tEW`0vULM-jpIu7&H3@7>5hxMRt*pPd!SCLMZ@I=QqvF;HX#@j`MFiqk^z zhU1G`Iit!Q`d$XZC6J+)qGm~^qc)>RiV)Zs{gmxgb<%^)A{<*w19cf>JtoagO=nh+ zT>cbXck!8rH3QohnB-j1E@vGE$q(d?Y!Os>oNt-=1>)dQ;l6}eB5i*hObCnoPN&c> zbUcZf0~Tf#KzWP^E5kKmKJk4oStR~5qJt7&QkFIXw$f>}4||=5k%g=@+>4+`suqBk zIu9xOmhr*5-Y-dV{aZ&?zXh}jzxjW65^dp%Yu@a9n))l?E*^w@_*27 zV~Soh-&p=#iRra9Gk^NCart9M9fxSqqvQ!DL=mt?V<>q zFZpQV(n97*MfExA5T(NwaO}Vy!?~X2-DNFtvsWdq2q{P{4DED}dnWem-NtwKwBF9i z)jxZJTHCg!ZEe$UX^~Z-+Rs5raHT2dObhj{ZS5#ZZ0|jX63vjVP+T>x(@Q1YQnSTyLwVt%{(Utb z!nY*ly7pG~I`e4rZyya>v%lxj>3>l*|IX)y!k@mq=-jjO+R+aU&X2FwoSM5+fszTK zSieJfIrGG)s^PWSYpm?qo0lkx?RH@lauF!4RVC#{>JjA=5@@J+ z9m zgL4i4XpLWttoQTvn#A=rx7_DY7RSnVuM~8 z->Y{$ygUEogAH41+NW=lEO;R;31xT}>PyYluNC`D~rk)`%|9e4MSF%zZ;t3|Q)>A(z=nqL6lfDVQCz!@LrYVdQ9f@xy!x95E zhi9Iggu+M4P?TvvcnZ|OouzH|xl43){`;%lYt-v2PTc$Wn})KGaO8|kkXWgl)ELh} zRAxXwoUd$D7>X+o^^D<3w7VaT;Em?;`P({YZ)!Fw3;&g1ICYl)la|9go`am!Pkf~GfRi^Zkm z3Xezi7-Kg|E~wiUyP~?AL${l|GIFp?NS#7_j9g(BA^fh`EJc?P7*f%}c(cLQou!R9 zsSI*w$5`Ciamq{aj;|cGa8*5HNCu_o4*x>q@y*IeZJj&Tr^ToF-lTxCguC+p=)0`F zd$oJxwnL94zWu@afm3_lowL9obB7&!CmOR3_<+R>dsQhfKc%P(hO+lTG8oOYyxzm@&zh_4YF((6GCMu zwinI+&{t$5i}$MA&dS=*~Ta4g_xyK>jK>?qsK43+ORd%$)S(w%@pHC315($V82 z+8XGB6B&ihAXj8EA_XK!eluJ0y;XnocO9E!?NgRL*O8UDXLIbCh}QK-V?QWH^1cT) zgaxsyT~X!lS9^|aIoo>9T4@qrT=mV4rT5SJ-nDUddiBy*W_@GqPNS5T#OzMwwHHP>6&K z8II#y`#z42e$Vr~&-+~8cYXi=`?;=k?tPDYuf6t~_geYR?iUqy4p_OxUz0~w!9`&` z@5A&WiPROqFrFE22I$U?NrJH zz7JiHg$xw{p+>K2fie^x~Tz%*Hw5=i* zz4`M3x5|o%tk*StP^)zX=V?aJr6!lg1Rh;#>P5_(0CWUM`}FdoP1gk&#@1ou^bEturrh{Z|(2b z(q8{>{Z7l=G-?ClF-_q{-^c#`whs+CJ|Mp1PeBs_EAO|k`AE0-?Bfd<9PEec6@Q(Q0qC7y*hsJWEgpm1<|oy+nxeFcNc;5$-|3YOwoPqQRq z&3#Fi4E0cM>09Tn?afyjO4sR^tmWWBj>ND^Ca}xYK2vAFUkCyD zNcIBr=G8i8v8OLha^lbO%i_=8;KVQ9EpO5hrGV3c0K_1Hqj-juY3aU@I^v{r{+l!D z8RcdVjzraLS9BYX{CVi=)vv)W&ZcL|ZZ#oaO^rP)^SdiC-5DkwW(dfsE{Orf#m?+t zW<%c5W$!b~RZwOnh zhO&|=2jq`mfoSJ60exctLTHtpywz$2EqTlE!mI_xTgKjV*O|<&y-ae5GSP8Xhfvqwf*2BKus@JJ=j*{z zT!;cm{Gfzns_C{4D1ak@k|9798e}R~aaz`Z(g2+=MdAPslI(GuU^s@L6Bh{j^mAZb z#*lQ65hYiVSY~$)YJ{x^c@!!b`9ea<{X2M?D!Fi~Q33~`P_)U)EavT8I$X>mj&P8k z#xvi>+*HOu^5zDKB@%@_79T7Mc=wdbFV)b5klR;?K%f*s^r4zScT5G4SO^u(r?cpw z-Ldr67TB@_0+=VJbj&#aXF}o;s$hS!4VkIs>pA|GH{eeP&vXV~D(t3VC$1Sf+}|)QiaF|EIS`Pzlx- zHB-RMpkeT+D>T6vDk%W{0=hbDNRR;(W%k5M15=A|BKi*&H5}~D&|yx+W7U|@jxtEf zz@Xm{5l@v*z^;R+I*6r!T$o82IYYP7Y5P#I%FOBaW=rhDSZcVSDovcmU&r`w?BafV z=s?e;h28BhH4Ix!1x%{J`UAzZAOHyq7G%nYly8;bdxtL}-cj3X$Yad(4Qc45P_WmS z5S3Id3DF=~nG9JAt8Z&&Hsn07(_A3+*2TEoji9$}mA1 zIl}$aaz-9faPTJh!{JH$@u`Cb;<2l45YAWMPG6#92b&NN087zBh6I)bE+#RS82l4L zNo;KDz^?jSKLxK)-pI4h2A0iYk!yQzZ>D9H_S#se*Bg+G%^YoJfwokG zK1@Rg^o!v&ivqspWqKu(BuG=GIL3tZE09_TXfy4)EWXB2xrPh!G!Z&toq)O@f$!ygCgdf9iw9pF5_Q3c3lC+)QrBy0uCb1|7B;BPCiyIF zoq+o{^#y5&M(e@O>Lg~KlpHaMB&W+*hJ% z3f0NmYnjDlq3L{iZ@AT3U;V4K>NP$|+w|i64%n1he>sz{*SaB{B(Bq$VWXg7<(krC zZe(32SPeOwWj1IccQjBEs6U;4v38$%_+&SZgp6|77y%X<^jX4Kcm!;WH5v8;&Pqcr zJcz{#41!3JJf8EVvatDS<gh0o`uI$zPQb%C639~rJtMe*3g+uT3aJrfJQ-lJH%Mty2PMZz*W#WKp z5ZLxf6d!<5MM<#BpebERB~6f|3APEz>(1D!sU+b-P!*6|OrnHeRQfQ2j*_9Bxip&q zm8!ym->=Ctvpp80bAPD%*p2fLIKZs?4@{gG;hd(@VB7BGK=M5>Zsj7f_TIp|n&P0>OKmc!OJfIdtT?Ncw;npw^r&d5LQ^S zSDSY`G5%CkSFn>ezvh~p1HqpJLz(r4YIv~c%5P0#Yly8KNml@hV`P0SJt2?HQ4BWO zfx|_|Szi5OLoA`!gk-<0_Sp{S{a^$KY!{T$%q-P`66n=2_;c|F{DS_RJ4wF%hYr~L zDO7aJJ^vNIl(v?k~q*h@WTXHOfW$- z5;DmVp!#}p94c}>?HdOz05%A%O2T2ejW9l~M^ksuuRNp?fuOJRrclj_fj-hK78DQ1iNOM`w-u9ztoB+0J!JDk70G*UgjaG@Y+lhiiJfXFBqOF@Rht z?zktz`c>He>z-dXaM?GBa}k#A$6ErwxC=%_lyL+~<^og5$Y@SCi|I3-Hw1d?3>A%!15Qfd{q2-Z&B_(SA^r&r9q56Xbi!;-$U=c?;_W}i ziWR{X1!WrwR{%~T=L)FD00(QhqW(Nh5C|4ZEXV}XA4=pd?$o1RiZl)Me|0qj;YnMZ z{#KoML!BIp+S+Ww{99bA*UW%8Eh@D-W@#jH?bv&gyrU$uw%JQ5j%2im*-+5DrFgqe9iXnsIKak#NFP#3JH3ZslW(ApZ z(8$X)Wk-A%Np6T_(EbqVK&B`161!2y(5_!7t;(*B zNu6NN@T~??AEIiuOa7*)!p%cU-_APYvS6RJK4~BDV-$G74q0BilGp|`)xdGPTwfo^ zFq0EpLh2yM9_pafu$ubnK-vhis2X`a$pv3i6*eR|-x;|MX#_Q+!|Gxivb8Nuuf;vT z4+?lgCb)c#ks`PKRM?SYUX^{^mB4UP@fnkq>D_h}DYCYY4yOcREHkRs8(A16l2{bh zXLW!>`2xwaCKw+u5AA!$S4sy{z2l4WwVbsw%h%msZhm|u{Z#U^Lepv=JxS&#=7GqE zVdI`wz7fB~VEM|z!;&`-nMCHeHmmZ~ug}ztaBd4a@HS(!y-7k|M~`97j*|dKETRO> z$jl#IN^lfw%7X*J8&dCAUok?pSr$*rm_8xakXon|_6LG+1jG?MKJ$aB1&;9maZHYm z!1lVMPz7Y8Nsj#!0TF-n`2ae6(n%ycF+SBtrWU8<0Cu3M#_Q!F6-p(;jun!5sAy^= z{&yDCG@d4KnwX)8%%piqL&?6?-Wmih8L;WvRM*IZc~r^Quv`YG5Wyb$2nPpS z6e{fiA&`aXOHs|xQXrd<>TY44UF0UH_q-e9M9WZ|C1(IY8%ui$`nj(9GpqtM0zQ~R zN(GJA;V(ewIx|xak1<4VIi)gnp^c#H$I^I(2pYz0QD(KZyVB-WZizOpy_TJwual!A zYm^|h5g0@nY1QRaUYp_TMiz~I0x61$fV}fCSuYk=DAgAZ&KV}4shC!~5UFBVF(S0| zrwQyorl94ZHPRC(Sobud0KZ~5NkB?F7$Vg=Nd27^R|EtJ$r!&W6OBSKVha7A{vjkT z-JK2)rTfqfB?9pfVJJrNrVp^MBQXjSwZOhSD?}eV2`{A?H!co;Kl7C74@lvlPx3#pNdX+4C~G$}1Q$C&5*-tLpC0um z0@f~23y0Mjc5wu2Y1sl(4w=grijI0Z_g9AL>^Al&U?Dy&&ICzEt;`3)fCF zi-mRP@eC(dhe>^x*)<>p!e^P3)?PDH-%=~HaMP?dZ_x^#ACUX30!lrAjY`LfA*#u2 zKsSKho-KV{)@_6R>KED4Nnru|)8`qQkixU&)fXw?Ph`Mfl3gWaD`9$nRq{F%3(5;R zD(Nh_n}d*a;KU#~gY>n}cI_cgiUE^Nd5vSw&Xk11H_IaNOLQ$AlmeXa-P<@I9tVd~ zR3Jk(wgGO|tO3Z`Jk7Y2YY%{N!U^27fGjMG36jZIC6)+EAXD^GKvEwrkyvL_Df#z4 zL6FRikUH(11MHzPcOHmWLbgYkyg&c;BNhtxdC#3b5(#RgtP$cU8|l zmFH)w*)hw5`ytsetPQ0oFrDgb|5lx7@&at&?7*K*eRP}*Q1DM7m?rI_V|a`$yutPT z4R~`2c-}PBD?w&5MfHEnP^cD3Q`Zpf_q9t5IEn&nWgB7~nVlFHOQxqwn}i!^5G0?Q z+MU}8a;U*J$o>laDh&dPAqR~jZ2Mql1{It^7g;UUQMmVq7}bv9WD##;T_T1tJyr zy9r?%6Rmf65eRH}+|?>gOgl&H5)ryEEF=J73y8r;(4qS~5C(M+4aMll2*Lq0%k+P< z1V`{V(d+)S_krh$j`aLZn5DfAd(}S*T0#kr{v^%xWcu&N%+OQJkMw8oI*b&fu83L9 zB2a157ic#~q|pXWOYWQogZNYIn8~j(L+GZ7-t2`G9f9RRpxcYZ4M~rxy^$k~G3q$g z`os!^A1eWh;E^}2^3OK7g7X(>DCG1<%M2PjrDd71|7$#Vh!OH#E>RVzrYqjuPSCW`*fo%E6MRP!gwwVRGxMt3mWv8gH$1_A2V>b&5O@VZ z9~E1c9N%zmd#P5Pj8Cy>yE(bPn;D55==DaxlZrIR;D$ z$wEP@n*_jR;RG?*Xfh$r5YR+(B^Amb$I_wkm)%1XJFT1(TLa8k3HZ zkcvq!H+kR1l2W7n&(n4Fo)+o$5>a|J$1LBP*Q~I3b=$_wPycJx9q(JW zYrCcJHs;-%QkOzBhiThYX7Lw18+r2bOz)?iPZfy{VBIh2W{5u!#trUC$gYIDpB#ol zJ`7XvDE=chqay(4#48fuqI1sY!%^#P`4T*SPMo|~&9_@z;S2B%uyZ&b)g~#i>Jput z#q5^~BC7W-EX&@dtx44f!WbZ4BB~4#HyPOJhOEe@0*DGQA5G(d?eoi#oCEAs^E`+y zn&;X8@f+kd{f69PGB2VAu)<85C?h4Y}Xp3mDhgL=73m43J260Z~VfsxeGcR~m0Z z*#@Iv7J%jgI|u0+nY=)N1Q`HMne$1DCy#Ik~}ITzzE(L14sao)sdlL5jB)mS?ws&AP{fFYr(Z6 zBOuykskIhtRNtLHuA1bwSj_UM!x3uu2Llmt4JJm$}Z-~XQfIu z;u%g@r?OmM1?d+V0Xj~=65<=;cJ!*BBRvCgadW!vBFHdtHAvu6Dbx1(QV{jD-(+2q z!*;XvrUD0`!-(PEf0CMBw9lAH^emUdULd32??AQ3@x2f0wqLp{{=?vQ_Vz(D0|;>M zad5i_3uD7Jq?!BCa91f#3(-|KEdtvKo+gqW3q&u(X|AJ^Qdn@fyt1Ah$N;MjAf3*0 z3VC|LqEPMQ?+ICXAv1Y$*zvTad9Xt=HHl@B?hbqdn7yF*p&ke()Y>m#`TBejh3(_FZul2kw_++ThiEX)HGDOL9VOJ7z zM2f?z36KyF(A27v%xpd!g7L;sOb6|j+D}3z{iP9L7h%vr z`zBIxhGO%o55RsZ_`3Rnq#CDbi3D;y1_wmTu^8VDS=wTLDEDSpD0Km%|$}rAR;= zXWkz~1Cy6%+R1D;I?!Dd&kr;DHzKD&qdBzSS+DVh%z?BM5Muv#w-Dmg%p}Q#m*{JTJk=bFhOiNWM4T*9R( zoEHbqi>E?KZmcWuS7i$AD#MVOa#wL@GO=Y_==8ND)+&SF57DlA3v2$r{>owPcs}xw z(L|T&vhkS-qMd`xi{^W=NmH_&24uR3o|M|30B5A0>!wXY#^jLX{D^>1G$H8t%=Bz{ zizcTG+R>(t_R3jvV}hZ>lEI|4ssRVFuDzqqVLZeB>1jQC+@p$Ev7Ow-$z3i(MV}>u z)XDEu-x+0Ma*2i=CKwhip^PW7OJtHqF_bX(=%2#T@QfsHGB(o`I-@pN2k2=4Btg6L z84dqtuH*DIHD^vgK4S|vuUB7eVlsyCmRu?XX8^miaWL=-3{Qu(6Zy!)MibrGxZ7ad zSz}c+#D-CmXaJ((a~8s9b$ybQ9Ke?zmJ*ECQfjKx=cun9l0Y!0KL>FBZ=ACZMDIb* z3}XGlmQr-|wV9Ub3|ul9t@T1H{L$SoAZ|3EE`%6or@{66Tyk_W-@<9&W=?EZ8+@+@ zV;4(Ko<8m}^~&ZR>*Yf|b=Ig(o-h|4Y?hn&ZtR)_uo)n>*a4v6<~5Yv+NeXyFk{)U zh|w!%ItFKn)U)EO0VlT3Oi1f7TG#UO`5IOsV54`aRtdF?puJdEIRaC({ljs1&k%YmUUKb>hTHmJdx~9Iwv{u-;fY!a3 zUgPQSQ7_QCGV2Kw$-fTg5P6-%1djVdnyj@lwX*Yw>z#IZiU z$q}i;wi^^op!%(Y_%$b`rV`bgQYFqOHFCZdPm+0do_SdxM9K@Uz8z_KOZex6%M^}i zMRtDc`8d9rQcG-foWcoM+IMKZbur7jas2&pnD_OZMib=Xc1u3%8nv#~1b85ZOxWdD zmB}T0XwxBU+sS!3YJESaaKUmT&6lTeTcxqvHGQuLY|h|#OKGew?h>1R>X{8)<3n7E zR#b?TcRfCFoc$wU?l64&X#H`z)>=NzblxS$GZ)-XO&q(lYxbNB*_DZ>~s9dXV@J@&L_$Gy5<$=3`o794> zwd5UkII9i?i$nr$=yt66RX>=s=wdg*A1|Vk(R#Iq#ql`R=quQjv_Z3p9t=&#!ePRR zO3E)`y}=Di0Y|O~!=ov0jrwvs!Ny9xf4cbsM2jWxU={Wfh>$a!ZfZ{sBiuDp{s0Rn znR46%-ddqa?pg?()`2)!1s{Mb=mV+&M`Ha)e}4d9^WIkk_0V;Q@{tgASF4Ru|=m>Ja?fTYg0Y7-bOfb*vKutrBQ{UtY6?!i) za1EKAc}^K6cL5C1`o%u`41>HJygD@%&c7L}_PB%*l&m`8*hEZqE(~J-GYlO-^A`=UQN4;a1q^%GRKYI@rWZ(Q3`eYgE# z!HR>x(RlYC$su;O0sA(+RU<2-Cl%4sz&+;SPnqCdGvb$^*4`~VF)F$9dUCUyrnx{c zaQK)^V&CTsF0Bg6S!flXgB89%d-bdI729ZMQcm~mtzVaz60nx*+V}gJR|v%}_oi@( z7mNFD>l~2@)df-Iap{&N$C(L*sS|sEg(J>dRs{E8(nI{Yu1FqpwL`RaXnrFsICpqj z-G&r!{u63poLhIKkjA33QftM3FjZrx4L7cXJFr!;bcs_eiJ-vOAVL!)j$ntE5ZhfV z9nld1fFHtf5mPm>^$maIwRW&O?DV`lij`VS+0V@xW-#1fjI z!5m;>L8+e>9XM&738wH_r-0c-zd{4MbQ1|;7%RNORL;BT zzW=n$Vw9&bEeqVnMWD0u@W2CDJ;bm*! zG{f`i11TG6a$?rBkY_$WA|iz< zm}hcEfDle=`AH-QW72a{Tml#> zR2Z4d99ux7KH$KejP)Ifk{IZaaGA87DoJA}sk#qRS0p794$p9i&Sa2?fM=jHKvgkr za`yOhP>|w~?)ENJ5h=iki~RjWyfAsl_FY&x_2(6+L0!|#2&Ne^BZeW6uQo1?q_i&NERXI@gNy8{vAUhpYxe2X}6RR`kw-!7iu zsi0%gsp~;^rSf_h5xO7iKXyNj?a{7N-v`&}-UqW}HZ6<@{d?C}!1IV(QN?ofxqsFC zJKv-eXqb~{G3*8Z`iEeX_ipsI*bo3@onvs(D4!_qur|z6(Yd&{tDTC%EOtyuM9`{_w#kC)K22a9gKvA~CD$ zvhs|X?xwvx}9*`@Z7WA>sISx5$Kz z0^4-c_7w>(J4oW6l#DXj|0yuC$CUMjnaNomGv1G@brYg|CW|j`@II#g>Myl-k{i5= z^$rj=5O%Wu7@^p<>KA$v<@%xE7VG|xLWyt467xK{Ysiv$##COeo-q4epWL^^xF`ymgyI_ z(N_x35#dkpDTCx#?q(IWm{F65*f&ko|7b2|-Pe!nLo-5tDsCl)4g}#l!npJvql= zPd&3JY-E&`SmC>-wsP4xY5i6n=GZ6{i~teRO^h+eR&a^&c%K{@PC4xf-(|hDoF^_= zF@W{hDST#UrQqGW1z2q#7Fn6BU zo;WwsT80jVm48Wo-P&ss?hC!>ZRzA;N|?f>ZR}4{+`_Rnlgw?lb$^Ib%_+~y7x_!) zHE=Z#xu#d`c-CQGaJuI56TzAd>ZM}1MzIa^%{B`Egy>H1ZC@r#f57~iN|?IoUr4Vh z5MXE`h@MC}^@uegFE+_Ku&6n>Hv4*0^<_QA@2>5-h1Hi=1TQCCNOr~FSIu)7#_wAY z#aMVhMGeNwvjql{CdR{D+T`>qH5Jd%_S5O6nb!ZC`B5`6~q2UzvX_40hXK# znk1Se^Gx>L>MaQ_1IOO2-sU`49_ZKFEk{!T{WQ-#E8y73;llHO@kCNVnqt7(rF_Ry zjzLWD&OI2o8B2cPT><^3PkY}g3SJ%zljzsv)C9zLt!%qiI~wRIfsF!a&>cTe4bKU~5(sr5B+>JQX ztf%E-JPhIkeI^OI^uFT%4 z)vuOL7sQSMTR@Q^lK6805}Y%hd~m~ZEN6_MVkU%u0gqYGo(aiTm|{;7+zgU0`@h|S zZ+j9D5@1*Oy&8eKts@5CAi)`kz|U|JeAD1^=M&2D39wz~DG(Ack2Mmu(qU0~d;#)6 zRhSAP>H>@2HvX_e@)Ypyq&L|uh0 z|5L$xa|lvoq?u@k|!4|4PaXtrp4CH z#hdk+-c(Gpz0dvr4g;Wvg;2a>GGV>7*6O@-j#6ylm79;8A>Ez~PoKh7D%DWPxuaIG z&ocKmroVLkA!76+)la+2*{Ih`pK^|m_-m}V?Dp}mP2-*(9ZT1ljHrcwv;8P=WReNu zrC(3s{DkQn9=Rir(;#}9vv`8s(ymS4(Eq4^KVxKsk8*~hoknyZ6b~)PgP28W3J-mQ zx1+dWJZ5s9ECf9d5`%^58$z%5JexSpIfYXtuC6Y=gKvQ}8pIX|*5**7Zw%Vf^*ogR z*ip81@{u2eKmMm<(7qFMl+s)RVP1(ZhjPCMZ>0FVPyF?jSQ1Ki{IGt)))Zp0{aPj| zM+Q=6H9qPW5|8XBc2<1xv>MTZ&}{V{qlurzt=jaBy@w`qvxt@3`XDlV<7m~a#`m-) zwSrs4gPlKBq$gGz!e>roOpe+1r_eWY6)5$!#a*jTK@4~t*=bf|#Po-7;|JTZ!7-uB z^ax-=->@PhhINZ&Xud5XLDyBv9p9I8io39;rYATIu_611WP<0izLfs4#~L)9hjn73NT34i#o( z$T=hYHhP$~jc{zljRA^giO6S#Du!BN%fSXMF!UD!yLh3Ii^sgW ztLa_5w8qGh8PB5aj9$SF5ApBkC*x*(%>VB5;Pp?OoaYK&{|RESU}z!HwH5P{9~{wwo+2+f3_yEC z^><_ML2!&i{DKy8lNVraa=O;b3ICBFSgb)h(f(T^HP(O=Vh!jTyd6aPuL?H`{O*FP zHcY}%%+D@AA&gS2fu9|l1iD79Jmo{}6s`*cd?Ps8XSKd~J)1a1jaH~pyFFBaMJqt` z0f+5(FL^9*pcSa|n+ba}cstDf6b{Mn^sS87rISU|@e0Ks#t&jK7Z82mCrsb)h#4u% zfS8Nr#S;`1b3xyb=I(96JoP&$FCgz7=BeicPd(N+2yS~mvh-WxJq5LKwof2Ck04L| ztk&k8w3#?X1%9I%-Aqs$d+jH0V>2;0Jrne;n!B^Ru$=l;Y8R#(v@SFg>3T5TSS#FE zh(#+TPKE;ZDOOO(a-$}bp%7&<-LQ%!9(GO~TKo`V6%Z!UH@?$7{rd@s&afvChq&&- z6Py&@uT!{2fsDy{xoQac01al287WQFV2@$$?=Er_pL@h#FlV83#}A7q4w}RKUGAKo zKl)a>aAPdi_ms)t-(NSOZ&<|;4|mgdp02?<(t%-c=B)1W6n|*2on$%;O9i2V$B~hD z0Q{}e71Y_MYZ#~%tScVu`Kh8mv0NYe=#q#qIE_+P4LC~!{Tt%F6Gr3gdugDjYsg~V zA087*qwn$T?nh7I{%QYh{^9piCiwKAGmcnia%CtV>(si$JRnj9EufEeL|TIY3y|7< zBl;(1KfV@wVh!!_26&v-=-5VSM3a~Ma@gqm9%+hC^z`KYK7N=xamWJt5>J~v`c}qU z--#n=`4+FPQ@edUcbsJxtPnpGZ8WR(v$!|MigNB$;;+f#vQWC?5C`E#ciS<5U)E$G zWOdwtp)TN_-8b8z`FEyq&$iJzcXD3eH0oi;M1+Q_CqAg%>u*8RXEixbvv%oQo9p)zU0RDf>%VvwjEF;=l=>SvGAqJ>=hLt7?g8uJ71fRdHFO609EgBWM7*lb6r{-b~>b>VbOv@%ZmYLVF(}P!x|Y1VWUz2LQ-L z1>h9!HXcEc_*seQCpRJ<37si|&KULQuAwxdSvx(2^WlQk>(bpawB*qg?ukFpqO(;e zxqEMHB#)CrcfwbRU3@TZZ%FPU{IT`kfdDr93+vo#4;w^V4sAJ^W?y6M@DJN|2A#=D>}T&;uU$($Rz35suj zZrTt&QF!-_aQ{uw++@`YIXg{#uYQWaM{3uIs`sUTyjvg|%M;<{X;wTl614iv4w0^J z**J2!;Y%l{JWkaY-$Dn1J%RRv0ySn?k~wjQhl$?}5fa*tE(l?r{V|=RV53jiZ&Ub5H*N0YYsLwH*(! z+!a0As3E{?Bc+A=)+HTJj}Z#<3JT0#I*>XDg2M;HBF9L$nxCs6oto|x?o5C(B-@K1 zwusz0GgjOQ6W*c~s(NIsdkXifDv9fei165##9uE{Uiwo!)=d7CUQ-cA@hqMkavFq~ zyo6jxP$+clAqMTqTsFVy9Jyn){ty!|*?YM^=uD?2((M4b{!NFMZjhlf3Qwi%N!9No-D8 zt}d=?Z#vv7I=53+#O02r6t9Eod7-VDng1;B-`x4RoRM=lK*NEeBg|y!0pXAvUmw+I z-dGx59Mk#f@TW)-*8&j_Fy7mm7`@hldG}T(bM(; zxVFr!RRsyH0Z->gguh}-yFCUb^^czZKG;m4i!?UG=M>fiV@ccFXno1*(>=>)@y zhBZbBTPYq`FR_rD1CF57aN!jkt`+*z?fyO-f~ z=H6Xmz4;GE?&u%>yh!BY>X)6N?;h9&3W(i|6Y?LBemWszY8ia>$u%ocSM7#P{9ku^ zsdO>DJY&%cyJOkh1A4S-t$y9Qn(BS#&b5h~kDs5l38@RVRVO>pe=EVUKFR)ZRYAa4D6WlBL=mwu5Ex zMHTI=Kn!F)RxTL&mLAdOvF6H~H>>$hs_eS-N}(rVNzoCuGkiq@2mD>;k-m^ZKROh-pASAH z#H#Mj-%gnjI_Ir?sVe2KkhGmgHRdle%yoQJcT?G`-`2Wb*>E0r7U7ghT6EH_8!qB) z*CM?cJ}0nd4&`o6ED^X|d@kpVWuMDryxUQi@a@Hq=F8U|tNE%j*sH$mSgC0FJ^aDL z2RSwn&hPtNf5VonSI~bTF5PJg$1R?}Nal{pxaA($ zTHr_Cf6AUYz2NlPupO1@6?r1j%U|~W(pKyBwDVR@-jo^@E_nFM{IHZwJg=jlKz&fI4PwbvT*vC zcZE-fUbD6E)+R`gpodG;?LVhFKhst02SbU>ai)?*=i92^bQv!pU%yq2uS@8zH#&cj zT+dz@^z$^SORaTL3}x|=??>{}`{dpZo)om_|3~InB8$@Dpb)tX12ZZ?Z8HhO-UTu2DetTyl&|a;&@Zs zU!M1}f-rIAwCrCQWNFU;+~BU(h~|SAETp=QZq4kuK41}%x2Rd-*|iVNpNGKLIQ*KL zh#ImtvsS8c4;it#yM6vVP;e2iGprpR`=&!Zazk$7m+a*G9Gjo6@qIPI(=uvJWK-+f zHidhw0$~(<+T;e|F#y7N+gbiIl0?DXP2D8Ffm>C*u_=1EPpcWCpDN=R`0aa!pRl@2 z;W&+2Str?S$F%dNaBWb1$1kJbmvUZ?oYFuXI8lB^V^lxQomldwjX1w2u|a9)3GG2q zUHRw+{uMZ`ig|W-AOCkh@vUXyaTrtR=RUG{vvAew>gJ7R3q$7lT{$y_6Z6epoRP4# z=gBYLxBu0bAJ~rNsSQGDz#)duP)2*Ex`&IqRmpfvkV4mSFB#s9u%!px&Ihwv+JE5g zX7==+j60(-l4q9lE%Csn7ID#Lm7_&RiG9~Bi3d3+H>}XMzI`%8#kviD=;X2ZuA%~o z@x|;bo{Ze&EVE7wez>L0&}e%tSu=a-;=4*OJVZ(*>?+^5T)E#+E-ZCH_Kd|P>sl6-%S}CiY9TLSo zzRHNciGTexu1_hUDKC9**8H{!O=C z?wm2+JqQ>|T=iL| zh`s*k>YzSB#?$rN5|->dvnIPcrhq}NSNZBL>9(DxUKRX$sb#gA+2f1b-eaxt_GYzh zBpD6UqN0!{2|w92gn9cqw`^U*{{;McH8pLH?H`>}j4ScGPHgF0^Tz%A8x7fK6%FiO zFF1o^-*MSh|8+_=gVXZiI&-fe*W6qRbHUq;jefFX{bj*f-|ofKyX}Qo2dAAW8`ho{ zc5Zii`Le>S;eP1F_av79XUlQTw&tB{E`Qx2wu|ZDezgt#M|A}~Eqhxo{SfQ_5t{m~ zpy6@6Vq&LPvc5-<gBJz1-b* zd#Jewf6!wGc6q5Wie9#KSjyGK?#hY#4QDTpw<5H zXeGRF3jXZ(&f%zh*t(;S1}d)MAWH3DuCxx}#pFGQ+Ku>)vOjzbJmjc(K{-hIn(kO* zQ)fSp!{|bSOkV%Np#8Od-`{uFr8tR=ZFQ=&8osPKfs#^O&%^L~bmE2Dg5f^S zH;%1+?8~`yQ#Dppe7NSjk5%UFVcnPQtRZd<4-+0;Z|i$jE_pn>y_nOGq56m3!~U|f z2KS%1Pcp1wJ}vy5oA+NQ>v~)~DmH}^^#@v75IQLl@mL)&n4S1)Rb_E6C=hz0N!;qm z$qybB+{usV_~HKfoLJM(*H109v)1_RUEQA8!12DO{8oRI{)u&ChU7OB`BS)Ko&~j^ zPYd9Na~F@sY4n%H{<7}~$kP%pR^CvM_@f_X>Jqb?%TFDjbhv!ytnV$MX8|d*jlEvS`lZ_VA20b9{(P&u zSi51h{PQMP%uUgX_{J=)d zoaJaTxV_Bx#8}24-xN;p=EgRccku@YC?lO#_YTD?N%rn-S7GNtey4i3GPGfi;u+)6FNV%`$QLwdHlNHq&Cnn zHvXulz4?)&eMVO^OU*J5ONj`Bq|SZjjf)e#QX_}0`yb%w^xjZSf$a$?jp>Bkrd}9<^4XO7Z^Yf|RW}PNwS}3ohTwbGUj{ zaG{pR3;UONhED`idCPNQ?`F}M=RpNlO7?dfKi%SwBgm93RSrW%HVF89A~t`KD?Q?D>Ry194P#% z$92t0XR1oHf2v(({Uv-SY4MrIUVT3X$+pE)xbH?^ZGK&#NbhcNHv+U^$!GN~|_0KPn_uD(BS$_*7C@F-0;)>!5FIqP5>H_bpI94-t zK^0H!fC{n*5UYnOttZCh6gHeVJy7X)Bw9?(`<;qqVKDRY&id~xx$6TB2Wy)OO<%lW zU5gX^xruzp=*44EA?bnilDVWdFh*1 zM%FEJBR@#+Sm}L|PpO@3HfolmP@ZRfqQzg)CVAIVeUHlp4$FROeV5)hF;H6{P@i2gjBE=que?Id4`g2+m{qvVC8UsW0iW9=Y59%&hRj zuV>L4Y?{{o<>8rj`=I9XTV~-wUj{3-?h!7v=etCfYb_Sm?%XP#oO?*&`pZ459oEVV zdCFQ8{#r3;ZZ~>4xkt${z99Ia6sf!}=nxOzZJWHsc>CnXs=w-6(mig-EqCv>T{?N; zU319_dF`6YwD_PkM-7I>Zf$TMqBw*t(EA^mIB>3@%K!MnF^`r!T~O?JXXGGjPb5X; z;g1J$oX&O)Zq9vqTU>)533#j2JWKq!fzUGCpZKmQcMQUtM|viuU*mIZkB1xbBv{ls ztQ($piudm1eYiv*I!)$K>I;rs@wsZhtZcpdV)syrmZ3J~vG7>!=WlgxRx`0xmxAMW%(bcdA zq#&VDH)NmTjq1w|b|?MU3tid7-!p2J?XJ~8Ty*wr{evABIlCnsLv$+1Z%dA>%dIU_ z|5ff`6-1G6k9o09+a;@fzM|79FH#-H>Wx{iL=&@04~}ZT5cqZOh-hKe&2wv?uwM&5 zr7f=H;2D&l#f5X~K8qVji2MvG67`x-yNGZ0QM{{X5q_yb@VTeM=YzSedt;_>ivAQB z>cfgMB9F3ud+h3awH+6h5AME7dhiv zida{Mk<}DVt8n+FPoAQBXAdVH`#JHW^vKytzUX?}CG$^y;k$PyK=Sq^`7W_*ZGQ2@ zrXEFOXX~=SRg3mEYehU2XW1#<`=;Vp(NSSry=z%y#(_t@lat?L#!tKrm47^TbD6HH z?pWw+^)PshocS*U~v?wIy)ceTKBf;73 za!<~#tW`FdAANGM+|}p;VQHys#h>OwGCzZacC@5<+>C3N6FwtS=(V7OS?9tY8M(-} zp^0t5m;L#N4j)wcrXaU$bfRsz#OY+e^z);A1cwA08{tQ**DEuHoKAbG_Pf**vYaJ1D=uhDNFuzC-?Oi$!EOE<5p^as8d}`Zzy8TqqZgk zLG=ksw05(nSVegpP#CXo^l0@`D>H6(zqMrl{KEsj{NZO>gNvdV zb$S~4%iME~{?r{yYDS?H>NY-#Nw3=I16-@B1lhP~p!wR!Zlet?8a{XDv-(O>CML!g zwAj?2q75wc4_nV3*5Ld-!n-H0=H{FT{h68F1!xP zgv~5|nVh=P%eAKzBi}GTH+-&^fP z@o3$ZP+t6D zZTmJ8p%|}9n}bV#3TI9D_q-@}9t#M-^{UD^M_X4spLCwsaX+M^u0c#U`S5j@OFW-{ z@tc)f^jn1v6B#PJWFw;A)rYgSR0Q3tme0Sy_QhdBsO`SB1mo$(#(1rf(~YkZPG4zy zwf5#;yFx`tP~>gS)jJ1+|0i%DWz%Nh1)jD=lB5(Z8?1AONqcz{z$c zXxpZg%Qk#1`Bg7j=j*bnUOh&FjZU*H?4QBR4;uDtW}wzeAg`RlUE`d>NsJ_t_kdJ_ zgtq=X%0gl#xay-$8&PC$sjej3R4G)jz|%6VE{hG-jKVLcE!Gs&OH!T8yr{#!kA3rS z`_=kTDEdej8o0#zlg-XgnkNX#VK&>JrbLTNz0^LcE%IRNfLvKguPm`uqL%!;j5Xn; zaJE;?XuwyeB<}Rod(i97{t$Mn??*?^z28&RbXn)iJeGG1t9ETJQ);jFD-dQnyQpH- zgy7?q52A8o&tHz%wxh{`(PGI}`gai^znWEr*d!b7WwUxjMG2YZA2q8cCG(Cuk)f1vyB z?U6<`UmOYB|JNqE3$5VapQR+k7Qy6KTk+iV@fY~C4)~r?(9NQ8n;+=Q)>vJr(HHIS zs8P!Qsygw^bo->X+6Qyi8yP4w_wZSqulfXByYMpB{_*_=lkWJFastAPI mini documentary released at the end of 2025, you can watch it online: + +FastAPI Mini Documentary + ## **Typer**, the FastAPI of CLIs { #typer-the-fastapi-of-clis } From 23caa2709be0d7dfb18b388299235a83258a19c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 25 Dec 2025 11:02:01 +0000 Subject: [PATCH 006/110] =?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 30d5c44039..dbaa2db873 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* ๐Ÿ“ Add documentary to website. PR [#14600](https://github.com/fastapi/fastapi/pull/14600) by [@tiangolo](https://github.com/tiangolo). + ### Translations * ๐ŸŒ Update translations for de (update-outdated). PR [#14581](https://github.com/fastapi/fastapi/pull/14581) by [@nilslindemann](https://github.com/nilslindemann). From a4d04c9b7e97ee5aff0c27e5ea92225f77c7a348 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 26 Dec 2025 09:53:59 +0100 Subject: [PATCH 007/110] =?UTF-8?q?=F0=9F=91=B7=20Remove=20`lint`=20job=20?= =?UTF-8?q?from=20`test`=20CI=20workflow=20(#14593)?= 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> --- .github/workflows/test.yml | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5a12d69c8b..3ad630d94b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,29 +16,6 @@ env: UV_SYSTEM_PYTHON: 1 jobs: - lint: - runs-on: ubuntu-latest - 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.11" - - name: Setup uv - uses: astral-sh/setup-uv@v7 - with: - cache-dependency-glob: | - requirements**.txt - pyproject.toml - - name: Install Dependencies - run: uv pip install -r requirements-tests.txt - - name: Lint - run: bash scripts/lint.sh - test: strategy: matrix: From 55b556a7d1cc71cca9f557aa82b144183e2da055 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 26 Dec 2025 08:54:23 +0000 Subject: [PATCH 008/110] =?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 dbaa2db873..d54193ffe7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -17,6 +17,7 @@ hide: ### Internal +* ๐Ÿ‘ท Remove `lint` job from `test` CI workflow. PR [#14593](https://github.com/fastapi/fastapi/pull/14593) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐Ÿ‘ท Update secrets check. PR [#14592](https://github.com/fastapi/fastapi/pull/14592) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Run CodSpeed tests in parallel to other tests to speed up CI. PR [#14586](https://github.com/fastapi/fastapi/pull/14586) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Update scripts and pre-commit to autofix files. PR [#14585](https://github.com/fastapi/fastapi/pull/14585) by [@tiangolo](https://github.com/tiangolo). From 7c751a2e1cea212a6a39032a9aac0fab937a2b20 Mon Sep 17 00:00:00 2001 From: Nils-Hero Lindemann Date: Fri, 26 Dec 2025 10:39:53 +0100 Subject: [PATCH 009/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20de=20(update-outdated)=20(#14602)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Sync with #14600 * A few changes The LLM suggested a few changes when retranslating the document, these are the good ones. I also added a term to the llm prompt, the LLM instead used just "Abdeckung", which is too broad in this context. --- docs/de/docs/index.md | 18 ++++++++++++------ docs/de/llm-prompt.md | 1 + 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md index 1920df8ff3..11fb6c9832 100644 --- a/docs/de/docs/index.md +++ b/docs/de/docs/index.md @@ -117,6 +117,12 @@ Seine Schlรผssel-Merkmale sind: --- +## FastAPI Mini-Dokumentarfilm { #fastapi-mini-documentary } + +Es gibt einen FastAPI-Mini-Dokumentarfilm, verรถffentlicht Ende 2025, Sie kรถnnen ihn online ansehen: + +FastAPI Mini-Dokumentarfilm + ## **Typer**, das FastAPI der CLIs { #typer-the-fastapi-of-clis } @@ -233,7 +239,7 @@ INFO: Application startup complete.

-Was der Befehl fastapi dev main.py macht ... +รœber den Befehl fastapi dev main.py ... Der Befehl `fastapi dev` liest Ihre `main.py`-Datei, erkennt die **FastAPI**-App darin und startet einen Server mit Uvicorn. @@ -276,7 +282,7 @@ Sie sehen die alternative automatische Dokumentation (bereitgestellt von Body eines `PUT`-Requests zu empfangen. @@ -326,7 +332,7 @@ Gehen Sie jetzt auf 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. @@ -452,7 +458,7 @@ Fรผr ein vollstรคndigeres Beispiel, mit weiteren Funktionen, siehe das FastAPI Cloud deployen, treten Sie der Warteliste bei, falls noch nicht geschehen. ๐Ÿš€ +Optional kรถnnen Sie Ihre FastAPI-App in die FastAPI Cloud deployen, gehen Sie und treten Sie der Warteliste bei, falls noch nicht geschehen. ๐Ÿš€ Wenn Sie bereits ein **FastAPI Cloud**-Konto haben (wir haben Sie von der Warteliste eingeladen ๐Ÿ˜‰), kรถnnen Sie Ihre Anwendung mit einem einzigen Befehl deployen. @@ -494,7 +500,7 @@ Es vereinfacht den Prozess des **Erstellens**, **Deployens** und **Zugreifens** Es bringt die gleiche **Developer-Experience** beim Erstellen von Apps mit FastAPI auch zum **Deployment** in der Cloud. ๐ŸŽ‰ -FastAPI Cloud ist der Hauptsponsor und Finanzierer der โ€žFastAPI and friendsโ€œ Open-Source-Projekte. โœจ +FastAPI Cloud ist der Hauptsponsor und Finanzierer der *FastAPI and friends* Open-Source-Projekte. โœจ #### Bei anderen Cloudanbietern deployen { #deploy-to-other-cloud-providers } diff --git a/docs/de/llm-prompt.md b/docs/de/llm-prompt.md index 5df904ac7a..35ca9f0692 100644 --- a/docs/de/llm-prompt.md +++ b/docs/de/llm-prompt.md @@ -251,6 +251,7 @@ Below is a list of English terms and their preferred German translations, separa * ยซthe buttonยป: ยซder Buttonยป * ยซthe cloud providerยป: ยซder Cloudanbieterยป * ยซthe CLIยป: ยซDas CLIยป +* ยซthe coverageยป: ยซDie Testabdeckungยป * ยซthe command line interfaceยป: ยซDas Kommandozeileninterfaceยป * ยซthe default valueยป: ยซder Defaultwertยป * ยซthe default valueยป: NOT ยซder Standardwertยป From 5eb8d6ed8ac4e638f4d5883c954a8031ef3f3c42 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 26 Dec 2025 09:40:18 +0000 Subject: [PATCH 010/110] =?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 d54193ffe7..bc2441f7f3 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-outdated). PR [#14602](https://github.com/fastapi/fastapi/pull/14602) by [@nilslindemann](https://github.com/nilslindemann). * ๐ŸŒ Update translations for de (update-outdated). PR [#14581](https://github.com/fastapi/fastapi/pull/14581) by [@nilslindemann](https://github.com/nilslindemann). ### Internal From 3063ada72f4cd493393e1d8f13bb6498f8d5cb93 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 26 Dec 2025 11:43:02 +0100 Subject: [PATCH 011/110] =?UTF-8?q?=E2=9C=85=20Add=20missing=20tests=20for?= =?UTF-8?q?=20code=20examples=20(#14569)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastiรกn Ramรญrez Co-authored-by: github-actions[bot] Co-authored-by: Nils-Hero Lindemann --- docs/de/docs/advanced/dataclasses.md | 6 +- docs/de/docs/how-to/graphql.md | 2 +- docs/en/docs/advanced/dataclasses.md | 6 +- docs/en/docs/how-to/graphql.md | 2 +- docs/es/docs/advanced/dataclasses.md | 6 +- docs/es/docs/how-to/graphql.md | 2 +- docs/pt/docs/advanced/dataclasses.md | 6 +- docs/pt/docs/how-to/graphql.md | 2 +- docs/ru/docs/advanced/dataclasses.md | 6 +- docs/ru/docs/how-to/graphql.md | 2 +- docs/zh/docs/advanced/dataclasses.md | 6 +- docs_src/additional_responses/__init__.py | 0 docs_src/additional_status_codes/__init__.py | 0 docs_src/advanced_middleware/__init__.py | 0 .../__init__.py | 0 docs_src/background_tasks/__init__.py | 0 docs_src/behind_a_proxy/__init__.py | 0 docs_src/body/__init__.py | 0 docs_src/body_fields/__init__.py | 0 docs_src/body_multiple_params/__init__.py | 0 docs_src/body_nested_models/__init__.py | 0 docs_src/body_updates/__init__.py | 0 docs_src/conditional_openapi/__init__.py | 0 docs_src/configure_swagger_ui/__init__.py | 0 docs_src/cookie_param_models/__init__.py | 0 docs_src/cookie_params/__init__.py | 0 docs_src/cors/__init__.py | 0 docs_src/custom_docs_ui/__init__.py | 0 docs_src/custom_request_and_route/__init__.py | 0 docs_src/custom_response/__init__.py | 0 docs_src/dataclasses_/__init__.py | 0 .../tutorial001_py310.py | 0 .../tutorial001_py39.py | 0 .../tutorial002_py310.py | 0 .../tutorial002_py39.py | 0 .../tutorial003_py310.py | 0 .../tutorial003_py39.py | 0 docs_src/debugging/__init__.py | 0 docs_src/dependencies/__init__.py | 0 docs_src/dependency_testing/__init__.py | 0 docs_src/encoder/__init__.py | 0 docs_src/events/__init__.py | 0 docs_src/extending_openapi/__init__.py | 0 docs_src/extra_data_types/__init__.py | 0 docs_src/extra_models/__init__.py | 0 docs_src/first_steps/__init__.py | 0 docs_src/generate_clients/__init__.py | 0 docs_src/graphql_/__init__.py | 0 .../{graphql => graphql_}/tutorial001_py39.py | 0 docs_src/handling_errors/__init__.py | 0 docs_src/header_param_models/__init__.py | 0 docs_src/header_params/__init__.py | 0 docs_src/metadata/__init__.py | 0 docs_src/middleware/__init__.py | 0 docs_src/openapi_callbacks/__init__.py | 0 docs_src/openapi_webhooks/__init__.py | 0 .../__init__.py | 0 .../path_operation_configuration/__init__.py | 0 docs_src/path_params/__init__.py | 0 .../__init__.py | 0 docs_src/pydantic_v1_in_v2/__init__.py | 0 docs_src/python_types/__init__.py | 0 docs_src/query_param_models/__init__.py | 0 docs_src/query_params/__init__.py | 0 .../query_params_str_validations/__init__.py | 0 docs_src/request_files/__init__.py | 0 docs_src/request_form_models/__init__.py | 0 docs_src/request_forms/__init__.py | 0 docs_src/request_forms_and_files/__init__.py | 0 .../response_change_status_code/__init__.py | 0 docs_src/response_cookies/__init__.py | 0 docs_src/response_directly/__init__.py | 0 docs_src/response_headers/__init__.py | 0 docs_src/response_model/__init__.py | 0 docs_src/response_status_code/__init__.py | 0 docs_src/schema_extra_example/__init__.py | 0 docs_src/security/__init__.py | 0 docs_src/separate_openapi_schemas/__init__.py | 0 docs_src/settings/__init__.py | 0 docs_src/static_files/__init__.py | 0 docs_src/sub_applications/__init__.py | 0 docs_src/templates/__init__.py | 0 docs_src/templates/static/__init__.py | 0 docs_src/templates/templates/__init__.py | 0 docs_src/using_request_directly/__init__.py | 0 docs_src/wsgi/__init__.py | 0 pyproject.toml | 11 + requirements-tests.txt | 1 + .../test_body/test_tutorial002.py | 161 ++++++++ .../test_body/test_tutorial003.py | 171 +++++++++ .../test_body/test_tutorial004.py | 182 +++++++++ .../test_tutorial002.py | 361 +++++++++++++++++ .../test_tutorial004.py | 290 ++++++++++++++ .../test_tutorial005.py | 272 +++++++++++++ ...est_tutorial001_tutorial002_tutorial003.py | 251 ++++++++++++ .../test_tutorial004.py | 275 +++++++++++++ .../test_tutorial005.py | 301 +++++++++++++++ .../test_tutorial006.py | 269 +++++++++++++ .../test_tutorial007.py | 344 +++++++++++++++++ .../test_tutorial008.py | 157 ++++++++ .../test_body_updates/test_tutorial002.py | 207 ++++++++++ .../test_custom_response/test_tutorial001.py | 20 +- ...est_tutorial002_tutorial003_tutorial004.py | 68 ++++ .../test_dataclasses/test_tutorial001.py | 2 +- .../test_dataclasses/test_tutorial002.py | 2 +- .../test_dataclasses/test_tutorial003.py | 2 +- .../test_tutorial/test_debugging/__init__.py | 0 .../test_debugging/test_tutorial001.py | 64 +++ ....py => test_tutorial001_tutorial001_02.py} | 31 +- ...st_tutorial002_tutorial003_tutorial004.py} | 23 +- .../test_dependencies/test_tutorial005.py | 139 +++++++ .../test_dependencies/test_tutorial007.py | 24 ++ .../test_dependencies/test_tutorial008.py | 58 +++ .../test_dependencies/test_tutorial010.py | 29 ++ .../test_dependencies/test_tutorial011.py | 120 ++++++ tests/test_tutorial/test_encoder/__init__.py | 0 .../test_encoder/test_tutorial001.py | 208 ++++++++++ .../test_tutorial001_tutorial002.py | 156 ++++++++ ...st_tutorial001_tutorial002_tutorial003.py} | 19 +- .../test_generate_clients/test_tutorial001.py | 142 +++++++ .../test_generate_clients/test_tutorial002.py | 187 +++++++++ .../test_generate_clients/test_tutorial004.py | 230 +++++++++++ tests/test_tutorial/test_graphql/__init__.py | 0 .../test_graphql/test_tutorial001.py | 70 ++++ .../test_tutorial002.py} | 36 +- .../test_metadata/test_tutorial003.py | 53 +++ .../test_tutorial/test_middleware/__init__.py | 0 .../test_middleware/test_tutorial001.py | 24 ++ .../test_tutorial001.py | 186 +++++++++ .../test_tutorial002.py | 223 +++++++++++ .../test_tutorial003_tutorial004.py | 208 ++++++++++ .../test_path_params/test_tutorial001.py | 116 ++++++ .../test_path_params/test_tutorial002.py | 124 ++++++ .../test_path_params/test_tutorial003.py | 133 +++++++ .../test_path_params/test_tutorial003b.py | 44 +++ .../__init__.py | 0 .../test_tutorial001.py | 164 ++++++++ .../test_tutorial002_tutorial003.py | 170 ++++++++ .../test_tutorial004.py | 185 +++++++++ .../test_tutorial005.py | 202 ++++++++++ .../test_tutorial006.py | 221 +++++++++++ .../test_python_types/__init__.py | 0 .../test_tutorial001_tutorial002.py | 18 + .../test_python_types/test_tutorial003.py | 12 + .../test_python_types/test_tutorial004.py | 5 + .../test_python_types/test_tutorial005.py | 12 + .../test_python_types/test_tutorial006.py | 16 + .../test_python_types/test_tutorial007.py | 8 + .../test_python_types/test_tutorial008.py | 17 + .../test_python_types/test_tutorial008b.py | 27 ++ .../test_tutorial009_tutorial009b.py | 33 ++ .../test_python_types/test_tutorial009c.py | 33 ++ .../test_python_types/test_tutorial010.py | 5 + .../test_python_types/test_tutorial011.py | 25 ++ .../test_python_types/test_tutorial012.py | 7 + .../test_python_types/test_tutorial013.py | 5 + .../test_query_params/test_tutorial001.py | 126 ++++++ .../test_query_params/test_tutorial002.py | 127 ++++++ .../test_query_params/test_tutorial003.py | 148 +++++++ .../test_query_params/test_tutorial004.py | 156 ++++++++ .../test_tutorial001.py | 121 ++++++ .../test_tutorial002.py | 142 +++++++ .../test_tutorial003.py | 153 ++++++++ .../test_tutorial004.py | 147 +++++++ .../test_tutorial005.py | 131 +++++++ .../test_tutorial006.py | 136 +++++++ .../test_tutorial006c.py | 148 +++++++ .../test_tutorial007.py | 136 +++++++ .../test_tutorial008.py | 138 +++++++ .../test_tutorial009.py | 123 ++++++ .../test_tutorial002.py | 65 ++++ .../test_tutorial001_tutorial001_01.py | 193 ++++++++++ .../test_response_model/test_tutorial002.py | 129 +++++++ .../test_response_status_code/__init__.py | 0 .../test_tutorial001_tutorial002.py | 96 +++++ .../test_tutorial002.py | 141 +++++++ .../test_tutorial003.py | 143 +++++++ .../test_security/test_tutorial002.py | 71 ++++ .../test_security/test_tutorial004.py | 363 ++++++++++++++++++ .../test_security/test_tutorial007.py | 89 +++++ .../test_tutorial/test_settings/test_app01.py | 78 ++++ .../test_static_files/__init__.py | 0 .../test_static_files/test_tutorial001.py | 40 ++ 183 files changed, 10459 insertions(+), 86 deletions(-) create mode 100644 docs_src/additional_responses/__init__.py create mode 100644 docs_src/additional_status_codes/__init__.py create mode 100644 docs_src/advanced_middleware/__init__.py create mode 100644 docs_src/authentication_error_status_code/__init__.py create mode 100644 docs_src/background_tasks/__init__.py create mode 100644 docs_src/behind_a_proxy/__init__.py create mode 100644 docs_src/body/__init__.py create mode 100644 docs_src/body_fields/__init__.py create mode 100644 docs_src/body_multiple_params/__init__.py create mode 100644 docs_src/body_nested_models/__init__.py create mode 100644 docs_src/body_updates/__init__.py create mode 100644 docs_src/conditional_openapi/__init__.py create mode 100644 docs_src/configure_swagger_ui/__init__.py create mode 100644 docs_src/cookie_param_models/__init__.py create mode 100644 docs_src/cookie_params/__init__.py create mode 100644 docs_src/cors/__init__.py create mode 100644 docs_src/custom_docs_ui/__init__.py create mode 100644 docs_src/custom_request_and_route/__init__.py create mode 100644 docs_src/custom_response/__init__.py create mode 100644 docs_src/dataclasses_/__init__.py rename docs_src/{dataclasses => dataclasses_}/tutorial001_py310.py (100%) rename docs_src/{dataclasses => dataclasses_}/tutorial001_py39.py (100%) rename docs_src/{dataclasses => dataclasses_}/tutorial002_py310.py (100%) rename docs_src/{dataclasses => dataclasses_}/tutorial002_py39.py (100%) rename docs_src/{dataclasses => dataclasses_}/tutorial003_py310.py (100%) rename docs_src/{dataclasses => dataclasses_}/tutorial003_py39.py (100%) create mode 100644 docs_src/debugging/__init__.py create mode 100644 docs_src/dependencies/__init__.py create mode 100644 docs_src/dependency_testing/__init__.py create mode 100644 docs_src/encoder/__init__.py create mode 100644 docs_src/events/__init__.py create mode 100644 docs_src/extending_openapi/__init__.py create mode 100644 docs_src/extra_data_types/__init__.py create mode 100644 docs_src/extra_models/__init__.py create mode 100644 docs_src/first_steps/__init__.py create mode 100644 docs_src/generate_clients/__init__.py create mode 100644 docs_src/graphql_/__init__.py rename docs_src/{graphql => graphql_}/tutorial001_py39.py (100%) create mode 100644 docs_src/handling_errors/__init__.py create mode 100644 docs_src/header_param_models/__init__.py create mode 100644 docs_src/header_params/__init__.py create mode 100644 docs_src/metadata/__init__.py create mode 100644 docs_src/middleware/__init__.py create mode 100644 docs_src/openapi_callbacks/__init__.py create mode 100644 docs_src/openapi_webhooks/__init__.py create mode 100644 docs_src/path_operation_advanced_configuration/__init__.py create mode 100644 docs_src/path_operation_configuration/__init__.py create mode 100644 docs_src/path_params/__init__.py create mode 100644 docs_src/path_params_numeric_validations/__init__.py create mode 100644 docs_src/pydantic_v1_in_v2/__init__.py create mode 100644 docs_src/python_types/__init__.py create mode 100644 docs_src/query_param_models/__init__.py create mode 100644 docs_src/query_params/__init__.py create mode 100644 docs_src/query_params_str_validations/__init__.py create mode 100644 docs_src/request_files/__init__.py create mode 100644 docs_src/request_form_models/__init__.py create mode 100644 docs_src/request_forms/__init__.py create mode 100644 docs_src/request_forms_and_files/__init__.py create mode 100644 docs_src/response_change_status_code/__init__.py create mode 100644 docs_src/response_cookies/__init__.py create mode 100644 docs_src/response_directly/__init__.py create mode 100644 docs_src/response_headers/__init__.py create mode 100644 docs_src/response_model/__init__.py create mode 100644 docs_src/response_status_code/__init__.py create mode 100644 docs_src/schema_extra_example/__init__.py create mode 100644 docs_src/security/__init__.py create mode 100644 docs_src/separate_openapi_schemas/__init__.py create mode 100644 docs_src/settings/__init__.py create mode 100644 docs_src/static_files/__init__.py create mode 100644 docs_src/sub_applications/__init__.py create mode 100644 docs_src/templates/__init__.py create mode 100644 docs_src/templates/static/__init__.py create mode 100644 docs_src/templates/templates/__init__.py create mode 100644 docs_src/using_request_directly/__init__.py create mode 100644 docs_src/wsgi/__init__.py create mode 100644 tests/test_tutorial/test_body/test_tutorial002.py create mode 100644 tests/test_tutorial/test_body/test_tutorial003.py create mode 100644 tests/test_tutorial/test_body/test_tutorial004.py create mode 100644 tests/test_tutorial/test_body_multiple_params/test_tutorial002.py create mode 100644 tests/test_tutorial/test_body_multiple_params/test_tutorial004.py create mode 100644 tests/test_tutorial/test_body_multiple_params/test_tutorial005.py create mode 100644 tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py create mode 100644 tests/test_tutorial/test_body_nested_models/test_tutorial004.py create mode 100644 tests/test_tutorial/test_body_nested_models/test_tutorial005.py create mode 100644 tests/test_tutorial/test_body_nested_models/test_tutorial006.py create mode 100644 tests/test_tutorial/test_body_nested_models/test_tutorial007.py create mode 100644 tests/test_tutorial/test_body_nested_models/test_tutorial008.py create mode 100644 tests/test_tutorial/test_body_updates/test_tutorial002.py create mode 100644 tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py create mode 100644 tests/test_tutorial/test_debugging/__init__.py create mode 100644 tests/test_tutorial/test_debugging/test_tutorial001.py rename tests/test_tutorial/test_dependencies/{test_tutorial001.py => test_tutorial001_tutorial001_02.py} (86%) rename tests/test_tutorial/test_dependencies/{test_tutorial004.py => test_tutorial002_tutorial003_tutorial004.py} (89%) create mode 100644 tests/test_tutorial/test_dependencies/test_tutorial005.py create mode 100644 tests/test_tutorial/test_dependencies/test_tutorial007.py create mode 100644 tests/test_tutorial/test_dependencies/test_tutorial008.py create mode 100644 tests/test_tutorial/test_dependencies/test_tutorial010.py create mode 100644 tests/test_tutorial/test_dependencies/test_tutorial011.py create mode 100644 tests/test_tutorial/test_encoder/__init__.py create mode 100644 tests/test_tutorial/test_encoder/test_tutorial001.py create mode 100644 tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py rename tests/test_tutorial/test_first_steps/{test_tutorial001.py => test_tutorial001_tutorial002_tutorial003.py} (70%) create mode 100644 tests/test_tutorial/test_generate_clients/test_tutorial001.py create mode 100644 tests/test_tutorial/test_generate_clients/test_tutorial002.py create mode 100644 tests/test_tutorial/test_generate_clients/test_tutorial004.py create mode 100644 tests/test_tutorial/test_graphql/__init__.py create mode 100644 tests/test_tutorial/test_graphql/test_tutorial001.py rename tests/test_tutorial/{test_custom_response/test_tutorial004.py => test_metadata/test_tutorial002.py} (61%) create mode 100644 tests/test_tutorial/test_metadata/test_tutorial003.py create mode 100644 tests/test_tutorial/test_middleware/__init__.py create mode 100644 tests/test_tutorial/test_middleware/test_tutorial001.py create mode 100644 tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py create mode 100644 tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py create mode 100644 tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py create mode 100644 tests/test_tutorial/test_path_params/test_tutorial001.py create mode 100644 tests/test_tutorial/test_path_params/test_tutorial002.py create mode 100644 tests/test_tutorial/test_path_params/test_tutorial003.py create mode 100644 tests/test_tutorial/test_path_params/test_tutorial003b.py create mode 100644 tests/test_tutorial/test_path_params_numeric_validations/__init__.py create mode 100644 tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py create mode 100644 tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py create mode 100644 tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py create mode 100644 tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py create mode 100644 tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py create mode 100644 tests/test_tutorial/test_python_types/__init__.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial003.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial004.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial005.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial006.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial007.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial008.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial008b.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial009c.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial010.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial011.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial012.py create mode 100644 tests/test_tutorial/test_python_types/test_tutorial013.py create mode 100644 tests/test_tutorial/test_query_params/test_tutorial001.py create mode 100644 tests/test_tutorial/test_query_params/test_tutorial002.py create mode 100644 tests/test_tutorial/test_query_params/test_tutorial003.py create mode 100644 tests/test_tutorial/test_query_params/test_tutorial004.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py create mode 100644 tests/test_tutorial/test_response_directly/test_tutorial002.py create mode 100644 tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py create mode 100644 tests/test_tutorial/test_response_model/test_tutorial002.py create mode 100644 tests/test_tutorial/test_response_status_code/__init__.py create mode 100644 tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py create mode 100644 tests/test_tutorial/test_schema_extra_example/test_tutorial002.py create mode 100644 tests/test_tutorial/test_schema_extra_example/test_tutorial003.py create mode 100644 tests/test_tutorial/test_security/test_tutorial002.py create mode 100644 tests/test_tutorial/test_security/test_tutorial004.py create mode 100644 tests/test_tutorial/test_security/test_tutorial007.py create mode 100644 tests/test_tutorial/test_settings/test_app01.py create mode 100644 tests/test_tutorial/test_static_files/__init__.py create mode 100644 tests/test_tutorial/test_static_files/test_tutorial001.py diff --git a/docs/de/docs/advanced/dataclasses.md b/docs/de/docs/advanced/dataclasses.md index e2d59c776e..52b9634aea 100644 --- a/docs/de/docs/advanced/dataclasses.md +++ b/docs/de/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI basiert auf **Pydantic**, und ich habe Ihnen gezeigt, wie Sie Pydantic-M Aber FastAPI unterstรผtzt auf die gleiche Weise auch die Verwendung von `dataclasses`: -{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} Das ist dank **Pydantic** ebenfalls mรถglich, da es `dataclasses` intern unterstรผtzt. @@ -32,7 +32,7 @@ Wenn Sie jedoch eine Menge Datenklassen herumliegen haben, ist dies ein guter Tr Sie kรถnnen `dataclasses` auch im Parameter `response_model` verwenden: -{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} Die Datenklasse wird automatisch in eine Pydantic-Datenklasse konvertiert. @@ -48,7 +48,7 @@ In einigen Fรคllen mรผssen Sie mรถglicherweise immer noch Pydantics Version von In diesem Fall kรถnnen Sie einfach die Standard-`dataclasses` durch `pydantic.dataclasses` ersetzen, was einen direkten Ersatz darstellt: -{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. Wir importieren `field` weiterhin von Standard-`dataclasses`. diff --git a/docs/de/docs/how-to/graphql.md b/docs/de/docs/how-to/graphql.md index 0583faf4a3..5c908cec4a 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_py39.py hl[3,22,25] *} Weitere Informationen zu Strawberry finden Sie in der Strawberry-Dokumentation. diff --git a/docs/en/docs/advanced/dataclasses.md b/docs/en/docs/advanced/dataclasses.md index 574beb65f4..dbc91409a5 100644 --- a/docs/en/docs/advanced/dataclasses.md +++ b/docs/en/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI is built on top of **Pydantic**, and I have been showing you how to use But FastAPI also supports using `dataclasses` the same way: -{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} This is still supported thanks to **Pydantic**, as it has internal support for `dataclasses`. @@ -32,7 +32,7 @@ But if you have a bunch of dataclasses laying around, this is a nice trick to us You can also use `dataclasses` in the `response_model` parameter: -{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} The dataclass will be automatically converted to a Pydantic dataclass. @@ -48,7 +48,7 @@ In some cases, you might still have to use Pydantic's version of `dataclasses`. In that case, you can simply swap the standard `dataclasses` with `pydantic.dataclasses`, which is a drop-in replacement: -{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. We still import `field` from standard `dataclasses`. diff --git a/docs/en/docs/how-to/graphql.md b/docs/en/docs/how-to/graphql.md index a002c08ca3..666f819b0f 100644 --- a/docs/en/docs/how-to/graphql.md +++ b/docs/en/docs/how-to/graphql.md @@ -35,7 +35,7 @@ Depending on your use case, you might prefer to use a different library, but if Here's a small preview of how you could integrate Strawberry with FastAPI: -{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *} +{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *} You can learn more about Strawberry in the Strawberry documentation. diff --git a/docs/es/docs/advanced/dataclasses.md b/docs/es/docs/advanced/dataclasses.md index 8d96171c7e..3a07482ad1 100644 --- a/docs/es/docs/advanced/dataclasses.md +++ b/docs/es/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI estรก construido sobre **Pydantic**, y te he estado mostrando cรณmo usar Pero FastAPI tambiรฉn soporta el uso de `dataclasses` de la misma manera: -{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} Esto sigue siendo soportado gracias a **Pydantic**, ya que tiene soporte interno para `dataclasses`. @@ -32,7 +32,7 @@ Pero si tienes un montรณn de dataclasses por ahรญ, este es un buen truco para us Tambiรฉn puedes usar `dataclasses` en el parรกmetro `response_model`: -{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} El dataclass serรก automรกticamente convertido a un dataclass de Pydantic. @@ -48,7 +48,7 @@ En algunos casos, todavรญa podrรญas tener que usar la versiรณn de `dataclasses` En ese caso, simplemente puedes intercambiar los `dataclasses` estรกndar con `pydantic.dataclasses`, que es un reemplazo directo: -{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. Todavรญa importamos `field` de los `dataclasses` estรกndar. diff --git a/docs/es/docs/how-to/graphql.md b/docs/es/docs/how-to/graphql.md index 2ebfb3dd08..e50c1ae0ac 100644 --- a/docs/es/docs/how-to/graphql.md +++ b/docs/es/docs/how-to/graphql.md @@ -35,7 +35,7 @@ Dependiendo de tu caso de uso, podrรญas preferir usar un paquete diferente, pero Aquรญ tienes una pequeรฑa vista previa de cรณmo podrรญas integrar Strawberry con FastAPI: -{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *} +{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *} Puedes aprender mรกs sobre Strawberry en la documentaciรณn de Strawberry. diff --git a/docs/pt/docs/advanced/dataclasses.md b/docs/pt/docs/advanced/dataclasses.md index 6467376967..6dc9feb299 100644 --- a/docs/pt/docs/advanced/dataclasses.md +++ b/docs/pt/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI รฉ construรญdo em cima do **Pydantic**, e eu tenho mostrado como usar mo Mas o FastAPI tambรฉm suporta o uso de `dataclasses` da mesma forma: -{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} Isso ainda รฉ suportado graรงas ao **Pydantic**, pois ele tem suporte interno para `dataclasses`. @@ -32,7 +32,7 @@ Mas se vocรช tem um monte de dataclasses por aรญ, este รฉ um truque legal para u Vocรช tambรฉm pode usar `dataclasses` no parรขmetro `response_model`: -{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} A dataclass serรก automaticamente convertida para uma dataclass Pydantic. @@ -48,7 +48,7 @@ Em alguns casos, vocรช ainda pode ter que usar a versรฃo do Pydantic das `datacl Nesse caso, vocรช pode simplesmente trocar as `dataclasses` padrรฃo por `pydantic.dataclasses`, que รฉ um substituto direto: -{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. Ainda importamos `field` das `dataclasses` padrรฃo. diff --git a/docs/pt/docs/how-to/graphql.md b/docs/pt/docs/how-to/graphql.md index 7af4c6b754..98266cc288 100644 --- a/docs/pt/docs/how-to/graphql.md +++ b/docs/pt/docs/how-to/graphql.md @@ -35,7 +35,7 @@ Dependendo do seu caso de uso, vocรช pode preferir usar uma biblioteca diferente Aqui estรก uma pequena prรฉvia de como vocรช poderia integrar Strawberry com FastAPI: -{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *} +{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *} Vocรช pode aprender mais sobre Strawberry na documentaรงรฃo do Strawberry. diff --git a/docs/ru/docs/advanced/dataclasses.md b/docs/ru/docs/advanced/dataclasses.md index c37ce30236..b3ced37c1e 100644 --- a/docs/ru/docs/advanced/dataclasses.md +++ b/docs/ru/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI ะฟะพัั‚ั€ะพะตะฝ ะฟะพะฒะตั€ั… **Pydantic**, ะธ ั ะฟะพะบะฐะทั‹ะฒะฐะป ะฒ ะะพ FastAPI ั‚ะฐะบะถะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต `dataclasses` ั‚ะตะผ ะถะต ัะฟะพัะพะฑะพะผ: -{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} ะญั‚ะพ ะฟะพ-ะฟั€ะตะถะฝะตะผัƒ ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั ะฑะปะฐะณะพะดะฐั€ั **Pydantic**, ั‚ะฐะบ ะบะฐะบ ะฒ ะฝั‘ะผ ะตัั‚ัŒ ะฒัั‚ั€ะพะตะฝะฝะฐั ะฟะพะดะดะตั€ะถะบะฐ `dataclasses`. @@ -32,7 +32,7 @@ FastAPI ะฟะพัั‚ั€ะพะตะฝ ะฟะพะฒะตั€ั… **Pydantic**, ะธ ั ะฟะพะบะฐะทั‹ะฒะฐะป ะฒ ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `dataclasses` ะฒ ะฟะฐั€ะฐะผะตั‚ั€ะต `response_model`: -{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} ะญั‚ะพั‚ dataclass ะฑัƒะดะตั‚ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐะฝ ะฒ Pydantic dataclass. @@ -48,7 +48,7 @@ FastAPI ะฟะพัั‚ั€ะพะตะฝ ะฟะพะฒะตั€ั… **Pydantic**, ะธ ั ะฟะพะบะฐะทั‹ะฒะฐะป ะฒ ะ’ ั‚ะฐะบะพะผ ัะปัƒั‡ะฐะต ะฒั‹ ะผะพะถะตั‚ะต ะฟั€ะพัั‚ะพ ะทะฐะผะตะฝะธั‚ัŒ ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะต `dataclasses` ะฝะฐ `pydantic.dataclasses`, ะบะพั‚ะพั€ะฐั ัะฒะปัะตั‚ัั ะฟะพะปะฝะพัั‚ัŒัŽ ัะพะฒะผะตัั‚ะธะผะพะน ะทะฐะผะตะฝะพะน (drop-in replacement): -{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. ะœั‹ ะฟะพ-ะฟั€ะตะถะฝะตะผัƒ ะธะผะฟะพั€ั‚ะธั€ัƒะตะผ `field` ะธะท ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ั… `dataclasses`. diff --git a/docs/ru/docs/how-to/graphql.md b/docs/ru/docs/how-to/graphql.md index 97278069ad..50c321e7dd 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_py39.py hl[3,22,25] *} ะŸะพะดั€ะพะฑะฝะตะต ะพ Strawberry ะผะพะถะฝะพ ัƒะทะฝะฐั‚ัŒ ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ Strawberry. diff --git a/docs/zh/docs/advanced/dataclasses.md b/docs/zh/docs/advanced/dataclasses.md index c74ce65c3e..4e8e77d2ac 100644 --- a/docs/zh/docs/advanced/dataclasses.md +++ b/docs/zh/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI ๅŸบไบŽ **Pydantic** ๆž„ๅปบ๏ผŒๅ‰ๆ–‡ๅทฒ็ปไป‹็ป่ฟ‡ๅฆ‚ไฝ•ไฝฟ็”จ Pydantic ไฝ† FastAPI ่ฟ˜ๅฏไปฅไฝฟ็”จๆ•ฐๆฎ็ฑป๏ผˆ`dataclasses`๏ผ‰๏ผš -{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} +{* ../../docs_src/dataclasses_/tutorial001.py hl[1,7:12,19:20] *} ่ฟ™่ฟ˜ๆ˜ฏๅ€ŸๅŠฉไบŽ **Pydantic** ๅŠๅ…ถๅ†…็ฝฎ็š„ย `dataclasses`ใ€‚ @@ -32,7 +32,7 @@ FastAPI ๅŸบไบŽ **Pydantic** ๆž„ๅปบ๏ผŒๅ‰ๆ–‡ๅทฒ็ปไป‹็ป่ฟ‡ๅฆ‚ไฝ•ไฝฟ็”จ Pydantic ๅœจ `response_model` ๅ‚ๆ•ฐไธญไฝฟ็”จ `dataclasses`๏ผš -{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} +{* ../../docs_src/dataclasses_/tutorial002.py hl[1,7:13,19] *} ๆœฌไพ‹ๆŠŠๆ•ฐๆฎ็ฑป่‡ชๅŠจ่ฝฌๆขไธบ Pydantic ๆ•ฐๆฎ็ฑปใ€‚ @@ -49,7 +49,7 @@ API ๆ–‡ๆกฃไธญไนŸไผšๆ˜พ็คบ็›ธๅ…ณๆฆ‚ๅ›พ๏ผš ๆœฌไพ‹ๆŠŠๆ ‡ๅ‡†็š„ `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.py!} ``` 1. ๆœฌไพ‹ไพ็„ถ่ฆไปŽๆ ‡ๅ‡†็š„ `dataclasses` ไธญๅฏผๅ…ฅ `field`๏ผ› diff --git a/docs_src/additional_responses/__init__.py b/docs_src/additional_responses/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/additional_status_codes/__init__.py b/docs_src/additional_status_codes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/advanced_middleware/__init__.py b/docs_src/advanced_middleware/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/authentication_error_status_code/__init__.py b/docs_src/authentication_error_status_code/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/background_tasks/__init__.py b/docs_src/background_tasks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/behind_a_proxy/__init__.py b/docs_src/behind_a_proxy/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/body/__init__.py b/docs_src/body/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/body_fields/__init__.py b/docs_src/body_fields/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/body_multiple_params/__init__.py b/docs_src/body_multiple_params/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/body_nested_models/__init__.py b/docs_src/body_nested_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/body_updates/__init__.py b/docs_src/body_updates/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/conditional_openapi/__init__.py b/docs_src/conditional_openapi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/configure_swagger_ui/__init__.py b/docs_src/configure_swagger_ui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/cookie_param_models/__init__.py b/docs_src/cookie_param_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/cookie_params/__init__.py b/docs_src/cookie_params/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/cors/__init__.py b/docs_src/cors/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/custom_docs_ui/__init__.py b/docs_src/custom_docs_ui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/custom_request_and_route/__init__.py b/docs_src/custom_request_and_route/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/custom_response/__init__.py b/docs_src/custom_response/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/dataclasses_/__init__.py b/docs_src/dataclasses_/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/dataclasses/tutorial001_py310.py b/docs_src/dataclasses_/tutorial001_py310.py similarity index 100% rename from docs_src/dataclasses/tutorial001_py310.py rename to docs_src/dataclasses_/tutorial001_py310.py diff --git a/docs_src/dataclasses/tutorial001_py39.py b/docs_src/dataclasses_/tutorial001_py39.py similarity index 100% rename from docs_src/dataclasses/tutorial001_py39.py rename to docs_src/dataclasses_/tutorial001_py39.py diff --git a/docs_src/dataclasses/tutorial002_py310.py b/docs_src/dataclasses_/tutorial002_py310.py similarity index 100% rename from docs_src/dataclasses/tutorial002_py310.py rename to docs_src/dataclasses_/tutorial002_py310.py diff --git a/docs_src/dataclasses/tutorial002_py39.py b/docs_src/dataclasses_/tutorial002_py39.py similarity index 100% rename from docs_src/dataclasses/tutorial002_py39.py rename to docs_src/dataclasses_/tutorial002_py39.py diff --git a/docs_src/dataclasses/tutorial003_py310.py b/docs_src/dataclasses_/tutorial003_py310.py similarity index 100% rename from docs_src/dataclasses/tutorial003_py310.py rename to docs_src/dataclasses_/tutorial003_py310.py diff --git a/docs_src/dataclasses/tutorial003_py39.py b/docs_src/dataclasses_/tutorial003_py39.py similarity index 100% rename from docs_src/dataclasses/tutorial003_py39.py rename to docs_src/dataclasses_/tutorial003_py39.py diff --git a/docs_src/debugging/__init__.py b/docs_src/debugging/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/dependencies/__init__.py b/docs_src/dependencies/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/dependency_testing/__init__.py b/docs_src/dependency_testing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/encoder/__init__.py b/docs_src/encoder/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/events/__init__.py b/docs_src/events/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/extending_openapi/__init__.py b/docs_src/extending_openapi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/extra_data_types/__init__.py b/docs_src/extra_data_types/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/extra_models/__init__.py b/docs_src/extra_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/first_steps/__init__.py b/docs_src/first_steps/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/generate_clients/__init__.py b/docs_src/generate_clients/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/graphql_/__init__.py b/docs_src/graphql_/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/graphql/tutorial001_py39.py b/docs_src/graphql_/tutorial001_py39.py similarity index 100% rename from docs_src/graphql/tutorial001_py39.py rename to docs_src/graphql_/tutorial001_py39.py diff --git a/docs_src/handling_errors/__init__.py b/docs_src/handling_errors/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/header_param_models/__init__.py b/docs_src/header_param_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/header_params/__init__.py b/docs_src/header_params/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/metadata/__init__.py b/docs_src/metadata/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/middleware/__init__.py b/docs_src/middleware/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/openapi_callbacks/__init__.py b/docs_src/openapi_callbacks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/openapi_webhooks/__init__.py b/docs_src/openapi_webhooks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/path_operation_advanced_configuration/__init__.py b/docs_src/path_operation_advanced_configuration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/path_operation_configuration/__init__.py b/docs_src/path_operation_configuration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/path_params/__init__.py b/docs_src/path_params/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/path_params_numeric_validations/__init__.py b/docs_src/path_params_numeric_validations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/pydantic_v1_in_v2/__init__.py b/docs_src/pydantic_v1_in_v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/python_types/__init__.py b/docs_src/python_types/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/query_param_models/__init__.py b/docs_src/query_param_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/query_params/__init__.py b/docs_src/query_params/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/query_params_str_validations/__init__.py b/docs_src/query_params_str_validations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/request_files/__init__.py b/docs_src/request_files/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/request_form_models/__init__.py b/docs_src/request_form_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/request_forms/__init__.py b/docs_src/request_forms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/request_forms_and_files/__init__.py b/docs_src/request_forms_and_files/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/response_change_status_code/__init__.py b/docs_src/response_change_status_code/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/response_cookies/__init__.py b/docs_src/response_cookies/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/response_directly/__init__.py b/docs_src/response_directly/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/response_headers/__init__.py b/docs_src/response_headers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/response_model/__init__.py b/docs_src/response_model/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/response_status_code/__init__.py b/docs_src/response_status_code/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/schema_extra_example/__init__.py b/docs_src/schema_extra_example/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/security/__init__.py b/docs_src/security/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/separate_openapi_schemas/__init__.py b/docs_src/separate_openapi_schemas/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/settings/__init__.py b/docs_src/settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/static_files/__init__.py b/docs_src/static_files/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/sub_applications/__init__.py b/docs_src/sub_applications/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/templates/__init__.py b/docs_src/templates/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/templates/static/__init__.py b/docs_src/templates/static/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/templates/templates/__init__.py b/docs_src/templates/templates/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/using_request_directly/__init__.py b/docs_src/using_request_directly/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/wsgi/__init__.py b/docs_src/wsgi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pyproject.toml b/pyproject.toml index ae97cb71bd..8f824af5d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -196,6 +196,17 @@ 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 + "docs_src/schema_extra_example/tutorial001_pv1_py310.py", + "docs_src/query_param_models/tutorial002_pv1_py310.py", + "docs_src/query_param_models/tutorial002_pv1_an_py310.py", + "docs_src/header_param_models/tutorial002_pv1_py310.py", + "docs_src/header_param_models/tutorial002_pv1_an_py310.py", + "docs_src/cookie_param_models/tutorial002_pv1_py310.py", + "docs_src/cookie_param_models/tutorial002_pv1_an_py310.py", ] [tool.coverage.report] diff --git a/requirements-tests.txt b/requirements-tests.txt index ee188b496c..1604a2858c 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -6,6 +6,7 @@ mypy ==1.14.1 dirty-equals ==0.9.0 sqlmodel==0.0.27 flask >=1.1.2,<4.0.0 +strawberry-graphql >=0.200.0,< 1.0.0 anyio[trio] >=3.2.1,<5.0.0 PyJWT==2.9.0 pyyaml >=5.3.1,<7.0.0 diff --git a/tests/test_tutorial/test_body/test_tutorial002.py b/tests/test_tutorial/test_body/test_tutorial002.py new file mode 100644 index 0000000000..b6d51d5235 --- /dev/null +++ b/tests/test_tutorial/test_body/test_tutorial002.py @@ -0,0 +1,161 @@ +import importlib +from typing import Union + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body.{request.param}") + + client = TestClient(mod.app) + return client + + +@pytest.mark.parametrize("price", ["50.5", 50.5]) +def test_post_with_tax(client: TestClient, price: Union[str, float]): + response = client.post( + "/items/", + json={"name": "Foo", "price": price, "description": "Some Foo", "tax": 0.3}, + ) + assert response.status_code == 200 + assert response.json() == { + "name": "Foo", + "price": 50.5, + "description": "Some Foo", + "tax": 0.3, + "price_with_tax": 50.8, + } + + +@pytest.mark.parametrize("price", ["50.5", 50.5]) +def test_post_without_tax(client: TestClient, price: Union[str, float]): + response = client.post( + "/items/", json={"name": "Foo", "price": price, "description": "Some Foo"} + ) + assert response.status_code == 200 + assert response.json() == { + "name": "Foo", + "price": 50.5, + "description": "Some Foo", + "tax": None, + } + + +def test_post_with_no_data(client: TestClient): + response = client.post("/items/", json={}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "name"], + "msg": "Field required", + "input": {}, + }, + { + "type": "missing", + "loc": ["body", "price"], + "msg": "Field required", + "input": {}, + }, + ] + } + + +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": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body/test_tutorial003.py b/tests/test_tutorial/test_body/test_tutorial003.py new file mode 100644 index 0000000000..227a125e78 --- /dev/null +++ b/tests/test_tutorial/test_body/test_tutorial003.py @@ -0,0 +1,171 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial003_py39"), + pytest.param("tutorial003_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_put_all(client: TestClient): + response = client.put( + "/items/123", + json={"name": "Foo", "price": 50.1, "description": "Some Foo", "tax": 0.3}, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 123, + "name": "Foo", + "price": 50.1, + "description": "Some Foo", + "tax": 0.3, + } + + +def test_put_only_required(client: TestClient): + response = client.put( + "/items/123", + json={"name": "Foo", "price": 50.1}, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 123, + "name": "Foo", + "price": 50.1, + "description": None, + "tax": None, + } + + +def test_put_with_no_data(client: TestClient): + response = client.put("/items/123", json={}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "name"], + "msg": "Field required", + "input": {}, + }, + { + "type": "missing", + "loc": ["body", "price"], + "msg": "Field required", + "input": {}, + }, + ] + } + + +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", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body/test_tutorial004.py b/tests/test_tutorial/test_body/test_tutorial004.py new file mode 100644 index 0000000000..10212843ee --- /dev/null +++ b/tests/test_tutorial/test_body/test_tutorial004.py @@ -0,0 +1,182 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial004_py39"), + pytest.param("tutorial004_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_put_all(client: TestClient): + response = client.put( + "/items/123", + json={"name": "Foo", "price": 50.1, "description": "Some Foo", "tax": 0.3}, + params={"q": "somequery"}, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 123, + "name": "Foo", + "price": 50.1, + "description": "Some Foo", + "tax": 0.3, + "q": "somequery", + } + + +def test_put_only_required(client: TestClient): + response = client.put( + "/items/123", + json={"name": "Foo", "price": 50.1}, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 123, + "name": "Foo", + "price": 50.1, + "description": None, + "tax": None, + } + + +def test_put_with_no_data(client: TestClient): + response = client.put("/items/123", json={}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "name"], + "msg": "Field required", + "input": {}, + }, + { + "type": "missing", + "loc": ["body", "price"], + "msg": "Field required", + "input": {}, + }, + ] + } + + +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", + }, + }, + { + "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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py new file mode 100644 index 0000000000..e98d5860fe --- /dev/null +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py @@ -0,0 +1,361 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_all(client: TestClient): + response = client.put( + "/items/5", + json={ + "item": { + "name": "Foo", + "price": 50.5, + "description": "Some Foo", + "tax": 0.1, + }, + "user": {"username": "johndoe", "full_name": "John Doe"}, + }, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 5, + "item": { + "name": "Foo", + "price": 50.5, + "description": "Some Foo", + "tax": 0.1, + }, + "user": {"username": "johndoe", "full_name": "John Doe"}, + } + + +def test_post_required(client: TestClient): + response = client.put( + "/items/5", + json={ + "item": {"name": "Foo", "price": 50.5}, + "user": {"username": "johndoe"}, + }, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 5, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + "user": {"username": "johndoe", "full_name": None}, + } + + +def test_post_no_body(client: TestClient): + response = client.put("/items/5", json=None) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "input": None, + "loc": [ + "body", + "item", + ], + "msg": "Field required", + "type": "missing", + }, + { + "input": None, + "loc": [ + "body", + "user", + ], + "msg": "Field required", + "type": "missing", + }, + ], + } + + +def test_post_no_item(client: TestClient): + response = client.put("/items/5", json={"user": {"username": "johndoe"}}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "input": None, + "loc": [ + "body", + "item", + ], + "msg": "Field required", + "type": "missing", + }, + ], + } + + +def test_post_no_user(client: TestClient): + response = client.put("/items/5", json={"item": {"name": "Foo", "price": 50.5}}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "input": None, + "loc": [ + "body", + "user", + ], + "msg": "Field required", + "type": "missing", + }, + ], + } + + +def test_post_missing_required_field_in_item(client: TestClient): + response = client.put( + "/items/5", json={"item": {"name": "Foo"}, "user": {"username": "johndoe"}} + ) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "input": {"name": "Foo"}, + "loc": [ + "body", + "item", + "price", + ], + "msg": "Field required", + "type": "missing", + }, + ], + } + + +def test_post_missing_required_field_in_user(client: TestClient): + response = client.put( + "/items/5", + json={"item": {"name": "Foo", "price": 50.5}, "user": {"ful_name": "John Doe"}}, + ) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "input": {"ful_name": "John Doe"}, + "loc": [ + "body", + "user", + "username", + ], + "msg": "Field required", + "type": "missing", + }, + ], + } + + +def test_post_id_foo(client: TestClient): + response = client.put( + "/items/foo", + json={ + "item": {"name": "Foo", "price": 50.5}, + "user": {"username": "johndoe"}, + }, + ) + assert response.status_code == 422 + assert response.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", + } + ] + } + + +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": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put", + }, + }, + }, + "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", + }, + }, + }, + "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", + }, + "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": { + "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_body_multiple_params/test_tutorial004.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py new file mode 100644 index 0000000000..979c054cd0 --- /dev/null +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py @@ -0,0 +1,290 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_put_all(client: TestClient): + response = client.put( + "/items/5", + json={ + "importance": 2, + "item": {"name": "Foo", "price": 50.5}, + "user": {"username": "Dave"}, + }, + params={"q": "somequery"}, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 5, + "importance": 2, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + "user": {"username": "Dave", "full_name": None}, + "q": "somequery", + } + + +def test_put_only_required(client: TestClient): + response = client.put( + "/items/5", + json={ + "importance": 2, + "item": {"name": "Foo", "price": 50.5}, + "user": {"username": "Dave"}, + }, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 5, + "importance": 2, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + "user": {"username": "Dave", "full_name": None}, + } + + +def test_put_missing_body(client: TestClient): + response = client.put("/items/5") + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "input": None, + "loc": [ + "body", + "item", + ], + "msg": "Field required", + "type": "missing", + }, + { + "input": None, + "loc": [ + "body", + "user", + ], + "msg": "Field required", + "type": "missing", + }, + { + "input": None, + "loc": [ + "body", + "importance", + ], + "msg": "Field required", + "type": "missing", + }, + ], + } + + +def test_put_empty_body(client: TestClient): + response = client.put("/items/5", json={}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "item"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "user"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "importance"], + "msg": "Field required", + "input": None, + }, + ] + } + + +def test_put_invalid_importance(client: TestClient): + response = client.put( + "/items/5", + json={ + "importance": 0, + "item": {"name": "Foo", "price": 50.5}, + "user": {"username": "Dave"}, + }, + ) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "loc": ["body", "importance"], + "msg": "Input should be greater than 0", + "type": "greater_than", + "input": 0, + "ctx": {"gt": 0}, + }, + ], + } + + +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": {}}}, + }, + "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": "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/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", + "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"}, + }, + }, + "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 new file mode 100644 index 0000000000..d47aa1b4f9 --- /dev/null +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py @@ -0,0 +1,272 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_all(client: TestClient): + response = client.put( + "/items/5", + json={ + "item": { + "name": "Foo", + "price": 50.5, + "description": "Some Foo", + "tax": 0.1, + }, + }, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 5, + "item": { + "name": "Foo", + "price": 50.5, + "description": "Some Foo", + "tax": 0.1, + }, + } + + +def test_post_required(client: TestClient): + response = client.put( + "/items/5", + json={ + "item": {"name": "Foo", "price": 50.5}, + }, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 5, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + } + + +def test_post_no_body(client: TestClient): + response = client.put("/items/5", json=None) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "input": None, + "loc": [ + "body", + "item", + ], + "msg": "Field required", + "type": "missing", + }, + ], + } + + +def test_post_like_not_embeded(client: TestClient): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "price": 50.5, + }, + ) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "input": None, + "loc": [ + "body", + "item", + ], + "msg": "Field required", + "type": "missing", + }, + ], + } + + +def test_post_missing_required_field_in_item(client: TestClient): + response = client.put( + "/items/5", json={"item": {"name": "Foo"}, "user": {"username": "johndoe"}} + ) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "input": {"name": "Foo"}, + "loc": [ + "body", + "item", + "price", + ], + "msg": "Field required", + "type": "missing", + }, + ], + } + + +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": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put", + }, + }, + }, + "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", + }, + }, + }, + "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", + }, + "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", + }, + "ValidationError": { + "properties": { + "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_body_nested_models/test_tutorial001_tutorial002_tutorial003.py b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py new file mode 100644 index 0000000000..d452929c38 --- /dev/null +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py @@ -0,0 +1,251 @@ +import importlib + +import pytest +from dirty_equals import IsList +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + +UNTYPED_LIST_SCHEMA = {"type": "array", "items": {}} + +LIST_OF_STR_SCHEMA = {"type": "array", "items": {"type": "string"}} + +SET_OF_STR_SCHEMA = {"type": "array", "items": {"type": "string"}, "uniqueItems": True} + + +@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), + ], +) +def get_mod_name(request: pytest.FixtureRequest): + return request.param + + +@pytest.fixture(name="client") +def get_client(mod_name: str): + mod = importlib.import_module(f"docs_src.body_nested_models.{mod_name}") + + client = TestClient(mod.app) + return client + + +def test_put_all(client: TestClient, mod_name: str): + if mod_name.startswith("tutorial003"): + tags_expected = IsList("foo", "bar", check_order=False) + else: + tags_expected = ["foo", "bar", "foo"] + + response = client.put( + "/items/123", + json={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + "tags": ["foo", "bar", "foo"], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "item_id": 123, + "item": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + "tags": tags_expected, + }, + } + + +def test_put_only_required(client: TestClient): + response = client.put( + "/items/5", + json={"name": "Foo", "price": 35.4}, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "item_id": 5, + "item": { + "name": "Foo", + "description": None, + "price": 35.4, + "tax": None, + "tags": [], + }, + } + + +def test_put_empty_body(client: TestClient): + response = client.put( + "/items/5", + json={}, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "name"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "price"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_put_missing_required(client: TestClient): + response = client.put( + "/items/5", + json={"description": "A very nice Item"}, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "name"], + "input": {"description": "A very nice Item"}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "price"], + "input": {"description": "A very nice Item"}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_openapi_schema(client: TestClient, mod_name: str): + tags_schema = {"default": [], "title": "Tags"} + if mod_name.startswith("tutorial001"): + tags_schema.update(UNTYPED_LIST_SCHEMA) + elif mod_name.startswith("tutorial002"): + tags_schema.update(LIST_OF_STR_SCHEMA) + elif mod_name.startswith("tutorial003"): + tags_schema.update(SET_OF_STR_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": { + "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" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$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"}, + }, + }, + "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 new file mode 100644 index 0000000000..ff9596943d --- /dev/null +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial004.py @@ -0,0 +1,275 @@ +import importlib + +import pytest +from dirty_equals import IsList +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial004_py39"), + pytest.param("tutorial004_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_put_all(client: TestClient): + response = client.put( + "/items/123", + json={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + "tags": ["foo", "bar", "foo"], + "image": {"url": "http://example.com/image.png", "name": "example image"}, + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "item_id": 123, + "item": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + "tags": IsList("foo", "bar", check_order=False), + "image": {"url": "http://example.com/image.png", "name": "example image"}, + }, + } + + +def test_put_only_required(client: TestClient): + response = client.put( + "/items/5", + json={"name": "Foo", "price": 35.4}, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "item_id": 5, + "item": { + "name": "Foo", + "description": None, + "price": 35.4, + "tax": None, + "tags": [], + "image": None, + }, + } + + +def test_put_empty_body(client: TestClient): + response = client.put( + "/items/5", + json={}, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "name"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "price"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_put_missing_required_in_item(client: TestClient): + response = client.put( + "/items/5", + json={"description": "A very nice Item"}, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "name"], + "input": {"description": "A very nice Item"}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "price"], + "input": {"description": "A very nice Item"}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_put_missing_required_in_image(client: TestClient): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "price": 35.4, + "image": {"url": "http://example.com/image.png"}, + }, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "image", "name"], + "input": {"url": "http://example.com/image.png"}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +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", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial005.py b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py new file mode 100644 index 0000000000..9a07a904e6 --- /dev/null +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py @@ -0,0 +1,301 @@ +import importlib + +import pytest +from dirty_equals import IsList +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial005_py39"), + pytest.param("tutorial005_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_put_all(client: TestClient): + response = client.put( + "/items/123", + json={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + "tags": ["foo", "bar", "foo"], + "image": {"url": "http://example.com/image.png", "name": "example image"}, + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "item_id": 123, + "item": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + "tags": IsList("foo", "bar", check_order=False), + "image": {"url": "http://example.com/image.png", "name": "example image"}, + }, + } + + +def test_put_only_required(client: TestClient): + response = client.put( + "/items/5", + json={"name": "Foo", "price": 35.4}, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "item_id": 5, + "item": { + "name": "Foo", + "description": None, + "price": 35.4, + "tax": None, + "tags": [], + "image": None, + }, + } + + +def test_put_empty_body(client: TestClient): + response = client.put( + "/items/5", + json={}, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "name"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "price"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_put_missing_required_in_item(client: TestClient): + response = client.put( + "/items/5", + json={"description": "A very nice Item"}, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "name"], + "input": {"description": "A very nice Item"}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "price"], + "input": {"description": "A very nice Item"}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_put_missing_required_in_image(client: TestClient): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "price": 35.4, + "image": {"url": "http://example.com/image.png"}, + }, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "image", "name"], + "input": {"url": "http://example.com/image.png"}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_put_wrong_url(client: TestClient): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "price": 35.4, + "image": {"url": "not a valid url", "name": "example image"}, + }, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "image", "url"], + "input": "not a valid url", + "msg": "Input should be a valid URL, relative URL without a base", + "type": "url_parsing", + "ctx": {"error": "relative URL without a base"}, + }, + ] + } + + +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", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial006.py b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py new file mode 100644 index 0000000000..088177cb95 --- /dev/null +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py @@ -0,0 +1,269 @@ +import importlib + +import pytest +from dirty_equals import IsList +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial006_py39"), + pytest.param("tutorial006_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_put_all(client: TestClient): + response = client.put( + "/items/123", + json={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + "tags": ["foo", "bar", "foo"], + "images": [ + {"url": "http://example.com/image.png", "name": "example image"} + ], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "item_id": 123, + "item": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + "tags": IsList("foo", "bar", check_order=False), + "images": [ + {"url": "http://example.com/image.png", "name": "example image"} + ], + }, + } + + +def test_put_only_required(client: TestClient): + response = client.put( + "/items/5", + json={"name": "Foo", "price": 35.4}, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "item_id": 5, + "item": { + "name": "Foo", + "description": None, + "price": 35.4, + "tax": None, + "tags": [], + "images": None, + }, + } + + +def test_put_empty_body(client: TestClient): + response = client.put( + "/items/5", + json={}, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "name"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "price"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_put_images_not_list(client: TestClient): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "price": 35.4, + "images": {"url": "http://example.com/image.png", "name": "example image"}, + }, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "images"], + "input": { + "url": "http://example.com/image.png", + "name": "example image", + }, + "msg": "Input should be a valid list", + "type": "list_type", + }, + ] + } + + +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", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial007.py b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py new file mode 100644 index 0000000000..a302819505 --- /dev/null +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py @@ -0,0 +1,344 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial007_py39"), + pytest.param("tutorial007_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_all(client: TestClient): + data = { + "name": "Special Offer", + "description": "This is a special offer", + "price": 38.6, + "items": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + "tags": ["foo"], + "images": [ + { + "url": "http://example.com/image.png", + "name": "example image", + } + ], + } + ], + } + + response = client.post( + "/offers/", + json=data, + ) + assert response.status_code == 200, response.text + assert response.json() == data + + +def test_put_only_required(client: TestClient): + response = client.post( + "/offers/", + json={ + "name": "Special Offer", + "price": 38.6, + "items": [ + { + "name": "Foo", + "price": 35.4, + "images": [ + { + "url": "http://example.com/image.png", + "name": "example image", + } + ], + } + ], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Special Offer", + "description": None, + "price": 38.6, + "items": [ + { + "name": "Foo", + "description": None, + "price": 35.4, + "tax": None, + "tags": [], + "images": [ + { + "url": "http://example.com/image.png", + "name": "example image", + } + ], + } + ], + } + + +def test_put_empty_body(client: TestClient): + response = client.post( + "/offers/", + json={}, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "name"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "price"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "items"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_put_missing_required_in_items(client: TestClient): + response = client.post( + "/offers/", + json={ + "name": "Special Offer", + "price": 38.6, + "items": [{}], + }, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "items", 0, "name"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "items", 0, "price"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +def test_put_missing_required_in_images(client: TestClient): + response = client.post( + "/offers/", + json={ + "name": "Special Offer", + "price": 38.6, + "items": [ + {"name": "Foo", "price": 35.4, "images": [{}]}, + ], + }, + ) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", "items", 0, "images", 0, "url"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + { + "loc": ["body", "items", 0, "images", 0, "name"], + "input": {}, + "msg": "Field required", + "type": "missing", + }, + ] + } + + +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": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial008.py b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py new file mode 100644 index 0000000000..32eb8ee75c --- /dev/null +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py @@ -0,0 +1,157 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial008_py39"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_body(client: TestClient): + data = [ + {"url": "http://example.com/", "name": "Example"}, + {"url": "http://fastapi.tiangolo.com/", "name": "FastAPI"}, + ] + response = client.post("/images/multiple", json=data) + assert response.status_code == 200, response.text + assert response.json() == data + + +def test_post_invalid_list_item(client: TestClient): + data = [{"url": "not a valid url", "name": "Example"}] + response = client.post("/images/multiple", json=data) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body", 0, "url"], + "input": "not a valid url", + "msg": "Input should be a valid URL, relative URL without a base", + "type": "url_parsing", + "ctx": {"error": "relative URL without a base"}, + }, + ] + } + + +def test_post_not_a_list(client: TestClient): + data = {"url": "http://example.com/", "name": "Example"} + response = client.post("/images/multiple", json=data) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["body"], + "input": { + "name": "Example", + "url": "http://example.com/", + }, + "msg": "Input should be a valid list", + "type": "list_type", + } + ] + } + + +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": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_updates/test_tutorial002.py b/tests/test_tutorial/test_body_updates/test_tutorial002.py new file mode 100644 index 0000000000..466e6af8fd --- /dev/null +++ b/tests/test_tutorial/test_body_updates/test_tutorial002.py @@ -0,0 +1,207 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_updates.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_get(client: TestClient): + response = client.get("/items/baz") + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Baz", + "description": None, + "price": 50.2, + "tax": 10.5, + "tags": [], + } + + +def test_patch_all(client: TestClient): + response = client.patch( + "/items/foo", + json={ + "name": "Fooz", + "description": "Item description", + "price": 3, + "tax": 10.5, + "tags": ["tag1", "tag2"], + }, + ) + assert response.json() == { + "name": "Fooz", + "description": "Item description", + "price": 3, + "tax": 10.5, + "tags": ["tag1", "tag2"], + } + + +def test_patch_name(client: TestClient): + response = client.patch( + "/items/bar", + json={"name": "Barz"}, + ) + assert response.json() == { + "name": "Barz", + "description": "The bartenders", + "price": 62, + "tax": 20.2, + "tags": [], + } + + +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", + "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, + }, + }, + } + }, + "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"}, + }, + }, + "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 c81e991ebf..f1d2accef2 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -1,17 +1,29 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial001_py39 import app -client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001_py39"), + pytest.param("tutorial010_py39"), + ], +) +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(): +def test_get_custom_response(client: TestClient): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { 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 new file mode 100644 index 0000000000..22e2e02540 --- /dev/null +++ b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py @@ -0,0 +1,68 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="mod_name", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial003_py39"), + pytest.param("tutorial004_py39"), + ], +) +def get_mod_name(request: pytest.FixtureRequest) -> str: + return request.param + + +@pytest.fixture(name="client") +def get_client(mod_name: str) -> TestClient: + mod = importlib.import_module(f"docs_src.custom_response.{mod_name}") + return TestClient(mod.app) + + +html_contents = """ + + + Some HTML in here + + +

Look ma! HTML!

+ + + """ + + +def test_get_custom_response(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.text == html_contents + + +def test_openapi_schema(client: TestClient, mod_name: str): + if mod_name.startswith("tutorial003"): + response_content = {"application/json": {"schema": {}}} + else: + response_content = {"text/html": {"schema": {"type": "string"}}} + + 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", + } + } + }, + } diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py index d5f230bc42..bc407234a1 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py @@ -15,7 +15,7 @@ from tests.utils import needs_py310 ], ) def get_client(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.dataclasses.{request.param}") + mod = importlib.import_module(f"docs_src.dataclasses_.{request.param}") client = TestClient(mod.app) client.headers.clear() diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py index 4cf8933805..995d926752 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py @@ -15,7 +15,7 @@ from tests.utils import needs_py310 ], ) def get_client(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.dataclasses.{request.param}") + mod = importlib.import_module(f"docs_src.dataclasses_.{request.param}") client = TestClient(mod.app) client.headers.clear() diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index cddf4a9be8..a6a9fc1c7e 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -14,7 +14,7 @@ from ...utils import needs_py310 ], ) def get_client(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.dataclasses.{request.param}") + mod = importlib.import_module(f"docs_src.dataclasses_.{request.param}") client = TestClient(mod.app) client.headers.clear() diff --git a/tests/test_tutorial/test_debugging/__init__.py b/tests/test_tutorial/test_debugging/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_debugging/test_tutorial001.py b/tests/test_tutorial/test_debugging/test_tutorial001.py new file mode 100644 index 0000000000..cf62c3b194 --- /dev/null +++ b/tests/test_tutorial/test_debugging/test_tutorial001.py @@ -0,0 +1,64 @@ +import importlib +import runpy +import sys +import unittest + +import pytest +from fastapi.testclient import TestClient + +MOD_NAME = "docs_src.debugging.tutorial001_py39" + + +@pytest.fixture(name="client") +def get_client(): + mod = importlib.import_module(MOD_NAME) + client = TestClient(mod.app) + return client + + +def test_uvicorn_run_is_not_called_on_import(): + if sys.modules.get(MOD_NAME): + del sys.modules[MOD_NAME] # pragma: no cover + with unittest.mock.patch("uvicorn.run") as uvicorn_run_mock: + importlib.import_module(MOD_NAME) + uvicorn_run_mock.assert_not_called() + + +def test_get_root(client: TestClient): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"hello world": "ba"} + + +def test_uvicorn_run_called_when_run_as_main(): # Just for coverage + if sys.modules.get(MOD_NAME): + del sys.modules[MOD_NAME] + with unittest.mock.patch("uvicorn.run") as uvicorn_run_mock: + runpy.run_module(MOD_NAME, run_name="__main__") + + uvicorn_run_mock.assert_called_once_with( + unittest.mock.ANY, host="0.0.0.0", port=8000 + ) + + +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": {}}}, + }, + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001.py b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py similarity index 86% rename from tests/test_tutorial/test_dependencies/test_tutorial001.py rename to tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py index 8dac99cf30..50d7c4108c 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -14,6 +13,8 @@ from ...utils import needs_py310 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), ], ) def get_client(request: pytest.FixtureRequest): @@ -69,16 +70,10 @@ def test_openapi_schema(client: TestClient): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, "name": "q", "in": "query", }, @@ -128,16 +123,10 @@ def test_openapi_schema(client: TestClient): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, "name": "q", "in": "query", }, diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py similarity index 89% rename from tests/test_tutorial/test_dependencies/test_tutorial004.py rename to tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py index 8a1346d0d2..f09d6f268d 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -10,6 +9,14 @@ 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"), @@ -107,16 +114,10 @@ def test_openapi_schema(client: TestClient): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, "name": "q", "in": "query", }, diff --git a/tests/test_tutorial/test_dependencies/test_tutorial005.py b/tests/test_tutorial/test_dependencies/test_tutorial005.py new file mode 100644 index 0000000000..a914936ba1 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial005.py @@ -0,0 +1,139 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dependencies.{request.param}") + + client = TestClient(mod.app) + return client + + +@pytest.mark.parametrize( + "path,cookie,expected_status,expected_response", + [ + ( + "/items", + "from_cookie", + 200, + {"q_or_cookie": "from_cookie"}, + ), + ( + "/items?q=foo", + "from_cookie", + 200, + {"q_or_cookie": "foo"}, + ), + ( + "/items", + None, + 200, + {"q_or_cookie": None}, + ), + ], +) +def test_get(path, cookie, expected_status, expected_response, client: TestClient): + if cookie is not None: + client.cookies.set("last_query", cookie) + else: + client.cookies.clear() + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +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" + } + } + }, + }, + }, + "summary": "Read Query", + "operationId": "read_query_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Last Query", + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial007.py b/tests/test_tutorial/test_dependencies/test_tutorial007.py new file mode 100644 index 0000000000..3e188abcf6 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial007.py @@ -0,0 +1,24 @@ +import asyncio +from contextlib import asynccontextmanager +from unittest.mock import Mock, patch + +from docs_src.dependencies.tutorial007_py39 import get_db + + +def test_get_db(): # Just for coverage + async def test_async_gen(): + cm = asynccontextmanager(get_db) + async with cm() as db_session: + return db_session + + dbsession_moock = Mock() + + with patch( + "docs_src.dependencies.tutorial007_py39.DBSession", + return_value=dbsession_moock, + create=True, + ): + value = asyncio.run(test_async_gen()) + + assert value is dbsession_moock + dbsession_moock.close.assert_called_once() diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008.py b/tests/test_tutorial/test_dependencies/test_tutorial008.py new file mode 100644 index 0000000000..9d7377ebe4 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial008.py @@ -0,0 +1,58 @@ +import importlib +from types import ModuleType +from typing import Annotated, Any +from unittest.mock import Mock, patch + +import pytest +from fastapi import Depends, FastAPI +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="module", + params=[ + "tutorial008_py39", + # Fails with `NameError: name 'DepA' is not defined` + pytest.param("tutorial008_an_py39", marks=pytest.mark.xfail), + ], +) +def get_module(request: pytest.FixtureRequest): + mod_name = f"docs_src.dependencies.{request.param}" + mod = importlib.import_module(mod_name) + return mod + + +def test_get_db(module: ModuleType): + app = FastAPI() + + @app.get("/") + def read_root(c: Annotated[Any, Depends(module.dependency_c)]): + return {"c": str(c)} + + client = TestClient(app) + + a_mock = Mock() + b_mock = Mock() + c_mock = Mock() + + with ( + patch( + f"{module.__name__}.generate_dep_a", + return_value=a_mock, + create=True, + ), + patch( + f"{module.__name__}.generate_dep_b", + return_value=b_mock, + create=True, + ), + patch( + f"{module.__name__}.generate_dep_c", + return_value=c_mock, + create=True, + ), + ): + response = client.get("/") + + assert response.status_code == 200 + assert response.json() == {"c": str(c_mock)} diff --git a/tests/test_tutorial/test_dependencies/test_tutorial010.py b/tests/test_tutorial/test_dependencies/test_tutorial010.py new file mode 100644 index 0000000000..6d3815ada2 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial010.py @@ -0,0 +1,29 @@ +from typing import Annotated, Any +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 + + +def test_get_db(): + app = FastAPI() + + @app.get("/") + def read_root(c: Annotated[Any, Depends(get_db)]): + return {"c": str(c)} + + client = TestClient(app) + + dbsession_mock = Mock() + + with patch( + "docs_src.dependencies.tutorial010_py39.DBSession", + return_value=dbsession_mock, + create=True, + ): + response = client.get("/") + + assert response.status_code == 200 + assert response.json() == {"c": str(dbsession_mock)} diff --git a/tests/test_tutorial/test_dependencies/test_tutorial011.py b/tests/test_tutorial/test_dependencies/test_tutorial011.py new file mode 100644 index 0000000000..4868254c0b --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial011.py @@ -0,0 +1,120 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + "tutorial011_py39", + pytest.param("tutorial011_an_py39"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dependencies.{request.param}") + + client = TestClient(mod.app) + return client + + +@pytest.mark.parametrize( + "path,expected_status,expected_response", + [ + ( + "/query-checker/", + 200, + {"fixed_content_in_query": False}, + ), + ( + "/query-checker/?q=qwerty", + 200, + {"fixed_content_in_query": False}, + ), + ( + "/query-checker/?q=foobar", + 200, + {"fixed_content_in_query": True}, + ), + ], +) +def test_get(path, expected_status, expected_response, client: TestClient): + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +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" + } + } + }, + }, + }, + "summary": "Read Query Check", + "operationId": "read_query_check_query_checker__get", + "parameters": [ + { + "required": False, + "schema": { + "type": "string", + "default": "", + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_encoder/__init__.py b/tests/test_tutorial/test_encoder/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_encoder/test_tutorial001.py b/tests/test_tutorial/test_encoder/test_tutorial001.py new file mode 100644 index 0000000000..5c8ee054d8 --- /dev/null +++ b/tests/test_tutorial/test_encoder/test_tutorial001.py @@ -0,0 +1,208 @@ +import importlib +from types import ModuleType + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial001_py39"), + pytest.param("tutorial001_py310", marks=needs_py310), + ], +) +def get_module(request: pytest.FixtureRequest): + module = importlib.import_module(f"docs_src.encoder.{request.param}") + return module + + +@pytest.fixture(name="client") +def get_client(mod: ModuleType): + client = TestClient(mod.app) + return client + + +def test_put(client: TestClient, mod: ModuleType): + fake_db = mod.fake_db + + response = client.put( + "/items/123", + json={ + "title": "Foo", + "timestamp": "2023-01-01T12:00:00", + "description": "An optional description", + }, + ) + assert response.status_code == 200 + assert "123" in fake_db + assert fake_db["123"] == { + "title": "Foo", + "timestamp": "2023-01-01T12:00:00", + "description": "An optional description", + } + + +def test_put_invalid_data(client: TestClient, mod: ModuleType): + fake_db = mod.fake_db + + response = client.put( + "/items/345", + json={ + "title": "Foo", + "timestamp": "not a date", + }, + ) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "loc": ["body", "timestamp"], + "msg": "Input should be a valid datetime or date, invalid character in year", + "type": "datetime_from_date_parsing", + "input": "not a date", + "ctx": {"error": "invalid character in year"}, + } + ] + } + assert "345" not in fake_db + + +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": { + "schema": { + "$ref": "#/components/schemas/Item", + }, + }, + }, + "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", + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "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": { + "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_extra_models/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py new file mode 100644 index 0000000000..3f2f508a11 --- /dev/null +++ b/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py @@ -0,0 +1,156 @@ +import importlib + +import pytest +from dirty_equals import IsList +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.extra_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post(client: TestClient): + response = client.post( + "/user/", + json={ + "username": "johndoe", + "password": "secret", + "email": "johndoe@example.com", + "full_name": "John Doe", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + } + + +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", + } + } + }, + }, + "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, + }, + } + } + }, + "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"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001.py b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py similarity index 70% rename from tests/test_tutorial/test_first_steps/test_tutorial001.py rename to tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py index c102bb9999..aa65218cde 100644 --- a/tests/test_tutorial/test_first_steps/test_tutorial001.py +++ b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py @@ -1,9 +1,20 @@ +import importlib + import pytest from fastapi.testclient import TestClient -from docs_src.first_steps.tutorial001_py39 import app -client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial001_py39", + "tutorial003_py39", + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.first_steps.{request.param}") + client = TestClient(mod.app) + return client @pytest.mark.parametrize( @@ -13,13 +24,13 @@ client = TestClient(app) ("/nonexistent", 404, {"detail": "Not Found"}), ], ) -def test_get_path(path, expected_status, expected_response): +def test_get_path(client: TestClient, path, expected_status, expected_response): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial001.py b/tests/test_tutorial/test_generate_clients/test_tutorial001.py new file mode 100644 index 0000000000..bbb66b4516 --- /dev/null +++ b/tests/test_tutorial/test_generate_clients/test_tutorial001.py @@ -0,0 +1,142 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001_py39"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.generate_clients.{request.param}") + client = TestClient(mod.app) + return client + + +def test_post_items(client: TestClient): + response = client.post("/items/", json={"name": "Foo", "price": 5}) + assert response.status_code == 200, response.text + assert response.json() == {"message": "item received"} + + +def test_get_items(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [ + {"name": "Plumbus", "price": 3}, + {"name": "Portal Gun", "price": 9001}, + ] + + +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"}, + } + } + }, + } + }, + }, + "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/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"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial002.py b/tests/test_tutorial/test_generate_clients/test_tutorial002.py new file mode 100644 index 0000000000..ab8bc4c11c --- /dev/null +++ b/tests/test_tutorial/test_generate_clients/test_tutorial002.py @@ -0,0 +1,187 @@ +from fastapi.testclient import TestClient + +from docs_src.generate_clients.tutorial002_py39 import app + +client = TestClient(app) + + +def test_post_items(): + response = client.post("/items/", json={"name": "Foo", "price": 5}) + assert response.status_code == 200, response.text + assert response.json() == {"message": "Item received"} + + +def test_post_users(): + response = client.post( + "/users/", json={"username": "Foo", "email": "foo@example.com"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"message": "User received"} + + +def test_get_items(): + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [ + {"name": "Plumbus", "price": 3}, + {"name": "Portal Gun", "price": 9001}, + ] + + +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"}, + } + } + }, + } + }, + }, + "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", + "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" + } + } + }, + }, + }, + } + }, + }, + "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"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial004.py b/tests/test_tutorial/test_generate_clients/test_tutorial004.py new file mode 100644 index 0000000000..e66f6d2a12 --- /dev/null +++ b/tests/test_tutorial/test_generate_clients/test_tutorial004.py @@ -0,0 +1,230 @@ +import importlib +import json +import pathlib +from unittest.mock import patch + +from docs_src.generate_clients import tutorial003_py39 + + +def test_remove_tags(tmp_path: pathlib.Path): + tmp_file = tmp_path / "openapi.json" + openapi_json = tutorial003_py39.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") + + modified_openapi = json.loads(tmp_file.read_text()) + assert modified_openapi == { + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "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": { + "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": { + "/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", + }, + }, + "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", + }, + }, + }, + "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", + ], + }, + }, + }, + } diff --git a/tests/test_tutorial/test_graphql/__init__.py b/tests/test_tutorial/test_graphql/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_graphql/test_tutorial001.py b/tests/test_tutorial/test_graphql/test_tutorial001.py new file mode 100644 index 0000000000..9ba7147b55 --- /dev/null +++ b/tests/test_tutorial/test_graphql/test_tutorial001.py @@ -0,0 +1,70 @@ +import warnings + +import pytest +from starlette.testclient import TestClient + +warnings.filterwarnings( + "ignore", + message=r"The 'lia' package has been renamed to 'cross_web'\..*", + category=DeprecationWarning, +) + +from docs_src.graphql_.tutorial001_py39 import app # noqa: E402 + + +@pytest.fixture(name="client") +def get_client() -> TestClient: + return TestClient(app) + + +def test_query(client: TestClient): + response = client.post("/graphql", json={"query": "{ user { name, age } }"}) + assert response.status_code == 200 + assert response.json() == {"data": {"user": {"name": "Patrick", "age": 100}}} + + +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": {}, + }, + }, + "description": "The GraphiQL integrated development environment.", + }, + "404": { + "description": "Not found if GraphiQL or query via GET are not enabled.", + }, + }, + "summary": "Handle Http Get", + }, + "post": { + "operationId": "handle_http_post_graphql_post", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + }, + "summary": "Handle Http Post", + }, + }, + }, + } diff --git a/tests/test_tutorial/test_custom_response/test_tutorial004.py b/tests/test_tutorial/test_metadata/test_tutorial002.py similarity index 61% rename from tests/test_tutorial/test_custom_response/test_tutorial004.py rename to tests/test_tutorial/test_metadata/test_tutorial002.py index 0e7d69791b..e2814c88f9 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial004.py +++ b/tests/test_tutorial/test_metadata/test_tutorial002.py @@ -1,45 +1,41 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial004_py39 import app +from docs_src.metadata.tutorial002_py39 import app client = TestClient(app) -html_contents = """ - - - Some HTML in here - - -

Look ma! HTML!

- - - """ - - -def test_get_custom_response(): +def test_items(): response = client.get("/items/") assert response.status_code == 200, response.text - assert response.text == html_contents + assert response.json() == [{"name": "Foo"}] + + +def test_get_openapi_json_default_url(): + response = client.get("/openapi.json") + assert response.status_code == 404, response.text def test_openapi_schema(): - response = client.get("/openapi.json") + 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"}, + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, "paths": { "/items/": { "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", "responses": { "200": { "description": "Successful Response", - "content": {"text/html": {"schema": {"type": "string"}}}, + "content": {"application/json": {"schema": {}}}, } }, - "summary": "Read Items", - "operationId": "read_items_items__get", } } }, diff --git a/tests/test_tutorial/test_metadata/test_tutorial003.py b/tests/test_tutorial/test_metadata/test_tutorial003.py new file mode 100644 index 0000000000..085c271cdb --- /dev/null +++ b/tests/test_tutorial/test_metadata/test_tutorial003.py @@ -0,0 +1,53 @@ +from fastapi.testclient import TestClient + +from docs_src.metadata.tutorial003_py39 import app + +client = TestClient(app) + + +def test_items(): + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [{"name": "Foo"}] + + +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": {}}}, + } + }, + } + } + }, + } + + +def test_swagger_ui_default_url(): + response = client.get("/docs") + assert response.status_code == 404, response.text + + +def test_swagger_ui_custom_url(): + response = client.get("/documentation") + assert response.status_code == 200, response.text + assert "FastAPI - Swagger UI" in response.text + + +def test_redoc_ui_default_url(): + response = client.get("/redoc") + assert response.status_code == 404, response.text diff --git a/tests/test_tutorial/test_middleware/__init__.py b/tests/test_tutorial/test_middleware/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_middleware/test_tutorial001.py b/tests/test_tutorial/test_middleware/test_tutorial001.py new file mode 100644 index 0000000000..cbcfd4146f --- /dev/null +++ b/tests/test_tutorial/test_middleware/test_tutorial001.py @@ -0,0 +1,24 @@ +from fastapi.testclient import TestClient + +from docs_src.middleware.tutorial001_py39 import app + +client = TestClient(app) + + +def test_response_headers(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert "X-Process-Time" in 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": {}, + } diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py new file mode 100644 index 0000000000..085d1f5e19 --- /dev/null +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py @@ -0,0 +1,186 @@ +import importlib + +import pytest +from dirty_equals import IsList +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001_py39"), + pytest.param("tutorial001_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest) -> TestClient: + mod = importlib.import_module( + f"docs_src.path_operation_configuration.{request.param}" + ) + return TestClient(mod.app) + + +def test_post_items(client: TestClient): + response = client.post( + "/items/", + json={ + "name": "Foo", + "description": "Item description", + "price": 42.0, + "tax": 3.2, + "tags": ["bar", "baz"], + }, + ) + assert response.status_code == 201, response.text + assert response.json() == { + "name": "Foo", + "description": "Item description", + "price": 42.0, + "tax": 3.2, + "tags": IsList("bar", "baz", check_order=False), + } + + +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", + "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", + }, + "title": "Detail", + "type": "array", + }, + }, + "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": { + "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_operation_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py new file mode 100644 index 0000000000..c7414d756a --- /dev/null +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py @@ -0,0 +1,223 @@ +import importlib + +import pytest +from dirty_equals import IsList +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest) -> TestClient: + mod = importlib.import_module( + f"docs_src.path_operation_configuration.{request.param}" + ) + return TestClient(mod.app) + + +def test_post_items(client: TestClient): + response = client.post( + "/items/", + json={ + "name": "Foo", + "description": "Item description", + "price": 42.0, + "tax": 3.2, + "tags": ["bar", "baz"], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Foo", + "description": "Item description", + "price": 42.0, + "tax": 3.2, + "tags": IsList("bar", "baz", check_order=False), + } + + +def test_get_items(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [{"name": "Foo", "price": 42}] + + +def test_get_users(client: TestClient): + response = client.get("/users/") + assert response.status_code == 200, response.text + assert response.json() == [{"username": "johndoe"}] + + +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"} + } + }, + "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" + } + } + }, + }, + }, + }, + }, + "/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", + }, + "title": "Detail", + "type": "array", + }, + }, + "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": { + "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_operation_configurations/test_tutorial003_tutorial004.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py new file mode 100644 index 0000000000..791db24625 --- /dev/null +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py @@ -0,0 +1,208 @@ +import importlib +from textwrap import dedent + +import pytest +from dirty_equals import IsList +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + +DESCRIPTIONS = { + "tutorial003": "Create an item with all the information, name, description, price, tax and a set of unique tags", + "tutorial004": dedent(""" + 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 + """).strip(), +} + + +@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), + ], +) +def get_mod_name(request: pytest.FixtureRequest) -> str: + return request.param + + +@pytest.fixture(name="client") +def get_client(mod_name: str) -> TestClient: + mod = importlib.import_module(f"docs_src.path_operation_configuration.{mod_name}") + return TestClient(mod.app) + + +def test_post_items(client: TestClient): + response = client.post( + "/items/", + json={ + "name": "Foo", + "description": "Item description", + "price": 42.0, + "tax": 3.2, + "tags": ["bar", "baz"], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Foo", + "description": "Item description", + "price": 42.0, + "tax": 3.2, + "tags": IsList("bar", "baz", check_order=False), + } + + +def test_openapi_schema(client: TestClient, mod_name: str): + mod_name = mod_name[:11] + + 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", + "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", + }, + "title": "Detail", + "type": "array", + }, + }, + "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": { + "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_tutorial001.py b/tests/test_tutorial/test_path_params/test_tutorial001.py new file mode 100644 index 0000000000..a898e386fb --- /dev/null +++ b/tests/test_tutorial/test_path_params/test_tutorial001.py @@ -0,0 +1,116 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.path_params.tutorial001_py39 import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + ("item_id", "expected_response"), + [ + (1, {"item_id": "1"}), + ("alice", {"item_id": "alice"}), + ], +) +def test_get_items(item_id, expected_response): + response = client.get(f"/items/{item_id}") + assert response.status_code == 200, response.text + assert response.json() == 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": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Read Item", + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "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_tutorial002.py b/tests/test_tutorial/test_path_params/test_tutorial002.py new file mode 100644 index 0000000000..0bfc9f807e --- /dev/null +++ b/tests/test_tutorial/test_path_params/test_tutorial002.py @@ -0,0 +1,124 @@ +from fastapi.testclient import TestClient + +from docs_src.path_params.tutorial002_py39 import app + +client = TestClient(app) + + +def test_get_items(): + response = client.get("/items/1") + assert response.status_code == 200, response.text + assert response.json() == {"item_id": 1} + + +def test_get_items_invalid_id(): + response = client.get("/items/item1") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "input": "item1", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "type": "int_parsing", + } + ] + } + + +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": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Read Item", + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "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_tutorial003.py b/tests/test_tutorial/test_path_params/test_tutorial003.py new file mode 100644 index 0000000000..cd2c39ab06 --- /dev/null +++ b/tests/test_tutorial/test_path_params/test_tutorial003.py @@ -0,0 +1,133 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.path_params.tutorial003_py39 import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + ("user_id", "expected_response"), + [ + ("me", {"user_id": "the current user"}), + ("alice", {"user_id": "alice"}), + ], +) +def test_get_users(user_id: str, expected_response: dict): + response = client.get(f"/users/{user_id}") + assert response.status_code == 200, response.text + assert response.json() == 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": { + "/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", + }, + }, + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Read User", + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "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 new file mode 100644 index 0000000000..8e4a26a1ca --- /dev/null +++ b/tests/test_tutorial/test_path_params/test_tutorial003b.py @@ -0,0 +1,44 @@ +import asyncio + +from fastapi.testclient import TestClient + +from docs_src.path_params.tutorial003b_py39 import app, read_users2 + +client = TestClient(app) + + +def test_get_users(): + response = client.get("/users") + assert response.status_code == 200, response.text + assert response.json() == ["Rick", "Morty"] + + +def test_read_users2(): # Just for coverage + assert asyncio.run(read_users2()) == ["Bean", "Elfo"] + + +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": {}, + }, + }, + "description": "Successful Response", + }, + }, + "summary": "Read Users2", + }, + }, + }, + } diff --git a/tests/test_tutorial/test_path_params_numeric_validations/__init__.py b/tests/test_tutorial/test_path_params_numeric_validations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..f1e3041030 --- /dev/null +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py @@ -0,0 +1,164 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest) -> TestClient: + mod = importlib.import_module( + f"docs_src.path_params_numeric_validations.{request.param}" + ) + return TestClient(mod.app) + + +@pytest.mark.parametrize( + "path,expected_response", + [ + ("/items/42", {"item_id": 42}), + ("/items/123?item-query=somequery", {"item_id": 123, "q": "somequery"}), + ], +) +def test_read_items(client: TestClient, path, expected_response): + response = client.get(path) + assert response.status_code == 200, response.text + assert response.json() == expected_response + + +def test_read_items_invalid_item_id(client: TestClient): + response = client.get("/items/invalid_id") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["path", "item_id"], + "input": "invalid_id", + "msg": "Input should be a valid integer, unable to parse string as an integer", + "type": "int_parsing", + } + ] + } + + +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", + }, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "null", + }, + ], + "title": "Item-Query", + }, + "name": "item-query", + "in": "query", + }, + ], + "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", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "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_numeric_validations/test_tutorial002_tutorial003.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py new file mode 100644 index 0000000000..467c915dcd --- /dev/null +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py @@ -0,0 +1,170 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_an_py39"), + pytest.param("tutorial003_py39"), + pytest.param("tutorial003_an_py39"), + ], +) +def get_client(request: pytest.FixtureRequest) -> TestClient: + mod = importlib.import_module( + f"docs_src.path_params_numeric_validations.{request.param}" + ) + return TestClient(mod.app) + + +@pytest.mark.parametrize( + "path,expected_response", + [ + ("/items/42?q=", {"item_id": 42}), + ("/items/123?q=somequery", {"item_id": 123, "q": "somequery"}), + ], +) +def test_read_items(client: TestClient, path, expected_response): + response = client.get(path) + assert response.status_code == 200, response.text + assert response.json() == expected_response + + +def test_read_items_invalid_item_id(client: TestClient): + response = client.get("/items/invalid_id?q=somequery") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["path", "item_id"], + "input": "invalid_id", + "msg": "Input should be a valid integer, unable to parse string as an integer", + "type": "int_parsing", + } + ] + } + + +def test_read_items_missing_q(client: TestClient): + response = client.get("/items/42") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["query", "q"], + "input": None, + "msg": "Field required", + "type": "missing", + } + ] + } + + +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", + }, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": { + "type": "string", + "title": "Q", + }, + "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", + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "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_numeric_validations/test_tutorial004.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py new file mode 100644 index 0000000000..d3593c984c --- /dev/null +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py @@ -0,0 +1,185 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial004_py39"), + pytest.param("tutorial004_an_py39"), + ], +) +def get_client(request: pytest.FixtureRequest) -> TestClient: + mod = importlib.import_module( + f"docs_src.path_params_numeric_validations.{request.param}" + ) + return TestClient(mod.app) + + +@pytest.mark.parametrize( + "path,expected_response", + [ + ("/items/42?q=", {"item_id": 42}), + ("/items/1?q=somequery", {"item_id": 1, "q": "somequery"}), + ], +) +def test_read_items(client: TestClient, path, expected_response): + response = client.get(path) + assert response.status_code == 200, response.text + assert response.json() == expected_response + + +def test_read_items_non_int_item_id(client: TestClient): + response = client.get("/items/invalid_id?q=somequery") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["path", "item_id"], + "input": "invalid_id", + "msg": "Input should be a valid integer, unable to parse string as an integer", + "type": "int_parsing", + } + ] + } + + +def test_read_items_item_id_less_than_one(client: TestClient): + response = client.get("/items/0?q=somequery") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["path", "item_id"], + "input": "0", + "msg": "Input should be greater than or equal to 1", + "type": "greater_than_equal", + "ctx": {"ge": 1}, + } + ] + } + + +def test_read_items_missing_q(client: TestClient): + response = client.get("/items/42") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["query", "q"], + "input": None, + "msg": "Field required", + "type": "missing", + } + ] + } + + +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, + }, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": { + "type": "string", + "title": "Q", + }, + "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", + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "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_numeric_validations/test_tutorial005.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py new file mode 100644 index 0000000000..296192593b --- /dev/null +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py @@ -0,0 +1,202 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial005_py39"), + pytest.param("tutorial005_an_py39"), + ], +) +def get_client(request: pytest.FixtureRequest) -> TestClient: + mod = importlib.import_module( + f"docs_src.path_params_numeric_validations.{request.param}" + ) + return TestClient(mod.app) + + +@pytest.mark.parametrize( + "path,expected_response", + [ + ("/items/1?q=", {"item_id": 1}), + ("/items/1000?q=somequery", {"item_id": 1000, "q": "somequery"}), + ], +) +def test_read_items(client: TestClient, path, expected_response): + response = client.get(path) + assert response.status_code == 200, response.text + assert response.json() == expected_response + + +def test_read_items_non_int_item_id(client: TestClient): + response = client.get("/items/invalid_id?q=somequery") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["path", "item_id"], + "input": "invalid_id", + "msg": "Input should be a valid integer, unable to parse string as an integer", + "type": "int_parsing", + } + ] + } + + +def test_read_items_item_id_less_than_one(client: TestClient): + response = client.get("/items/0?q=somequery") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["path", "item_id"], + "input": "0", + "msg": "Input should be greater than 0", + "type": "greater_than", + "ctx": {"gt": 0}, + } + ] + } + + +def test_read_items_item_id_greater_than_one_thousand(client: TestClient): + response = client.get("/items/1001?q=somequery") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["path", "item_id"], + "input": "1001", + "msg": "Input should be less than or equal to 1000", + "type": "less_than_equal", + "ctx": {"le": 1000}, + } + ] + } + + +def test_read_items_missing_q(client: TestClient): + response = client.get("/items/42") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["query", "q"], + "input": None, + "msg": "Field required", + "type": "missing", + } + ] + } + + +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, + }, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": { + "type": "string", + "title": "Q", + }, + "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", + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "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_numeric_validations/test_tutorial006.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py new file mode 100644 index 0000000000..9dc7d7aac2 --- /dev/null +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py @@ -0,0 +1,221 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial006_py39"), + pytest.param("tutorial006_an_py39"), + ], +) +def get_client(request: pytest.FixtureRequest) -> TestClient: + mod = importlib.import_module( + f"docs_src.path_params_numeric_validations.{request.param}" + ) + return TestClient(mod.app) + + +@pytest.mark.parametrize( + "path,expected_response", + [ + ( + "/items/0?q=&size=0.1", + {"item_id": 0, "size": 0.1}, + ), + ( + "/items/1000?q=somequery&size=10.4", + {"item_id": 1000, "q": "somequery", "size": 10.4}, + ), + ], +) +def test_read_items(client: TestClient, path, expected_response): + response = client.get(path) + assert response.status_code == 200, response.text + assert response.json() == expected_response + + +def test_read_items_item_id_less_than_zero(client: TestClient): + response = client.get("/items/-1?q=somequery&size=5") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["path", "item_id"], + "input": "-1", + "msg": "Input should be greater than or equal to 0", + "type": "greater_than_equal", + "ctx": {"ge": 0}, + } + ] + } + + +def test_read_items_item_id_greater_than_one_thousand(client: TestClient): + response = client.get("/items/1001?q=somequery&size=5") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["path", "item_id"], + "input": "1001", + "msg": "Input should be less than or equal to 1000", + "type": "less_than_equal", + "ctx": {"le": 1000}, + } + ] + } + + +def test_read_items_size_too_small(client: TestClient): + response = client.get("/items/1?q=somequery&size=0.0") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["query", "size"], + "input": "0.0", + "msg": "Input should be greater than 0", + "type": "greater_than", + "ctx": {"gt": 0.0}, + } + ] + } + + +def test_read_items_size_too_large(client: TestClient): + response = client.get("/items/1?q=somequery&size=10.5") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["query", "size"], + "input": "10.5", + "msg": "Input should be less than 10.5", + "type": "less_than", + "ctx": {"lt": 10.5}, + } + ] + } + + +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, + }, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": { + "type": "string", + "title": "Q", + }, + "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", + }, + }, + }, + "description": "Validation Error", + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "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_python_types/__init__.py b/tests/test_tutorial/test_python_types/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py new file mode 100644 index 0000000000..ccb0968576 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py @@ -0,0 +1,18 @@ +import runpy +from unittest.mock import patch + +import pytest + + +@pytest.mark.parametrize( + "module_name", + [ + "tutorial001_py39", + "tutorial002_py39", + ], +) +def test_run_module(module_name: str): + with patch("builtins.print") as mock_print: + runpy.run_module(f"docs_src.python_types.{module_name}", run_name="__main__") + + mock_print.assert_called_with("John Doe") diff --git a/tests/test_tutorial/test_python_types/test_tutorial003.py b/tests/test_tutorial/test_python_types/test_tutorial003.py new file mode 100644 index 0000000000..34d2649171 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial003.py @@ -0,0 +1,12 @@ +import pytest + +from docs_src.python_types.tutorial003_py39 import get_name_with_age + + +def test_get_name_with_age_pass_int(): + with pytest.raises(TypeError): + get_name_with_age("John", 30) + + +def test_get_name_with_age_pass_str(): + assert get_name_with_age("John", "30") == "John is this old: 30" diff --git a/tests/test_tutorial/test_python_types/test_tutorial004.py b/tests/test_tutorial/test_python_types/test_tutorial004.py new file mode 100644 index 0000000000..24af32883e --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial004.py @@ -0,0 +1,5 @@ +from docs_src.python_types.tutorial004_py39 import get_name_with_age + + +def test_get_name_with_age_pass_int(): + assert get_name_with_age("John", 30) == "John is this old: 30" diff --git a/tests/test_tutorial/test_python_types/test_tutorial005.py b/tests/test_tutorial/test_python_types/test_tutorial005.py new file mode 100644 index 0000000000..6d67ec4716 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial005.py @@ -0,0 +1,12 @@ +from docs_src.python_types.tutorial005_py39 import get_items + + +def test_get_items(): + res = get_items( + "item_a", + "item_b", + "item_c", + "item_d", + "item_e", + ) + assert res == ("item_a", "item_b", "item_c", "item_d", "item_e") diff --git a/tests/test_tutorial/test_python_types/test_tutorial006.py b/tests/test_tutorial/test_python_types/test_tutorial006.py new file mode 100644 index 0000000000..50976926e7 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial006.py @@ -0,0 +1,16 @@ +from unittest.mock import patch + +from docs_src.python_types.tutorial006_py39 import process_items + + +def test_process_items(): + with patch("builtins.print") as mock_print: + process_items(["item_a", "item_b", "item_c"]) + + assert mock_print.call_count == 3 + call_args = [arg.args for arg in mock_print.call_args_list] + assert call_args == [ + ("item_a",), + ("item_b",), + ("item_c",), + ] diff --git a/tests/test_tutorial/test_python_types/test_tutorial007.py b/tests/test_tutorial/test_python_types/test_tutorial007.py new file mode 100644 index 0000000000..c045294652 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial007.py @@ -0,0 +1,8 @@ +from docs_src.python_types.tutorial007_py39 import process_items + + +def test_process_items(): + items_t = (1, 2, "foo") + items_s = {b"a", b"b", b"c"} + + assert process_items(items_t, items_s) == (items_t, items_s) diff --git a/tests/test_tutorial/test_python_types/test_tutorial008.py b/tests/test_tutorial/test_python_types/test_tutorial008.py new file mode 100644 index 0000000000..33cf6cbfbc --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial008.py @@ -0,0 +1,17 @@ +from unittest.mock import patch + +from docs_src.python_types.tutorial008_py39 import process_items + + +def test_process_items(): + with patch("builtins.print") as mock_print: + process_items({"a": 1.0, "b": 2.5}) + + assert mock_print.call_count == 4 + call_args = [arg.args for arg in mock_print.call_args_list] + assert call_args == [ + ("a",), + (1.0,), + ("b",), + (2.5,), + ] diff --git a/tests/test_tutorial/test_python_types/test_tutorial008b.py b/tests/test_tutorial/test_python_types/test_tutorial008b.py new file mode 100644 index 0000000000..1ef0d4ea16 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial008b.py @@ -0,0 +1,27 @@ +import importlib +from types import ModuleType +from unittest.mock import patch + +import pytest + +from ...utils import needs_py310 + + +@pytest.fixture( + name="module", + params=[ + pytest.param("tutorial008b_py39"), + pytest.param("tutorial008b_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_process_items(module: ModuleType): + with patch("builtins.print") as mock_print: + module.process_item("a") + + assert mock_print.call_count == 1 + mock_print.assert_called_with("a") diff --git a/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py b/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py new file mode 100644 index 0000000000..34046c5c48 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py @@ -0,0 +1,33 @@ +import importlib +from types import ModuleType +from unittest.mock import patch + +import pytest + +from ...utils import needs_py310 + + +@pytest.fixture( + name="module", + params=[ + pytest.param("tutorial009_py39"), + pytest.param("tutorial009_py310", marks=needs_py310), + pytest.param("tutorial009b_py39"), + ], +) +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") + module.say_hi() + + assert mock_print.call_count == 2 + call_args = [arg.args for arg in mock_print.call_args_list] + assert call_args == [ + ("Hey FastAPI!",), + ("Hello World",), + ] diff --git a/tests/test_tutorial/test_python_types/test_tutorial009c.py b/tests/test_tutorial/test_python_types/test_tutorial009c.py new file mode 100644 index 0000000000..7bd4049113 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial009c.py @@ -0,0 +1,33 @@ +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_py39"), + 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() diff --git a/tests/test_tutorial/test_python_types/test_tutorial010.py b/tests/test_tutorial/test_python_types/test_tutorial010.py new file mode 100644 index 0000000000..9e4d2e36bf --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial010.py @@ -0,0 +1,5 @@ +from docs_src.python_types.tutorial010_py39 import Person, get_person_name + + +def test_get_person_name(): + assert get_person_name(Person("John Doe")) == "John Doe" diff --git a/tests/test_tutorial/test_python_types/test_tutorial011.py b/tests/test_tutorial/test_python_types/test_tutorial011.py new file mode 100644 index 0000000000..a05751b974 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial011.py @@ -0,0 +1,25 @@ +import runpy +from unittest.mock import patch + +import pytest + +from ...utils import needs_py310 + + +@pytest.mark.parametrize( + "module_name", + [ + pytest.param("tutorial011_py39"), + pytest.param("tutorial011_py310", marks=needs_py310), + ], +) +def test_run_module(module_name: str): + with patch("builtins.print") as mock_print: + runpy.run_module(f"docs_src.python_types.{module_name}", run_name="__main__") + + assert mock_print.call_count == 2 + call_args = [str(arg.args[0]) for arg in mock_print.call_args_list] + assert call_args == [ + "id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]", + "123", + ] diff --git a/tests/test_tutorial/test_python_types/test_tutorial012.py b/tests/test_tutorial/test_python_types/test_tutorial012.py new file mode 100644 index 0000000000..e578048204 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial012.py @@ -0,0 +1,7 @@ +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 new file mode 100644 index 0000000000..5602ef76f8 --- /dev/null +++ b/tests/test_tutorial/test_python_types/test_tutorial013.py @@ -0,0 +1,5 @@ +from docs_src.python_types.tutorial013_py39 import say_hello + + +def test_say_hello(): + assert say_hello("FastAPI") == "Hello FastAPI" diff --git a/tests/test_tutorial/test_query_params/test_tutorial001.py b/tests/test_tutorial/test_query_params/test_tutorial001.py new file mode 100644 index 0000000000..4c92b57b8d --- /dev/null +++ b/tests/test_tutorial/test_query_params/test_tutorial001.py @@ -0,0 +1,126 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001_py39"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.query_params.{request.param}") + + client = TestClient(mod.app) + return client + + +@pytest.mark.parametrize( + ("path", "expected_json"), + [ + ( + "/items/", + [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}], + ), + ( + "/items/?skip=1", + [{"item_name": "Bar"}, {"item_name": "Baz"}], + ), + ( + "/items/?skip=1&limit=1", + [{"item_name": "Bar"}], + ), + ], +) +def test_read_user_item(client: TestClient, path, expected_json): + response = client.get(path) + assert response.status_code == 200 + assert response.json() == 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, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 10, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "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"}, + }, + }, + "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 new file mode 100644 index 0000000000..ae3ee7613d --- /dev/null +++ b/tests/test_tutorial/test_query_params/test_tutorial002.py @@ -0,0 +1,127 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.query_params.{request.param}") + + client = TestClient(mod.app) + return client + + +@pytest.mark.parametrize( + ("path", "expected_json"), + [ + ( + "/items/foo", + {"item_id": "foo"}, + ), + ( + "/items/bar?q=somequery", + {"item_id": "bar", "q": "somequery"}, + ), + ], +) +def test_read_user_item(client: TestClient, path, expected_json): + response = client.get(path) + assert response.status_code == 200 + assert response.json() == 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", + }, + ], + }, + "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", + }, + }, + } + } + }, + "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"}, + }, + }, + "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 new file mode 100644 index 0000000000..c0b7e3b133 --- /dev/null +++ b/tests/test_tutorial/test_query_params/test_tutorial003.py @@ -0,0 +1,148 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial003_py39"), + pytest.param("tutorial003_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.query_params.{request.param}") + + client = TestClient(mod.app) + return client + + +@pytest.mark.parametrize( + ("path", "expected_json"), + [ + ( + "/items/foo", + { + "item_id": "foo", + "description": "This is an amazing item that has a long description", + }, + ), + ( + "/items/bar?q=somequery", + { + "item_id": "bar", + "q": "somequery", + "description": "This is an amazing item that has a long description", + }, + ), + ( + "/items/baz?short=true", + {"item_id": "baz"}, + ), + ], +) +def test_read_user_item(client: TestClient, path, expected_json): + response = client.get(path) + assert response.status_code == 200 + assert response.json() == 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", + }, + ], + }, + "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", + }, + }, + } + } + }, + "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"}, + }, + }, + "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 new file mode 100644 index 0000000000..9be18b74df --- /dev/null +++ b/tests/test_tutorial/test_query_params/test_tutorial004.py @@ -0,0 +1,156 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial004_py39"), + pytest.param("tutorial004_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.query_params.{request.param}") + + client = TestClient(mod.app) + return client + + +@pytest.mark.parametrize( + ("path", "expected_json"), + [ + ( + "/users/123/items/foo", + { + "item_id": "foo", + "owner_id": 123, + "description": "This is an amazing item that has a long description", + }, + ), + ( + "/users/1/items/bar?q=somequery", + { + "item_id": "bar", + "owner_id": 1, + "q": "somequery", + "description": "This is an amazing item that has a long description", + }, + ), + ( + "/users/42/items/baz?short=true", + {"item_id": "baz", "owner_id": 42}, + ), + ], +) +def test_read_user_item(client: TestClient, path, expected_json): + response = client.get(path) + assert response.status_code == 200 + assert response.json() == 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", + }, + ], + }, + "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", + }, + }, + } + } + }, + "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"}, + }, + }, + "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 new file mode 100644 index 0000000000..f1af7e08c1 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py @@ -0,0 +1,121 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001_py39"), + pytest.param("tutorial001_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +def test_query_params_str_validations_q_empty_str(client: TestClient): + response = client.get("/items/", params={"q": ""}) + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +def test_query_params_str_validations_q_query(client: TestClient): + response = client.get("/items/", params={"q": "query"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "query", + } + + +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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } 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 new file mode 100644 index 0000000000..62018b80b5 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py @@ -0,0 +1,142 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +def test_query_params_str_validations_q_empty_str(client: TestClient): + response = client.get("/items/", params={"q": ""}) + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +def test_query_params_str_validations_q_query(client: TestClient): + response = client.get("/items/", params={"q": "query"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "query", + } + + +def test_query_params_str_validations_q_too_long(client: TestClient): + response = client.get("/items/", params={"q": "q" * 51}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "string_too_long", + "loc": ["query", "q"], + "msg": "String should have at most 50 characters", + "input": "q" * 51, + "ctx": {"max_length": 50}, + } + ] + } + + +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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } 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 new file mode 100644 index 0000000000..a4ad7a63ba --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py @@ -0,0 +1,153 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +def test_query_params_str_validations_q_query(client: TestClient): + response = client.get("/items/", params={"q": "query"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "query", + } + + +def test_query_params_str_validations_q_too_short(client: TestClient): + response = client.get("/items/", params={"q": "qu"}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "string_too_short", + "loc": ["query", "q"], + "msg": "String should have at least 3 characters", + "input": "qu", + "ctx": {"min_length": 3}, + } + ] + } + + +def test_query_params_str_validations_q_too_long(client: TestClient): + response = client.get("/items/", params={"q": "q" * 51}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "string_too_long", + "loc": ["query", "q"], + "msg": "String should have at most 50 characters", + "input": "q" * 51, + "ctx": {"max_length": 50}, + } + ] + } + + +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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } 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 new file mode 100644 index 0000000000..95efab2dc7 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py @@ -0,0 +1,147 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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:DeprecationWarning" + ), + ), + ), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +def test_query_params_str_validations_q_fixedquery(client: TestClient): + response = client.get("/items/", params={"q": "fixedquery"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "fixedquery", + } + + +def test_query_params_str_validations_q_nonregexquery(client: TestClient): + response = client.get("/items/", params={"q": "nonregexquery"}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "string_pattern_mismatch", + "loc": ["query", "q"], + "msg": "String should match pattern '^fixedquery$'", + "input": "nonregexquery", + "ctx": {"pattern": "^fixedquery$"}, + } + ] + } + + +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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } 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 new file mode 100644 index 0000000000..52462fe33b --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py @@ -0,0 +1,131 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial005_py39"), + pytest.param("tutorial005_an_py39"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "fixedquery", + } + + +def test_query_params_str_validations_q_query(client: TestClient): + response = client.get("/items/", params={"q": "query"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "query", + } + + +def test_query_params_str_validations_q_short(client: TestClient): + response = client.get("/items/", params={"q": "fa"}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "string_too_short", + "loc": ["query", "q"], + "msg": "String should have at least 3 characters", + "input": "fa", + "ctx": {"min_length": 3}, + } + ] + } + + +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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } 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 new file mode 100644 index 0000000000..640cedce19 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py @@ -0,0 +1,136 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial006_py39"), + pytest.param("tutorial006_an_py39"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "q"], + "msg": "Field required", + "input": None, + } + ] + } + + +def test_query_params_str_validations_q_fixedquery(client: TestClient): + response = client.get("/items/", params={"q": "fixedquery"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "fixedquery", + } + + +def test_query_params_str_validations_q_fixedquery_too_short(client: TestClient): + response = client.get("/items/", params={"q": "fa"}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "string_too_short", + "loc": ["query", "q"], + "msg": "String should have at least 3 characters", + "input": "fa", + "ctx": {"min_length": 3}, + } + ] + } + + +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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } 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 new file mode 100644 index 0000000000..f287b5dcd8 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py @@ -0,0 +1,148 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + client = TestClient(mod.app) + return client + + +@pytest.mark.xfail( + reason="Code example is not valid. See https://github.com/fastapi/fastapi/issues/12419" +) +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == { # pragma: no cover + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + } + + +@pytest.mark.xfail( + reason="Code example is not valid. See https://github.com/fastapi/fastapi/issues/12419" +) +def test_query_params_str_validations_empty_str(client: TestClient): + response = client.get("/items/?q=") + assert response.status_code == 200 + assert response.json() == { # pragma: no cover + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + } + + +def test_query_params_str_validations_q_query(client: TestClient): + response = client.get("/items/", params={"q": "query"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "query", + } + + +def test_query_params_str_validations_q_short(client: TestClient): + response = client.get("/items/", params={"q": "fa"}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "string_too_short", + "loc": ["query", "q"], + "msg": "String should have at least 3 characters", + "input": "fa", + "ctx": {"min_length": 3}, + } + ] + } + + +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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } 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 new file mode 100644 index 0000000000..b17bc27719 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py @@ -0,0 +1,136 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +def test_query_params_str_validations_q_fixedquery(client: TestClient): + response = client.get("/items/", params={"q": "fixedquery"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "fixedquery", + } + + +def test_query_params_str_validations_q_fixedquery_too_short(client: TestClient): + response = client.get("/items/", params={"q": "fa"}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "string_too_short", + "loc": ["query", "q"], + "msg": "String should have at least 3 characters", + "input": "fa", + "ctx": {"min_length": 3}, + } + ] + } + + +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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } 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 new file mode 100644 index 0000000000..c631115744 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py @@ -0,0 +1,138 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +def test_query_params_str_validations_q_fixedquery(client: TestClient): + response = client.get("/items/", params={"q": "fixedquery"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "fixedquery", + } + + +def test_query_params_str_validations_q_fixedquery_too_short(client: TestClient): + response = client.get("/items/", params={"q": "fa"}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "string_too_short", + "loc": ["query", "q"], + "msg": "String should have at least 3 characters", + "input": "fa", + "ctx": {"min_length": 3}, + } + ] + } + + +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" + } + } + }, + }, + }, + "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", + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } 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 new file mode 100644 index 0000000000..7e9d69d41c --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py @@ -0,0 +1,123 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations_no_query(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +def test_query_params_str_validations_item_query_fixedquery(client: TestClient): + response = client.get("/items/", params={"item-query": "fixedquery"}) + assert response.status_code == 200 + assert response.json() == { + "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + "q": "fixedquery", + } + + +def test_query_params_str_validations_q_fixedquery(client: TestClient): + response = client.get("/items/", params={"q": "fixedquery"}) + assert response.status_code == 200 + assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + + +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" + } + } + }, + }, + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_directly/test_tutorial002.py b/tests/test_tutorial/test_response_directly/test_tutorial002.py new file mode 100644 index 0000000000..ef84575723 --- /dev/null +++ b/tests/test_tutorial/test_response_directly/test_tutorial002.py @@ -0,0 +1,65 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002_py39"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_directly.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_path_operation(client: TestClient): + expected_content = """ + +
+ Apply shampoo here. +
+ + You'll have to use soap here. + +
+ """ + + response = client.get("/legacy/") + assert response.status_code == 200, response.text + assert response.headers["content-type"] == "application/xml" + assert response.text == expected_content + + +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": {}, + }, + }, + "description": "Successful Response", + }, + }, + "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 new file mode 100644 index 0000000000..10692f9904 --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py @@ -0,0 +1,193 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_model.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_read_items(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [ + { + "name": "Portal Gun", + "description": None, + "price": 42.0, + "tags": [], + "tax": None, + }, + { + "name": "Plumbus", + "description": None, + "price": 32.0, + "tags": [], + "tax": None, + }, + ] + + +def test_create_item(client: TestClient): + item_data = { + "name": "Test Item", + "description": "A test item", + "price": 10.5, + "tax": 1.5, + "tags": ["test", "item"], + } + response = client.post("/items/", json=item_data) + assert response.status_code == 200, response.text + assert response.json() == item_data + + +def test_create_item_only_required(client: TestClient): + response = client.post( + "/items/", + json={ + "name": "Test Item", + "price": 10.5, + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Test Item", + "price": 10.5, + "description": None, + "tax": None, + "tags": [], + } + + +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", + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + }, + "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"}, + } + }, + }, + "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": [], + }, + }, + }, + "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"}, + }, + }, + "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 new file mode 100644 index 0000000000..216d4c420c --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial002.py @@ -0,0 +1,129 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_model.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_user(client: TestClient): + user_data = { + "username": "foo", + "password": "fighter", + "email": "foo@example.com", + "full_name": "Grave Dohl", + } + response = client.post( + "/user/", + json=user_data, + ) + assert response.status_code == 200, response.text + assert response.json() == user_data + + +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/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, + }, + } + } + }, + "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"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_status_code/__init__.py b/tests/test_tutorial/test_response_status_code/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ddf55a045d --- /dev/null +++ b/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py @@ -0,0 +1,96 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001_py39"), + pytest.param("tutorial002_py39"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_status_code.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_create_item(client: TestClient): + response = client.post("/items/", params={"name": "Test Item"}) + assert response.status_code == 201, response.text + assert response.json() == {"name": "Test Item"} + + +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" + } + } + }, + }, + }, + } + } + }, + "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"}, + }, + }, + "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_tutorial002.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py new file mode 100644 index 0000000000..4f52408605 --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py @@ -0,0 +1,141 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_body_example(client: TestClient): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + ) + assert response.status_code == 200 + + +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"} + } + }, + }, + "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": { + "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 new file mode 100644 index 0000000000..3529a9bf02 --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py @@ -0,0 +1,143 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_body_example(client: TestClient): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + ) + assert response.status_code == 200 + + +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, + } + ], + }, + } + }, + }, + "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": { + "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_security/test_tutorial002.py b/tests/test_tutorial/test_security/test_tutorial002.py new file mode 100644 index 0000000000..85c076b1d2 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial002.py @@ -0,0 +1,71 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.security.{request.param}") + client = TestClient(mod.app) + return client + + +def test_no_token(client: TestClient): + response = client.get("/users/me") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_token(client: TestClient): + response = client.get("/users/me", headers={"Authorization": "Bearer testtoken"}) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "testtokenfakedecoded", + "email": "john@example.com", + "full_name": "John Doe", + "disabled": None, + } + + +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"}}, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial004.py b/tests/test_tutorial/test_security/test_tutorial004.py new file mode 100644 index 0000000000..b5e3d39ef7 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial004.py @@ -0,0 +1,363 @@ +import importlib +from types import ModuleType +from unittest.mock import patch + +import pytest +from fastapi.testclient import TestClient + +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), + ], +) +def get_mod(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.security.{request.param}") + + return mod + + +def get_access_token(*, username="johndoe", password="secret", client: TestClient): + data = {"username": username, "password": password} + response = client.post("/token", data=data) + content = response.json() + access_token = content.get("access_token") + return access_token + + +def test_login(mod: ModuleType): + client = TestClient(mod.app) + response = client.post("/token", data={"username": "johndoe", "password": "secret"}) + assert response.status_code == 200, response.text + content = response.json() + assert "access_token" in content + assert content["token_type"] == "bearer" + + +def test_login_incorrect_password(mod: ModuleType): + client = TestClient(mod.app) + response = client.post( + "/token", data={"username": "johndoe", "password": "incorrect"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +def test_login_incorrect_username(mod: ModuleType): + client = TestClient(mod.app) + response = client.post("/token", data={"username": "foo", "password": "secret"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +def test_no_token(mod: ModuleType): + client = TestClient(mod.app) + response = client.get("/users/me") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_token(mod: ModuleType): + client = TestClient(mod.app) + access_token = get_access_token(client=client) + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "johndoe", + "full_name": "John Doe", + "email": "johndoe@example.com", + "disabled": False, + } + + +def test_incorrect_token(mod: ModuleType): + client = TestClient(mod.app) + response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_incorrect_token_type(mod: ModuleType): + client = TestClient(mod.app) + response = client.get( + "/users/me", headers={"Authorization": "Notexistent testtoken"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_verify_password(mod: ModuleType): + assert mod.verify_password( + "secret", mod.fake_users_db["johndoe"]["hashed_password"] + ) + + +def test_get_password_hash(mod: ModuleType): + assert mod.get_password_hash("johndoe") + + +def test_create_access_token(mod: ModuleType): + access_token = mod.create_access_token(data={"data": "foo"}) + assert access_token + + +def test_token_no_sub(mod: ModuleType): + client = TestClient(mod.app) + + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_token_no_username(mod: ModuleType): + client = TestClient(mod.app) + + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_token_nonexistent_user(mod: ModuleType): + client = TestClient(mod.app) + + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_token_inactive_user(mod: ModuleType): + client = TestClient(mod.app) + alice_user_data = { + "username": "alice", + "full_name": "Alice Wonderson", + "email": "alice@example.com", + "hashed_password": mod.get_password_hash("secretalice"), + "disabled": True, + } + with patch.dict(f"{mod.__name__}.fake_users_db", {"alice": alice_user_data}): + access_token = get_access_token( + username="alice", password="secretalice", client=client + ) + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Inactive user"} + + +def test_read_items(mod: ModuleType): + client = TestClient(mod.app) + access_token = get_access_token(client=client) + response = client.get( + "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}] + + +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"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "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" + } + } + }, + "required": True, + }, + } + }, + "/users/me/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + "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"}] + }, + }, + "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"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": {}, + "tokenUrl": "token", + } + }, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial007.py b/tests/test_tutorial/test_security/test_tutorial007.py new file mode 100644 index 0000000000..28b70a2d43 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial007.py @@ -0,0 +1,89 @@ +import importlib +from base64 import b64encode + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial007_py39"), + pytest.param("tutorial007_an_py39"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.security.{request.param}") + return TestClient(mod.app) + + +def test_security_http_basic(client: TestClient): + response = client.get("/users/me", auth=("stanleyjobson", "swordfish")) + assert response.status_code == 200, response.text + assert response.json() == {"username": "stanleyjobson"} + + +def test_security_http_basic_no_credentials(client: TestClient): + response = client.get("/users/me") + assert response.json() == {"detail": "Not authenticated"} + assert response.status_code == 401, response.text + assert response.headers["WWW-Authenticate"] == "Basic" + + +def test_security_http_basic_invalid_credentials(client: TestClient): + response = client.get( + "/users/me", headers={"Authorization": "Basic notabase64token"} + ) + assert response.status_code == 401, response.text + assert response.headers["WWW-Authenticate"] == "Basic" + assert response.json() == {"detail": "Not authenticated"} + + +def test_security_http_basic_non_basic_credentials(client: TestClient): + payload = b64encode(b"johnsecret").decode("ascii") + auth_header = f"Basic {payload}" + response = client.get("/users/me", headers={"Authorization": auth_header}) + assert response.status_code == 401, response.text + assert response.headers["WWW-Authenticate"] == "Basic" + assert response.json() == {"detail": "Not authenticated"} + + +def test_security_http_basic_invalid_username(client: TestClient): + response = client.get("/users/me", auth=("alice", "swordfish")) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Incorrect username or password"} + assert response.headers["WWW-Authenticate"] == "Basic" + + +def test_security_http_basic_invalid_password(client: TestClient): + response = client.get("/users/me", auth=("stanleyjobson", "wrongpassword")) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Incorrect username or password"} + assert response.headers["WWW-Authenticate"] == "Basic" + + +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": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } diff --git a/tests/test_tutorial/test_settings/test_app01.py b/tests/test_tutorial/test_settings/test_app01.py new file mode 100644 index 0000000000..0c5e440f1a --- /dev/null +++ b/tests/test_tutorial/test_settings/test_app01.py @@ -0,0 +1,78 @@ +import importlib +import sys + +import pytest +from dirty_equals import IsAnyStr +from fastapi.testclient import TestClient +from pydantic import ValidationError +from pytest import MonkeyPatch + + +@pytest.fixture( + name="mod_name", + params=[ + pytest.param("app01_py39"), + ], +) +def get_mod_name(request: pytest.FixtureRequest): + return f"docs_src.settings.{request.param}.main" + + +@pytest.fixture(name="client") +def get_test_client(mod_name: str, monkeypatch: MonkeyPatch) -> TestClient: + if mod_name in sys.modules: + del sys.modules[mod_name] + monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") + main_mod = importlib.import_module(mod_name) + return TestClient(main_mod.app) + + +def test_settings_validation_error(mod_name: str, monkeypatch: MonkeyPatch): + monkeypatch.delenv("ADMIN_EMAIL", raising=False) + if mod_name in sys.modules: + del sys.modules[mod_name] # pragma: no cover + + with pytest.raises(ValidationError) as exc_info: + importlib.import_module(mod_name) + assert exc_info.value.errors() == [ + { + "loc": ("admin_email",), + "msg": "Field required", + "type": "missing", + "input": {}, + "url": IsAnyStr, + } + ] + + +def test_app(client: TestClient): + response = client.get("/info") + data = response.json() + assert data == { + "app_name": "Awesome API", + "admin_email": "admin@example.com", + "items_per_user": 50, + } + + +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", + } + } + }, + } diff --git a/tests/test_tutorial/test_static_files/__init__.py b/tests/test_tutorial/test_static_files/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_static_files/test_tutorial001.py b/tests/test_tutorial/test_static_files/test_tutorial001.py new file mode 100644 index 0000000000..4fbf19ae82 --- /dev/null +++ b/tests/test_tutorial/test_static_files/test_tutorial001.py @@ -0,0 +1,40 @@ +import os +from pathlib import Path + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture(scope="module") +def client(): + static_dir: Path = Path(os.getcwd()) / "static" + 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 + + with TestClient(app) as client: + yield client + sample_file.unlink() + static_dir.rmdir() + + +def test_static_files(client: TestClient): + response = client.get("/static/sample.txt") + assert response.status_code == 200, response.text + assert response.text == "This is a sample static file." + + +def test_static_files_not_found(client: TestClient): + response = client.get("/static/non_existent_file.txt") + assert response.status_code == 404, response.text + + +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": {}, + } From 8cefc4b7cc9acff703f3e7327aba7274d83022c2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 26 Dec 2025 10:43:27 +0000 Subject: [PATCH 012/110] =?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 bc2441f7f3..11dae3333e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Internal +* โœ… Add missing tests for code examples. PR [#14569](https://github.com/fastapi/fastapi/pull/14569) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐Ÿ‘ท Remove `lint` job from `test` CI workflow. PR [#14593](https://github.com/fastapi/fastapi/pull/14593) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐Ÿ‘ท Update secrets check. PR [#14592](https://github.com/fastapi/fastapi/pull/14592) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Run CodSpeed tests in parallel to other tests to speed up CI. PR [#14586](https://github.com/fastapi/fastapi/pull/14586) by [@tiangolo](https://github.com/tiangolo). From d98f4eb56eae3155034fe631d9639d95171fb9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 26 Dec 2025 03:36:58 -0800 Subject: [PATCH 013/110] =?UTF-8?q?=F0=9F=94=A7=20Update=20pre-commit=20to?= =?UTF-8?q?=20use=20local=20Ruff=20instead=20of=20hook=20(#14604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 77e06bd96c..10a0949e48 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,15 +12,23 @@ repos: - --unsafe - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.3 - hooks: - - id: ruff - args: - - --fix - - id: ruff-format + - repo: local hooks: + - id: local-ruff-check + name: ruff check + entry: uv run ruff check --force-exclude --fix --exit-non-zero-on-fix + require_serial: true + language: unsupported + types: [python] + + - id: local-ruff-format + name: ruff format + entry: uv run ruff format --force-exclude --exit-non-zero-on-format + require_serial: true + language: unsupported + types: [python] + - id: add-permalinks-pages language: unsupported name: add-permalinks-pages @@ -28,18 +36,21 @@ repos: args: - --update-existing files: ^docs/en/docs/.*\.md$ + - id: generate-readme language: unsupported name: generate README.md from index.md entry: uv run ./scripts/docs.py generate-readme files: ^docs/en/docs/index\.md|docs/en/data/sponsors\.yml|scripts/docs\.py$ pass_filenames: false + - id: update-languages language: unsupported name: update languages entry: uv run ./scripts/docs.py update-languages files: ^docs/.*|scripts/docs\.py$ pass_filenames: false + - id: ensure-non-translated language: unsupported name: ensure non-translated files are not modified From 6b53786f626db95a42fd0f66afd360bab161f4b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 26 Dec 2025 11:37:18 +0000 Subject: [PATCH 014/110] =?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 11dae3333e..98fd67da23 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Internal +* ๐Ÿ”ง Update pre-commit to use local Ruff instead of hook. PR [#14604](https://github.com/fastapi/fastapi/pull/14604) by [@tiangolo](https://github.com/tiangolo). * โœ… Add missing tests for code examples. PR [#14569](https://github.com/fastapi/fastapi/pull/14569) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐Ÿ‘ท Remove `lint` job from `test` CI workflow. PR [#14593](https://github.com/fastapi/fastapi/pull/14593) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐Ÿ‘ท Update secrets check. PR [#14592](https://github.com/fastapi/fastapi/pull/14592) by [@tiangolo](https://github.com/tiangolo). From 535b5daa317a9d1d1f9a1058e57650a7beefa861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 26 Dec 2025 04:45:20 -0800 Subject: [PATCH 015/110] =?UTF-8?q?=F0=9F=94=8A=20Add=20a=20custom=20`Fast?= =?UTF-8?q?APIDeprecationWarning`=20(#14605)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/dependencies/utils.py | 4 ++-- fastapi/exceptions.py | 7 +++++++ fastapi/openapi/utils.py | 5 +++-- fastapi/params.py | 9 ++++---- fastapi/routing.py | 5 +++-- fastapi/temp_pydantic_v1_params.py | 9 ++++---- fastapi/utils.py | 5 +++-- tests/benchmarks/test_general_performance.py | 3 ++- tests/test_compat_params_v1.py | 9 ++++---- .../test_pydantic_v1_deprecation_warnings.py | 9 ++++---- tests/test_regex_deprecated_body.py | 3 ++- tests/test_regex_deprecated_params.py | 3 ++- tests/test_schema_extra_examples.py | 21 ++++++++++--------- .../test_tutorial002.py | 3 ++- .../test_tutorial003.py | 3 ++- .../test_tutorial004.py | 3 ++- .../test_tutorial004.py | 2 +- .../test_tutorial002_pv1.py | 3 ++- .../test_tutorial001_pv1.py | 3 ++- 19 files changed, 66 insertions(+), 43 deletions(-) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 39d0bd89cd..af2bed9ad9 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -51,7 +51,7 @@ from fastapi.concurrency import ( contextmanager_in_threadpool, ) from fastapi.dependencies.models import Dependant -from fastapi.exceptions import DependencyScopeError +from fastapi.exceptions import DependencyScopeError, FastAPIDeprecationWarning from fastapi.logger import logger from fastapi.security.oauth2 import SecurityScopes from fastapi.types import DependencyCacheKey @@ -327,7 +327,7 @@ def get_dependant( warnings.warn( "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." f" Please update the param {param_name}: {param_details.type_annotation!r}.", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=5, ) if isinstance( diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 8e0c559023..53e5052818 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -231,3 +231,10 @@ class ResponseValidationError(ValidationException): ) -> None: super().__init__(errors, endpoint_ctx=endpoint_ctx) self.body = body + + +class FastAPIDeprecationWarning(UserWarning): + """ + A custom deprecation warning as DeprecationWarning is ignored + Ref: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries + """ diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index a99d4188e7..6180fcde6a 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -23,6 +23,7 @@ from fastapi.dependencies.utils import ( get_validation_alias, ) from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX from fastapi.openapi.models import OpenAPI from fastapi.params import Body, ParamTypes @@ -215,9 +216,9 @@ def generate_operation_id( *, route: routing.APIRoute, method: str ) -> str: # pragma: nocover warnings.warn( - "fastapi.openapi.utils.generate_operation_id() was deprecated, " + message="fastapi.openapi.utils.generate_operation_id() was deprecated, " "it is not used internally, and will be removed soon", - DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=2, ) if route.operation_id: diff --git a/fastapi/params.py b/fastapi/params.py index c776c4a59e..cc2934f44d 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from enum import Enum from typing import Annotated, Any, Callable, Optional, Union +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.openapi.models import Example from pydantic.fields import FieldInfo from typing_extensions import Literal, deprecated @@ -75,7 +76,7 @@ class Param(FieldInfo): # type: ignore[misc] if example is not _Unset: warnings.warn( "`example` has been deprecated, please use `examples` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.example = example @@ -105,7 +106,7 @@ class Param(FieldInfo): # type: ignore[misc] if regex is not None: warnings.warn( "`regex` has been deprecated, please use `pattern` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra @@ -530,7 +531,7 @@ class Body(FieldInfo): # type: ignore[misc] if example is not _Unset: warnings.warn( "`example` has been deprecated, please use `examples` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.example = example @@ -560,7 +561,7 @@ class Body(FieldInfo): # type: ignore[misc] if regex is not None: warnings.warn( "`regex` has been deprecated, please use `pattern` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra diff --git a/fastapi/routing.py b/fastapi/routing.py index 2770e3253d..3f78e93e84 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -47,6 +47,7 @@ from fastapi.dependencies.utils import ( from fastapi.encoders import jsonable_encoder from fastapi.exceptions import ( EndpointContext, + FastAPIDeprecationWarning, FastAPIError, RequestValidationError, ResponseValidationError, @@ -640,7 +641,7 @@ class APIRoute(routing.Route): warnings.warn( "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." f" Please update the response model {self.response_model!r}.", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.response_field = create_model_field( @@ -680,7 +681,7 @@ class APIRoute(routing.Route): warnings.warn( "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." f" In responses={{}}, please update {model}.", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) response_field = create_model_field( diff --git a/fastapi/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py index 1bda0ea9b2..62230e42cf 100644 --- a/fastapi/temp_pydantic_v1_params.py +++ b/fastapi/temp_pydantic_v1_params.py @@ -1,6 +1,7 @@ import warnings from typing import Annotated, Any, Callable, Optional, Union +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.openapi.models import Example from fastapi.params import ParamTypes from typing_extensions import deprecated @@ -63,7 +64,7 @@ class Param(FieldInfo): if example is not _Unset: warnings.warn( "`example` has been deprecated, please use `examples` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.example = example @@ -93,7 +94,7 @@ class Param(FieldInfo): if regex is not None: warnings.warn( "`regex` has been deprecated, please use `pattern` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra @@ -503,7 +504,7 @@ class Body(FieldInfo): if example is not _Unset: warnings.warn( "`example` has been deprecated, please use `examples` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.example = example @@ -533,7 +534,7 @@ class Body(FieldInfo): if regex is not None: warnings.warn( "`regex` has been deprecated, please use `pattern` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra diff --git a/fastapi/utils.py b/fastapi/utils.py index c4631d7ed2..8ae50aa145 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -23,6 +23,7 @@ from fastapi._compat import ( may_v1, ) from fastapi.datastructures import DefaultPlaceholder, DefaultType +from fastapi.exceptions import FastAPIDeprecationWarning from pydantic import BaseModel from pydantic.fields import FieldInfo from typing_extensions import Literal @@ -195,9 +196,9 @@ def generate_operation_id_for_path( *, name: str, path: str, method: str ) -> str: # pragma: nocover warnings.warn( - "fastapi.utils.generate_operation_id_for_path() was deprecated, " + message="fastapi.utils.generate_operation_id_for_path() was deprecated, " "it is not used internally, and will be removed soon", - DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=2, ) operation_id = f"{name}{path}" diff --git a/tests/benchmarks/test_general_performance.py b/tests/benchmarks/test_general_performance.py index 2da74b95c5..dac297e4ed 100644 --- a/tests/benchmarks/test_general_performance.py +++ b/tests/benchmarks/test_general_performance.py @@ -6,6 +6,7 @@ from typing import Annotated, Any import pytest from fastapi import Depends, FastAPI +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient if "--codspeed" not in sys.argv: @@ -89,7 +90,7 @@ def app(basemodel_class: type[Any]) -> FastAPI: warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) @app.post("/sync/validated", response_model=ItemOut) diff --git a/tests/test_compat_params_v1.py b/tests/test_compat_params_v1.py index 2ac96993a8..704b3f77a6 100644 --- a/tests/test_compat_params_v1.py +++ b/tests/test_compat_params_v1.py @@ -3,6 +3,7 @@ import warnings from typing import Optional import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from tests.utils import skip_module_if_py_gte_314 @@ -504,23 +505,23 @@ def test_body_repr(): # Deprecation warning tests for regex parameter def test_query_regex_deprecation_warning(): - with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"): + with pytest.warns(FastAPIDeprecationWarning, match="`regex` has been deprecated"): Query(regex="^test$") def test_body_regex_deprecation_warning(): - with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"): + with pytest.warns(FastAPIDeprecationWarning, match="`regex` has been deprecated"): Body(regex="^test$") # Deprecation warning tests for example parameter def test_query_example_deprecation_warning(): - with pytest.warns(DeprecationWarning, match="`example` has been deprecated"): + with pytest.warns(FastAPIDeprecationWarning, match="`example` has been deprecated"): Query(example="test example") def test_body_example_deprecation_warning(): - with pytest.warns(DeprecationWarning, match="`example` has been deprecated"): + with pytest.warns(FastAPIDeprecationWarning, match="`example` has been deprecated"): Body(example={"test": "example"}) diff --git a/tests/test_pydantic_v1_deprecation_warnings.py b/tests/test_pydantic_v1_deprecation_warnings.py index e0008e2183..89ca6a8658 100644 --- a/tests/test_pydantic_v1_deprecation_warnings.py +++ b/tests/test_pydantic_v1_deprecation_warnings.py @@ -1,6 +1,7 @@ import sys import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from tests.utils import skip_module_if_py_gte_314 @@ -19,7 +20,7 @@ def test_warns_pydantic_v1_model_in_endpoint_param() -> None: app = FastAPI() with pytest.warns( - DeprecationWarning, + FastAPIDeprecationWarning, match=r"pydantic\.v1 is deprecated.*Please update the param data:", ): @@ -40,7 +41,7 @@ def test_warns_pydantic_v1_model_in_return_type() -> None: app = FastAPI() with pytest.warns( - DeprecationWarning, + FastAPIDeprecationWarning, match=r"pydantic\.v1 is deprecated.*Please update the response model", ): @@ -61,7 +62,7 @@ def test_warns_pydantic_v1_model_in_response_model() -> None: app = FastAPI() with pytest.warns( - DeprecationWarning, + FastAPIDeprecationWarning, match=r"pydantic\.v1 is deprecated.*Please update the response model", ): @@ -82,7 +83,7 @@ def test_warns_pydantic_v1_model_in_additional_responses_model() -> None: app = FastAPI() with pytest.warns( - DeprecationWarning, + FastAPIDeprecationWarning, match=r"pydantic\.v1 is deprecated.*In responses=\{\}, please update", ): diff --git a/tests/test_regex_deprecated_body.py b/tests/test_regex_deprecated_body.py index cfbff19c87..9d58c5dae1 100644 --- a/tests/test_regex_deprecated_body.py +++ b/tests/test_regex_deprecated_body.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from dirty_equals import IsDict from fastapi import FastAPI, Form +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from .utils import needs_py310 @@ -10,7 +11,7 @@ from .utils import needs_py310 def get_client(): app = FastAPI() - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.post("/items/") async def read_items( diff --git a/tests/test_regex_deprecated_params.py b/tests/test_regex_deprecated_params.py index 7d9988f9f8..8973657a90 100644 --- a/tests/test_regex_deprecated_params.py +++ b/tests/test_regex_deprecated_params.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from dirty_equals import IsDict from fastapi import FastAPI, Query +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from .utils import needs_py310 @@ -10,7 +11,7 @@ from .utils import needs_py310 def get_client(): app = FastAPI() - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/items/") async def read_items( diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py index 176b5588d7..8f5195ba11 100644 --- a/tests/test_schema_extra_examples.py +++ b/tests/test_schema_extra_examples.py @@ -3,6 +3,7 @@ from typing import Union import pytest from dirty_equals import IsDict from fastapi import Body, Cookie, FastAPI, Header, Path, Query +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict @@ -21,7 +22,7 @@ def create_app(): def schema_extra(item: Item): return item - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.post("/example/") def example(item: Item = Body(example={"data": "Data in Body example"})): @@ -38,7 +39,7 @@ def create_app(): ): return item - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.post("/example_examples/") def example_examples( @@ -83,7 +84,7 @@ def create_app(): # ): # return lastname - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/path_example/{item_id}") def path_example( @@ -101,7 +102,7 @@ def create_app(): ): return item_id - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/path_example_examples/{item_id}") def path_example_examples( @@ -112,7 +113,7 @@ def create_app(): ): return item_id - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/query_example/") def query_example( @@ -132,7 +133,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/query_example_examples/") def query_example_examples( @@ -144,7 +145,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/header_example/") def header_example( @@ -167,7 +168,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/header_example_examples/") def header_example_examples( @@ -179,7 +180,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/cookie_example/") def cookie_example( @@ -199,7 +200,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/cookie_example_examples/") def cookie_example_examples( diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py index ab7e1d8a77..9d1baf8530 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py @@ -2,6 +2,7 @@ import sys import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from inline_snapshot import snapshot from tests.utils import skip_module_if_py_gte_314 @@ -29,7 +30,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py index c45e042484..23b236888d 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py @@ -2,6 +2,7 @@ import sys import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from inline_snapshot import snapshot from tests.utils import skip_module_if_py_gte_314 @@ -29,7 +30,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py index f3da849e04..61c0f63571 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py @@ -2,6 +2,7 @@ import sys import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from inline_snapshot import snapshot from tests.utils import skip_module_if_py_gte_314 @@ -29,7 +30,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") 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 95efab2dc7..585989a827 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 @@ -18,7 +18,7 @@ from ...utils import needs_py310 marks=( needs_py310, pytest.mark.filterwarnings( - "ignore:`regex` has been deprecated, please use `pattern` instead:DeprecationWarning" + "ignore:`regex` has been deprecated, please use `pattern` instead:fastapi.exceptions.FastAPIDeprecationWarning" ), ), ), diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py index 515a5a8d78..50be458962 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py @@ -2,6 +2,7 @@ import importlib import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from ...utils import needs_pydanticv1 @@ -19,7 +20,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.request_form_models.{request.param}") diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py index c5526b19cd..83c7176567 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py @@ -2,6 +2,7 @@ import importlib import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -20,7 +21,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") From 93f4dfd88b7d62bda89eb521ebd9322e357e6fff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 26 Dec 2025 12:46:00 +0000 Subject: [PATCH 016/110] =?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 98fd67da23..2af8abdfd4 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Refactors + +* ๐Ÿ”Š Add a custom `FastAPIDeprecationWarning`. PR [#14605](https://github.com/fastapi/fastapi/pull/14605) by [@tiangolo](https://github.com/tiangolo). + ### Docs * ๐Ÿ“ Add documentary to website. PR [#14600](https://github.com/fastapi/fastapi/pull/14600) by [@tiangolo](https://github.com/tiangolo). From cd90c78391f86f3fd06d5b327bffad9666b05b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 26 Dec 2025 14:02:41 +0100 Subject: [PATCH 017/110] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?7.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 2af8abdfd4..3e9084d475 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.127.1 + ### Refactors * ๐Ÿ”Š Add a custom `FastAPIDeprecationWarning`. PR [#14605](https://github.com/fastapi/fastapi/pull/14605) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 73df6dc6c9..dc447c8bfd 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.127.0" +__version__ = "0.127.1" from starlette import status as status From 34e884156f38dff4094a7764ff895c4d4b872060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 26 Dec 2025 12:40:26 -0800 Subject: [PATCH 018/110] =?UTF-8?q?=E2=9C=85=20Run=20performance=20tests?= =?UTF-8?q?=20only=20on=20Pydantic=20v2=20(#14608)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/benchmarks/test_general_performance.py | 272 +++++++++---------- 1 file changed, 133 insertions(+), 139 deletions(-) diff --git a/tests/benchmarks/test_general_performance.py b/tests/benchmarks/test_general_performance.py index dac297e4ed..87add6d174 100644 --- a/tests/benchmarks/test_general_performance.py +++ b/tests/benchmarks/test_general_performance.py @@ -1,13 +1,12 @@ import json import sys -import warnings from collections.abc import Iterator from typing import Annotated, Any import pytest from fastapi import Depends, FastAPI -from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient +from pydantic import BaseModel if "--codspeed" not in sys.argv: pytest.skip( @@ -47,148 +46,143 @@ def dep_b(a: Annotated[int, Depends(dep_a)]): return a + 2 -@pytest.fixture( - scope="module", - params=[ - "pydantic-v2", - "pydantic-v1", - ], -) -def basemodel_class(request: pytest.FixtureRequest) -> type[Any]: - if request.param == "pydantic-v2": - from pydantic import BaseModel +class ItemIn(BaseModel): + name: str + value: int - return BaseModel - else: - from pydantic.v1 import BaseModel - return BaseModel +class ItemOut(BaseModel): + name: str + value: int + dep: int + + +class LargeIn(BaseModel): + items: list[dict[str, Any]] + metadata: dict[str, Any] + + +class LargeOut(BaseModel): + items: list[dict[str, Any]] + metadata: dict[str, Any] + + +app = FastAPI() + + +@app.post("/sync/validated", response_model=ItemOut) +def sync_validated(item: ItemIn, dep: Annotated[int, Depends(dep_b)]): + return ItemOut(name=item.name, value=item.value, dep=dep) + + +@app.get("/sync/dict-no-response-model") +def sync_dict_no_response_model(): + return {"name": "foo", "value": 123} + + +@app.get("/sync/dict-with-response-model", response_model=ItemOut) +def sync_dict_with_response_model( + dep: Annotated[int, Depends(dep_b)], +): + return {"name": "foo", "value": 123, "dep": dep} + + +@app.get("/sync/model-no-response-model") +def sync_model_no_response_model(dep: Annotated[int, Depends(dep_b)]): + return ItemOut(name="foo", value=123, dep=dep) + + +@app.get("/sync/model-with-response-model", response_model=ItemOut) +def sync_model_with_response_model(dep: Annotated[int, Depends(dep_b)]): + return ItemOut(name="foo", value=123, dep=dep) + + +@app.post("/async/validated", response_model=ItemOut) +async def async_validated( + item: ItemIn, + dep: Annotated[int, Depends(dep_b)], +): + return ItemOut(name=item.name, value=item.value, dep=dep) + + +@app.post("/sync/large-receive") +def sync_large_receive(payload: LargeIn): + return {"received": len(payload.items)} + + +@app.post("/async/large-receive") +async def async_large_receive(payload: LargeIn): + return {"received": len(payload.items)} + + +@app.get("/sync/large-dict-no-response-model") +def sync_large_dict_no_response_model(): + return LARGE_PAYLOAD + + +@app.get("/sync/large-dict-with-response-model", response_model=LargeOut) +def sync_large_dict_with_response_model(): + return LARGE_PAYLOAD + + +@app.get("/sync/large-model-no-response-model") +def sync_large_model_no_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + + +@app.get("/sync/large-model-with-response-model", response_model=LargeOut) +def sync_large_model_with_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + + +@app.get("/async/large-dict-no-response-model") +async def async_large_dict_no_response_model(): + return LARGE_PAYLOAD + + +@app.get("/async/large-dict-with-response-model", response_model=LargeOut) +async def async_large_dict_with_response_model(): + return LARGE_PAYLOAD + + +@app.get("/async/large-model-no-response-model") +async def async_large_model_no_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + + +@app.get("/async/large-model-with-response-model", response_model=LargeOut) +async def async_large_model_with_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + + +@app.get("/async/dict-no-response-model") +async def async_dict_no_response_model(): + return {"name": "foo", "value": 123} + + +@app.get("/async/dict-with-response-model", response_model=ItemOut) +async def async_dict_with_response_model( + dep: Annotated[int, Depends(dep_b)], +): + return {"name": "foo", "value": 123, "dep": dep} + + +@app.get("/async/model-no-response-model") +async def async_model_no_response_model( + dep: Annotated[int, Depends(dep_b)], +): + return ItemOut(name="foo", value=123, dep=dep) + + +@app.get("/async/model-with-response-model", response_model=ItemOut) +async def async_model_with_response_model( + dep: Annotated[int, Depends(dep_b)], +): + return ItemOut(name="foo", value=123, dep=dep) @pytest.fixture(scope="module") -def app(basemodel_class: type[Any]) -> FastAPI: - class ItemIn(basemodel_class): - name: str - value: int - - class ItemOut(basemodel_class): - name: str - value: int - dep: int - - class LargeIn(basemodel_class): - items: list[dict[str, Any]] - metadata: dict[str, Any] - - class LargeOut(basemodel_class): - items: list[dict[str, Any]] - metadata: dict[str, Any] - - app = FastAPI() - - with warnings.catch_warnings(record=True): - warnings.filterwarnings( - "ignore", - message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=FastAPIDeprecationWarning, - ) - - @app.post("/sync/validated", response_model=ItemOut) - def sync_validated(item: ItemIn, dep: Annotated[int, Depends(dep_b)]): - return ItemOut(name=item.name, value=item.value, dep=dep) - - @app.get("/sync/dict-no-response-model") - def sync_dict_no_response_model(): - return {"name": "foo", "value": 123} - - @app.get("/sync/dict-with-response-model", response_model=ItemOut) - def sync_dict_with_response_model( - dep: Annotated[int, Depends(dep_b)], - ): - return {"name": "foo", "value": 123, "dep": dep} - - @app.get("/sync/model-no-response-model") - def sync_model_no_response_model(dep: Annotated[int, Depends(dep_b)]): - return ItemOut(name="foo", value=123, dep=dep) - - @app.get("/sync/model-with-response-model", response_model=ItemOut) - def sync_model_with_response_model(dep: Annotated[int, Depends(dep_b)]): - return ItemOut(name="foo", value=123, dep=dep) - - @app.post("/async/validated", response_model=ItemOut) - async def async_validated( - item: ItemIn, - dep: Annotated[int, Depends(dep_b)], - ): - return ItemOut(name=item.name, value=item.value, dep=dep) - - @app.post("/sync/large-receive") - def sync_large_receive(payload: LargeIn): - return {"received": len(payload.items)} - - @app.post("/async/large-receive") - async def async_large_receive(payload: LargeIn): - return {"received": len(payload.items)} - - @app.get("/sync/large-dict-no-response-model") - def sync_large_dict_no_response_model(): - return LARGE_PAYLOAD - - @app.get("/sync/large-dict-with-response-model", response_model=LargeOut) - def sync_large_dict_with_response_model(): - return LARGE_PAYLOAD - - @app.get("/sync/large-model-no-response-model") - def sync_large_model_no_response_model(): - return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) - - @app.get("/sync/large-model-with-response-model", response_model=LargeOut) - def sync_large_model_with_response_model(): - return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) - - @app.get("/async/large-dict-no-response-model") - async def async_large_dict_no_response_model(): - return LARGE_PAYLOAD - - @app.get("/async/large-dict-with-response-model", response_model=LargeOut) - async def async_large_dict_with_response_model(): - return LARGE_PAYLOAD - - @app.get("/async/large-model-no-response-model") - async def async_large_model_no_response_model(): - return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) - - @app.get("/async/large-model-with-response-model", response_model=LargeOut) - async def async_large_model_with_response_model(): - return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) - - @app.get("/async/dict-no-response-model") - async def async_dict_no_response_model(): - return {"name": "foo", "value": 123} - - @app.get("/async/dict-with-response-model", response_model=ItemOut) - async def async_dict_with_response_model( - dep: Annotated[int, Depends(dep_b)], - ): - return {"name": "foo", "value": 123, "dep": dep} - - @app.get("/async/model-no-response-model") - async def async_model_no_response_model( - dep: Annotated[int, Depends(dep_b)], - ): - return ItemOut(name="foo", value=123, dep=dep) - - @app.get("/async/model-with-response-model", response_model=ItemOut) - async def async_model_with_response_model( - dep: Annotated[int, Depends(dep_b)], - ): - return ItemOut(name="foo", value=123, dep=dep) - - return app - - -@pytest.fixture(scope="module") -def client(app: FastAPI) -> Iterator[TestClient]: +def client() -> Iterator[TestClient]: with TestClient(app) as client: yield client From 1b3bea8b6be2220cc1544653ea785d2134fcb35b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 26 Dec 2025 20:40:51 +0000 Subject: [PATCH 019/110] =?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 3e9084d475..10b262b489 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* โœ… Run performance tests only on Pydantic v2. PR [#14608](https://github.com/fastapi/fastapi/pull/14608) by [@tiangolo](https://github.com/tiangolo). + ## 0.127.1 ### Refactors From e3006305518a56ea35f62a31748ad26fe4356fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 27 Dec 2025 04:54:56 -0800 Subject: [PATCH 020/110] =?UTF-8?q?=E2=9E=96=20Drop=20support=20for=20`pyd?= =?UTF-8?q?antic.v1`=20(#14609)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tutorial002_pv1_an_py39.py | 20 - .../tutorial002_pv1_py39.py | 18 - fastapi/_compat/__init__.py | 64 +- fastapi/_compat/main.py | 264 ---- fastapi/_compat/may_v1.py | 124 -- fastapi/_compat/model_field.py | 50 - fastapi/_compat/shared.py | 24 +- fastapi/_compat/v1.py | 222 --- fastapi/_compat/v2.py | 59 +- fastapi/datastructures.py | 15 +- fastapi/dependencies/utils.py | 116 +- fastapi/encoders.py | 37 +- fastapi/exceptions.py | 6 + fastapi/openapi/models.py | 24 +- fastapi/openapi/utils.py | 13 +- fastapi/param_functions.py | 29 +- fastapi/params.py | 33 +- fastapi/routing.py | 104 +- fastapi/temp_pydantic_v1_params.py | 718 --------- fastapi/utils.py | 109 +- pyproject.toml | 22 +- tests/test_compat.py | 67 +- tests/test_compat_params_v1.py | 1060 ------------- tests/test_datetime_custom_encoder.py | 34 - .../__init__.py | 0 .../test_filter_pydantic_sub_model/app_pv1.py | 45 - .../test_filter_pydantic_sub_model_pv1.py | 146 -- ...t_get_model_definitions_formfeed_escape.py | 47 +- tests/test_inherited_custom_class.py | 45 - tests/test_jsonable_encoder.py | 63 +- .../test_pydantic_v1_deprecation_warnings.py | 99 -- tests/test_pydantic_v1_error.py | 97 ++ tests/test_pydantic_v1_v2_01.py | 439 ----- tests/test_pydantic_v1_v2_list.py | 682 -------- tests/test_pydantic_v1_v2_mixed.py | 1408 ----------------- .../test_pydantic_v1_v2_multifile/__init__.py | 0 tests/test_pydantic_v1_v2_multifile/main.py | 137 -- .../test_pydantic_v1_v2_multifile/modelsv1.py | 19 - .../test_pydantic_v1_v2_multifile/modelsv2.py | 19 - .../modelsv2b.py | 19 - .../test_multifile.py | 951 ----------- tests/test_pydantic_v1_v2_noneable.py | 692 -------- tests/test_read_with_orm_mode.py | 50 - ...est_response_model_as_return_annotation.py | 26 - .../test_tutorial007_pv1.py | 115 -- .../test_pydantic_v1_in_v2/__init__.py | 0 .../test_tutorial001.py | 31 - .../test_tutorial002.py | 143 -- .../test_tutorial003.py | 158 -- .../test_tutorial004.py | 156 -- .../test_tutorial002_pv1.py | 128 -- .../test_tutorial001_pv1.py | 152 -- .../test_tutorial/test_settings/test_app03.py | 12 - .../test_settings/test_tutorial001.py | 10 +- tests/utils.py | 2 - 55 files changed, 338 insertions(+), 8785 deletions(-) delete mode 100644 docs_src/request_form_models/tutorial002_pv1_an_py39.py delete mode 100644 docs_src/request_form_models/tutorial002_pv1_py39.py delete mode 100644 fastapi/_compat/main.py delete mode 100644 fastapi/_compat/may_v1.py delete mode 100644 fastapi/_compat/model_field.py delete mode 100644 fastapi/_compat/v1.py delete mode 100644 fastapi/temp_pydantic_v1_params.py delete mode 100644 tests/test_compat_params_v1.py delete mode 100644 tests/test_filter_pydantic_sub_model/__init__.py delete mode 100644 tests/test_filter_pydantic_sub_model/app_pv1.py delete mode 100644 tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py delete mode 100644 tests/test_pydantic_v1_deprecation_warnings.py create mode 100644 tests/test_pydantic_v1_error.py delete mode 100644 tests/test_pydantic_v1_v2_01.py delete mode 100644 tests/test_pydantic_v1_v2_list.py delete mode 100644 tests/test_pydantic_v1_v2_mixed.py delete mode 100644 tests/test_pydantic_v1_v2_multifile/__init__.py delete mode 100644 tests/test_pydantic_v1_v2_multifile/main.py delete mode 100644 tests/test_pydantic_v1_v2_multifile/modelsv1.py delete mode 100644 tests/test_pydantic_v1_v2_multifile/modelsv2.py delete mode 100644 tests/test_pydantic_v1_v2_multifile/modelsv2b.py delete mode 100644 tests/test_pydantic_v1_v2_multifile/test_multifile.py delete mode 100644 tests/test_pydantic_v1_v2_noneable.py delete mode 100644 tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py delete mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py delete mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py delete mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py delete mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py delete mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py delete mode 100644 tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py delete mode 100644 tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py diff --git a/docs_src/request_form_models/tutorial002_pv1_an_py39.py b/docs_src/request_form_models/tutorial002_pv1_an_py39.py deleted file mode 100644 index 392e6873cb..0000000000 --- a/docs_src/request_form_models/tutorial002_pv1_an_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI -from fastapi.temp_pydantic_v1_params import Form -from pydantic.v1 import BaseModel - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - - class Config: - extra = "forbid" - - -@app.post("/login/") -async def login(data: Annotated[FormData, Form()]): - return data diff --git a/docs_src/request_form_models/tutorial002_pv1_py39.py b/docs_src/request_form_models/tutorial002_pv1_py39.py deleted file mode 100644 index da160b3a54..0000000000 --- a/docs_src/request_form_models/tutorial002_pv1_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import FastAPI -from fastapi.temp_pydantic_v1_params import Form -from pydantic.v1 import BaseModel - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - - class Config: - extra = "forbid" - - -@app.post("/login/") -async def login(data: FormData = Form()): - return data diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index fd1df8c6a7..3dfaf9b712 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -1,43 +1,9 @@ -from .main import BaseConfig as BaseConfig -from .main import PydanticSchemaGenerationError as PydanticSchemaGenerationError -from .main import RequiredParam as RequiredParam -from .main import Undefined as Undefined -from .main import UndefinedType as UndefinedType -from .main import Url as Url -from .main import Validator as Validator -from .main import _get_model_config as _get_model_config -from .main import _is_error_wrapper as _is_error_wrapper -from .main import _is_model_class as _is_model_class -from .main import _is_model_field as _is_model_field -from .main import _is_undefined as _is_undefined -from .main import _model_dump as _model_dump -from .main import copy_field_info as copy_field_info -from .main import create_body_model as create_body_model -from .main import evaluate_forwardref as evaluate_forwardref -from .main import get_annotation_from_field_info as get_annotation_from_field_info -from .main import get_cached_model_fields as get_cached_model_fields -from .main import get_compat_model_name_map as get_compat_model_name_map -from .main import get_definitions as get_definitions -from .main import get_missing_field_error as get_missing_field_error -from .main import get_schema_from_model_field as get_schema_from_model_field -from .main import is_bytes_field as is_bytes_field -from .main import is_bytes_sequence_field as is_bytes_sequence_field -from .main import is_scalar_field as is_scalar_field -from .main import is_scalar_sequence_field as is_scalar_sequence_field -from .main import is_sequence_field as is_sequence_field -from .main import serialize_sequence_value as serialize_sequence_value -from .main import ( - with_info_plain_validator_function as with_info_plain_validator_function, -) -from .may_v1 import CoreSchema as CoreSchema -from .may_v1 import GetJsonSchemaHandler as GetJsonSchemaHandler -from .may_v1 import JsonSchemaValue as JsonSchemaValue -from .may_v1 import _normalize_errors as _normalize_errors -from .model_field import ModelField as ModelField 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 +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, ) @@ -47,3 +13,29 @@ 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_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 +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/main.py b/fastapi/_compat/main.py deleted file mode 100644 index 95053a2374..0000000000 --- a/fastapi/_compat/main.py +++ /dev/null @@ -1,264 +0,0 @@ -import sys -from collections.abc import Sequence -from functools import lru_cache -from typing import ( - Any, -) - -from fastapi._compat import may_v1 -from fastapi._compat.shared import lenient_issubclass -from fastapi.types import ModelNameMap -from pydantic import BaseModel -from typing_extensions import Literal - -from . import v2 -from .model_field import ModelField -from .v2 import BaseConfig as BaseConfig -from .v2 import FieldInfo as FieldInfo -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 evaluate_forwardref as evaluate_forwardref -from .v2 import get_missing_field_error as get_missing_field_error -from .v2 import ( - with_info_plain_validator_function as with_info_plain_validator_function, -) - - -@lru_cache -def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]: - if lenient_issubclass(model, may_v1.BaseModel): - from fastapi._compat import v1 - - return v1.get_model_fields(model) # type: ignore[arg-type,return-value] - else: - from . import v2 - - return v2.get_model_fields(model) # type: ignore[return-value] - - -def _is_undefined(value: object) -> bool: - if isinstance(value, may_v1.UndefinedType): - return True - - return isinstance(value, v2.UndefinedType) - - -def _get_model_config(model: BaseModel) -> Any: - if isinstance(model, may_v1.BaseModel): - from fastapi._compat import v1 - - return v1._get_model_config(model) - - return v2._get_model_config(model) - - -def _model_dump( - model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any -) -> Any: - if isinstance(model, may_v1.BaseModel): - from fastapi._compat import v1 - - return v1._model_dump(model, mode=mode, **kwargs) - - return v2._model_dump(model, mode=mode, **kwargs) - - -def _is_error_wrapper(exc: Exception) -> bool: - if isinstance(exc, may_v1.ErrorWrapper): - return True - - return isinstance(exc, v2.ErrorWrapper) - - -def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: - if isinstance(field_info, may_v1.FieldInfo): - from fastapi._compat import v1 - - return v1.copy_field_info(field_info=field_info, annotation=annotation) - - return v2.copy_field_info(field_info=field_info, annotation=annotation) - - -def create_body_model( - *, fields: Sequence[ModelField], model_name: str -) -> type[BaseModel]: - if fields and isinstance(fields[0], may_v1.ModelField): - from fastapi._compat import v1 - - return v1.create_body_model(fields=fields, model_name=model_name) - - return v2.create_body_model(fields=fields, model_name=model_name) # type: ignore[arg-type] - - -def get_annotation_from_field_info( - annotation: Any, field_info: FieldInfo, field_name: str -) -> Any: - if isinstance(field_info, may_v1.FieldInfo): - from fastapi._compat import v1 - - return v1.get_annotation_from_field_info( - annotation=annotation, field_info=field_info, field_name=field_name - ) - - return v2.get_annotation_from_field_info( - annotation=annotation, field_info=field_info, field_name=field_name - ) - - -def is_bytes_field(field: ModelField) -> bool: - if isinstance(field, may_v1.ModelField): - from fastapi._compat import v1 - - return v1.is_bytes_field(field) - - return v2.is_bytes_field(field) # type: ignore[arg-type] - - -def is_bytes_sequence_field(field: ModelField) -> bool: - if isinstance(field, may_v1.ModelField): - from fastapi._compat import v1 - - return v1.is_bytes_sequence_field(field) - - return v2.is_bytes_sequence_field(field) # type: ignore[arg-type] - - -def is_scalar_field(field: ModelField) -> bool: - if isinstance(field, may_v1.ModelField): - from fastapi._compat import v1 - - return v1.is_scalar_field(field) - - return v2.is_scalar_field(field) # type: ignore[arg-type] - - -def is_scalar_sequence_field(field: ModelField) -> bool: - return v2.is_scalar_sequence_field(field) # type: ignore[arg-type] - - -def is_sequence_field(field: ModelField) -> bool: - if isinstance(field, may_v1.ModelField): - from fastapi._compat import v1 - - return v1.is_sequence_field(field) - - return v2.is_sequence_field(field) # type: ignore[arg-type] - - -def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: - if isinstance(field, may_v1.ModelField): - from fastapi._compat import v1 - - return v1.serialize_sequence_value(field=field, value=value) - - return v2.serialize_sequence_value(field=field, value=value) # type: ignore[arg-type] - - -def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap: - v1_model_fields = [ - field for field in fields if isinstance(field, may_v1.ModelField) - ] - if v1_model_fields: - from fastapi._compat import v1 - - v1_flat_models = v1.get_flat_models_from_fields( - v1_model_fields, # type: ignore[arg-type] - known_models=set(), - ) - all_flat_models = v1_flat_models - else: - all_flat_models = set() - - v2_model_fields = [field for field in fields if isinstance(field, v2.ModelField)] - v2_flat_models = v2.get_flat_models_from_fields(v2_model_fields, known_models=set()) - all_flat_models = all_flat_models.union(v2_flat_models) # type: ignore[arg-type] - - model_name_map = v2.get_model_name_map(all_flat_models) # type: ignore[arg-type] - return model_name_map - - -def get_definitions( - *, - fields: list[ModelField], - model_name_map: ModelNameMap, - separate_input_output_schemas: bool = True, -) -> tuple[ - dict[ - tuple[ModelField, Literal["validation", "serialization"]], - may_v1.JsonSchemaValue, - ], - dict[str, dict[str, Any]], -]: - if sys.version_info < (3, 14): - v1_fields = [field for field in fields if isinstance(field, may_v1.ModelField)] - v1_field_maps, v1_definitions = may_v1.get_definitions( - fields=v1_fields, # type: ignore[arg-type] - model_name_map=model_name_map, - separate_input_output_schemas=separate_input_output_schemas, - ) - - v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] - v2_field_maps, v2_definitions = v2.get_definitions( - fields=v2_fields, - model_name_map=model_name_map, - separate_input_output_schemas=separate_input_output_schemas, - ) - all_definitions = {**v1_definitions, **v2_definitions} - all_field_maps = {**v1_field_maps, **v2_field_maps} # type: ignore[misc] - return all_field_maps, all_definitions - - # Pydantic v1 is not supported since Python 3.14 - else: - v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] - v2_field_maps, v2_definitions = v2.get_definitions( - fields=v2_fields, - model_name_map=model_name_map, - separate_input_output_schemas=separate_input_output_schemas, - ) - return v2_field_maps, v2_definitions - - -def get_schema_from_model_field( - *, - field: ModelField, - model_name_map: ModelNameMap, - field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], - may_v1.JsonSchemaValue, - ], - separate_input_output_schemas: bool = True, -) -> dict[str, Any]: - if isinstance(field, may_v1.ModelField): - from fastapi._compat import v1 - - return v1.get_schema_from_model_field( - field=field, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - - return v2.get_schema_from_model_field( - field=field, # type: ignore[arg-type] - model_name_map=model_name_map, - field_mapping=field_mapping, # type: ignore[arg-type] - separate_input_output_schemas=separate_input_output_schemas, - ) - - -def _is_model_field(value: Any) -> bool: - if isinstance(value, may_v1.ModelField): - return True - - return isinstance(value, v2.ModelField) - - -def _is_model_class(value: Any) -> bool: - if lenient_issubclass(value, may_v1.BaseModel): - return True - - return lenient_issubclass(value, v2.BaseModel) # type: ignore[attr-defined] diff --git a/fastapi/_compat/may_v1.py b/fastapi/_compat/may_v1.py deleted file mode 100644 index 3ac86aa98b..0000000000 --- a/fastapi/_compat/may_v1.py +++ /dev/null @@ -1,124 +0,0 @@ -import sys -from collections.abc import Sequence -from typing import Any, Literal, Union - -from fastapi.types import ModelNameMap - -if sys.version_info >= (3, 14): - - class AnyUrl: - pass - - class BaseConfig: - pass - - class BaseModel: - pass - - class Color: - pass - - class CoreSchema: - pass - - class ErrorWrapper: - pass - - class FieldInfo: - pass - - class GetJsonSchemaHandler: - pass - - class JsonSchemaValue: - pass - - class ModelField: - pass - - class NameEmail: - pass - - class RequiredParam: - pass - - class SecretBytes: - pass - - class SecretStr: - pass - - class Undefined: - pass - - class UndefinedType: - pass - - class Url: - pass - - from .v2 import ValidationError, create_model - - def get_definitions( - *, - fields: list[ModelField], - model_name_map: ModelNameMap, - separate_input_output_schemas: bool = True, - ) -> tuple[ - dict[ - tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue - ], - dict[str, dict[str, Any]], - ]: - return {}, {} # pragma: no cover - - -else: - from .v1 import AnyUrl as AnyUrl - from .v1 import BaseConfig as BaseConfig - from .v1 import BaseModel as BaseModel - from .v1 import Color as Color - from .v1 import CoreSchema as CoreSchema - from .v1 import ErrorWrapper as ErrorWrapper - from .v1 import FieldInfo as FieldInfo - from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler - from .v1 import JsonSchemaValue as JsonSchemaValue - from .v1 import ModelField as ModelField - from .v1 import NameEmail as NameEmail - from .v1 import RequiredParam as RequiredParam - from .v1 import SecretBytes as SecretBytes - from .v1 import SecretStr as SecretStr - from .v1 import Undefined as Undefined - from .v1 import UndefinedType as UndefinedType - from .v1 import Url as Url - from .v1 import ValidationError, create_model - from .v1 import get_definitions as get_definitions - - -RequestErrorModel: type[BaseModel] = create_model("Request") - - -def _normalize_errors(errors: Sequence[Any]) -> list[dict[str, Any]]: - use_errors: list[Any] = [] - for error in errors: - if isinstance(error, ErrorWrapper): - new_errors = ValidationError( - errors=[error], model=RequestErrorModel - ).errors() - use_errors.extend(new_errors) - elif isinstance(error, list): - use_errors.extend(_normalize_errors(error)) - else: - use_errors.append(error) - return use_errors - - -def _regenerate_error_with_loc( - *, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...] -) -> list[dict[str, Any]]: - updated_loc_errors: list[Any] = [ - {**err, "loc": loc_prefix + err.get("loc", ())} - for err in _normalize_errors(errors) - ] - - return updated_loc_errors diff --git a/fastapi/_compat/model_field.py b/fastapi/_compat/model_field.py deleted file mode 100644 index 47d05cb946..0000000000 --- a/fastapi/_compat/model_field.py +++ /dev/null @@ -1,50 +0,0 @@ -from typing import ( - Any, - Union, -) - -from fastapi.types import IncEx -from pydantic.fields import FieldInfo -from typing_extensions import Literal, Protocol - - -class ModelField(Protocol): - field_info: "FieldInfo" - name: str - mode: Literal["validation", "serialization"] = "validation" - _version: Literal["v1", "v2"] = "v1" - - @property - def alias(self) -> str: ... - - @property - def required(self) -> bool: ... - - @property - def default(self) -> Any: ... - - @property - def type_(self) -> Any: ... - - def get_default(self) -> Any: ... - - def validate( - self, - value: Any, - values: dict[str, Any] = {}, # noqa: B006 - *, - loc: tuple[Union[int, str], ...] = (), - ) -> tuple[Any, Union[list[dict[str, Any]], None]]: ... - - def serialize( - self, - value: Any, - *, - mode: Literal["json", "python"] = "json", - include: Union[IncEx, None] = None, - exclude: Union[IncEx, None] = None, - by_alias: bool = True, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - ) -> Any: ... diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index 3a11e88ac9..419b58f7f2 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -1,6 +1,7 @@ import sys import types import typing +import warnings from collections import deque from collections.abc import Mapping, Sequence from dataclasses import is_dataclass @@ -10,7 +11,6 @@ from typing import ( Union, ) -from fastapi._compat import may_v1 from fastapi.types import UnionType from pydantic import BaseModel from pydantic.version import VERSION as PYDANTIC_VERSION @@ -81,9 +81,7 @@ def value_is_sequence(value: Any) -> bool: def _annotation_is_complex(annotation: Union[type[Any], None]) -> bool: return ( - lenient_issubclass( - annotation, (BaseModel, may_v1.BaseModel, Mapping, UploadFile) - ) + lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile)) or _annotation_is_sequence(annotation) or is_dataclass(annotation) ) @@ -179,13 +177,27 @@ 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 + 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 + return lenient_issubclass(cls, v1.BaseModel) + + def annotation_is_pydantic_v1(annotation: Any) -> bool: - if lenient_issubclass(annotation, may_v1.BaseModel): + if is_pydantic_v1_model_class(annotation): return True origin = get_origin(annotation) if origin is Union or origin is UnionType: for arg in get_args(annotation): - if lenient_issubclass(arg, may_v1.BaseModel): + if is_pydantic_v1_model_class(arg): return True if field_annotation_is_sequence(annotation): for sub_annotation in get_args(annotation): diff --git a/fastapi/_compat/v1.py b/fastapi/_compat/v1.py deleted file mode 100644 index b0a9dd35f1..0000000000 --- a/fastapi/_compat/v1.py +++ /dev/null @@ -1,222 +0,0 @@ -from collections.abc import Sequence -from copy import copy -from dataclasses import dataclass, is_dataclass -from enum import Enum -from typing import ( - Any, - Callable, - Union, -) - -from fastapi._compat import shared -from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX -from fastapi.types import ModelNameMap -from pydantic.v1 import BaseConfig as BaseConfig -from pydantic.v1 import BaseModel as BaseModel -from pydantic.v1 import ValidationError as ValidationError -from pydantic.v1 import create_model as create_model -from pydantic.v1.class_validators import Validator as Validator -from pydantic.v1.color import Color as Color -from pydantic.v1.error_wrappers import ErrorWrapper as ErrorWrapper -from pydantic.v1.fields import ( - SHAPE_FROZENSET, - SHAPE_LIST, - SHAPE_SEQUENCE, - SHAPE_SET, - SHAPE_SINGLETON, - SHAPE_TUPLE, - SHAPE_TUPLE_ELLIPSIS, -) -from pydantic.v1.fields import FieldInfo as FieldInfo -from pydantic.v1.fields import ModelField as ModelField -from pydantic.v1.fields import Undefined as Undefined -from pydantic.v1.fields import UndefinedType as UndefinedType -from pydantic.v1.networks import AnyUrl as AnyUrl -from pydantic.v1.networks import NameEmail as NameEmail -from pydantic.v1.schema import TypeModelSet as TypeModelSet -from pydantic.v1.schema import field_schema, model_process_schema -from pydantic.v1.schema import ( - get_annotation_from_field_info as get_annotation_from_field_info, -) -from pydantic.v1.schema import ( - get_flat_models_from_field as get_flat_models_from_field, -) -from pydantic.v1.schema import ( - get_flat_models_from_fields as get_flat_models_from_fields, -) -from pydantic.v1.schema import get_model_name_map as get_model_name_map -from pydantic.v1.types import SecretBytes as SecretBytes -from pydantic.v1.types import SecretStr as SecretStr -from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref -from pydantic.v1.utils import lenient_issubclass as lenient_issubclass -from pydantic.version import VERSION as PYDANTIC_VERSION -from typing_extensions import Literal - -PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2]) -PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2 -# Keeping old "Required" functionality from Pydantic V1, without -# shadowing typing.Required. -RequiredParam: Any = Ellipsis - - -GetJsonSchemaHandler = Any -JsonSchemaValue = dict[str, Any] -CoreSchema = Any -Url = AnyUrl - -sequence_shapes = { - SHAPE_LIST, - SHAPE_SET, - SHAPE_FROZENSET, - SHAPE_TUPLE, - SHAPE_SEQUENCE, - SHAPE_TUPLE_ELLIPSIS, -} -sequence_shape_to_type = { - SHAPE_LIST: list, - SHAPE_SET: set, - SHAPE_TUPLE: tuple, - SHAPE_SEQUENCE: list, - SHAPE_TUPLE_ELLIPSIS: list, -} - - -@dataclass -class GenerateJsonSchema: - ref_template: str - - -class PydanticSchemaGenerationError(Exception): - pass - - -RequestErrorModel: type[BaseModel] = create_model("Request") - - -def with_info_plain_validator_function( - function: Callable[..., Any], - *, - ref: Union[str, None] = None, - metadata: Any = None, - serialization: Any = None, -) -> Any: - return {} - - -def get_model_definitions( - *, - flat_models: set[Union[type[BaseModel], type[Enum]]], - model_name_map: dict[Union[type[BaseModel], type[Enum]], str], -) -> dict[str, Any]: - definitions: dict[str, dict[str, Any]] = {} - for model in flat_models: - m_schema, m_definitions, m_nested_models = model_process_schema( - model, model_name_map=model_name_map, ref_prefix=REF_PREFIX - ) - definitions.update(m_definitions) - model_name = model_name_map[model] - definitions[model_name] = m_schema - for m_schema in definitions.values(): - if "description" in m_schema: - m_schema["description"] = m_schema["description"].split("\f")[0] - return definitions - - -def is_pv1_scalar_field(field: ModelField) -> bool: - from fastapi import params - - field_info = field.field_info - if not ( - field.shape == SHAPE_SINGLETON - and not lenient_issubclass(field.type_, BaseModel) - and not lenient_issubclass(field.type_, dict) - and not shared.field_annotation_is_sequence(field.type_) - and not is_dataclass(field.type_) - and not isinstance(field_info, params.Body) - ): - return False - if field.sub_fields: - if not all(is_pv1_scalar_field(f) for f in field.sub_fields): - return False - return True - - -def _model_dump( - model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any -) -> Any: - return model.dict(**kwargs) - - -def _get_model_config(model: BaseModel) -> Any: - return model.__config__ - - -def get_schema_from_model_field( - *, - field: ModelField, - model_name_map: ModelNameMap, - field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue - ], - separate_input_output_schemas: bool = True, -) -> dict[str, Any]: - return field_schema( - field, - model_name_map=model_name_map, # type: ignore[arg-type] - ref_prefix=REF_PREFIX, - )[0] - - -# def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: -# models = get_flat_models_from_fields(fields, known_models=set()) -# return get_model_name_map(models) # type: ignore[no-any-return] - - -def get_definitions( - *, - fields: list[ModelField], - model_name_map: ModelNameMap, - separate_input_output_schemas: bool = True, -) -> tuple[ - dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], - dict[str, dict[str, Any]], -]: - models = get_flat_models_from_fields(fields, known_models=set()) - return {}, get_model_definitions(flat_models=models, model_name_map=model_name_map) # type: ignore[arg-type] - - -def is_scalar_field(field: ModelField) -> bool: - return is_pv1_scalar_field(field) - - -def is_sequence_field(field: ModelField) -> bool: - return field.shape in sequence_shapes or shared._annotation_is_sequence(field.type_) - - -def is_bytes_field(field: ModelField) -> bool: - return lenient_issubclass(field.type_, bytes) - - -def is_bytes_sequence_field(field: ModelField) -> bool: - return field.shape in sequence_shapes and lenient_issubclass(field.type_, bytes) - - -def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: - return copy(field_info) - - -def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: - return sequence_shape_to_type[field.shape](value) # type: ignore[no-any-return] - - -def create_body_model( - *, fields: Sequence[ModelField], model_name: str -) -> type[BaseModel]: - BodyModel = create_model(model_name) - for f in fields: - BodyModel.__fields__[f.name] = f - return BodyModel - - -def get_model_fields(model: type[BaseModel]) -> list[ModelField]: - return list(model.__fields__.values()) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index cbcb98e1a2..25b6814536 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -4,6 +4,7 @@ from collections.abc import Sequence from copy import copy, deepcopy from dataclasses import dataclass, is_dataclass from enum import Enum +from functools import lru_cache from typing import ( Annotated, Any, @@ -11,7 +12,7 @@ from typing import ( cast, ) -from fastapi._compat import may_v1, shared +from fastapi._compat import shared from fastapi.openapi.constants import REF_TEMPLATE from fastapi.types import IncEx, ModelNameMap, UnionType from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model @@ -175,7 +176,7 @@ class ModelField: None, ) except ValidationError as exc: - return None, may_v1._regenerate_error_with_loc( + return None, _regenerate_error_with_loc( errors=exc.errors(include_url=False), loc_prefix=loc ) @@ -210,22 +211,6 @@ class ModelField: return id(self) -def get_annotation_from_field_info( - annotation: Any, field_info: FieldInfo, field_name: str -) -> Any: - return annotation - - -def _model_dump( - model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any -) -> Any: - return model.model_dump(mode=mode, **kwargs) - - -def _get_model_config(model: BaseModel) -> Any: - return model.model_config - - def _has_computed_fields(field: ModelField) -> bool: computed_fields = field._type_adapter.core_schema.get("schema", {}).get( "computed_fields", [] @@ -490,6 +475,11 @@ def get_model_fields(model: type[BaseModel]) -> list[ModelField]: return model_fields +@lru_cache +def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]: + return get_model_fields(model) # type: ignore[return-value] + + # Duplicate of several schema functions from Pydantic v1 to make them compatible with # Pydantic v2 and allow mixing the models @@ -503,22 +493,23 @@ def normalize_name(name: str) -> str: def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str]: name_model_map = {} - conflicting_names: set[str] = set() for model in unique_models: model_name = normalize_name(model.__name__) - if model_name in conflicting_names: - model_name = get_long_model_name(model) - name_model_map[model_name] = model - elif model_name in name_model_map: - conflicting_names.add(model_name) - conflicting_model = name_model_map.pop(model_name) - name_model_map[get_long_model_name(conflicting_model)] = conflicting_model - name_model_map[get_long_model_name(model)] = model - else: - name_model_map[model_name] = model + name_model_map[model_name] = model return {v: k for k, v in name_model_map.items()} +def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap: + all_flat_models = set() + + v2_model_fields = [field for field in fields if isinstance(field, ModelField)] + v2_flat_models = get_flat_models_from_fields(v2_model_fields, known_models=set()) + all_flat_models = all_flat_models.union(v2_flat_models) # type: ignore[arg-type] + + model_name_map = get_model_name_map(all_flat_models) # type: ignore[arg-type] + return model_name_map + + def get_flat_models_from_model( model: type["BaseModel"], known_models: Union[TypeModelSet, None] = None ) -> TypeModelSet: @@ -567,5 +558,11 @@ def get_flat_models_from_fields( return known_models -def get_long_model_name(model: TypeModelOrEnum) -> str: - return f"{model.__module__}__{model.__qualname__}".replace(".", "__") +def _regenerate_error_with_loc( + *, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...] +) -> list[dict[str, Any]]: + updated_loc_errors: list[Any] = [ + {**err, "loc": loc_prefix + err.get("loc", ())} for err in errors + ] + + return updated_loc_errors diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index 492cbfcccb..2bf5fdb262 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -1,3 +1,4 @@ +from collections.abc import Mapping from typing import ( Annotated, Any, @@ -9,11 +10,7 @@ from typing import ( ) from annotated_doc import Doc -from fastapi._compat import ( - CoreSchema, - GetJsonSchemaHandler, - JsonSchemaValue, -) +from pydantic import GetJsonSchemaHandler from starlette.datastructures import URL as URL # noqa: F401 from starlette.datastructures import Address as Address # noqa: F401 from starlette.datastructures import FormData as FormData # noqa: F401 @@ -142,14 +139,14 @@ class UploadFile(StarletteUploadFile): @classmethod def __get_pydantic_json_schema__( - cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler - ) -> JsonSchemaValue: + cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler + ) -> dict[str, Any]: return {"type": "string", "format": "binary"} @classmethod def __get_pydantic_core_schema__( - cls, source: type[Any], handler: Callable[[Any], CoreSchema] - ) -> CoreSchema: + cls, source: type[Any], handler: Callable[[Any], Mapping[str, Any]] + ) -> Mapping[str, Any]: from ._compat.v2 import with_info_plain_validator_function return with_info_plain_validator_function(cls._validate) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index af2bed9ad9..45e1ff3ed1 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,7 +1,6 @@ import dataclasses import inspect import sys -import warnings from collections.abc import Coroutine, Mapping, Sequence from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy @@ -22,13 +21,11 @@ from fastapi._compat import ( ModelField, RequiredParam, Undefined, - _is_error_wrapper, - _is_model_class, + _regenerate_error_with_loc, copy_field_info, create_body_model, evaluate_forwardref, field_annotation_is_scalar, - get_annotation_from_field_info, get_cached_model_fields, get_missing_field_error, is_bytes_field, @@ -39,19 +36,17 @@ from fastapi._compat import ( is_uploadfile_or_nonable_uploadfile_annotation, is_uploadfile_sequence_annotation, lenient_issubclass, - may_v1, sequence_types, serialize_sequence_value, value_is_sequence, ) -from fastapi._compat.shared import annotation_is_pydantic_v1 from fastapi.background import BackgroundTasks from fastapi.concurrency import ( asynccontextmanager, contextmanager_in_threadpool, ) from fastapi.dependencies.models import Dependant -from fastapi.exceptions import DependencyScopeError, FastAPIDeprecationWarning +from fastapi.exceptions import DependencyScopeError from fastapi.logger import logger from fastapi.security.oauth2 import SecurityScopes from fastapi.types import DependencyCacheKey @@ -72,8 +67,6 @@ from starlette.responses import Response from starlette.websockets import WebSocket from typing_extensions import Literal, get_args, get_origin -from .. import temp_pydantic_v1_params - multipart_not_installed_error = ( 'Form data requires "python-multipart" to be installed. \n' 'You can install "python-multipart" with: \n\n' @@ -189,7 +182,7 @@ 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 _is_model_class(first_field.type_): + if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): fields_to_extract = get_cached_model_fields(first_field.type_) return fields_to_extract return fields @@ -323,16 +316,7 @@ def get_dependant( ) continue assert param_details.field is not None - if isinstance(param_details.field, may_v1.ModelField): - warnings.warn( - "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." - f" Please update the param {param_name}: {param_details.type_annotation!r}.", - category=FastAPIDeprecationWarning, - stacklevel=5, - ) - if isinstance( - param_details.field.field_info, (params.Body, temp_pydantic_v1_params.Body) - ): + if isinstance(param_details.field.field_info, params.Body): dependant.body_params.append(param_details.field) else: add_param_to_fields(field=param_details.field, dependant=dependant) @@ -391,7 +375,7 @@ def analyze_param( fastapi_annotations = [ arg for arg in annotated_args[1:] - if isinstance(arg, (FieldInfo, may_v1.FieldInfo, params.Depends)) + if isinstance(arg, (FieldInfo, params.Depends)) ] fastapi_specific_annotations = [ arg @@ -400,30 +384,27 @@ def analyze_param( arg, ( params.Param, - temp_pydantic_v1_params.Param, params.Body, - temp_pydantic_v1_params.Body, params.Depends, ), ) ] if fastapi_specific_annotations: - fastapi_annotation: Union[ - FieldInfo, may_v1.FieldInfo, params.Depends, None - ] = fastapi_specific_annotations[-1] + fastapi_annotation: Union[FieldInfo, params.Depends, None] = ( + fastapi_specific_annotations[-1] + ) else: fastapi_annotation = None # Set default for Annotated FieldInfo - if isinstance(fastapi_annotation, (FieldInfo, may_v1.FieldInfo)): + if isinstance(fastapi_annotation, FieldInfo): # Copy `field_info` because we mutate `field_info.default` below. field_info = copy_field_info( field_info=fastapi_annotation, # type: ignore[arg-type] annotation=use_annotation, ) - assert field_info.default in { - Undefined, - may_v1.Undefined, - } or field_info.default in {RequiredParam, may_v1.RequiredParam}, ( + assert ( + field_info.default == Undefined or field_info.default == RequiredParam + ), ( f"`{field_info.__class__.__name__}` default value cannot be set in" f" `Annotated` for {param_name!r}. Set the default value with `=` instead." ) @@ -447,7 +428,7 @@ def analyze_param( ) depends = value # Get FieldInfo from default value - elif isinstance(value, (FieldInfo, may_v1.FieldInfo)): + elif isinstance(value, FieldInfo): assert field_info is None, ( "Cannot specify FastAPI annotations in `Annotated` and default value" f" together for {param_name!r}" @@ -491,14 +472,7 @@ def analyze_param( ) or is_uploadfile_sequence_annotation(type_annotation): field_info = params.File(annotation=use_annotation, default=default_value) elif not field_annotation_is_scalar(annotation=type_annotation): - if annotation_is_pydantic_v1(use_annotation): - field_info = temp_pydantic_v1_params.Body( # type: ignore[assignment] - annotation=use_annotation, default=default_value - ) - else: - field_info = params.Body( - annotation=use_annotation, default=default_value - ) + field_info = params.Body(annotation=use_annotation, default=default_value) else: field_info = params.Query(annotation=use_annotation, default=default_value) @@ -507,23 +481,17 @@ def analyze_param( if field_info is not None: # Handle field_info.in_ if is_path_param: - assert isinstance( - field_info, (params.Path, temp_pydantic_v1_params.Path) - ), ( + assert isinstance(field_info, params.Path), ( f"Cannot use `{field_info.__class__.__name__}` for path param" f" {param_name!r}" ) elif ( - isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)) + isinstance(field_info, params.Param) and getattr(field_info, "in_", None) is None ): field_info.in_ = params.ParamTypes.query - use_annotation_from_field_info = get_annotation_from_field_info( - use_annotation, - field_info, - param_name, - ) - if isinstance(field_info, (params.Form, temp_pydantic_v1_params.Form)): + use_annotation_from_field_info = use_annotation + if isinstance(field_info, params.Form): ensure_multipart_is_installed() if not field_info.alias and getattr(field_info, "convert_underscores", None): alias = param_name.replace("_", "-") @@ -535,20 +503,19 @@ def analyze_param( type_=use_annotation_from_field_info, default=field_info.default, alias=alias, - required=field_info.default - in (RequiredParam, may_v1.RequiredParam, Undefined), + required=field_info.default in (RequiredParam, Undefined), field_info=field_info, ) if is_path_param: assert is_scalar_field(field=field), ( "Path params must be of one of the supported types" ) - elif isinstance(field_info, (params.Query, temp_pydantic_v1_params.Query)): + elif isinstance(field_info, params.Query): assert ( is_scalar_field(field) or is_scalar_sequence_field(field) or ( - _is_model_class(field.type_) + lenient_issubclass(field.type_, BaseModel) # For Pydantic v1 and getattr(field, "shape", 1) == 1 ) @@ -742,10 +709,8 @@ def _validate_value_with_model_field( else: return deepcopy(field.default), [] v_, errors_ = field.validate(value, values, loc=loc) - if _is_error_wrapper(errors_): # type: ignore[arg-type] - return None, [errors_] - elif isinstance(errors_, list): - new_errors = may_v1._regenerate_error_with_loc(errors=errors_, loc_prefix=()) + if isinstance(errors_, list): + new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=()) return None, new_errors else: return v_, [] @@ -762,7 +727,7 @@ def _get_multidict_value( if ( value is None or ( - isinstance(field.field_info, (params.Form, temp_pydantic_v1_params.Form)) + isinstance(field.field_info, params.Form) and isinstance(value, str) # For type checks and value == "" ) @@ -832,7 +797,7 @@ def request_params_to_args( if single_not_embedded_field: field_info = first_field.field_info - assert isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)), ( + assert isinstance(field_info, params.Param), ( "Params must be subclasses of Param" ) loc: tuple[str, ...] = (field_info.in_.value,) @@ -844,7 +809,7 @@ def request_params_to_args( for field in fields: value = _get_multidict_value(field, received_params) field_info = field.field_info - assert isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)), ( + assert isinstance(field_info, params.Param), ( "Params must be subclasses of Param" ) loc = (field_info.in_.value, get_validation_alias(field)) @@ -871,7 +836,7 @@ def is_union_of_base_models(field_type: Any) -> bool: union_args = get_args(field_type) for arg in union_args: - if not _is_model_class(arg): + if not lenient_issubclass(arg, BaseModel): return False return True @@ -893,8 +858,8 @@ def _should_embed_body_fields(fields: list[ModelField]) -> bool: # If it's a Form (or File) field, it has to be a BaseModel (or a union of BaseModels) to be top level # otherwise it has to be embedded, so that the key value pair can be extracted if ( - isinstance(first_field.field_info, (params.Form, temp_pydantic_v1_params.Form)) - and not _is_model_class(first_field.type_) + 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_) ): return True @@ -911,14 +876,14 @@ async def _extract_form_body( value = _get_multidict_value(field, received_body) field_info = field.field_info if ( - isinstance(field_info, (params.File, temp_pydantic_v1_params.File)) + isinstance(field_info, params.File) and is_bytes_field(field) and isinstance(value, UploadFile) ): value = await value.read() elif ( is_bytes_sequence_field(field) - and isinstance(field_info, (params.File, temp_pydantic_v1_params.File)) + and isinstance(field_info, params.File) and value_is_sequence(value) ): # For types @@ -964,7 +929,7 @@ async def request_body_to_args( if ( single_not_embedded_field - and _is_model_class(first_field.type_) + and lenient_issubclass(first_field.type_, BaseModel) and isinstance(received_body, FormData) ): fields_to_extract = get_cached_model_fields(first_field.type_) @@ -1029,28 +994,15 @@ def get_body_field( BodyFieldInfo_kwargs["default"] = None if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params): BodyFieldInfo: type[params.Body] = params.File - elif any( - isinstance(f.field_info, temp_pydantic_v1_params.File) - for f in flat_dependant.body_params - ): - BodyFieldInfo: type[temp_pydantic_v1_params.Body] = temp_pydantic_v1_params.File # type: ignore[no-redef] elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params): BodyFieldInfo = params.Form - elif any( - isinstance(f.field_info, temp_pydantic_v1_params.Form) - for f in flat_dependant.body_params - ): - BodyFieldInfo = temp_pydantic_v1_params.Form # type: ignore[assignment] else: - if annotation_is_pydantic_v1(BodyModel): - BodyFieldInfo = temp_pydantic_v1_params.Body # type: ignore[assignment] - else: - BodyFieldInfo = params.Body + BodyFieldInfo = params.Body body_param_media_types = [ f.field_info.media_type for f in flat_dependant.body_params - if isinstance(f.field_info, (params.Body, temp_pydantic_v1_params.Body)) + if isinstance(f.field_info, params.Body) ] if len(set(body_param_media_types)) == 1: BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0] diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 549da32797..e8610c983b 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -18,14 +18,18 @@ from typing import Annotated, Any, Callable, Optional, Union from uuid import UUID from annotated_doc import Doc -from fastapi._compat import may_v1 +from fastapi.exceptions import PydanticV1NotSupportedError from fastapi.types import IncEx from pydantic import BaseModel from pydantic.color import Color from pydantic.networks import AnyUrl, NameEmail from pydantic.types import SecretBytes, SecretStr +from pydantic_core import PydanticUndefinedType -from ._compat import Url, _is_undefined, _model_dump +from ._compat import ( + Url, + is_pydantic_v1_model_instance, +) # Taken from Pydantic v1 as is @@ -63,7 +67,6 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]: ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { bytes: lambda o: o.decode(), Color: str, - may_v1.Color: str, datetime.date: isoformat, datetime.datetime: isoformat, datetime.time: isoformat, @@ -80,19 +83,14 @@ ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { IPv6Interface: str, IPv6Network: str, NameEmail: str, - may_v1.NameEmail: str, Path: str, Pattern: lambda o: o.pattern, SecretBytes: str, - may_v1.SecretBytes: str, SecretStr: str, - may_v1.SecretStr: str, set: list, UUID: str, Url: str, - may_v1.Url: str, AnyUrl: str, - may_v1.AnyUrl: str, } @@ -224,15 +222,8 @@ def jsonable_encoder( include = set(include) if exclude is not None and not isinstance(exclude, (set, dict)): exclude = set(exclude) - if isinstance(obj, (BaseModel, may_v1.BaseModel)): - # TODO: remove when deprecating Pydantic v1 - encoders: dict[Any, Any] = {} - if isinstance(obj, may_v1.BaseModel): - encoders = getattr(obj.__config__, "json_encoders", {}) - if custom_encoder: - encoders = {**encoders, **custom_encoder} - obj_dict = _model_dump( - obj, # type: ignore[arg-type] + if isinstance(obj, BaseModel): + obj_dict = obj.model_dump( mode="json", include=include, exclude=exclude, @@ -241,14 +232,10 @@ def jsonable_encoder( exclude_none=exclude_none, exclude_defaults=exclude_defaults, ) - if "__root__" in obj_dict: - obj_dict = obj_dict["__root__"] return jsonable_encoder( obj_dict, exclude_none=exclude_none, exclude_defaults=exclude_defaults, - # TODO: remove when deprecating Pydantic v1 - custom_encoder=encoders, sqlalchemy_safe=sqlalchemy_safe, ) if dataclasses.is_dataclass(obj): @@ -271,7 +258,7 @@ def jsonable_encoder( return str(obj) if isinstance(obj, (str, int, float, type(None))): return obj - if _is_undefined(obj): + if isinstance(obj, PydanticUndefinedType): return None if isinstance(obj, dict): encoded_dict = {} @@ -331,7 +318,11 @@ def jsonable_encoder( for encoder, classes_tuple in encoders_by_class_tuples.items(): if isinstance(obj, classes_tuple): return encoder(obj) - + if is_pydantic_v1_model_instance(obj): + raise PydanticV1NotSupportedError( + "pydantic.v1 models are no longer supported by FastAPI." + f" Please update the model {obj!r}." + ) try: data = dict(obj) except Exception as e: diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 53e5052818..1a3abd80c2 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -233,6 +233,12 @@ class ResponseValidationError(ValidationException): self.body = body +class PydanticV1NotSupportedError(FastAPIError): + """ + A pydantic.v1 model is used, which is no longer supported. + """ + + class FastAPIDeprecationWarning(UserWarning): """ A custom deprecation warning as DeprecationWarning is ignored diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index 680f678325..ac6a6d52c3 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -1,15 +1,15 @@ -from collections.abc import Iterable +from collections.abc import Iterable, Mapping from enum import Enum from typing import Annotated, Any, Callable, Optional, Union -from fastapi._compat import ( - CoreSchema, - GetJsonSchemaHandler, - JsonSchemaValue, - with_info_plain_validator_function, -) +from fastapi._compat import with_info_plain_validator_function from fastapi.logger import logger -from pydantic import AnyUrl, BaseModel, Field +from pydantic import ( + AnyUrl, + BaseModel, + Field, + GetJsonSchemaHandler, +) from typing_extensions import Literal, TypedDict from typing_extensions import deprecated as typing_deprecated @@ -43,14 +43,14 @@ except ImportError: # pragma: no cover @classmethod def __get_pydantic_json_schema__( - cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler - ) -> JsonSchemaValue: + cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler + ) -> dict[str, Any]: return {"type": "string", "format": "email"} @classmethod def __get_pydantic_core_schema__( - cls, source: type[Any], handler: Callable[[Any], CoreSchema] - ) -> CoreSchema: + cls, source: type[Any], handler: Callable[[Any], Mapping[str, Any]] + ) -> Mapping[str, Any]: return with_info_plain_validator_function(cls._validate) diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 6180fcde6a..75ff261025 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -6,7 +6,6 @@ from typing import Any, Optional, Union, cast from fastapi import routing from fastapi._compat import ( - JsonSchemaValue, ModelField, Undefined, get_compat_model_name_map, @@ -39,8 +38,6 @@ from starlette.responses import JSONResponse from starlette.routing import BaseRoute from typing_extensions import Literal -from .._compat import _is_model_field - validation_error_definition = { "title": "ValidationError", "type": "object", @@ -109,7 +106,7 @@ def _get_openapi_operation_parameters( dependant: Dependant, model_name_map: ModelNameMap, field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any] ], separate_input_output_schemas: bool = True, ) -> list[dict[str, Any]]: @@ -182,13 +179,13 @@ def get_openapi_operation_request_body( body_field: Optional[ModelField], model_name_map: ModelNameMap, field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any] ], separate_input_output_schemas: bool = True, ) -> Optional[dict[str, Any]]: if not body_field: return None - assert _is_model_field(body_field) + assert isinstance(body_field, ModelField) body_schema = get_schema_from_model_field( field=body_field, model_name_map=model_name_map, @@ -265,7 +262,7 @@ def get_openapi_path( operation_ids: set[str], model_name_map: ModelNameMap, field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any] ], separate_input_output_schemas: bool = True, ) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]: @@ -457,7 +454,7 @@ def get_fields_from_routes( route, routing.APIRoute ): if route.body_field: - assert _is_model_field(route.body_field), ( + assert isinstance(route.body_field, ModelField), ( "A request body must be a Pydantic Field" ) body_fields_from_routes.append(route.body_field) diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index 844542594b..0834fd741a 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -5,6 +5,7 @@ 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 _Unset: Any = Undefined @@ -54,10 +55,8 @@ def Path( # noqa: N802 """ ), ] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None validation_alias: Annotated[ - Union[str, None], + Union[str, AliasPath, AliasChoices, None], Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -379,10 +378,8 @@ def Query( # noqa: N802 """ ), ] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None validation_alias: Annotated[ - Union[str, None], + Union[str, AliasPath, AliasChoices, None], Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -683,10 +680,8 @@ def Header( # noqa: N802 """ ), ] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None validation_alias: Annotated[ - Union[str, None], + Union[str, AliasPath, AliasChoices, None], Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -999,10 +994,8 @@ def Cookie( # noqa: N802 """ ), ] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None validation_alias: Annotated[ - Union[str, None], + Union[str, AliasPath, AliasChoices, None], Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -1326,10 +1319,8 @@ def Body( # noqa: N802 """ ), ] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None validation_alias: Annotated[ - Union[str, None], + Union[str, AliasPath, AliasChoices, None], Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -1641,10 +1632,8 @@ def Form( # noqa: N802 """ ), ] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None validation_alias: Annotated[ - Union[str, None], + Union[str, AliasPath, AliasChoices, None], Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -1955,10 +1944,8 @@ def File( # noqa: N802 """ ), ] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None validation_alias: Annotated[ - Union[str, None], + Union[str, AliasPath, AliasChoices, None], Doc( """ 'Whitelist' validation step. The parameter field will be the single one diff --git a/fastapi/params.py b/fastapi/params.py index cc2934f44d..72e797f833 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -6,6 +6,7 @@ from typing import Annotated, Any, Callable, Optional, Union 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 @@ -34,9 +35,7 @@ class Param(FieldInfo): # type: ignore[misc] annotation: Optional[Any] = None, alias: Optional[str] = None, alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, None] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Union[str, None] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -147,9 +146,7 @@ class Path(Param): # type: ignore[misc] annotation: Optional[Any] = None, alias: Optional[str] = None, alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, None] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Union[str, None] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -233,9 +230,7 @@ class Query(Param): # type: ignore[misc] annotation: Optional[Any] = None, alias: Optional[str] = None, alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, None] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Union[str, None] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -317,9 +312,7 @@ class Header(Param): # type: ignore[misc] annotation: Optional[Any] = None, alias: Optional[str] = None, alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, None] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Union[str, None] = None, convert_underscores: bool = True, title: Optional[str] = None, @@ -403,9 +396,7 @@ class Cookie(Param): # type: ignore[misc] annotation: Optional[Any] = None, alias: Optional[str] = None, alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, None] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Union[str, None] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -487,9 +478,7 @@ class Body(FieldInfo): # type: ignore[misc] media_type: str = "application/json", alias: Optional[str] = None, alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, None] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Union[str, None] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -600,9 +589,7 @@ class Form(Body): # type: ignore[misc] media_type: str = "application/x-www-form-urlencoded", alias: Optional[str] = None, alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, None] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Union[str, None] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -684,9 +671,7 @@ class File(Form): # type: ignore[misc] media_type: str = "multipart/form-data", alias: Optional[str] = None, alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, None] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Union[str, None] = None, title: Optional[str] = None, description: Optional[str] = None, diff --git a/fastapi/routing.py b/fastapi/routing.py index 3f78e93e84..9ca2f46732 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -2,7 +2,6 @@ import email.message import functools import inspect import json -import warnings from collections.abc import ( AsyncIterator, Awaitable, @@ -22,16 +21,12 @@ from typing import ( ) from annotated_doc import Doc -from fastapi import params, temp_pydantic_v1_params +from fastapi import params from fastapi._compat import ( ModelField, Undefined, - _get_model_config, - _model_dump, - _normalize_errors, annotation_is_pydantic_v1, lenient_issubclass, - may_v1, ) from fastapi.datastructures import Default, DefaultPlaceholder from fastapi.dependencies.models import Dependant @@ -47,8 +42,8 @@ from fastapi.dependencies.utils import ( from fastapi.encoders import jsonable_encoder from fastapi.exceptions import ( EndpointContext, - FastAPIDeprecationWarning, FastAPIError, + PydanticV1NotSupportedError, RequestValidationError, ResponseValidationError, WebSocketRequestValidationError, @@ -148,51 +143,6 @@ def websocket_session( return app -def _prepare_response_content( - res: Any, - *, - exclude_unset: bool, - exclude_defaults: bool = False, - exclude_none: bool = False, -) -> Any: - if isinstance(res, may_v1.BaseModel): - read_with_orm_mode = getattr(_get_model_config(res), "read_with_orm_mode", None) # type: ignore[arg-type] - if read_with_orm_mode: - # Let from_orm extract the data from this model instead of converting - # it now to a dict. - # Otherwise, there's no way to extract lazy data that requires attribute - # access instead of dict iteration, e.g. lazy relationships. - return res - return _model_dump( - res, # type: ignore[arg-type] - by_alias=True, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - elif isinstance(res, list): - return [ - _prepare_response_content( - item, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - for item in res - ] - elif isinstance(res, dict): - return { - k: _prepare_response_content( - v, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - for k, v in res.items() - } - return res - - def _merge_lifespan_context( original_context: Lifespan[Any], nested_context: Lifespan[Any] ) -> Lifespan[Any]: @@ -252,14 +202,6 @@ async def serialize_response( ) -> Any: if field: errors = [] - if not hasattr(field, "serialize"): - # pydantic v1 - response_content = _prepare_response_content( - response_content, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) if is_coroutine: value, errors_ = field.validate(response_content, {}, loc=("response",)) else: @@ -268,28 +210,15 @@ async def serialize_response( ) if isinstance(errors_, list): errors.extend(errors_) - elif errors_: - errors.append(errors_) if errors: ctx = endpoint_ctx or EndpointContext() raise ResponseValidationError( - errors=_normalize_errors(errors), + errors=errors, body=response_content, endpoint_ctx=ctx, ) - if hasattr(field, "serialize"): - return field.serialize( - value, - include=include, - exclude=exclude, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - - return jsonable_encoder( + return field.serialize( value, include=include, exclude=exclude, @@ -298,6 +227,7 @@ async def serialize_response( exclude_defaults=exclude_defaults, exclude_none=exclude_none, ) + else: return jsonable_encoder(response_content) @@ -332,9 +262,7 @@ def get_request_handler( ) -> Callable[[Request], Coroutine[Any, Any, Response]]: assert dependant.call is not None, "dependant.call must be a function" is_coroutine = dependant.is_coroutine_callable - is_body_form = body_field and isinstance( - body_field.field_info, (params.Form, temp_pydantic_v1_params.Form) - ) + is_body_form = body_field and isinstance(body_field.field_info, params.Form) if isinstance(response_class, DefaultPlaceholder): actual_response_class: type[Response] = response_class.value else: @@ -464,7 +392,7 @@ def get_request_handler( response.headers.raw.extend(solved_result.response.headers.raw) if errors: validation_error = RequestValidationError( - _normalize_errors(errors), body=body, endpoint_ctx=endpoint_ctx + errors, body=body, endpoint_ctx=endpoint_ctx ) raise validation_error @@ -503,7 +431,7 @@ def get_websocket_app( ) if solved_result.errors: raise WebSocketRequestValidationError( - _normalize_errors(solved_result.errors), + solved_result.errors, endpoint_ctx=endpoint_ctx, ) assert dependant.call is not None, "dependant.call must be a function" @@ -638,11 +566,9 @@ class APIRoute(routing.Route): ) response_name = "Response_" + self.unique_id if annotation_is_pydantic_v1(self.response_model): - warnings.warn( - "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." - f" Please update the response model {self.response_model!r}.", - category=FastAPIDeprecationWarning, - stacklevel=4, + 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, @@ -678,11 +604,9 @@ class APIRoute(routing.Route): ) response_name = f"Response_{additional_status_code}_{self.unique_id}" if annotation_is_pydantic_v1(model): - warnings.warn( - "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." - f" In responses={{}}, please update {model}.", - category=FastAPIDeprecationWarning, - stacklevel=4, + 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/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py deleted file mode 100644 index 62230e42cf..0000000000 --- a/fastapi/temp_pydantic_v1_params.py +++ /dev/null @@ -1,718 +0,0 @@ -import warnings -from typing import Annotated, Any, Callable, Optional, Union - -from fastapi.exceptions import FastAPIDeprecationWarning -from fastapi.openapi.models import Example -from fastapi.params import ParamTypes -from typing_extensions import deprecated - -from ._compat.may_v1 import FieldInfo, Undefined - -_Unset: Any = Undefined - - -class Param(FieldInfo): - in_: ParamTypes - - def __init__( - 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, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, 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, - regex: Annotated[ - Optional[str], - 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, - example: Annotated[ - Optional[Any], - 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, - include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, - **extra: Any, - ): - if example is not _Unset: - warnings.warn( - "`example` has been deprecated, please use `examples` instead", - category=FastAPIDeprecationWarning, - stacklevel=4, - ) - self.example = example - self.include_in_schema = include_in_schema - self.openapi_examples = openapi_examples - kwargs = dict( - default=default, - default_factory=default_factory, - alias=alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - discriminator=discriminator, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - **extra, - ) - if examples is not None: - kwargs["examples"] = examples - if regex is not None: - warnings.warn( - "`regex` has been deprecated, please use `pattern` instead", - category=FastAPIDeprecationWarning, - stacklevel=4, - ) - current_json_schema_extra = json_schema_extra or extra - kwargs["deprecated"] = deprecated - kwargs["regex"] = pattern or regex - kwargs.update(**current_json_schema_extra) - use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} - - super().__init__(**use_kwargs) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.default})" - - -class Path(Param): - in_ = ParamTypes.path - - def __init__( - self, - default: Any = ..., - *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, 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, - regex: Annotated[ - Optional[str], - 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, - example: Annotated[ - Optional[Any], - 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, - include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, - **extra: Any, - ): - assert default is ..., "Path parameters cannot have a default value" - self.in_ = self.in_ - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class Query(Param): - in_ = ParamTypes.query - - def __init__( - 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, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, 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, - regex: Annotated[ - Optional[str], - 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, - example: Annotated[ - Optional[Any], - 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, - include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, - **extra: Any, - ): - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class Header(Param): - in_ = ParamTypes.header - - def __init__( - 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, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, None] = None, - serialization_alias: Union[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, - regex: Annotated[ - Optional[str], - 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, - example: Annotated[ - Optional[Any], - 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, - include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, - **extra: Any, - ): - self.convert_underscores = convert_underscores - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class Cookie(Param): - in_ = ParamTypes.cookie - - def __init__( - 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, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, 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, - regex: Annotated[ - Optional[str], - 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, - example: Annotated[ - Optional[Any], - 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, - include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, - **extra: Any, - ): - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class Body(FieldInfo): - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - embed: Union[bool, None] = None, - media_type: str = "application/json", - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, 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, - regex: Annotated[ - Optional[str], - 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, - example: Annotated[ - Optional[Any], - 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, - include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, - **extra: Any, - ): - self.embed = embed - self.media_type = media_type - if example is not _Unset: - warnings.warn( - "`example` has been deprecated, please use `examples` instead", - category=FastAPIDeprecationWarning, - stacklevel=4, - ) - self.example = example - self.include_in_schema = include_in_schema - self.openapi_examples = openapi_examples - kwargs = dict( - default=default, - default_factory=default_factory, - alias=alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - discriminator=discriminator, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - **extra, - ) - if examples is not None: - kwargs["examples"] = examples - if regex is not None: - warnings.warn( - "`regex` has been deprecated, please use `pattern` instead", - category=FastAPIDeprecationWarning, - stacklevel=4, - ) - current_json_schema_extra = json_schema_extra or extra - kwargs["deprecated"] = deprecated - kwargs["regex"] = pattern or regex - kwargs.update(**current_json_schema_extra) - - use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} - - super().__init__(**use_kwargs) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.default})" - - -class Form(Body): - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - media_type: str = "application/x-www-form-urlencoded", - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, 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, - regex: Annotated[ - Optional[str], - 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, - example: Annotated[ - Optional[Any], - 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, - include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, - **extra: Any, - ): - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - media_type=media_type, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class File(Form): - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - media_type: str = "multipart/form-data", - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - # TODO: update when deprecating Pydantic v1, import these types - # validation_alias: str | AliasPath | AliasChoices | None - validation_alias: Union[str, 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, - regex: Annotated[ - Optional[str], - 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, - example: Annotated[ - Optional[Any], - 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, - include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, - **extra: Any, - ): - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - media_type=media_type, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) diff --git a/fastapi/utils.py b/fastapi/utils.py index 8ae50aa145..78fdcbb5b4 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -6,7 +6,6 @@ from typing import ( Any, Optional, Union, - cast, ) from weakref import WeakKeyDictionary @@ -19,11 +18,9 @@ from fastapi._compat import ( UndefinedType, Validator, annotation_is_pydantic_v1, - lenient_issubclass, - may_v1, ) from fastapi.datastructures import DefaultPlaceholder, DefaultType -from fastapi.exceptions import FastAPIDeprecationWarning +from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError from pydantic import BaseModel from pydantic.fields import FieldInfo from typing_extensions import Literal @@ -83,52 +80,18 @@ def create_model_field( 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 {} - v1_model_config = may_v1.BaseConfig - v1_field_info = field_info or may_v1.FieldInfo() - v1_kwargs = { - "name": name, - "field_info": v1_field_info, - "type_": type_, - "class_validators": class_validators, - "default": default, - "required": required, - "model_config": v1_model_config, - "alias": alias, - } - - if ( - annotation_is_pydantic_v1(type_) - or isinstance(field_info, may_v1.FieldInfo) - or version == "1" - ): - from fastapi._compat import v1 - - try: - return v1.ModelField(**v1_kwargs) # type: ignore[return-value] - except RuntimeError: - raise fastapi.exceptions.FastAPIError( - _invalid_args_message.format(type_=type_) - ) from None - else: - field_info = field_info or FieldInfo( - annotation=type_, default=default, alias=alias - ) - kwargs = {"mode": mode, "name": name, "field_info": field_info} - try: - return v2.ModelField(**kwargs) # type: ignore[return-value,arg-type] - except PydanticSchemaGenerationError: - raise fastapi.exceptions.FastAPIError( - _invalid_args_message.format(type_=type_) - ) from None - # Pydantic v2 is not installed, but it's not a Pydantic v1 ModelField, it could be - # a Pydantic v1 type, like a constrained int - from fastapi._compat import v1 - + field_info = field_info or FieldInfo(annotation=type_, default=default, alias=alias) + kwargs = {"mode": mode, "name": name, "field_info": field_info} try: - return v1.ModelField(**v1_kwargs) - except RuntimeError: + return v2.ModelField(**kwargs) # type: ignore[return-value,arg-type] + except PydanticSchemaGenerationError: raise fastapi.exceptions.FastAPIError( _invalid_args_message.format(type_=type_) ) from None @@ -139,57 +102,7 @@ def create_cloned_field( *, cloned_types: Optional[MutableMapping[type[BaseModel], type[BaseModel]]] = None, ) -> ModelField: - if isinstance(field, v2.ModelField): - return field - - from fastapi._compat import v1 - - # cloned_types caches already cloned types to support recursive models and improve - # performance by avoiding unnecessary cloning - if cloned_types is None: - cloned_types = _CLONED_TYPES_CACHE - - original_type = field.type_ - use_type = original_type - if lenient_issubclass(original_type, v1.BaseModel): - original_type = cast(type[v1.BaseModel], original_type) - use_type = cloned_types.get(original_type) - if use_type is None: - use_type = v1.create_model(original_type.__name__, __base__=original_type) - cloned_types[original_type] = use_type - for f in original_type.__fields__.values(): - use_type.__fields__[f.name] = create_cloned_field( - f, - cloned_types=cloned_types, - ) - new_field = create_model_field(name=field.name, type_=use_type, version="1") - new_field.has_alias = field.has_alias # type: ignore[attr-defined] - new_field.alias = field.alias # type: ignore[misc] - new_field.class_validators = field.class_validators # type: ignore[attr-defined] - new_field.default = field.default # type: ignore[misc] - new_field.default_factory = field.default_factory # type: ignore[attr-defined] - new_field.required = field.required # type: ignore[misc] - new_field.model_config = field.model_config # type: ignore[attr-defined] - new_field.field_info = field.field_info - new_field.allow_none = field.allow_none # type: ignore[attr-defined] - new_field.validate_always = field.validate_always # type: ignore[attr-defined] - if field.sub_fields: # type: ignore[attr-defined] - new_field.sub_fields = [ # type: ignore[attr-defined] - create_cloned_field(sub_field, cloned_types=cloned_types) - for sub_field in field.sub_fields # type: ignore[attr-defined] - ] - if field.key_field: # type: ignore[attr-defined] - new_field.key_field = create_cloned_field( # type: ignore[attr-defined] - field.key_field, # type: ignore[attr-defined] - cloned_types=cloned_types, - ) - new_field.validators = field.validators # type: ignore[attr-defined] - new_field.pre_validators = field.pre_validators # type: ignore[attr-defined] - new_field.post_validators = field.post_validators # type: ignore[attr-defined] - new_field.parse_json = field.parse_json # type: ignore[attr-defined] - new_field.shape = field.shape # type: ignore[attr-defined] - new_field.populate_validators() # type: ignore[attr-defined] - return new_field + return field def generate_operation_id_for_path( diff --git a/pyproject.toml b/pyproject.toml index 8f824af5d5..9c2c35a9f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,14 +199,22 @@ omit = [ "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 + # 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/query_param_models/tutorial002_pv1_py310.py", - "docs_src/query_param_models/tutorial002_pv1_an_py310.py", - "docs_src/header_param_models/tutorial002_pv1_py310.py", - "docs_src/header_param_models/tutorial002_pv1_an_py310.py", - "docs_src/cookie_param_models/tutorial002_pv1_py310.py", - "docs_src/cookie_param_models/tutorial002_pv1_an_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", ] [tool.coverage.report] diff --git a/tests/test_compat.py b/tests/test_compat.py index 8d20710303..0b5600f8f5 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,20 +1,16 @@ -from typing import Any, Union +from typing import Union from fastapi import FastAPI, UploadFile from fastapi._compat import ( Undefined, - _get_model_config, - get_cached_model_fields, - is_scalar_field, is_uploadfile_sequence_annotation, - may_v1, ) from fastapi._compat.shared import is_bytes_sequence_annotation from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict from pydantic.fields import FieldInfo -from .utils import needs_py310, needs_py_lt_314 +from .utils import needs_py310 def test_model_field_default_required(): @@ -26,35 +22,6 @@ def test_model_field_default_required(): assert field.default is Undefined -@needs_py_lt_314 -def test_v1_plain_validator_function(): - from fastapi._compat import v1 - - # For coverage - def func(v): # pragma: no cover - return v - - result = v1.with_info_plain_validator_function(func) - assert result == {} - - -def test_is_model_field(): - # For coverage - from fastapi._compat import _is_model_field - - assert not _is_model_field(str) - - -def test_get_model_config(): - # For coverage in Pydantic v2 - class Foo(BaseModel): - model_config = ConfigDict(from_attributes=True) - - foo = Foo() - config = _get_model_config(foo) - assert config == {"from_attributes": True} - - def test_complex(): app = FastAPI() @@ -165,33 +132,3 @@ def test_serialize_sequence_value_with_none_first_in_union(): result = v2.serialize_sequence_value(field=field, value=["x", "y"]) assert result == ["x", "y"] assert isinstance(result, list) - - -@needs_py_lt_314 -def test_is_pv1_scalar_field(): - from fastapi._compat import v1 - - # For coverage - class Model(v1.BaseModel): - foo: Union[str, dict[str, Any]] - - fields = v1.get_model_fields(Model) - assert not is_scalar_field(fields[0]) - - -@needs_py_lt_314 -def test_get_model_fields_cached(): - from fastapi._compat import v1 - - class Model(may_v1.BaseModel): - foo: str - - non_cached_fields = v1.get_model_fields(Model) - non_cached_fields2 = v1.get_model_fields(Model) - cached_fields = get_cached_model_fields(Model) - cached_fields2 = get_cached_model_fields(Model) - for f1, f2 in zip(cached_fields, cached_fields2): - assert f1 is f2 - - assert non_cached_fields is not non_cached_fields2 - assert cached_fields is cached_fields2 diff --git a/tests/test_compat_params_v1.py b/tests/test_compat_params_v1.py deleted file mode 100644 index 704b3f77a6..0000000000 --- a/tests/test_compat_params_v1.py +++ /dev/null @@ -1,1060 +0,0 @@ -import sys -import warnings -from typing import Optional - -import pytest -from fastapi.exceptions import FastAPIDeprecationWarning - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - -from typing import Annotated - -from fastapi import FastAPI -from fastapi._compat.v1 import BaseModel -from fastapi.temp_pydantic_v1_params import ( - Body, - Cookie, - File, - Form, - Header, - Path, - Query, -) -from fastapi.testclient import TestClient -from inline_snapshot import snapshot - - -class Item(BaseModel): - name: str - price: float - description: Optional[str] = None - - -app = FastAPI() - -with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.get("/items/{item_id}") - def get_item_with_path( - item_id: Annotated[int, Path(title="The ID of the item", ge=1, le=1000)], - ): - return {"item_id": item_id} - - @app.get("/items/") - def get_items_with_query( - q: Annotated[ - Optional[str], - Query(min_length=3, max_length=50, pattern="^[a-zA-Z0-9 ]+$"), - ] = None, - skip: Annotated[int, Query(ge=0)] = 0, - limit: Annotated[int, Query(ge=1, le=100, examples=[5])] = 10, - ): - return {"q": q, "skip": skip, "limit": limit} - - @app.get("/users/") - def get_user_with_header( - x_custom: Annotated[Optional[str], Header()] = None, - x_token: Annotated[Optional[str], Header(convert_underscores=True)] = None, - ): - return {"x_custom": x_custom, "x_token": x_token} - - @app.get("/cookies/") - def get_cookies( - session_id: Annotated[Optional[str], Cookie()] = None, - tracking_id: Annotated[Optional[str], Cookie(min_length=10)] = None, - ): - return {"session_id": session_id, "tracking_id": tracking_id} - - @app.post("/items/") - def create_item( - item: Annotated[ - Item, - Body( - examples=[{"name": "Foo", "price": 35.4, "description": "The Foo item"}] - ), - ], - ): - return {"item": item} - - @app.post("/items-embed/") - def create_item_embed( - item: Annotated[Item, Body(embed=True)], - ): - return {"item": item} - - @app.put("/items/{item_id}") - def update_item( - item_id: Annotated[int, Path(ge=1)], - item: Annotated[Item, Body()], - importance: Annotated[int, Body(gt=0, le=10)], - ): - return {"item": item, "importance": importance} - - @app.post("/form-data/") - def submit_form( - username: Annotated[str, Form(min_length=3, max_length=50)], - password: Annotated[str, Form(min_length=8)], - email: Annotated[Optional[str], Form()] = None, - ): - return {"username": username, "password": password, "email": email} - - @app.post("/upload/") - def upload_file( - file: Annotated[bytes, File()], - description: Annotated[Optional[str], Form()] = None, - ): - return {"file_size": len(file), "description": description} - - @app.post("/upload-multiple/") - def upload_multiple_files( - files: Annotated[list[bytes], File()], - note: Annotated[str, Form()] = "", - ): - return { - "file_count": len(files), - "total_size": sum(len(f) for f in files), - "note": note, - } - - -client = TestClient(app) - - -# Path parameter tests -def test_path_param_valid(): - response = client.get("/items/50") - assert response.status_code == 200 - assert response.json() == {"item_id": 50} - - -def test_path_param_too_large(): - response = client.get("/items/1001") - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["path", "item_id"] - - -def test_path_param_too_small(): - response = client.get("/items/0") - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["path", "item_id"] - - -# Query parameter tests -def test_query_params_valid(): - response = client.get("/items/?q=test search&skip=5&limit=20") - assert response.status_code == 200 - assert response.json() == {"q": "test search", "skip": 5, "limit": 20} - - -def test_query_params_defaults(): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"q": None, "skip": 0, "limit": 10} - - -def test_query_param_too_short(): - response = client.get("/items/?q=ab") - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["query", "q"] - - -def test_query_param_invalid_pattern(): - response = client.get("/items/?q=test@#$") - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["query", "q"] - - -def test_query_param_limit_too_large(): - response = client.get("/items/?limit=101") - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["query", "limit"] - - -# Header parameter tests -def test_header_params(): - response = client.get( - "/users/", - headers={"X-Custom": "Plumbus", "X-Token": "secret-token"}, - ) - assert response.status_code == 200 - assert response.json() == { - "x_custom": "Plumbus", - "x_token": "secret-token", - } - - -def test_header_underscore_conversion(): - response = client.get( - "/users/", - headers={"x-token": "secret-token-with-dash"}, - ) - assert response.status_code == 200 - assert response.json()["x_token"] == "secret-token-with-dash" - - -def test_header_params_none(): - response = client.get("/users/") - assert response.status_code == 200 - assert response.json() == {"x_custom": None, "x_token": None} - - -# Cookie parameter tests -def test_cookie_params(): - with TestClient(app) as test_client: - test_client.cookies.set("session_id", "abc123") - test_client.cookies.set("tracking_id", "1234567890abcdef") - response = test_client.get("/cookies/") - assert response.status_code == 200 - assert response.json() == { - "session_id": "abc123", - "tracking_id": "1234567890abcdef", - } - - -def test_cookie_tracking_id_too_short(): - with TestClient(app) as test_client: - test_client.cookies.set("tracking_id", "short") - response = test_client.get("/cookies/") - assert response.status_code == 422 - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["cookie", "tracking_id"], - "msg": "ensure this value has at least 10 characters", - "type": "value_error.any_str.min_length", - "ctx": {"limit_value": 10}, - } - ] - } - ) - - -def test_cookie_params_none(): - response = client.get("/cookies/") - assert response.status_code == 200 - assert response.json() == {"session_id": None, "tracking_id": None} - - -# Body parameter tests -def test_body_param(): - response = client.post( - "/items/", - json={"name": "Test Item", "price": 29.99, "description": "A test item"}, - ) - assert response.status_code == 200 - assert response.json() == { - "item": { - "name": "Test Item", - "price": 29.99, - "description": "A test item", - } - } - - -def test_body_param_minimal(): - response = client.post( - "/items/", - json={"name": "Minimal", "price": 9.99}, - ) - assert response.status_code == 200 - assert response.json() == { - "item": {"name": "Minimal", "price": 9.99, "description": None} - } - - -def test_body_param_missing_required(): - response = client.post( - "/items/", - json={"name": "Incomplete"}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "price"] - - -def test_body_embed(): - response = client.post( - "/items-embed/", - json={"item": {"name": "Embedded", "price": 15.0}}, - ) - assert response.status_code == 200 - assert response.json() == { - "item": {"name": "Embedded", "price": 15.0, "description": None} - } - - -def test_body_embed_wrong_structure(): - response = client.post( - "/items-embed/", - json={"name": "Not Embedded", "price": 15.0}, - ) - assert response.status_code == 422 - - -# Multiple body parameters test -def test_multiple_body_params(): - response = client.put( - "/items/5", - json={ - "item": {"name": "Updated Item", "price": 49.99}, - "importance": 8, - }, - ) - assert response.status_code == 200 - assert response.json() == snapshot( - { - "item": {"name": "Updated Item", "price": 49.99, "description": None}, - "importance": 8, - } - ) - - -def test_multiple_body_params_importance_too_large(): - response = client.put( - "/items/5", - json={ - "item": {"name": "Item", "price": 10.0}, - "importance": 11, - }, - ) - assert response.status_code == 422 - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "importance"], - "msg": "ensure this value is less than or equal to 10", - "type": "value_error.number.not_le", - "ctx": {"limit_value": 10}, - } - ] - } - ) - - -def test_multiple_body_params_importance_too_small(): - response = client.put( - "/items/5", - json={ - "item": {"name": "Item", "price": 10.0}, - "importance": 0, - }, - ) - assert response.status_code == 422 - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "importance"], - "msg": "ensure this value is greater than 0", - "type": "value_error.number.not_gt", - "ctx": {"limit_value": 0}, - } - ] - } - ) - - -# Form parameter tests -def test_form_data_valid(): - response = client.post( - "/form-data/", - data={ - "username": "testuser", - "password": "password123", - "email": "test@example.com", - }, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "username": "testuser", - "password": "password123", - "email": "test@example.com", - } - - -def test_form_data_optional_field(): - response = client.post( - "/form-data/", - data={"username": "testuser", "password": "password123"}, - ) - assert response.status_code == 200 - assert response.json() == { - "username": "testuser", - "password": "password123", - "email": None, - } - - -def test_form_data_username_too_short(): - response = client.post( - "/form-data/", - data={"username": "ab", "password": "password123"}, - ) - assert response.status_code == 422 - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "ensure this value has at least 3 characters", - "type": "value_error.any_str.min_length", - "ctx": {"limit_value": 3}, - } - ] - } - ) - - -def test_form_data_password_too_short(): - response = client.post( - "/form-data/", - data={"username": "testuser", "password": "short"}, - ) - assert response.status_code == 422 - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "password"], - "msg": "ensure this value has at least 8 characters", - "type": "value_error.any_str.min_length", - "ctx": {"limit_value": 8}, - } - ] - } - ) - - -# File upload tests -def test_upload_file(): - response = client.post( - "/upload/", - files={"file": ("test.txt", b"Hello, World!", "text/plain")}, - data={"description": "A test file"}, - ) - assert response.status_code == 200 - assert response.json() == { - "file_size": 13, - "description": "A test file", - } - - -def test_upload_file_without_description(): - response = client.post( - "/upload/", - files={"file": ("test.txt", b"Hello!", "text/plain")}, - ) - assert response.status_code == 200 - assert response.json() == { - "file_size": 6, - "description": None, - } - - -def test_upload_multiple_files(): - response = client.post( - "/upload-multiple/", - files=[ - ("files", ("file1.txt", b"Content 1", "text/plain")), - ("files", ("file2.txt", b"Content 2", "text/plain")), - ("files", ("file3.txt", b"Content 3", "text/plain")), - ], - data={"note": "Multiple files uploaded"}, - ) - assert response.status_code == 200 - assert response.json() == { - "file_count": 3, - "total_size": 27, - "note": "Multiple files uploaded", - } - - -def test_upload_multiple_files_empty_note(): - response = client.post( - "/upload-multiple/", - files=[ - ("files", ("file1.txt", b"Test", "text/plain")), - ], - ) - assert response.status_code == 200 - assert response.json()["file_count"] == 1 - assert response.json()["note"] == "" - - -# __repr__ tests -def test_query_repr(): - query_param = Query(default=None, min_length=3) - assert repr(query_param) == "Query(None)" - - -def test_body_repr(): - body_param = Body(default=None) - assert repr(body_param) == "Body(None)" - - -# Deprecation warning tests for regex parameter -def test_query_regex_deprecation_warning(): - with pytest.warns(FastAPIDeprecationWarning, match="`regex` has been deprecated"): - Query(regex="^test$") - - -def test_body_regex_deprecation_warning(): - with pytest.warns(FastAPIDeprecationWarning, match="`regex` has been deprecated"): - Body(regex="^test$") - - -# Deprecation warning tests for example parameter -def test_query_example_deprecation_warning(): - with pytest.warns(FastAPIDeprecationWarning, match="`example` has been deprecated"): - Query(example="test example") - - -def test_body_example_deprecation_warning(): - with pytest.warns(FastAPIDeprecationWarning, match="`example` has been deprecated"): - Body(example={"test": "example"}) - - -def test_openapi_schema(): - 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/{item_id}": { - "get": { - "summary": "Get Item With Path", - "operationId": "get_item_with_path_items__item_id__get", - "parameters": [ - { - "name": "item_id", - "in": "path", - "required": True, - "schema": { - "title": "The ID of the item", - "minimum": 1, - "maximum": 1000, - "type": "integer", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "name": "item_id", - "in": "path", - "required": True, - "schema": { - "title": "Item Id", - "minimum": 1, - "type": "integer", - }, - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": { - "title": "Body", - "allOf": [ - { - "$ref": "#/components/schemas/Body_update_item_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" - } - } - }, - }, - }, - }, - }, - "/items/": { - "get": { - "summary": "Get Items With Query", - "operationId": "get_items_with_query_items__get", - "parameters": [ - { - "name": "q", - "in": "query", - "required": False, - "schema": { - "title": "Q", - "maxLength": 50, - "minLength": 3, - "pattern": "^[a-zA-Z0-9 ]+$", - "type": "string", - }, - }, - { - "name": "skip", - "in": "query", - "required": False, - "schema": { - "title": "Skip", - "default": 0, - "minimum": 0, - "type": "integer", - }, - }, - { - "name": "limit", - "in": "query", - "required": False, - "schema": { - "title": "Limit", - "default": 10, - "minimum": 1, - "maximum": 100, - "examples": [5], - "type": "integer", - }, - }, - ], - "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": { - "required": True, - "content": { - "application/json": { - "schema": { - "title": "Item", - "examples": [ - { - "name": "Foo", - "price": 35.4, - "description": "The Foo item", - } - ], - "allOf": [ - {"$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" - } - } - }, - }, - }, - }, - }, - "/users/": { - "get": { - "summary": "Get User With Header", - "operationId": "get_user_with_header_users__get", - "parameters": [ - { - "name": "x-custom", - "in": "header", - "required": False, - "schema": {"title": "X-Custom", "type": "string"}, - }, - { - "name": "x-token", - "in": "header", - "required": False, - "schema": {"title": "X-Token", "type": "string"}, - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/cookies/": { - "get": { - "summary": "Get Cookies", - "operationId": "get_cookies_cookies__get", - "parameters": [ - { - "name": "session_id", - "in": "cookie", - "required": False, - "schema": {"title": "Session Id", "type": "string"}, - }, - { - "name": "tracking_id", - "in": "cookie", - "required": False, - "schema": { - "title": "Tracking Id", - "minLength": 10, - "type": "string", - }, - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/items-embed/": { - "post": { - "summary": "Create Item Embed", - "operationId": "create_item_embed_items_embed__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_item_embed_items_embed__post" - } - ], - "title": "Body", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/form-data/": { - "post": { - "summary": "Submit Form", - "operationId": "submit_form_form_data__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/Body_submit_form_form_data__post" - } - ], - "title": "Body", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/upload/": { - "post": { - "summary": "Upload File", - "operationId": "upload_file_upload__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/Body_upload_file_upload__post" - } - ], - "title": "Body", - }, - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/upload-multiple/": { - "post": { - "summary": "Upload Multiple Files", - "operationId": "upload_multiple_files_upload_multiple__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/Body_upload_multiple_files_upload_multiple__post" - } - ], - "title": "Body", - } - } - }, - "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_item_embed_items_embed__post": { - "properties": { - "item": { - "allOf": [{"$ref": "#/components/schemas/Item"}], - "title": "Item", - } - }, - "type": "object", - "required": ["item"], - "title": "Body_create_item_embed_items_embed__post", - }, - "Body_submit_form_form_data__post": { - "properties": { - "username": { - "type": "string", - "maxLength": 50, - "minLength": 3, - "title": "Username", - }, - "password": { - "type": "string", - "minLength": 8, - "title": "Password", - }, - "email": {"type": "string", "title": "Email"}, - }, - "type": "object", - "required": ["username", "password"], - "title": "Body_submit_form_form_data__post", - }, - "Body_update_item_items__item_id__put": { - "properties": { - "item": { - "allOf": [{"$ref": "#/components/schemas/Item"}], - "title": "Item", - }, - "importance": { - "type": "integer", - "maximum": 10.0, - "exclusiveMinimum": 0.0, - "title": "Importance", - }, - }, - "type": "object", - "required": ["item", "importance"], - "title": "Body_update_item_items__item_id__put", - }, - "Body_upload_file_upload__post": { - "properties": { - "file": { - "type": "string", - "format": "binary", - "title": "File", - }, - "description": {"type": "string", "title": "Description"}, - }, - "type": "object", - "required": ["file"], - "title": "Body_upload_file_upload__post", - }, - "Body_upload_multiple_files_upload_multiple__post": { - "properties": { - "files": { - "items": {"type": "string", "format": "binary"}, - "type": "array", - "title": "Files", - }, - "note": {"type": "string", "title": "Note", "default": ""}, - }, - "type": "object", - "required": ["files"], - "title": "Body_upload_multiple_files_upload_multiple__post", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "price": {"type": "number", "title": "Price"}, - "description": {"type": "string", "title": "Description"}, - }, - "type": "object", - "required": ["name", "price"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "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_datetime_custom_encoder.py b/tests/test_datetime_custom_encoder.py index 56b6780f04..f154ede029 100644 --- a/tests/test_datetime_custom_encoder.py +++ b/tests/test_datetime_custom_encoder.py @@ -1,12 +1,9 @@ -import warnings from datetime import datetime, timezone from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel -from .utils import needs_pydanticv1 - def test_pydanticv2(): from pydantic import field_serializer @@ -29,34 +26,3 @@ def test_pydanticv2(): with client: response = client.get("/model") assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"} - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_pydanticv1(): - from pydantic import v1 - - class ModelWithDatetimeField(v1.BaseModel): - dt_field: datetime - - class Config: - json_encoders = { - datetime: lambda dt: dt.replace( - microsecond=0, tzinfo=timezone.utc - ).isoformat() - } - - app = FastAPI() - model = ModelWithDatetimeField(dt_field=datetime(2019, 1, 1, 8)) - - with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.get("/model", response_model=ModelWithDatetimeField) - def get_model(): - return model - - client = TestClient(app) - with client: - response = client.get("/model") - assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"} diff --git a/tests/test_filter_pydantic_sub_model/__init__.py b/tests/test_filter_pydantic_sub_model/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/test_filter_pydantic_sub_model/app_pv1.py b/tests/test_filter_pydantic_sub_model/app_pv1.py deleted file mode 100644 index d6f2ce7d2d..0000000000 --- a/tests/test_filter_pydantic_sub_model/app_pv1.py +++ /dev/null @@ -1,45 +0,0 @@ -import warnings -from typing import Optional - -from fastapi import Depends, FastAPI -from pydantic.v1 import BaseModel, validator - -app = FastAPI() - - -class ModelB(BaseModel): - username: str - - -class ModelC(ModelB): - password: str - - -class ModelA(BaseModel): - name: str - description: Optional[str] = None - model_b: ModelB - tags: dict[str, str] = {} - - @validator("name") - def lower_username(cls, name: str, values): - if not name.endswith("A"): - raise ValueError("name must end in A") - return name - - -async def get_model_c() -> ModelC: - return ModelC(username="test-user", password="test-password") - - -with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.get("/model/{name}", response_model=ModelA) - async def get_model_a(name: str, model_c=Depends(get_model_c)): - return { - "name": name, - "description": "model-a-desc", - "model_b": model_c, - "tags": {"key1": "value1", "key2": "value2"}, - } diff --git a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py b/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py deleted file mode 100644 index b464b4f572..0000000000 --- a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py +++ /dev/null @@ -1,146 +0,0 @@ -import pytest -from fastapi.exceptions import ResponseValidationError -from fastapi.testclient import TestClient -from inline_snapshot import snapshot - -from ..utils import needs_pydanticv1 - - -@pytest.fixture(name="client") -def get_client(): - from .app_pv1 import app - - client = TestClient(app) - return client - - -@needs_pydanticv1 -def test_filter_sub_model(client: TestClient): - response = client.get("/model/modelA") - assert response.status_code == 200, response.text - assert response.json() == { - "name": "modelA", - "description": "model-a-desc", - "model_b": {"username": "test-user"}, - "tags": {"key1": "value1", "key2": "value2"}, - } - - -@needs_pydanticv1 -def test_validator_is_cloned(client: TestClient): - with pytest.raises(ResponseValidationError) as err: - client.get("/model/modelX") - assert err.value.errors() == [ - { - "loc": ("response", "name"), - "msg": "name must end in A", - "type": "value_error", - } - ] - - -@needs_pydanticv1 -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": { - "/model/{name}": { - "get": { - "summary": "Get Model A", - "operationId": "get_model_a_model__name__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Name", "type": "string"}, - "name": "name", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModelA" - } - } - }, - }, - "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" - }, - } - }, - }, - "ModelA": { - "title": "ModelA", - "required": ["name", "model_b"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "model_b": {"$ref": "#/components/schemas/ModelB"}, - "tags": { - "additionalProperties": {"type": "string"}, - "type": "object", - "title": "Tags", - "default": {}, - }, - }, - }, - "ModelB": { - "title": "ModelB", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "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"}, - }, - }, - } - }, - } - ) diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py index dee5955544..eb7939b69a 100644 --- a/tests/test_get_model_definitions_formfeed_escape.py +++ b/tests/test_get_model_definitions_formfeed_escape.py @@ -1,25 +1,12 @@ -import warnings - import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot -from .utils import needs_pydanticv1 - -@pytest.fixture( - name="client", - params=[ - pytest.param("pydantic-v1", marks=needs_pydanticv1), - "pydantic-v2", - ], -) -def client_fixture(request: pytest.FixtureRequest) -> TestClient: - if request.param == "pydantic-v1": - from pydantic.v1 import BaseModel - else: - from pydantic import BaseModel +@pytest.fixture(name="client") +def client_fixture() -> TestClient: + from pydantic import BaseModel class Address(BaseModel): """ @@ -38,28 +25,12 @@ def client_fixture(request: pytest.FixtureRequest) -> TestClient: app = FastAPI() - if request.param == "pydantic-v1": - with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.get("/facilities/{facility_id}") - def get_facility(facility_id: str) -> Facility: - return Facility( - id=facility_id, - address=Address( - line_1="123 Main St", city="Anytown", state_province="CA" - ), - ) - else: - - @app.get("/facilities/{facility_id}") - def get_facility(facility_id: str) -> Facility: - return Facility( - id=facility_id, - address=Address( - line_1="123 Main St", city="Anytown", state_province="CA" - ), - ) + @app.get("/facilities/{facility_id}") + def get_facility(facility_id: str) -> Facility: + return Facility( + id=facility_id, + address=Address(line_1="123 Main St", city="Anytown", state_province="CA"), + ) client = TestClient(app) return client diff --git a/tests/test_inherited_custom_class.py b/tests/test_inherited_custom_class.py index 7f29fe33ed..8cf8952f92 100644 --- a/tests/test_inherited_custom_class.py +++ b/tests/test_inherited_custom_class.py @@ -5,8 +5,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel -from .utils import needs_pydanticv1 - class MyUuid: def __init__(self, uuid_string: str): @@ -67,46 +65,3 @@ def test_pydanticv2(): assert response_pydantic.json() == { "a_uuid": "b8799909-f914-42de-91bc-95c819218d01" } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_pydanticv1(): - from pydantic import v1 - - app = FastAPI() - - @app.get("/fast_uuid") - def return_fast_uuid(): - asyncpg_uuid = MyUuid("a10ff360-3b1e-4984-a26f-d3ab460bdb51") - assert isinstance(asyncpg_uuid, uuid.UUID) - assert type(asyncpg_uuid) is not uuid.UUID - with pytest.raises(TypeError): - vars(asyncpg_uuid) - return {"fast_uuid": asyncpg_uuid} - - class SomeCustomClass(v1.BaseModel): - class Config: - arbitrary_types_allowed = True - json_encoders = {uuid.UUID: str} - - a_uuid: MyUuid - - @app.get("/get_custom_class") - def return_some_user(): - # Test that the fix also works for custom pydantic classes - return SomeCustomClass(a_uuid=MyUuid("b8799909-f914-42de-91bc-95c819218d01")) - - client = TestClient(app) - - with client: - response_simple = client.get("/fast_uuid") - response_pydantic = client.get("/get_custom_class") - - assert response_simple.json() == { - "fast_uuid": "a10ff360-3b1e-4984-a26f-d3ab460bdb51" - } - - assert response_pydantic.json() == { - "a_uuid": "b8799909-f914-42de-91bc-95c819218d01" - } diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 81bf94ece0..4528dff440 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -1,3 +1,4 @@ +import warnings from collections import deque from dataclasses import dataclass from datetime import datetime, timezone @@ -5,15 +6,14 @@ from decimal import Decimal from enum import Enum from math import isinf, isnan from pathlib import PurePath, PurePosixPath, PureWindowsPath -from typing import Optional +from typing import Optional, TypedDict import pytest from fastapi._compat import Undefined from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import PydanticV1NotSupportedError from pydantic import BaseModel, Field, ValidationError -from .utils import needs_pydanticv1 - class Person: def __init__(self, name: str): @@ -156,29 +156,17 @@ def test_encode_custom_json_encoders_model_pydanticv2(): assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"} -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_encode_custom_json_encoders_model_pydanticv1(): - from pydantic import v1 +def test_json_encoder_error_with_pydanticv1(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", UserWarning) + from pydantic import v1 - class ModelWithCustomEncoder(v1.BaseModel): - dt_field: datetime + class ModelV1(v1.BaseModel): + name: str - class Config: - json_encoders = { - datetime: lambda dt: dt.replace( - microsecond=0, tzinfo=timezone.utc - ).isoformat() - } - - class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder): - class Config: - pass - - model = ModelWithCustomEncoder(dt_field=datetime(2019, 1, 1, 8)) - assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"} - subclass_model = ModelWithCustomEncoderSubclass(dt_field=datetime(2019, 1, 1, 8)) - assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"} + data = ModelV1(name="test") + with pytest.raises(PydanticV1NotSupportedError): + jsonable_encoder(data) def test_encode_model_with_config(): @@ -214,25 +202,27 @@ def test_encode_model_with_default(): } -@needs_pydanticv1 def test_custom_encoders(): - from pydantic import v1 - class safe_datetime(datetime): pass - class MyModel(v1.BaseModel): + class MyDict(TypedDict): dt_field: safe_datetime - instance = MyModel(dt_field=safe_datetime.now()) + instance = MyDict(dt_field=safe_datetime.now()) encoded_instance = jsonable_encoder( instance, custom_encoder={safe_datetime: lambda o: o.strftime("%H:%M:%S")} ) - assert encoded_instance["dt_field"] == instance.dt_field.strftime("%H:%M:%S") + assert encoded_instance["dt_field"] == instance["dt_field"].strftime("%H:%M:%S") + + encoded_instance = jsonable_encoder( + instance, custom_encoder={datetime: lambda o: o.strftime("%H:%M:%S")} + ) + assert encoded_instance["dt_field"] == instance["dt_field"].strftime("%H:%M:%S") encoded_instance2 = jsonable_encoder(instance) - assert encoded_instance2["dt_field"] == instance.dt_field.isoformat() + assert encoded_instance2["dt_field"] == instance["dt_field"].isoformat() def test_custom_enum_encoders(): @@ -287,17 +277,6 @@ def test_encode_pure_path(): assert jsonable_encoder({"path": test_path}) == {"path": str(test_path)} -@needs_pydanticv1 -def test_encode_root(): - from pydantic import v1 - - class ModelWithRoot(v1.BaseModel): - __root__: str - - model = ModelWithRoot(__root__="Foo") - assert jsonable_encoder(model) == "Foo" - - def test_decimal_encoder_float(): data = {"value": Decimal(1.23)} assert jsonable_encoder(data) == {"value": 1.23} diff --git a/tests/test_pydantic_v1_deprecation_warnings.py b/tests/test_pydantic_v1_deprecation_warnings.py deleted file mode 100644 index 89ca6a8658..0000000000 --- a/tests/test_pydantic_v1_deprecation_warnings.py +++ /dev/null @@ -1,99 +0,0 @@ -import sys - -import pytest -from fastapi.exceptions import FastAPIDeprecationWarning - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - -from fastapi import FastAPI -from fastapi._compat.v1 import BaseModel -from fastapi.testclient import TestClient - - -def test_warns_pydantic_v1_model_in_endpoint_param() -> None: - class ParamModelV1(BaseModel): - name: str - - app = FastAPI() - - with pytest.warns( - FastAPIDeprecationWarning, - match=r"pydantic\.v1 is deprecated.*Please update the param data:", - ): - - @app.post("/param") - def endpoint(data: ParamModelV1): - return data - - client = TestClient(app) - response = client.post("/param", json={"name": "test"}) - assert response.status_code == 200, response.text - assert response.json() == {"name": "test"} - - -def test_warns_pydantic_v1_model_in_return_type() -> None: - class ReturnModelV1(BaseModel): - name: str - - app = FastAPI() - - with pytest.warns( - FastAPIDeprecationWarning, - match=r"pydantic\.v1 is deprecated.*Please update the response model", - ): - - @app.get("/return") - def endpoint() -> ReturnModelV1: - return ReturnModelV1(name="test") - - client = TestClient(app) - response = client.get("/return") - assert response.status_code == 200, response.text - assert response.json() == {"name": "test"} - - -def test_warns_pydantic_v1_model_in_response_model() -> None: - class ResponseModelV1(BaseModel): - name: str - - app = FastAPI() - - with pytest.warns( - FastAPIDeprecationWarning, - match=r"pydantic\.v1 is deprecated.*Please update the response model", - ): - - @app.get("/response-model", response_model=ResponseModelV1) - def endpoint(): - return {"name": "test"} - - client = TestClient(app) - response = client.get("/response-model") - assert response.status_code == 200, response.text - assert response.json() == {"name": "test"} - - -def test_warns_pydantic_v1_model_in_additional_responses_model() -> None: - class ErrorModelV1(BaseModel): - detail: str - - app = FastAPI() - - with pytest.warns( - FastAPIDeprecationWarning, - match=r"pydantic\.v1 is deprecated.*In responses=\{\}, please update", - ): - - @app.get( - "/responses", response_model=None, responses={400: {"model": ErrorModelV1}} - ) - def endpoint(): - return {"ok": True} - - client = TestClient(app) - response = client.get("/responses") - assert response.status_code == 200, response.text - assert response.json() == {"ok": True} diff --git a/tests/test_pydantic_v1_error.py b/tests/test_pydantic_v1_error.py new file mode 100644 index 0000000000..13229a3137 --- /dev/null +++ b/tests/test_pydantic_v1_error.py @@ -0,0 +1,97 @@ +import sys +import warnings +from typing import Union + +import pytest + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +from fastapi import FastAPI +from fastapi.exceptions import PydanticV1NotSupportedError + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", UserWarning) + from pydantic.v1 import BaseModel + + +def test_raises_pydantic_v1_model_in_endpoint_param() -> None: + class ParamModelV1(BaseModel): + name: str + + app = FastAPI() + + with pytest.raises(PydanticV1NotSupportedError): + + @app.post("/param") + def endpoint(data: ParamModelV1): # pragma: no cover + return data + + +def test_raises_pydantic_v1_model_in_return_type() -> None: + class ReturnModelV1(BaseModel): + name: str + + app = FastAPI() + + with pytest.raises(PydanticV1NotSupportedError): + + @app.get("/return") + def endpoint() -> ReturnModelV1: # pragma: no cover + return ReturnModelV1(name="test") + + +def test_raises_pydantic_v1_model_in_response_model() -> None: + class ResponseModelV1(BaseModel): + name: str + + app = FastAPI() + + with pytest.raises(PydanticV1NotSupportedError): + + @app.get("/response-model", response_model=ResponseModelV1) + def endpoint(): # pragma: no cover + return {"name": "test"} + + +def test_raises_pydantic_v1_model_in_additional_responses_model() -> None: + class ErrorModelV1(BaseModel): + detail: str + + app = FastAPI() + + with pytest.raises(PydanticV1NotSupportedError): + + @app.get( + "/responses", response_model=None, responses={400: {"model": ErrorModelV1}} + ) + def endpoint(): # pragma: no cover + return {"ok": True} + + +def test_raises_pydantic_v1_model_in_union() -> None: + class ModelV1A(BaseModel): + name: str + + app = FastAPI() + + with pytest.raises(PydanticV1NotSupportedError): + + @app.post("/union") + def endpoint(data: Union[dict, ModelV1A]): # pragma: no cover + return data + + +def test_raises_pydantic_v1_model_in_sequence() -> None: + class ModelV1A(BaseModel): + name: str + + app = FastAPI() + + with pytest.raises(PydanticV1NotSupportedError): + + @app.post("/sequence") + def endpoint(data: list[ModelV1A]): # pragma: no cover + return data diff --git a/tests/test_pydantic_v1_v2_01.py b/tests/test_pydantic_v1_v2_01.py deleted file mode 100644 index 4868e5d223..0000000000 --- a/tests/test_pydantic_v1_v2_01.py +++ /dev/null @@ -1,439 +0,0 @@ -import sys -import warnings -from typing import Any, Union - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - -from fastapi import FastAPI -from fastapi._compat.v1 import BaseModel -from fastapi.testclient import TestClient -from inline_snapshot import snapshot - - -class SubItem(BaseModel): - name: str - - -class Item(BaseModel): - title: str - size: int - description: Union[str, None] = None - sub: SubItem - multi: list[SubItem] = [] - - -app = FastAPI() - -with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.post("/simple-model") - def handle_simple_model(data: SubItem) -> SubItem: - return data - - @app.post("/simple-model-filter", response_model=SubItem) - def handle_simple_model_filter(data: SubItem) -> Any: - extended_data = data.dict() - extended_data.update({"secret_price": 42}) - return extended_data - - @app.post("/item") - def handle_item(data: Item) -> Item: - return data - - @app.post("/item-filter", response_model=Item) - def handle_item_filter(data: Item) -> Any: - extended_data = data.dict() - extended_data.update({"secret_data": "classified", "internal_id": 12345}) - extended_data["sub"].update({"internal_id": 67890}) - return extended_data - - -client = TestClient(app) - - -def test_old_simple_model(): - response = client.post( - "/simple-model", - json={"name": "Foo"}, - ) - assert response.status_code == 200, response.text - assert response.json() == {"name": "Foo"} - - -def test_old_simple_model_validation_error(): - response = client.post( - "/simple-model", - json={"wrong_name": "Foo"}, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "name"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_old_simple_model_filter(): - response = client.post( - "/simple-model-filter", - json={"name": "Foo"}, - ) - assert response.status_code == 200, response.text - assert response.json() == {"name": "Foo"} - - -def test_item_model(): - response = client.post( - "/item", - json={ - "title": "Test Item", - "size": 100, - "description": "This is a test item", - "sub": {"name": "SubItem1"}, - "multi": [{"name": "Multi1"}, {"name": "Multi2"}], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "Test Item", - "size": 100, - "description": "This is a test item", - "sub": {"name": "SubItem1"}, - "multi": [{"name": "Multi1"}, {"name": "Multi2"}], - } - - -def test_item_model_minimal(): - response = client.post( - "/item", - json={"title": "Minimal Item", "size": 50, "sub": {"name": "SubMin"}}, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "Minimal Item", - "size": 50, - "description": None, - "sub": {"name": "SubMin"}, - "multi": [], - } - - -def test_item_model_validation_errors(): - response = client.post( - "/item", - json={"title": "Missing fields"}, - ) - assert response.status_code == 422, response.text - error_detail = response.json()["detail"] - assert len(error_detail) == 2 - assert { - "loc": ["body", "size"], - "msg": "field required", - "type": "value_error.missing", - } in error_detail - assert { - "loc": ["body", "sub"], - "msg": "field required", - "type": "value_error.missing", - } in error_detail - - -def test_item_model_nested_validation_error(): - response = client.post( - "/item", - json={"title": "Test Item", "size": 100, "sub": {"wrong_field": "test"}}, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "sub", "name"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_item_model_invalid_type(): - response = client.post( - "/item", - json={"title": "Test Item", "size": "not_a_number", "sub": {"name": "SubItem"}}, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "size"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) - - -def test_item_filter(): - response = client.post( - "/item-filter", - json={ - "title": "Filtered Item", - "size": 200, - "description": "Test filtering", - "sub": {"name": "SubFiltered"}, - "multi": [], - }, - ) - assert response.status_code == 200, response.text - result = response.json() - assert result == { - "title": "Filtered Item", - "size": 200, - "description": "Test filtering", - "sub": {"name": "SubFiltered"}, - "multi": [], - } - assert "secret_data" not in result - assert "internal_id" not in result - - -def test_openapi_schema(): - 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": { - "/simple-model": { - "post": { - "summary": "Handle Simple Model", - "operationId": "handle_simple_model_simple_model_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/SubItem"} - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SubItem" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/simple-model-filter": { - "post": { - "summary": "Handle Simple Model Filter", - "operationId": "handle_simple_model_filter_simple_model_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/SubItem"} - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SubItem" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/item": { - "post": { - "summary": "Handle Item", - "operationId": "handle_item_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Data", - } - } - }, - "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" - } - } - }, - }, - }, - } - }, - "/item-filter": { - "post": { - "summary": "Handle Item Filter", - "operationId": "handle_item_filter_item_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Data", - } - } - }, - "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" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "title": {"type": "string", "title": "Title"}, - "size": {"type": "integer", "title": "Size"}, - "description": {"type": "string", "title": "Description"}, - "sub": {"$ref": "#/components/schemas/SubItem"}, - "multi": { - "items": {"$ref": "#/components/schemas/SubItem"}, - "type": "array", - "title": "Multi", - "default": [], - }, - }, - "type": "object", - "required": ["title", "size", "sub"], - "title": "Item", - }, - "SubItem": { - "properties": {"name": {"type": "string", "title": "Name"}}, - "type": "object", - "required": ["name"], - "title": "SubItem", - }, - "ValidationError": { - "properties": { - "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_pydantic_v1_v2_list.py b/tests/test_pydantic_v1_v2_list.py deleted file mode 100644 index 108f231faa..0000000000 --- a/tests/test_pydantic_v1_v2_list.py +++ /dev/null @@ -1,682 +0,0 @@ -import sys -import warnings -from typing import Any, Union - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - -from fastapi import FastAPI -from fastapi._compat.v1 import BaseModel -from fastapi.testclient import TestClient -from inline_snapshot import snapshot - - -class SubItem(BaseModel): - name: str - - -class Item(BaseModel): - title: str - size: int - description: Union[str, None] = None - sub: SubItem - multi: list[SubItem] = [] - - -app = FastAPI() - - -with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.post("/item") - def handle_item(data: Item) -> list[Item]: - return [data, data] - - @app.post("/item-filter", response_model=list[Item]) - def handle_item_filter(data: Item) -> Any: - extended_data = data.dict() - extended_data.update({"secret_data": "classified", "internal_id": 12345}) - extended_data["sub"].update({"internal_id": 67890}) - return [extended_data, extended_data] - - @app.post("/item-list") - def handle_item_list(data: list[Item]) -> Item: - if data: - return data[0] - return Item(title="", size=0, sub=SubItem(name="")) - - @app.post("/item-list-filter", response_model=Item) - def handle_item_list_filter(data: list[Item]) -> Any: - if data: - extended_data = data[0].dict() - extended_data.update({"secret_data": "classified", "internal_id": 12345}) - extended_data["sub"].update({"internal_id": 67890}) - return extended_data - return Item(title="", size=0, sub=SubItem(name="")) - - @app.post("/item-list-to-list") - def handle_item_list_to_list(data: list[Item]) -> list[Item]: - return data - - @app.post("/item-list-to-list-filter", response_model=list[Item]) - def handle_item_list_to_list_filter(data: list[Item]) -> Any: - if data: - extended_data = data[0].dict() - extended_data.update({"secret_data": "classified", "internal_id": 12345}) - extended_data["sub"].update({"internal_id": 67890}) - return [extended_data, extended_data] - return [] - - -client = TestClient(app) - - -def test_item_to_list(): - response = client.post( - "/item", - json={ - "title": "Test Item", - "size": 100, - "description": "This is a test item", - "sub": {"name": "SubItem1"}, - "multi": [{"name": "Multi1"}, {"name": "Multi2"}], - }, - ) - assert response.status_code == 200, response.text - result = response.json() - assert isinstance(result, list) - assert len(result) == 2 - for item in result: - assert item == { - "title": "Test Item", - "size": 100, - "description": "This is a test item", - "sub": {"name": "SubItem1"}, - "multi": [{"name": "Multi1"}, {"name": "Multi2"}], - } - - -def test_item_to_list_filter(): - response = client.post( - "/item-filter", - json={ - "title": "Filtered Item", - "size": 200, - "description": "Test filtering", - "sub": {"name": "SubFiltered"}, - "multi": [], - }, - ) - assert response.status_code == 200, response.text - result = response.json() - assert isinstance(result, list) - assert len(result) == 2 - for item in result: - assert item == { - "title": "Filtered Item", - "size": 200, - "description": "Test filtering", - "sub": {"name": "SubFiltered"}, - "multi": [], - } - # Verify secret fields are filtered out - assert "secret_data" not in item - assert "internal_id" not in item - assert "internal_id" not in item["sub"] - - -def test_list_to_item(): - response = client.post( - "/item-list", - json=[ - {"title": "First Item", "size": 50, "sub": {"name": "First Sub"}}, - {"title": "Second Item", "size": 75, "sub": {"name": "Second Sub"}}, - ], - ) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "First Item", - "size": 50, - "description": None, - "sub": {"name": "First Sub"}, - "multi": [], - } - - -def test_list_to_item_empty(): - response = client.post( - "/item-list", - json=[], - ) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "", - "size": 0, - "description": None, - "sub": {"name": ""}, - "multi": [], - } - - -def test_list_to_item_filter(): - response = client.post( - "/item-list-filter", - json=[ - { - "title": "First Item", - "size": 100, - "sub": {"name": "First Sub"}, - "multi": [{"name": "Multi1"}], - }, - {"title": "Second Item", "size": 200, "sub": {"name": "Second Sub"}}, - ], - ) - assert response.status_code == 200, response.text - result = response.json() - assert result == { - "title": "First Item", - "size": 100, - "description": None, - "sub": {"name": "First Sub"}, - "multi": [{"name": "Multi1"}], - } - # Verify secret fields are filtered out - assert "secret_data" not in result - assert "internal_id" not in result - - -def test_list_to_item_filter_no_data(): - response = client.post("/item-list-filter", json=[]) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "", - "size": 0, - "description": None, - "sub": {"name": ""}, - "multi": [], - } - - -def test_list_to_list(): - input_items = [ - {"title": "Item 1", "size": 10, "sub": {"name": "Sub1"}}, - { - "title": "Item 2", - "size": 20, - "description": "Second item", - "sub": {"name": "Sub2"}, - "multi": [{"name": "M1"}, {"name": "M2"}], - }, - {"title": "Item 3", "size": 30, "sub": {"name": "Sub3"}}, - ] - response = client.post( - "/item-list-to-list", - json=input_items, - ) - assert response.status_code == 200, response.text - result = response.json() - assert isinstance(result, list) - assert len(result) == 3 - assert result[0] == { - "title": "Item 1", - "size": 10, - "description": None, - "sub": {"name": "Sub1"}, - "multi": [], - } - assert result[1] == { - "title": "Item 2", - "size": 20, - "description": "Second item", - "sub": {"name": "Sub2"}, - "multi": [{"name": "M1"}, {"name": "M2"}], - } - assert result[2] == { - "title": "Item 3", - "size": 30, - "description": None, - "sub": {"name": "Sub3"}, - "multi": [], - } - - -def test_list_to_list_filter(): - response = client.post( - "/item-list-to-list-filter", - json=[{"title": "Item 1", "size": 100, "sub": {"name": "Sub1"}}], - ) - assert response.status_code == 200, response.text - result = response.json() - assert isinstance(result, list) - assert len(result) == 2 - for item in result: - assert item == { - "title": "Item 1", - "size": 100, - "description": None, - "sub": {"name": "Sub1"}, - "multi": [], - } - # Verify secret fields are filtered out - assert "secret_data" not in item - assert "internal_id" not in item - - -def test_list_to_list_filter_no_data(): - response = client.post( - "/item-list-to-list-filter", - json=[], - ) - assert response.status_code == 200, response.text - assert response.json() == [] - - -def test_list_validation_error(): - response = client.post( - "/item-list", - json=[ - {"title": "Valid Item", "size": 100, "sub": {"name": "Sub1"}}, - { - "title": "Invalid Item" - # Missing required fields: size and sub - }, - ], - ) - assert response.status_code == 422, response.text - error_detail = response.json()["detail"] - assert len(error_detail) == 2 - assert { - "loc": ["body", 1, "size"], - "msg": "field required", - "type": "value_error.missing", - } in error_detail - assert { - "loc": ["body", 1, "sub"], - "msg": "field required", - "type": "value_error.missing", - } in error_detail - - -def test_list_nested_validation_error(): - response = client.post( - "/item-list", - json=[ - {"title": "Item with bad sub", "size": 100, "sub": {"wrong_field": "value"}} - ], - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", 0, "sub", "name"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_list_type_validation_error(): - response = client.post( - "/item-list", - json=[{"title": "Item", "size": "not_a_number", "sub": {"name": "Sub"}}], - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", 0, "size"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) - - -def test_invalid_list_structure(): - response = client.post( - "/item-list", - json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}}, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid list", - "type": "type_error.list", - } - ] - } - ) - - -def test_openapi_schema(): - 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": { - "/item": { - "post": { - "summary": "Handle Item", - "operationId": "handle_item_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Item" - }, - "type": "array", - "title": "Response Handle Item Item Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/item-filter": { - "post": { - "summary": "Handle Item Filter", - "operationId": "handle_item_filter_item_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Item" - }, - "type": "array", - "title": "Response Handle Item Filter Item Filter Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/item-list": { - "post": { - "summary": "Handle Item List", - "operationId": "handle_item_list_item_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Data", - } - } - }, - "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" - } - } - }, - }, - }, - } - }, - "/item-list-filter": { - "post": { - "summary": "Handle Item List Filter", - "operationId": "handle_item_list_filter_item_list_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Data", - } - } - }, - "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" - } - } - }, - }, - }, - } - }, - "/item-list-to-list": { - "post": { - "summary": "Handle Item List To List", - "operationId": "handle_item_list_to_list_item_list_to_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Item" - }, - "type": "array", - "title": "Response Handle Item List To List Item List To List Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/item-list-to-list-filter": { - "post": { - "summary": "Handle Item List To List Filter", - "operationId": "handle_item_list_to_list_filter_item_list_to_list_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Item" - }, - "type": "array", - "title": "Response Handle Item List To List Filter Item List To List Filter Post", - } - } - }, - }, - "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": { - "title": {"type": "string", "title": "Title"}, - "size": {"type": "integer", "title": "Size"}, - "description": {"type": "string", "title": "Description"}, - "sub": {"$ref": "#/components/schemas/SubItem"}, - "multi": { - "items": {"$ref": "#/components/schemas/SubItem"}, - "type": "array", - "title": "Multi", - "default": [], - }, - }, - "type": "object", - "required": ["title", "size", "sub"], - "title": "Item", - }, - "SubItem": { - "properties": {"name": {"type": "string", "title": "Name"}}, - "type": "object", - "required": ["name"], - "title": "SubItem", - }, - "ValidationError": { - "properties": { - "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_pydantic_v1_v2_mixed.py b/tests/test_pydantic_v1_v2_mixed.py deleted file mode 100644 index 895835a4c0..0000000000 --- a/tests/test_pydantic_v1_v2_mixed.py +++ /dev/null @@ -1,1408 +0,0 @@ -import sys -import warnings -from typing import Any, Union - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - -from fastapi import FastAPI -from fastapi._compat.v1 import BaseModel -from fastapi.testclient import TestClient -from inline_snapshot import snapshot -from pydantic import BaseModel as NewBaseModel - - -class SubItem(BaseModel): - name: str - - -class Item(BaseModel): - title: str - size: int - description: Union[str, None] = None - sub: SubItem - multi: list[SubItem] = [] - - -class NewSubItem(NewBaseModel): - new_sub_name: str - - -class NewItem(NewBaseModel): - new_title: str - new_size: int - new_description: Union[str, None] = None - new_sub: NewSubItem - new_multi: list[NewSubItem] = [] - - -app = FastAPI() - -with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.post("/v1-to-v2/item") - def handle_v1_item_to_v2(data: Item) -> NewItem: - return NewItem( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=NewSubItem(new_sub_name=data.sub.name), - new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], - ) - - @app.post("/v1-to-v2/item-filter", response_model=NewItem) - def handle_v1_item_to_v2_filter(data: Item) -> Any: - result = { - "new_title": data.title, - "new_size": data.size, - "new_description": data.description, - "new_sub": { - "new_sub_name": data.sub.name, - "new_sub_secret": "sub_hidden", - }, - "new_multi": [ - {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} - for s in data.multi - ], - "secret": "hidden_v1_to_v2", - } - return result - - @app.post("/v2-to-v1/item") - def handle_v2_item_to_v1(data: NewItem) -> Item: - return Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=SubItem(name=data.new_sub.new_sub_name), - multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], - ) - - @app.post("/v2-to-v1/item-filter", response_model=Item) - def handle_v2_item_to_v1_filter(data: NewItem) -> Any: - result = { - "title": data.new_title, - "size": data.new_size, - "description": data.new_description, - "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, - "multi": [ - {"name": s.new_sub_name, "sub_secret": "sub_hidden"} - for s in data.new_multi - ], - "secret": "hidden_v2_to_v1", - } - return result - - @app.post("/v1-to-v2/item-to-list") - def handle_v1_item_to_v2_list(data: Item) -> list[NewItem]: - converted = NewItem( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=NewSubItem(new_sub_name=data.sub.name), - new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], - ) - return [converted, converted] - - @app.post("/v1-to-v2/list-to-list") - def handle_v1_list_to_v2_list(data: list[Item]) -> list[NewItem]: - result = [] - for item in data: - result.append( - NewItem( - new_title=item.title, - new_size=item.size, - new_description=item.description, - new_sub=NewSubItem(new_sub_name=item.sub.name), - new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi], - ) - ) - return result - - @app.post("/v1-to-v2/list-to-list-filter", response_model=list[NewItem]) - def handle_v1_list_to_v2_list_filter(data: list[Item]) -> Any: - result = [] - for item in data: - converted = { - "new_title": item.title, - "new_size": item.size, - "new_description": item.description, - "new_sub": { - "new_sub_name": item.sub.name, - "new_sub_secret": "sub_hidden", - }, - "new_multi": [ - {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} - for s in item.multi - ], - "secret": "hidden_v2_to_v1", - } - result.append(converted) - return result - - @app.post("/v1-to-v2/list-to-item") - def handle_v1_list_to_v2_item(data: list[Item]) -> NewItem: - if data: - item = data[0] - return NewItem( - new_title=item.title, - new_size=item.size, - new_description=item.description, - new_sub=NewSubItem(new_sub_name=item.sub.name), - new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi], - ) - return NewItem(new_title="", new_size=0, new_sub=NewSubItem(new_sub_name="")) - - @app.post("/v2-to-v1/item-to-list") - def handle_v2_item_to_v1_list(data: NewItem) -> list[Item]: - converted = Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=SubItem(name=data.new_sub.new_sub_name), - multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], - ) - return [converted, converted] - - @app.post("/v2-to-v1/list-to-list") - def handle_v2_list_to_v1_list(data: list[NewItem]) -> list[Item]: - result = [] - for item in data: - result.append( - Item( - title=item.new_title, - size=item.new_size, - description=item.new_description, - sub=SubItem(name=item.new_sub.new_sub_name), - multi=[SubItem(name=s.new_sub_name) for s in item.new_multi], - ) - ) - return result - - @app.post("/v2-to-v1/list-to-list-filter", response_model=list[Item]) - def handle_v2_list_to_v1_list_filter(data: list[NewItem]) -> Any: - result = [] - for item in data: - converted = { - "title": item.new_title, - "size": item.new_size, - "description": item.new_description, - "sub": { - "name": item.new_sub.new_sub_name, - "sub_secret": "sub_hidden", - }, - "multi": [ - {"name": s.new_sub_name, "sub_secret": "sub_hidden"} - for s in item.new_multi - ], - "secret": "hidden_v2_to_v1", - } - result.append(converted) - return result - - @app.post("/v2-to-v1/list-to-item") - def handle_v2_list_to_v1_item(data: list[NewItem]) -> Item: - if data: - item = data[0] - return Item( - title=item.new_title, - size=item.new_size, - description=item.new_description, - sub=SubItem(name=item.new_sub.new_sub_name), - multi=[SubItem(name=s.new_sub_name) for s in item.new_multi], - ) - return Item(title="", size=0, sub=SubItem(name="")) - - -client = TestClient(app) - - -def test_v1_to_v2_item(): - response = client.post( - "/v1-to-v2/item", - json={ - "title": "Old Item", - "size": 100, - "description": "V1 description", - "sub": {"name": "V1 Sub"}, - "multi": [{"name": "M1"}, {"name": "M2"}], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "new_title": "Old Item", - "new_size": 100, - "new_description": "V1 description", - "new_sub": {"new_sub_name": "V1 Sub"}, - "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}], - } - - -def test_v1_to_v2_item_minimal(): - response = client.post( - "/v1-to-v2/item", - json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}}, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "new_title": "Minimal", - "new_size": 50, - "new_description": None, - "new_sub": {"new_sub_name": "MinSub"}, - "new_multi": [], - } - - -def test_v1_to_v2_item_filter(): - response = client.post( - "/v1-to-v2/item-filter", - json={ - "title": "Filtered Item", - "size": 50, - "sub": {"name": "Sub"}, - "multi": [{"name": "Multi1"}], - }, - ) - assert response.status_code == 200, response.text - result = response.json() - assert result == snapshot( - { - "new_title": "Filtered Item", - "new_size": 50, - "new_description": None, - "new_sub": {"new_sub_name": "Sub"}, - "new_multi": [{"new_sub_name": "Multi1"}], - } - ) - # Verify secret fields are filtered out - assert "secret" not in result - assert "new_sub_secret" not in result["new_sub"] - assert "new_sub_secret" not in result["new_multi"][0] - - -def test_v2_to_v1_item(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "New Item", - "new_size": 200, - "new_description": "V2 description", - "new_sub": {"new_sub_name": "V2 Sub"}, - "new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "New Item", - "size": 200, - "description": "V2 description", - "sub": {"name": "V2 Sub"}, - "multi": [{"name": "N1"}, {"name": "N2"}], - } - - -def test_v2_to_v1_item_minimal(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "MinimalNew", - "new_size": 75, - "new_sub": {"new_sub_name": "MinNewSub"}, - }, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "MinimalNew", - "size": 75, - "description": None, - "sub": {"name": "MinNewSub"}, - "multi": [], - } - - -def test_v2_to_v1_item_filter(): - response = client.post( - "/v2-to-v1/item-filter", - json={ - "new_title": "Filtered New", - "new_size": 75, - "new_sub": {"new_sub_name": "NewSub"}, - "new_multi": [], - }, - ) - assert response.status_code == 200, response.text - result = response.json() - assert result == snapshot( - { - "title": "Filtered New", - "size": 75, - "description": None, - "sub": {"name": "NewSub"}, - "multi": [], - } - ) - # Verify secret fields are filtered out - assert "secret" not in result - assert "sub_secret" not in result["sub"] - - -def test_v1_item_to_v2_list(): - response = client.post( - "/v1-to-v2/item-to-list", - json={ - "title": "Single to List", - "size": 150, - "description": "Convert to list", - "sub": {"name": "Sub1"}, - "multi": [], - }, - ) - assert response.status_code == 200, response.text - result = response.json() - assert result == [ - { - "new_title": "Single to List", - "new_size": 150, - "new_description": "Convert to list", - "new_sub": {"new_sub_name": "Sub1"}, - "new_multi": [], - }, - { - "new_title": "Single to List", - "new_size": 150, - "new_description": "Convert to list", - "new_sub": {"new_sub_name": "Sub1"}, - "new_multi": [], - }, - ] - - -def test_v1_list_to_v2_list(): - response = client.post( - "/v1-to-v2/list-to-list", - json=[ - {"title": "Item1", "size": 10, "sub": {"name": "Sub1"}}, - { - "title": "Item2", - "size": 20, - "description": "Second item", - "sub": {"name": "Sub2"}, - "multi": [{"name": "M1"}, {"name": "M2"}], - }, - {"title": "Item3", "size": 30, "sub": {"name": "Sub3"}}, - ], - ) - assert response.status_code == 200, response.text - assert response.json() == [ - { - "new_title": "Item1", - "new_size": 10, - "new_description": None, - "new_sub": {"new_sub_name": "Sub1"}, - "new_multi": [], - }, - { - "new_title": "Item2", - "new_size": 20, - "new_description": "Second item", - "new_sub": {"new_sub_name": "Sub2"}, - "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}], - }, - { - "new_title": "Item3", - "new_size": 30, - "new_description": None, - "new_sub": {"new_sub_name": "Sub3"}, - "new_multi": [], - }, - ] - - -def test_v1_list_to_v2_list_filter(): - response = client.post( - "/v1-to-v2/list-to-list-filter", - json=[{"title": "FilterMe", "size": 30, "sub": {"name": "SubF"}}], - ) - assert response.status_code == 200, response.text - result = response.json() - assert result == snapshot( - [ - { - "new_title": "FilterMe", - "new_size": 30, - "new_description": None, - "new_sub": {"new_sub_name": "SubF"}, - "new_multi": [], - } - ] - ) - # Verify secret fields are filtered out - assert "secret" not in result[0] - assert "new_sub_secret" not in result[0]["new_sub"] - - -def test_v1_list_to_v2_item(): - response = client.post( - "/v1-to-v2/list-to-item", - json=[ - {"title": "First", "size": 100, "sub": {"name": "FirstSub"}}, - {"title": "Second", "size": 200, "sub": {"name": "SecondSub"}}, - ], - ) - assert response.status_code == 200, response.text - assert response.json() == { - "new_title": "First", - "new_size": 100, - "new_description": None, - "new_sub": {"new_sub_name": "FirstSub"}, - "new_multi": [], - } - - -def test_v1_list_to_v2_item_empty(): - response = client.post("/v1-to-v2/list-to-item", json=[]) - assert response.status_code == 200, response.text - assert response.json() == { - "new_title": "", - "new_size": 0, - "new_description": None, - "new_sub": {"new_sub_name": ""}, - "new_multi": [], - } - - -def test_v2_item_to_v1_list(): - response = client.post( - "/v2-to-v1/item-to-list", - json={ - "new_title": "Single New", - "new_size": 250, - "new_description": "New to list", - "new_sub": {"new_sub_name": "NewSub"}, - "new_multi": [], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == [ - { - "title": "Single New", - "size": 250, - "description": "New to list", - "sub": {"name": "NewSub"}, - "multi": [], - }, - { - "title": "Single New", - "size": 250, - "description": "New to list", - "sub": {"name": "NewSub"}, - "multi": [], - }, - ] - - -def test_v2_list_to_v1_list(): - response = client.post( - "/v2-to-v1/list-to-list", - json=[ - {"new_title": "New1", "new_size": 15, "new_sub": {"new_sub_name": "NS1"}}, - { - "new_title": "New2", - "new_size": 25, - "new_description": "Second new", - "new_sub": {"new_sub_name": "NS2"}, - "new_multi": [{"new_sub_name": "NM1"}], - }, - ], - ) - assert response.status_code == 200, response.text - assert response.json() == [ - { - "title": "New1", - "size": 15, - "description": None, - "sub": {"name": "NS1"}, - "multi": [], - }, - { - "title": "New2", - "size": 25, - "description": "Second new", - "sub": {"name": "NS2"}, - "multi": [{"name": "NM1"}], - }, - ] - - -def test_v2_list_to_v1_list_filter(): - response = client.post( - "/v2-to-v1/list-to-list-filter", - json=[ - { - "new_title": "FilterNew", - "new_size": 35, - "new_sub": {"new_sub_name": "NSF"}, - } - ], - ) - assert response.status_code == 200, response.text - result = response.json() - assert result == snapshot( - [ - { - "title": "FilterNew", - "size": 35, - "description": None, - "sub": {"name": "NSF"}, - "multi": [], - } - ] - ) - # Verify secret fields are filtered out - assert "secret" not in result[0] - assert "sub_secret" not in result[0]["sub"] - - -def test_v2_list_to_v1_item(): - response = client.post( - "/v2-to-v1/list-to-item", - json=[ - { - "new_title": "FirstNew", - "new_size": 300, - "new_sub": {"new_sub_name": "FNS"}, - }, - { - "new_title": "SecondNew", - "new_size": 400, - "new_sub": {"new_sub_name": "SNS"}, - }, - ], - ) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "FirstNew", - "size": 300, - "description": None, - "sub": {"name": "FNS"}, - "multi": [], - } - - -def test_v2_list_to_v1_item_empty(): - response = client.post("/v2-to-v1/list-to-item", json=[]) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "", - "size": 0, - "description": None, - "sub": {"name": ""}, - "multi": [], - } - - -def test_v1_to_v2_validation_error(): - response = client.post("/v1-to-v2/item", json={"title": "Missing fields"}) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "size"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "sub"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_v1_to_v2_nested_validation_error(): - response = client.post( - "/v1-to-v2/item", - json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}}, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "sub", "name"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_v1_to_v2_type_validation_error(): - response = client.post( - "/v1-to-v2/item", - json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}}, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "size"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) - - -def test_v2_to_v1_validation_error(): - response = client.post( - "/v2-to-v1/item", - json={"new_title": "Missing fields"}, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "new_size"], - "msg": "Field required", - "input": {"new_title": "Missing fields"}, - }, - { - "type": "missing", - "loc": ["body", "new_sub"], - "msg": "Field required", - "input": {"new_title": "Missing fields"}, - }, - ] - } - ) - - -def test_v2_to_v1_nested_validation_error(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "Bad sub", - "new_size": 200, - "new_sub": {"wrong_field": "value"}, - }, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "new_sub", "new_sub_name"], - "msg": "Field required", - "input": {"wrong_field": "value"}, - } - ] - } - ) - - -def test_v1_list_validation_error(): - response = client.post( - "/v1-to-v2/list-to-list", - json=[ - {"title": "Valid", "size": 10, "sub": {"name": "S"}}, - {"title": "Invalid"}, - ], - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", 1, "size"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", 1, "sub"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_v2_list_validation_error(): - response = client.post( - "/v2-to-v1/list-to-list", - json=[ - {"new_title": "Valid", "new_size": 10, "new_sub": {"new_sub_name": "NS"}}, - {"new_title": "Invalid"}, - ], - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "type": "missing", - "loc": ["body", 1, "new_size"], - "msg": "Field required", - "input": {"new_title": "Invalid"}, - }, - { - "type": "missing", - "loc": ["body", 1, "new_sub"], - "msg": "Field required", - "input": {"new_title": "Invalid"}, - }, - ] - } - ) - - -def test_invalid_list_structure_v1(): - response = client.post( - "/v1-to-v2/list-to-list", - json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}}, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid list", - "type": "type_error.list", - } - ] - } - ) - - -def test_invalid_list_structure_v2(): - response = client.post( - "/v2-to-v1/list-to-list", - json={ - "new_title": "Not a list", - "new_size": 100, - "new_sub": {"new_sub_name": "Sub"}, - }, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "type": "list_type", - "loc": ["body"], - "msg": "Input should be a valid list", - "input": { - "new_title": "Not a list", - "new_size": 100, - "new_sub": {"new_sub_name": "Sub"}, - }, - } - ] - } - ) - - -def test_openapi_schema(): - 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": { - "/v1-to-v2/item": { - "post": { - "summary": "Handle V1 Item To V2", - "operationId": "handle_v1_item_to_v2_v1_to_v2_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewItem" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v1-to-v2/item-filter": { - "post": { - "summary": "Handle V1 Item To V2 Filter", - "operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewItem" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/item": { - "post": { - "summary": "Handle V2 Item To V1", - "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/NewItem"} - } - }, - "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" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/item-filter": { - "post": { - "summary": "Handle V2 Item To V1 Filter", - "operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/NewItem"} - } - }, - "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" - } - } - }, - }, - }, - } - }, - "/v1-to-v2/item-to-list": { - "post": { - "summary": "Handle V1 Item To V2 List", - "operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/NewItem" - }, - "type": "array", - "title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v1-to-v2/list-to-list": { - "post": { - "summary": "Handle V1 List To V2 List", - "operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/NewItem" - }, - "type": "array", - "title": "Response Handle V1 List To V2 List V1 To V2 List To List Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v1-to-v2/list-to-list-filter": { - "post": { - "summary": "Handle V1 List To V2 List Filter", - "operationId": "handle_v1_list_to_v2_list_filter_v1_to_v2_list_to_list_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/NewItem" - }, - "type": "array", - "title": "Response Handle V1 List To V2 List Filter V1 To V2 List To List Filter Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v1-to-v2/list-to-item": { - "post": { - "summary": "Handle V1 List To V2 Item", - "operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewItem" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/item-to-list": { - "post": { - "summary": "Handle V2 Item To V1 List", - "operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/NewItem"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Item" - }, - "type": "array", - "title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/list-to-list": { - "post": { - "summary": "Handle V2 List To V1 List", - "operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/NewItem" - }, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Item" - }, - "type": "array", - "title": "Response Handle V2 List To V1 List V2 To V1 List To List Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/list-to-list-filter": { - "post": { - "summary": "Handle V2 List To V1 List Filter", - "operationId": "handle_v2_list_to_v1_list_filter_v2_to_v1_list_to_list_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/NewItem" - }, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Item" - }, - "type": "array", - "title": "Response Handle V2 List To V1 List Filter V2 To V1 List To List Filter Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/list-to-item": { - "post": { - "summary": "Handle V2 List To V1 Item", - "operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/NewItem" - }, - "type": "array", - "title": "Data", - } - } - }, - "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" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "title": {"type": "string", "title": "Title"}, - "size": {"type": "integer", "title": "Size"}, - "description": {"type": "string", "title": "Description"}, - "sub": {"$ref": "#/components/schemas/SubItem"}, - "multi": { - "items": {"$ref": "#/components/schemas/SubItem"}, - "type": "array", - "title": "Multi", - "default": [], - }, - }, - "type": "object", - "required": ["title", "size", "sub"], - "title": "Item", - }, - "NewItem": { - "properties": { - "new_title": {"type": "string", "title": "New Title"}, - "new_size": {"type": "integer", "title": "New Size"}, - "new_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "New Description", - }, - "new_sub": {"$ref": "#/components/schemas/NewSubItem"}, - "new_multi": { - "items": {"$ref": "#/components/schemas/NewSubItem"}, - "type": "array", - "title": "New Multi", - "default": [], - }, - }, - "type": "object", - "required": ["new_title", "new_size", "new_sub"], - "title": "NewItem", - }, - "NewSubItem": { - "properties": { - "new_sub_name": {"type": "string", "title": "New Sub Name"} - }, - "type": "object", - "required": ["new_sub_name"], - "title": "NewSubItem", - }, - "SubItem": { - "properties": {"name": {"type": "string", "title": "Name"}}, - "type": "object", - "required": ["name"], - "title": "SubItem", - }, - "ValidationError": { - "properties": { - "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_pydantic_v1_v2_multifile/__init__.py b/tests/test_pydantic_v1_v2_multifile/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/test_pydantic_v1_v2_multifile/main.py b/tests/test_pydantic_v1_v2_multifile/main.py deleted file mode 100644 index 4180ec3bf5..0000000000 --- a/tests/test_pydantic_v1_v2_multifile/main.py +++ /dev/null @@ -1,137 +0,0 @@ -import warnings - -from fastapi import FastAPI - -from . import modelsv1, modelsv2, modelsv2b - -app = FastAPI() - -with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.post("/v1-to-v2/item") - def handle_v1_item_to_v2(data: modelsv1.Item) -> modelsv2.Item: - return modelsv2.Item( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), - new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], - ) - - @app.post("/v2-to-v1/item") - def handle_v2_item_to_v1(data: modelsv2.Item) -> modelsv1.Item: - return modelsv1.Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), - multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], - ) - - @app.post("/v1-to-v2/item-to-list") - def handle_v1_item_to_v2_list(data: modelsv1.Item) -> list[modelsv2.Item]: - converted = modelsv2.Item( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), - new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], - ) - return [converted, converted] - - @app.post("/v1-to-v2/list-to-list") - def handle_v1_list_to_v2_list(data: list[modelsv1.Item]) -> list[modelsv2.Item]: - result = [] - for item in data: - result.append( - modelsv2.Item( - new_title=item.title, - new_size=item.size, - new_description=item.description, - new_sub=modelsv2.SubItem(new_sub_name=item.sub.name), - new_multi=[ - modelsv2.SubItem(new_sub_name=s.name) for s in item.multi - ], - ) - ) - return result - - @app.post("/v1-to-v2/list-to-item") - def handle_v1_list_to_v2_item(data: list[modelsv1.Item]) -> modelsv2.Item: - if data: - item = data[0] - return modelsv2.Item( - new_title=item.title, - new_size=item.size, - new_description=item.description, - new_sub=modelsv2.SubItem(new_sub_name=item.sub.name), - new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi], - ) - return modelsv2.Item( - new_title="", new_size=0, new_sub=modelsv2.SubItem(new_sub_name="") - ) - - @app.post("/v2-to-v1/item-to-list") - def handle_v2_item_to_v1_list(data: modelsv2.Item) -> list[modelsv1.Item]: - converted = modelsv1.Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), - multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], - ) - return [converted, converted] - - @app.post("/v2-to-v1/list-to-list") - def handle_v2_list_to_v1_list(data: list[modelsv2.Item]) -> list[modelsv1.Item]: - result = [] - for item in data: - result.append( - modelsv1.Item( - title=item.new_title, - size=item.new_size, - description=item.new_description, - sub=modelsv1.SubItem(name=item.new_sub.new_sub_name), - multi=[ - modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi - ], - ) - ) - return result - - @app.post("/v2-to-v1/list-to-item") - def handle_v2_list_to_v1_item(data: list[modelsv2.Item]) -> modelsv1.Item: - if data: - item = data[0] - return modelsv1.Item( - title=item.new_title, - size=item.new_size, - description=item.new_description, - sub=modelsv1.SubItem(name=item.new_sub.new_sub_name), - multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi], - ) - return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name="")) - - @app.post("/v2-to-v1/same-name") - def handle_v2_same_name_to_v1( - item1: modelsv2.Item, item2: modelsv2b.Item - ) -> modelsv1.Item: - return modelsv1.Item( - title=item1.new_title, - size=item2.dup_size, - description=item1.new_description, - sub=modelsv1.SubItem(name=item1.new_sub.new_sub_name), - multi=[modelsv1.SubItem(name=s.dup_sub_name) for s in item2.dup_multi], - ) - - @app.post("/v2-to-v1/list-of-items-to-list-of-items") - def handle_v2_items_in_list_to_v1_item_in_list( - data1: list[modelsv2.ItemInList], data2: list[modelsv2b.ItemInList] - ) -> list[modelsv1.ItemInList]: - item1 = data1[0] - item2 = data2[0] - return [ - modelsv1.ItemInList(name1=item1.name2), - modelsv1.ItemInList(name1=item2.dup_name2), - ] diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv1.py b/tests/test_pydantic_v1_v2_multifile/modelsv1.py deleted file mode 100644 index 0cc8de4559..0000000000 --- a/tests/test_pydantic_v1_v2_multifile/modelsv1.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi._compat.v1 import BaseModel - - -class SubItem(BaseModel): - name: str - - -class Item(BaseModel): - title: str - size: int - description: Union[str, None] = None - sub: SubItem - multi: list[SubItem] = [] - - -class ItemInList(BaseModel): - name1: str diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2.py b/tests/test_pydantic_v1_v2_multifile/modelsv2.py deleted file mode 100644 index d80b77e103..0000000000 --- a/tests/test_pydantic_v1_v2_multifile/modelsv2.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from pydantic import BaseModel - - -class SubItem(BaseModel): - new_sub_name: str - - -class Item(BaseModel): - new_title: str - new_size: int - new_description: Union[str, None] = None - new_sub: SubItem - new_multi: list[SubItem] = [] - - -class ItemInList(BaseModel): - name2: str diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2b.py b/tests/test_pydantic_v1_v2_multifile/modelsv2b.py deleted file mode 100644 index e992bea2e1..0000000000 --- a/tests/test_pydantic_v1_v2_multifile/modelsv2b.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from pydantic import BaseModel - - -class SubItem(BaseModel): - dup_sub_name: str - - -class Item(BaseModel): - dup_title: str - dup_size: int - dup_description: Union[str, None] = None - dup_sub: SubItem - dup_multi: list[SubItem] = [] - - -class ItemInList(BaseModel): - dup_name2: str diff --git a/tests/test_pydantic_v1_v2_multifile/test_multifile.py b/tests/test_pydantic_v1_v2_multifile/test_multifile.py deleted file mode 100644 index 32d9019616..0000000000 --- a/tests/test_pydantic_v1_v2_multifile/test_multifile.py +++ /dev/null @@ -1,951 +0,0 @@ -import sys - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - -from fastapi.testclient import TestClient -from inline_snapshot import snapshot - -from .main import app - -client = TestClient(app) - - -def test_v1_to_v2_item(): - response = client.post( - "/v1-to-v2/item", - json={"title": "Test", "size": 10, "sub": {"name": "SubTest"}}, - ) - assert response.status_code == 200 - assert response.json() == { - "new_title": "Test", - "new_size": 10, - "new_description": None, - "new_sub": {"new_sub_name": "SubTest"}, - "new_multi": [], - } - - -def test_v2_to_v1_item(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "NewTest", - "new_size": 20, - "new_sub": {"new_sub_name": "NewSubTest"}, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "title": "NewTest", - "size": 20, - "description": None, - "sub": {"name": "NewSubTest"}, - "multi": [], - } - - -def test_v1_to_v2_item_to_list(): - response = client.post( - "/v1-to-v2/item-to-list", - json={"title": "ListTest", "size": 30, "sub": {"name": "SubListTest"}}, - ) - assert response.status_code == 200 - assert response.json() == [ - { - "new_title": "ListTest", - "new_size": 30, - "new_description": None, - "new_sub": {"new_sub_name": "SubListTest"}, - "new_multi": [], - }, - { - "new_title": "ListTest", - "new_size": 30, - "new_description": None, - "new_sub": {"new_sub_name": "SubListTest"}, - "new_multi": [], - }, - ] - - -def test_v1_to_v2_list_to_list(): - response = client.post( - "/v1-to-v2/list-to-list", - json=[ - {"title": "Item1", "size": 40, "sub": {"name": "Sub1"}}, - {"title": "Item2", "size": 50, "sub": {"name": "Sub2"}}, - ], - ) - assert response.status_code == 200 - assert response.json() == [ - { - "new_title": "Item1", - "new_size": 40, - "new_description": None, - "new_sub": {"new_sub_name": "Sub1"}, - "new_multi": [], - }, - { - "new_title": "Item2", - "new_size": 50, - "new_description": None, - "new_sub": {"new_sub_name": "Sub2"}, - "new_multi": [], - }, - ] - - -def test_v1_to_v2_list_to_item(): - response = client.post( - "/v1-to-v2/list-to-item", - json=[ - {"title": "FirstItem", "size": 60, "sub": {"name": "FirstSub"}}, - {"title": "SecondItem", "size": 70, "sub": {"name": "SecondSub"}}, - ], - ) - assert response.status_code == 200 - assert response.json() == { - "new_title": "FirstItem", - "new_size": 60, - "new_description": None, - "new_sub": {"new_sub_name": "FirstSub"}, - "new_multi": [], - } - - -def test_v2_to_v1_item_to_list(): - response = client.post( - "/v2-to-v1/item-to-list", - json={ - "new_title": "ListNew", - "new_size": 80, - "new_sub": {"new_sub_name": "SubListNew"}, - }, - ) - assert response.status_code == 200 - assert response.json() == [ - { - "title": "ListNew", - "size": 80, - "description": None, - "sub": {"name": "SubListNew"}, - "multi": [], - }, - { - "title": "ListNew", - "size": 80, - "description": None, - "sub": {"name": "SubListNew"}, - "multi": [], - }, - ] - - -def test_v2_to_v1_list_to_list(): - response = client.post( - "/v2-to-v1/list-to-list", - json=[ - { - "new_title": "New1", - "new_size": 90, - "new_sub": {"new_sub_name": "NewSub1"}, - }, - { - "new_title": "New2", - "new_size": 100, - "new_sub": {"new_sub_name": "NewSub2"}, - }, - ], - ) - assert response.status_code == 200 - assert response.json() == [ - { - "title": "New1", - "size": 90, - "description": None, - "sub": {"name": "NewSub1"}, - "multi": [], - }, - { - "title": "New2", - "size": 100, - "description": None, - "sub": {"name": "NewSub2"}, - "multi": [], - }, - ] - - -def test_v2_to_v1_list_to_item(): - response = client.post( - "/v2-to-v1/list-to-item", - json=[ - { - "new_title": "FirstNew", - "new_size": 110, - "new_sub": {"new_sub_name": "FirstNewSub"}, - }, - { - "new_title": "SecondNew", - "new_size": 120, - "new_sub": {"new_sub_name": "SecondNewSub"}, - }, - ], - ) - assert response.status_code == 200 - assert response.json() == { - "title": "FirstNew", - "size": 110, - "description": None, - "sub": {"name": "FirstNewSub"}, - "multi": [], - } - - -def test_v1_to_v2_list_to_item_empty(): - response = client.post("/v1-to-v2/list-to-item", json=[]) - assert response.status_code == 200 - assert response.json() == { - "new_title": "", - "new_size": 0, - "new_description": None, - "new_sub": {"new_sub_name": ""}, - "new_multi": [], - } - - -def test_v2_to_v1_list_to_item_empty(): - response = client.post("/v2-to-v1/list-to-item", json=[]) - assert response.status_code == 200 - assert response.json() == { - "title": "", - "size": 0, - "description": None, - "sub": {"name": ""}, - "multi": [], - } - - -def test_v2_same_name_to_v1(): - response = client.post( - "/v2-to-v1/same-name", - json={ - "item1": { - "new_title": "Title1", - "new_size": 100, - "new_description": "Description1", - "new_sub": {"new_sub_name": "Sub1"}, - "new_multi": [{"new_sub_name": "Multi1"}], - }, - "item2": { - "dup_title": "Title2", - "dup_size": 200, - "dup_description": "Description2", - "dup_sub": {"dup_sub_name": "Sub2"}, - "dup_multi": [ - {"dup_sub_name": "Multi2a"}, - {"dup_sub_name": "Multi2b"}, - ], - }, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "title": "Title1", - "size": 200, - "description": "Description1", - "sub": {"name": "Sub1"}, - "multi": [{"name": "Multi2a"}, {"name": "Multi2b"}], - } - - -def test_v2_items_in_list_to_v1_item_in_list(): - response = client.post( - "/v2-to-v1/list-of-items-to-list-of-items", - json={ - "data1": [{"name2": "Item1"}, {"name2": "Item2"}], - "data2": [{"dup_name2": "Item3"}, {"dup_name2": "Item4"}], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == [ - {"name1": "Item1"}, - {"name1": "Item3"}, - ] - - -def test_openapi_schema(): - 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": { - "/v1-to-v2/item": { - "post": { - "summary": "Handle V1 Item To V2", - "operationId": "handle_v1_item_to_v2_v1_to_v2_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - } - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/item": { - "post": { - "summary": "Handle V2 Item To V1", - "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - }, - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v1-to-v2/item-to-list": { - "post": { - "summary": "Handle V1 Item To V2 List", - "operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - } - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" - }, - "type": "array", - "title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v1-to-v2/list-to-list": { - "post": { - "summary": "Handle V1 List To V2 List", - "operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - }, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" - }, - "type": "array", - "title": "Response Handle V1 List To V2 List V1 To V2 List To List Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v1-to-v2/list-to-item": { - "post": { - "summary": "Handle V1 List To V2 Item", - "operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - }, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/item-to-list": { - "post": { - "summary": "Handle V2 Item To V1 List", - "operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - }, - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - }, - "type": "array", - "title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/list-to-list": { - "post": { - "summary": "Handle V2 List To V1 List", - "operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - }, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - }, - "type": "array", - "title": "Response Handle V2 List To V1 List V2 To V1 List To List Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/list-to-item": { - "post": { - "summary": "Handle V2 List To V1 Item", - "operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - }, - "type": "array", - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/same-name": { - "post": { - "summary": "Handle V2 Same Name To V1", - "operationId": "handle_v2_same_name_to_v1_v2_to_v1_same_name_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/list-of-items-to-list-of-items": { - "post": { - "summary": "Handle V2 Items In List To V1 Item In List", - "operationId": "handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList" - }, - "type": "array", - "title": "Response Handle V2 Items In List To V1 Item In List V2 To V1 List Of Items To List Of Items Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": { - "properties": { - "data1": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList" - }, - "type": "array", - "title": "Data1", - }, - "data2": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList" - }, - "type": "array", - "title": "Data2", - }, - }, - "type": "object", - "required": ["data1", "data2"], - "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", - }, - "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": { - "properties": { - "item1": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - }, - "item2": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item" - }, - }, - "type": "object", - "required": ["item1", "item2"], - "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "ValidationError": { - "properties": { - "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", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": { - "properties": { - "title": {"type": "string", "title": "Title"}, - "size": {"type": "integer", "title": "Size"}, - "description": { - "type": "string", - "title": "Description", - }, - "sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" - }, - "multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" - }, - "type": "array", - "title": "Multi", - "default": [], - }, - }, - "type": "object", - "required": ["title", "size", "sub"], - "title": "Item", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": { - "properties": {"name1": {"type": "string", "title": "Name1"}}, - "type": "object", - "required": ["name1"], - "title": "ItemInList", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": { - "properties": {"name": {"type": "string", "title": "Name"}}, - "type": "object", - "required": ["name"], - "title": "SubItem", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": { - "properties": { - "new_title": { - "type": "string", - "title": "New Title", - }, - "new_size": { - "type": "integer", - "title": "New Size", - }, - "new_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "New Description", - }, - "new_sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "new_multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "type": "array", - "title": "New Multi", - "default": [], - }, - }, - "type": "object", - "required": ["new_title", "new_size", "new_sub"], - "title": "Item", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input": { - "properties": { - "new_title": { - "type": "string", - "title": "New Title", - }, - "new_size": { - "type": "integer", - "title": "New Size", - }, - "new_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "New Description", - }, - "new_sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "new_multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "type": "array", - "title": "New Multi", - "default": [], - }, - }, - "type": "object", - "required": ["new_title", "new_size", "new_sub"], - "title": "Item", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": { - "properties": {"name2": {"type": "string", "title": "Name2"}}, - "type": "object", - "required": ["name2"], - "title": "ItemInList", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": { - "properties": { - "new_sub_name": { - "type": "string", - "title": "New Sub Name", - } - }, - "type": "object", - "required": ["new_sub_name"], - "title": "SubItem", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": { - "properties": { - "dup_title": { - "type": "string", - "title": "Dup Title", - }, - "dup_size": { - "type": "integer", - "title": "Dup Size", - }, - "dup_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Dup Description", - }, - "dup_sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" - }, - "dup_multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" - }, - "type": "array", - "title": "Dup Multi", - "default": [], - }, - }, - "type": "object", - "required": ["dup_title", "dup_size", "dup_sub"], - "title": "Item", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": { - "properties": { - "dup_name2": { - "type": "string", - "title": "Dup Name2", - } - }, - "type": "object", - "required": ["dup_name2"], - "title": "ItemInList", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": { - "properties": { - "dup_sub_name": { - "type": "string", - "title": "Dup Sub Name", - } - }, - "type": "object", - "required": ["dup_sub_name"], - "title": "SubItem", - }, - }, - }, - } - ) diff --git a/tests/test_pydantic_v1_v2_noneable.py b/tests/test_pydantic_v1_v2_noneable.py deleted file mode 100644 index ba98b5653c..0000000000 --- a/tests/test_pydantic_v1_v2_noneable.py +++ /dev/null @@ -1,692 +0,0 @@ -import sys -import warnings -from typing import Any, Union - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - -from fastapi import FastAPI -from fastapi._compat.v1 import BaseModel -from fastapi.testclient import TestClient -from inline_snapshot import snapshot -from pydantic import BaseModel as NewBaseModel - - -class SubItem(BaseModel): - name: str - - -class Item(BaseModel): - title: str - size: int - description: Union[str, None] = None - sub: SubItem - multi: list[SubItem] = [] - - -class NewSubItem(NewBaseModel): - new_sub_name: str - - -class NewItem(NewBaseModel): - new_title: str - new_size: int - new_description: Union[str, None] = None - new_sub: NewSubItem - new_multi: list[NewSubItem] = [] - - -app = FastAPI() - -with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.post("/v1-to-v2/") - def handle_v1_item_to_v2(data: Item) -> Union[NewItem, None]: - if data.size < 0: - return None - return NewItem( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=NewSubItem(new_sub_name=data.sub.name), - new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], - ) - - @app.post("/v1-to-v2/item-filter", response_model=Union[NewItem, None]) - def handle_v1_item_to_v2_filter(data: Item) -> Any: - if data.size < 0: - return None - result = { - "new_title": data.title, - "new_size": data.size, - "new_description": data.description, - "new_sub": { - "new_sub_name": data.sub.name, - "new_sub_secret": "sub_hidden", - }, - "new_multi": [ - {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} - for s in data.multi - ], - "secret": "hidden_v1_to_v2", - } - return result - - @app.post("/v2-to-v1/item") - def handle_v2_item_to_v1(data: NewItem) -> Union[Item, None]: - if data.new_size < 0: - return None - return Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=SubItem(name=data.new_sub.new_sub_name), - multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], - ) - - @app.post("/v2-to-v1/item-filter", response_model=Union[Item, None]) - def handle_v2_item_to_v1_filter(data: NewItem) -> Any: - if data.new_size < 0: - return None - result = { - "title": data.new_title, - "size": data.new_size, - "description": data.new_description, - "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, - "multi": [ - {"name": s.new_sub_name, "sub_secret": "sub_hidden"} - for s in data.new_multi - ], - "secret": "hidden_v2_to_v1", - } - return result - - -client = TestClient(app) - - -def test_v1_to_v2_item_success(): - response = client.post( - "/v1-to-v2/", - json={ - "title": "Old Item", - "size": 100, - "description": "V1 description", - "sub": {"name": "V1 Sub"}, - "multi": [{"name": "M1"}, {"name": "M2"}], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "new_title": "Old Item", - "new_size": 100, - "new_description": "V1 description", - "new_sub": {"new_sub_name": "V1 Sub"}, - "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}], - } - - -def test_v1_to_v2_item_returns_none(): - response = client.post( - "/v1-to-v2/", - json={"title": "Invalid Item", "size": -10, "sub": {"name": "Sub"}}, - ) - assert response.status_code == 200, response.text - assert response.json() is None - - -def test_v1_to_v2_item_minimal(): - response = client.post( - "/v1-to-v2/", json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}} - ) - assert response.status_code == 200, response.text - assert response.json() == { - "new_title": "Minimal", - "new_size": 50, - "new_description": None, - "new_sub": {"new_sub_name": "MinSub"}, - "new_multi": [], - } - - -def test_v1_to_v2_item_filter_success(): - response = client.post( - "/v1-to-v2/item-filter", - json={ - "title": "Filtered Item", - "size": 50, - "sub": {"name": "Sub"}, - "multi": [{"name": "Multi1"}], - }, - ) - assert response.status_code == 200, response.text - result = response.json() - assert result["new_title"] == "Filtered Item" - assert result["new_size"] == 50 - assert result["new_sub"]["new_sub_name"] == "Sub" - assert result["new_multi"][0]["new_sub_name"] == "Multi1" - # Verify secret fields are filtered out - assert "secret" not in result - assert "new_sub_secret" not in result["new_sub"] - assert "new_sub_secret" not in result["new_multi"][0] - - -def test_v1_to_v2_item_filter_returns_none(): - response = client.post( - "/v1-to-v2/item-filter", - json={"title": "Invalid", "size": -1, "sub": {"name": "Sub"}}, - ) - assert response.status_code == 200, response.text - assert response.json() is None - - -def test_v2_to_v1_item_success(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "New Item", - "new_size": 200, - "new_description": "V2 description", - "new_sub": {"new_sub_name": "V2 Sub"}, - "new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "New Item", - "size": 200, - "description": "V2 description", - "sub": {"name": "V2 Sub"}, - "multi": [{"name": "N1"}, {"name": "N2"}], - } - - -def test_v2_to_v1_item_returns_none(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "Invalid New", - "new_size": -5, - "new_sub": {"new_sub_name": "NewSub"}, - }, - ) - assert response.status_code == 200, response.text - assert response.json() is None - - -def test_v2_to_v1_item_minimal(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "MinimalNew", - "new_size": 75, - "new_sub": {"new_sub_name": "MinNewSub"}, - }, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "title": "MinimalNew", - "size": 75, - "description": None, - "sub": {"name": "MinNewSub"}, - "multi": [], - } - - -def test_v2_to_v1_item_filter_success(): - response = client.post( - "/v2-to-v1/item-filter", - json={ - "new_title": "Filtered New", - "new_size": 75, - "new_sub": {"new_sub_name": "NewSub"}, - "new_multi": [], - }, - ) - assert response.status_code == 200, response.text - result = response.json() - assert result["title"] == "Filtered New" - assert result["size"] == 75 - assert result["sub"]["name"] == "NewSub" - # Verify secret fields are filtered out - assert "secret" not in result - assert "sub_secret" not in result["sub"] - - -def test_v2_to_v1_item_filter_returns_none(): - response = client.post( - "/v2-to-v1/item-filter", - json={ - "new_title": "Invalid Filtered", - "new_size": -100, - "new_sub": {"new_sub_name": "Sub"}, - }, - ) - assert response.status_code == 200, response.text - assert response.json() is None - - -def test_v1_to_v2_validation_error(): - response = client.post("/v1-to-v2/", json={"title": "Missing fields"}) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "loc": ["body", "size"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "sub"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_v1_to_v2_nested_validation_error(): - response = client.post( - "/v1-to-v2/", - json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}}, - ) - assert response.status_code == 422, response.text - error_detail = response.json()["detail"] - assert len(error_detail) == 1 - assert error_detail[0]["loc"] == ["body", "sub", "name"] - - -def test_v1_to_v2_type_validation_error(): - response = client.post( - "/v1-to-v2/", - json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}}, - ) - assert response.status_code == 422, response.text - error_detail = response.json()["detail"] - assert len(error_detail) == 1 - assert error_detail[0]["loc"] == ["body", "size"] - - -def test_v2_to_v1_validation_error(): - response = client.post("/v2-to-v1/item", json={"new_title": "Missing fields"}) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "new_size"], - "msg": "Field required", - "input": {"new_title": "Missing fields"}, - }, - { - "type": "missing", - "loc": ["body", "new_sub"], - "msg": "Field required", - "input": {"new_title": "Missing fields"}, - }, - ] - } - ) - - -def test_v2_to_v1_nested_validation_error(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "Bad sub", - "new_size": 200, - "new_sub": {"wrong_field": "value"}, - }, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "new_sub", "new_sub_name"], - "msg": "Field required", - "input": {"wrong_field": "value"}, - } - ] - } - ) - - -def test_v2_to_v1_type_validation_error(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "Bad type", - "new_size": "not_a_number", - "new_sub": {"new_sub_name": "Sub"}, - }, - ) - assert response.status_code == 422, response.text - assert response.json() == snapshot( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["body", "new_size"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "not_a_number", - } - ] - } - ) - - -def test_v1_to_v2_with_multi_items(): - response = client.post( - "/v1-to-v2/", - json={ - "title": "Complex Item", - "size": 300, - "description": "Item with multiple sub-items", - "sub": {"name": "Main Sub"}, - "multi": [{"name": "Sub1"}, {"name": "Sub2"}, {"name": "Sub3"}], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == snapshot( - { - "new_title": "Complex Item", - "new_size": 300, - "new_description": "Item with multiple sub-items", - "new_sub": {"new_sub_name": "Main Sub"}, - "new_multi": [ - {"new_sub_name": "Sub1"}, - {"new_sub_name": "Sub2"}, - {"new_sub_name": "Sub3"}, - ], - } - ) - - -def test_v2_to_v1_with_multi_items(): - response = client.post( - "/v2-to-v1/item", - json={ - "new_title": "Complex New Item", - "new_size": 400, - "new_description": "New item with multiple sub-items", - "new_sub": {"new_sub_name": "Main New Sub"}, - "new_multi": [{"new_sub_name": "NewSub1"}, {"new_sub_name": "NewSub2"}], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == snapshot( - { - "title": "Complex New Item", - "size": 400, - "description": "New item with multiple sub-items", - "sub": {"name": "Main New Sub"}, - "multi": [{"name": "NewSub1"}, {"name": "NewSub2"}], - } - ) - - -def test_openapi_schema(): - 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": { - "/v1-to-v2/": { - "post": { - "summary": "Handle V1 Item To V2", - "operationId": "handle_v1_item_to_v2_v1_to_v2__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/NewItem" - }, - {"type": "null"}, - ], - "title": "Response Handle V1 Item To V2 V1 To V2 Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v1-to-v2/item-filter": { - "post": { - "summary": "Handle V1 Item To V2 Filter", - "operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/NewItem" - }, - {"type": "null"}, - ], - "title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/item": { - "post": { - "summary": "Handle V2 Item To V1", - "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/NewItem"} - } - }, - "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" - } - } - }, - }, - }, - } - }, - "/v2-to-v1/item-filter": { - "post": { - "summary": "Handle V2 Item To V1 Filter", - "operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/NewItem"} - } - }, - "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" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "title": {"type": "string", "title": "Title"}, - "size": {"type": "integer", "title": "Size"}, - "description": {"type": "string", "title": "Description"}, - "sub": {"$ref": "#/components/schemas/SubItem"}, - "multi": { - "items": {"$ref": "#/components/schemas/SubItem"}, - "type": "array", - "title": "Multi", - "default": [], - }, - }, - "type": "object", - "required": ["title", "size", "sub"], - "title": "Item", - }, - "NewItem": { - "properties": { - "new_title": {"type": "string", "title": "New Title"}, - "new_size": {"type": "integer", "title": "New Size"}, - "new_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "New Description", - }, - "new_sub": {"$ref": "#/components/schemas/NewSubItem"}, - "new_multi": { - "items": {"$ref": "#/components/schemas/NewSubItem"}, - "type": "array", - "title": "New Multi", - "default": [], - }, - }, - "type": "object", - "required": ["new_title", "new_size", "new_sub"], - "title": "NewItem", - }, - "NewSubItem": { - "properties": { - "new_sub_name": {"type": "string", "title": "New Sub Name"} - }, - "type": "object", - "required": ["new_sub_name"], - "title": "NewSubItem", - }, - "SubItem": { - "properties": {"name": {"type": "string", "title": "Name"}}, - "type": "object", - "required": ["name"], - "title": "SubItem", - }, - "ValidationError": { - "properties": { - "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_read_with_orm_mode.py b/tests/test_read_with_orm_mode.py index a195634b8a..cd7389252a 100644 --- a/tests/test_read_with_orm_mode.py +++ b/tests/test_read_with_orm_mode.py @@ -1,12 +1,9 @@ -import warnings from typing import Any from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict -from .utils import needs_pydanticv1 - def test_read_with_orm_mode() -> None: class PersonBase(BaseModel): @@ -44,50 +41,3 @@ def test_read_with_orm_mode() -> None: assert data["name"] == person_data["name"] assert data["lastname"] == person_data["lastname"] assert data["full_name"] == person_data["name"] + " " + person_data["lastname"] - - -@needs_pydanticv1 -def test_read_with_orm_mode_pv1() -> None: - from pydantic import v1 - - class PersonBase(v1.BaseModel): - name: str - lastname: str - - class Person(PersonBase): - @property - def full_name(self) -> str: - return f"{self.name} {self.lastname}" - - class Config: - orm_mode = True - read_with_orm_mode = True - - class PersonCreate(PersonBase): - pass - - class PersonRead(PersonBase): - full_name: str - - class Config: - orm_mode = True - - app = FastAPI() - - with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - @app.post("/people/", response_model=PersonRead) - def create_person(person: PersonCreate) -> Any: - db_person = Person.from_orm(person) - return db_person - - client = TestClient(app) - - person_data = {"name": "Dive", "lastname": "Wilson"} - response = client.post("/people/", json=person_data) - data = response.json() - assert response.status_code == 200, response.text - assert data["name"] == person_data["name"] - assert data["lastname"] == person_data["lastname"] - assert data["full_name"] == person_data["name"] + " " + person_data["lastname"] diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py index 9e527d6a01..58fba89f1a 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -1,4 +1,3 @@ -import warnings from typing import Union import pytest @@ -8,8 +7,6 @@ from fastapi.responses import JSONResponse, Response from fastapi.testclient import TestClient from pydantic import BaseModel -from tests.utils import needs_pydanticv1 - class BaseUser(BaseModel): name: str @@ -512,29 +509,6 @@ def test_invalid_response_model_field(): assert "parameter response_model=None" in e.value.args[0] -# TODO: remove when dropping Pydantic v1 support -@needs_pydanticv1 -def test_invalid_response_model_field_pv1(): - from fastapi._compat import v1 - - app = FastAPI() - - class Model(v1.BaseModel): - foo: str - - with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - - with pytest.raises(FastAPIError) as e: - - @app.get("/") - def read_root() -> Union[Response, Model, None]: - return Response(content="Foo") # pragma: no cover - - assert "valid Pydantic field type" in e.value.args[0] - assert "parameter response_model=None" in e.value.args[0] - - def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py deleted file mode 100644 index 62b67a98c1..0000000000 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py +++ /dev/null @@ -1,115 +0,0 @@ -import importlib - -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_pydanticv1 - - -@pytest.fixture( - name="client", - params=[ - pytest.param("tutorial007_pv1_py39"), - ], -) -def get_client(request: pytest.FixtureRequest): - mod = importlib.import_module( - f"docs_src.path_operation_advanced_configuration.{request.param}" - ) - - client = TestClient(mod.app) - return client - - -@needs_pydanticv1 -def test_post(client: TestClient): - yaml_data = """ - name: Deadpoolio - tags: - - x-force - - x-men - - x-avengers - """ - response = client.post("/items/", content=yaml_data) - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Deadpoolio", - "tags": ["x-force", "x-men", "x-avengers"], - } - - -@needs_pydanticv1 -def test_post_broken_yaml(client: TestClient): - yaml_data = """ - name: Deadpoolio - tags: - x - x-force - x - x-men - x - x-avengers - """ - response = client.post("/items/", content=yaml_data) - assert response.status_code == 422, response.text - assert response.json() == {"detail": "Invalid YAML"} - - -@needs_pydanticv1 -def test_post_invalid(client: TestClient): - yaml_data = """ - name: Deadpoolio - tags: - - x-force - - x-men - - x-avengers - - sneaky: object - """ - response = client.post("/items/", content=yaml_data) - assert response.status_code == 422, response.text - assert response.json() == { - "detail": [ - {"loc": ["tags", 3], "msg": "str type expected", "type": "type_error.str"} - ] - } - - -@needs_pydanticv1 -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"}, - }, - }, - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, - } diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py b/tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py deleted file mode 100644 index 4090eba012..0000000000 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py +++ /dev/null @@ -1,31 +0,0 @@ -import sys -from typing import Any - -import pytest - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - - -import importlib - -from ...utils import needs_py310 - - -@pytest.fixture( - name="mod", - params=[ - "tutorial001_an_py39", - pytest.param("tutorial001_an_py310", marks=needs_py310), - ], -) -def get_mod(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") - return mod - - -def test_model(mod: Any): - item = mod.Item(name="Foo", size=3.4) - assert item.dict() == {"name": "Foo", "description": None, "size": 3.4} diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py deleted file mode 100644 index 9d1baf8530..0000000000 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py +++ /dev/null @@ -1,143 +0,0 @@ -import sys -import warnings - -import pytest -from fastapi.exceptions import FastAPIDeprecationWarning -from inline_snapshot import snapshot - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - - -import importlib - -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture( - name="client", - params=[ - "tutorial002_an_py39", - pytest.param("tutorial002_an_py310", marks=needs_py310), - ], -) -def get_client(request: pytest.FixtureRequest): - with warnings.catch_warnings(record=True): - warnings.filterwarnings( - "ignore", - message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=FastAPIDeprecationWarning, - ) - mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") - - c = TestClient(mod.app) - return c - - -def test_call(client: TestClient): - response = client.post("/items/", json={"name": "Foo", "size": 3.4}) - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Foo", - "description": None, - "size": 3.4, - } - - -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/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "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" - } - } - }, - }, - }, - } - } - }, - "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": {"type": "string", "title": "Description"}, - "size": {"type": "number", "title": "Size"}, - }, - "type": "object", - "required": ["name", "size"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "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_pydantic_v1_in_v2/test_tutorial003.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py deleted file mode 100644 index 23b236888d..0000000000 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py +++ /dev/null @@ -1,158 +0,0 @@ -import sys -import warnings - -import pytest -from fastapi.exceptions import FastAPIDeprecationWarning -from inline_snapshot import snapshot - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - - -import importlib - -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture( - name="client", - params=[ - "tutorial003_an_py39", - pytest.param("tutorial003_an_py310", marks=needs_py310), - ], -) -def get_client(request: pytest.FixtureRequest): - with warnings.catch_warnings(record=True): - warnings.filterwarnings( - "ignore", - message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=FastAPIDeprecationWarning, - ) - mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") - - c = TestClient(mod.app) - return c - - -def test_call(client: TestClient): - response = client.post("/items/", json={"name": "Foo", "size": 3.4}) - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Foo", - "description": None, - "size": 3.4, - } - - -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/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ItemV2" - } - } - }, - }, - "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": {"type": "string", "title": "Description"}, - "size": {"type": "number", "title": "Size"}, - }, - "type": "object", - "required": ["name", "size"], - "title": "Item", - }, - "ItemV2": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "size": {"type": "number", "title": "Size"}, - }, - "type": "object", - "required": ["name", "size"], - "title": "ItemV2", - }, - "ValidationError": { - "properties": { - "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_pydantic_v1_in_v2/test_tutorial004.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py deleted file mode 100644 index 61c0f63571..0000000000 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py +++ /dev/null @@ -1,156 +0,0 @@ -import sys -import warnings - -import pytest -from fastapi.exceptions import FastAPIDeprecationWarning -from inline_snapshot import snapshot - -from tests.utils import skip_module_if_py_gte_314 - -if sys.version_info >= (3, 14): - skip_module_if_py_gte_314() - - -import importlib - -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture( - name="client", - params=[ - pytest.param("tutorial004_an_py39"), - pytest.param("tutorial004_an_py310", marks=needs_py310), - ], -) -def get_client(request: pytest.FixtureRequest): - with warnings.catch_warnings(record=True): - warnings.filterwarnings( - "ignore", - message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=FastAPIDeprecationWarning, - ) - mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") - - c = TestClient(mod.app) - return c - - -def test_call(client: TestClient): - response = client.post("/items/", json={"item": {"name": "Foo", "size": 3.4}}) - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Foo", - "description": None, - "size": 3.4, - } - - -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/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_item_items__post" - } - ], - "title": "Body", - } - } - }, - "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" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "Body_create_item_items__post": { - "properties": { - "item": { - "allOf": [{"$ref": "#/components/schemas/Item"}], - "title": "Item", - } - }, - "type": "object", - "required": ["item"], - "title": "Body_create_item_items__post", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": {"type": "string", "title": "Description"}, - "size": {"type": "number", "title": "Size"}, - }, - "type": "object", - "required": ["name", "size"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "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_form_models/test_tutorial002_pv1.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py deleted file mode 100644 index 50be458962..0000000000 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py +++ /dev/null @@ -1,128 +0,0 @@ -import importlib -import warnings - -import pytest -from fastapi.exceptions import FastAPIDeprecationWarning -from fastapi.testclient import TestClient - -from ...utils import needs_pydanticv1 - - -@pytest.fixture( - name="client", - params=[ - "tutorial002_pv1_py39", - "tutorial002_pv1_an_py39", - ], -) -def get_client(request: pytest.FixtureRequest): - with warnings.catch_warnings(record=True): - warnings.filterwarnings( - "ignore", - message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=FastAPIDeprecationWarning, - ) - mod = importlib.import_module(f"docs_src.request_form_models.{request.param}") - - client = TestClient(mod.app) - return client - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_post_body_form(client: TestClient): - response = client.post("/login/", data={"username": "Foo", "password": "secret"}) - assert response.status_code == 200 - assert response.json() == {"username": "Foo", "password": "secret"} - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_post_body_extra_form(client: TestClient): - response = client.post( - "/login/", data={"username": "Foo", "password": "secret", "extra": "extra"} - ) - assert response.status_code == 422 - assert response.json() == { - "detail": [ - { - "type": "value_error.extra", - "loc": ["body", "extra"], - "msg": "extra fields not permitted", - } - ] - } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_post_body_form_no_password(client: TestClient): - response = client.post("/login/", data={"username": "Foo"}) - assert response.status_code == 422 - assert response.json() == { - "detail": [ - { - "type": "value_error.missing", - "loc": ["body", "password"], - "msg": "field required", - } - ] - } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_post_body_form_no_username(client: TestClient): - response = client.post("/login/", data={"password": "secret"}) - assert response.status_code == 422 - assert response.json() == { - "detail": [ - { - "type": "value_error.missing", - "loc": ["body", "username"], - "msg": "field required", - } - ] - } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_post_body_form_no_data(client: TestClient): - response = client.post("/login/") - assert response.status_code == 422 - assert response.json() == { - "detail": [ - { - "type": "value_error.missing", - "loc": ["body", "username"], - "msg": "field required", - }, - { - "type": "value_error.missing", - "loc": ["body", "password"], - "msg": "field required", - }, - ] - } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_post_body_json(client: TestClient): - response = client.post("/login/", json={"username": "Foo", "password": "secret"}) - assert response.status_code == 422, response.text - assert response.json() == { - "detail": [ - { - "type": "value_error.missing", - "loc": ["body", "username"], - "msg": "field required", - }, - { - "type": "value_error.missing", - "loc": ["body", "password"], - "msg": "field required", - }, - ] - } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py deleted file mode 100644 index 83c7176567..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py +++ /dev/null @@ -1,152 +0,0 @@ -import importlib -import warnings - -import pytest -from fastapi.exceptions import FastAPIDeprecationWarning -from fastapi.testclient import TestClient -from inline_snapshot import snapshot - -from ...utils import needs_py310, needs_pydanticv1 - - -@pytest.fixture( - name="client", - params=[ - pytest.param("tutorial001_pv1_py39"), - pytest.param("tutorial001_pv1_py310", marks=needs_py310), - ], -) -def get_client(request: pytest.FixtureRequest): - with warnings.catch_warnings(record=True): - warnings.filterwarnings( - "ignore", - message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=FastAPIDeprecationWarning, - ) - mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") - - client = TestClient(mod.app) - return client - - -@needs_pydanticv1 -def test_post_body_example(client: TestClient): - response = client.put( - "/items/5", - json={ - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - ) - assert response.status_code == 200 - - -@needs_pydanticv1 -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/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"type": "integer", "title": "Item Id"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Item", - "allOf": [ - {"$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": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": {"type": "string", "title": "Description"}, - "price": {"type": "number", "title": "Price"}, - "tax": {"type": "number", "title": "Tax"}, - }, - "type": "object", - "required": ["name", "price"], - "title": "Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } - ], - }, - "ValidationError": { - "properties": { - "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_app03.py b/tests/test_tutorial/test_settings/test_app03.py index 06e82398d1..72de497967 100644 --- a/tests/test_tutorial/test_settings/test_app03.py +++ b/tests/test_tutorial/test_settings/test_app03.py @@ -5,8 +5,6 @@ import pytest from fastapi.testclient import TestClient from pytest import MonkeyPatch -from ...utils import needs_pydanticv1 - @pytest.fixture( name="mod_path", @@ -34,16 +32,6 @@ def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch): assert settings.items_per_user == 50 -@needs_pydanticv1 -def test_settings_pv1(mod_path: str, monkeypatch: MonkeyPatch): - monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") - config_mod = importlib.import_module(f"{mod_path}.config_pv1") - settings = config_mod.Settings() - assert settings.app_name == "Awesome API" - assert settings.admin_email == "admin@example.com" - assert settings.items_per_user == 50 - - def test_endpoint(main_mod: ModuleType, monkeypatch: MonkeyPatch): monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") client = TestClient(main_mod.app) diff --git a/tests/test_tutorial/test_settings/test_tutorial001.py b/tests/test_tutorial/test_settings/test_tutorial001.py index 6a08096989..f4576a0d21 100644 --- a/tests/test_tutorial/test_settings/test_tutorial001.py +++ b/tests/test_tutorial/test_settings/test_tutorial001.py @@ -4,16 +4,8 @@ import pytest from fastapi.testclient import TestClient from pytest import MonkeyPatch -from ...utils import needs_pydanticv1 - -@pytest.fixture( - name="app", - params=[ - pytest.param("tutorial001_py39"), - pytest.param("tutorial001_pv1_py39", marks=needs_pydanticv1), - ], -) +@pytest.fixture(name="app", params=[pytest.param("tutorial001_py39")]) 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/utils.py b/tests/utils.py index b896d4527f..efa0bfd52b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -10,8 +10,6 @@ needs_py_lt_314 = pytest.mark.skipif( sys.version_info >= (3, 14), reason="requires python3.13-" ) -needs_pydanticv1 = needs_py_lt_314 - def skip_module_if_py_gte_314(): """Skip entire module on Python 3.14+ at import time.""" From 4b2cfcfd34d0b2e158a05b377426e0c8376b94de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 27 Dec 2025 12:55:22 +0000 Subject: [PATCH 021/110] =?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 10b262b489..21523b2e2f 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 `pydantic.v1`. PR [#14609](https://github.com/fastapi/fastapi/pull/14609) by [@tiangolo](https://github.com/tiangolo). + ### Internal * โœ… Run performance tests only on Pydantic v2. PR [#14608](https://github.com/fastapi/fastapi/pull/14608) by [@tiangolo](https://github.com/tiangolo). From 8322a4445a3b25acd9b26b61192571b2d92f9bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 27 Dec 2025 16:19:50 +0100 Subject: [PATCH 022/110] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?8.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 21523b2e2f..ef7894037c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.128.0 + ### Breaking Changes * โž– Drop support for `pydantic.v1`. PR [#14609](https://github.com/fastapi/fastapi/pull/14609) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index dc447c8bfd..6133787b06 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.127.1" +__version__ = "0.128.0" from starlette import status as status From 44c849c4fc3636f54eae74ab4e586c8ca9f3fb59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 27 Dec 2025 10:19:10 -0800 Subject: [PATCH 023/110] =?UTF-8?q?=F0=9F=94=A5=20Remove=20Pydantic=20v1?= =?UTF-8?q?=20=20specific=20test=20variants=20(#14611)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_additional_properties_bool.py | 21 +- ...onal_responses_custom_model_in_callback.py | 207 ++- tests/test_annotated.py | 48 +- tests/test_application.py | 25 +- tests/test_dependency_duplicates.py | 34 +- tests/test_dependency_overrides.py | 405 ++---- tests/test_extra_routes.py | 13 +- tests/test_filter_pydantic_sub_model_pv2.py | 45 +- tests/test_forms_single_model.py | 84 +- tests/test_infer_param_optionality.py | 57 +- tests/test_multi_body_errors.py | 185 +-- tests/test_multi_query_errors.py | 51 +- tests/test_openapi_examples.py | 114 +- .../test_openapi_query_parameter_extension.py | 21 +- tests/test_openapi_servers.py | 64 +- tests/test_params_repr.py | 43 +- tests/test_path.py | 1182 +++++------------ tests/test_query.py | 364 ++--- tests/test_regex_deprecated_body.py | 196 ++- tests/test_regex_deprecated_params.py | 61 +- .../test_body/test_list.py | 66 +- .../test_body/test_optional_list.py | 285 ++-- .../test_body/test_optional_str.py | 249 ++-- .../test_body/test_required_str.py | 101 +- .../test_cookie/test_optional_str.py | 59 +- .../test_cookie/test_required_str.py | 107 +- .../test_file/test_list.py | 99 +- .../test_file/test_optional.py | 89 +- .../test_file/test_optional_list.py | 103 +- .../test_file/test_required.py | 100 +- .../test_form/test_list.py | 66 +- .../test_form/test_optional_list.py | 153 +-- .../test_form/test_optional_str.py | 117 +- .../test_form/test_required_str.py | 101 +- .../test_header/test_list.py | 66 +- .../test_header/test_optional_list.py | 75 +- .../test_header/test_optional_str.py | 59 +- .../test_header/test_required_str.py | 101 +- .../test_query/test_list.py | 66 +- .../test_query/test_optional_list.py | 75 +- .../test_query/test_optional_str.py | 59 +- .../test_query/test_required_str.py | 107 +- tests/test_schema_extra_examples.py | 221 +-- tests/test_security_oauth2.py | 165 +-- tests/test_security_oauth2_optional.py | 165 +-- ...st_security_oauth2_optional_description.py | 165 +-- tests/test_sub_callbacks.py | 51 +- tests/test_tuples.py | 97 +- .../test_tutorial002.py | 15 +- .../test_tutorial004.py | 15 +- .../test_behind_a_proxy/test_tutorial003.py | 68 +- .../test_behind_a_proxy/test_tutorial004.py | 66 +- .../test_bigger_applications/test_main.py | 315 ++--- .../test_body/test_tutorial001.py | 355 ++--- .../test_body_fields/test_tutorial001.py | 75 +- .../test_tutorial001.py | 96 +- .../test_tutorial003.py | 177 +-- .../test_tutorial009.py | 34 +- .../test_tutorial001.py | 69 +- .../test_tutorial002.py | 52 +- .../test_cookie_params/test_tutorial001.py | 15 +- .../test_tutorial002.py | 45 +- .../test_dataclasses/test_tutorial001.py | 62 +- .../test_dataclasses/test_tutorial002.py | 108 +- .../test_dependencies/test_tutorial006.py | 51 +- .../test_dependencies/test_tutorial012.py | 101 +- .../test_extra_data_types/test_tutorial001.py | 217 ++- .../test_extra_models/test_tutorial003.py | 200 +-- .../test_handling_errors/test_tutorial005.py | 37 +- .../test_handling_errors/test_tutorial006.py | 34 +- .../test_tutorial001.py | 71 +- .../test_tutorial002.py | 79 +- .../test_tutorial003.py | 115 +- .../test_header_params/test_tutorial001.py | 15 +- .../test_header_params/test_tutorial002.py | 15 +- .../test_header_params/test_tutorial003.py | 25 +- .../test_tutorial001.py | 51 +- .../test_path_params/test_tutorial005.py | 58 +- .../test_tutorial001.py | 81 +- .../test_tutorial002.py | 103 +- .../test_query_params/test_tutorial005.py | 34 +- .../test_query_params/test_tutorial006.py | 82 +- .../test_tutorial010.py | 88 +- .../test_tutorial011.py | 25 +- .../test_request_files/test_tutorial001.py | 67 +- .../test_request_files/test_tutorial001_02.py | 268 ++-- .../test_request_files/test_tutorial002.py | 67 +- .../test_tutorial001.py | 167 +-- .../test_request_forms/test_tutorial001.py | 167 +-- .../test_tutorial001.py | 235 ++-- .../test_response_model/test_tutorial003.py | 212 ++- .../test_tutorial003_01.py | 218 ++- .../test_response_model/test_tutorial004.py | 178 ++- .../test_response_model/test_tutorial005.py | 230 ++-- .../test_response_model/test_tutorial006.py | 230 ++-- .../test_tutorial004.py | 85 +- .../test_tutorial005.py | 40 +- .../test_security/test_tutorial003.py | 59 +- .../test_security/test_tutorial005.py | 364 +++-- .../test_sql_databases/test_tutorial001.py | 36 +- .../test_sql_databases/test_tutorial002.py | 87 +- tests/test_union_body.py | 17 +- tests/test_union_body_discriminator.py | 21 +- tests/test_union_inherited_body.py | 29 +- 104 files changed, 4139 insertions(+), 8074 deletions(-) diff --git a/tests/test_additional_properties_bool.py b/tests/test_additional_properties_bool.py index 3756b7d7c9..063297a3f2 100644 --- a/tests/test_additional_properties_bool.py +++ b/tests/test_additional_properties_bool.py @@ -1,6 +1,5 @@ from typing import Union -from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict @@ -52,19 +51,13 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": IsDict( - { - "anyOf": [ - {"$ref": "#/components/schemas/Foo"}, - {"type": "null"}, - ], - "title": "Foo", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"$ref": "#/components/schemas/Foo"} - ) + "schema": { + "anyOf": [ + {"$ref": "#/components/schemas/Foo"}, + {"type": "null"}, + ], + "title": "Foo", + } } } }, diff --git a/tests/test_additional_responses_custom_model_in_callback.py b/tests/test_additional_responses_custom_model_in_callback.py index 2ad5754551..376d7714ed 100644 --- a/tests/test_additional_responses_custom_model_in_callback.py +++ b/tests/test_additional_responses_custom_model_in_callback.py @@ -1,6 +1,6 @@ -from dirty_equals import IsDict from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, HttpUrl from starlette.responses import JSONResponse @@ -32,121 +32,114 @@ 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": { - "/": { - "post": { - "summary": "Main Route", - "operationId": "main_route__post", - "parameters": [ - { - "required": True, - "schema": IsDict( - { - "title": "Callback Url", - "minLength": 1, - "type": "string", - "format": "uri", - } - ) - # TODO: remove when deprecating Pydantic v1 - | IsDict( - { + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Main Route", + "operationId": "main_route__post", + "parameters": [ + { + "required": True, + "schema": { "title": "Callback Url", "maxLength": 2083, "minLength": 1, "type": "string", "format": "uri", - } - ), - "name": "callback_url", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + }, + "name": "callback_url", + "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" + "callbacks": { + "callback_route": { + "{$callback_url}/callback/": { + "get": { + "summary": "Callback Route", + "operationId": "callback_route__callback_url__callback__get", + "responses": { + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomModel" + } + } + }, + "description": "Bad Request", + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + }, } } - }, + } + }, + } + } + }, + "components": { + "schemas": { + "CustomModel": { + "title": "CustomModel", + "required": ["a"], + "type": "object", + "properties": {"a": {"title": "A", "type": "integer"}}, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, }, - "callbacks": { - "callback_route": { - "{$callback_url}/callback/": { - "get": { - "summary": "Callback Route", - "operationId": "callback_route__callback_url__callback__get", - "responses": { - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CustomModel" - } - } - }, - "description": "Bad Request", - }, - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - }, - } - } - } + "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"}, + }, }, } - } - }, - "components": { - "schemas": { - "CustomModel": { - "title": "CustomModel", - "required": ["a"], - "type": "object", - "properties": {"a": {"title": "A", "type": "integer"}}, - }, - "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"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_annotated.py b/tests/test_annotated.py index a9e7c78c99..39f6f83b29 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -1,7 +1,6 @@ from typing import Annotated import pytest -from dirty_equals import IsDict from fastapi import APIRouter, FastAPI, Query from fastapi.testclient import TestClient @@ -32,44 +31,23 @@ client = TestClient(app) foo_is_missing = { "detail": [ - IsDict( - { - "loc": ["query", "foo"], - "msg": "Field required", - "type": "missing", - "input": None, - } - ) - # TODO: remove when deprecating Pydantic v1 - | IsDict( - { - "loc": ["query", "foo"], - "msg": "field required", - "type": "value_error.missing", - } - ) + { + "loc": ["query", "foo"], + "msg": "Field required", + "type": "missing", + "input": None, + } ] } foo_is_short = { "detail": [ - IsDict( - { - "ctx": {"min_length": 1}, - "loc": ["query", "foo"], - "msg": "String should have at least 1 character", - "type": "string_too_short", - "input": "", - } - ) - # TODO: remove when deprecating Pydantic v1 - | IsDict( - { - "ctx": {"limit_value": 1}, - "loc": ["query", "foo"], - "msg": "ensure this value has at least 1 characters", - "type": "value_error.any_str.min_length", - } - ) + { + "ctx": {"min_length": 1}, + "loc": ["query", "foo"], + "msg": "String should have at least 1 character", + "type": "string_too_short", + "input": "", + } ] } diff --git a/tests/test_application.py b/tests/test_application.py index 8f1b0a18d3..001586ff78 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -1,5 +1,4 @@ import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from .main import app @@ -274,14 +273,10 @@ def test_openapi_schema(): "name": "item_id", "in": "path", "required": True, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Item Id", - } - ) - # TODO: remove when deprecating Pydantic v1 - | IsDict({"title": "Item Id", "type": "string"}), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Item Id", + }, } ], } @@ -984,14 +979,10 @@ def test_openapi_schema(): "name": "query", "in": "query", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Query", - } - ) - # TODO: remove when deprecating Pydantic v1 - | IsDict({"title": "Query", "type": "integer"}), + "schema": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Query", + }, } ], } diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py index 7c6717e2aa..a8658e03bb 100644 --- a/tests/test_dependency_duplicates.py +++ b/tests/test_dependency_duplicates.py @@ -1,4 +1,3 @@ -from dirty_equals import IsDict from fastapi import Depends, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -46,29 +45,16 @@ async def no_duplicates_sub( def test_no_duplicates_invalid(): response = client.post("/no-duplicates", json={"item": {"data": "myitem"}}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item2"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item2"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "item2"], + "msg": "Field required", + "input": None, + } + ] + } def test_no_duplicates(): diff --git a/tests/test_dependency_overrides.py b/tests/test_dependency_overrides.py index 154937fa0b..e25db624d8 100644 --- a/tests/test_dependency_overrides.py +++ b/tests/test_dependency_overrides.py @@ -1,7 +1,6 @@ from typing import Optional import pytest -from dirty_equals import IsDict from fastapi import APIRouter, Depends, FastAPI from fastapi.testclient import TestClient @@ -54,29 +53,16 @@ async def overrider_dependency_with_sub(msg: dict = Depends(overrider_sub_depend def test_main_depends(): response = client.get("/main-depends/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "q"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "q"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "q"], + "msg": "Field required", + "input": None, + } + ] + } def test_main_depends_q_foo(): @@ -100,29 +86,16 @@ def test_main_depends_q_foo_skip_100_limit_200(): def test_decorator_depends(): response = client.get("/decorator-depends/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "q"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "q"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "q"], + "msg": "Field required", + "input": None, + } + ] + } def test_decorator_depends_q_foo(): @@ -140,29 +113,16 @@ def test_decorator_depends_q_foo_skip_100_limit_200(): def test_router_depends(): response = client.get("/router-depends/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "q"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "q"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "q"], + "msg": "Field required", + "input": None, + } + ] + } def test_router_depends_q_foo(): @@ -186,29 +146,16 @@ def test_router_depends_q_foo_skip_100_limit_200(): def test_router_decorator_depends(): response = client.get("/router-decorator-depends/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "q"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "q"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "q"], + "msg": "Field required", + "input": None, + } + ] + } def test_router_decorator_depends_q_foo(): @@ -272,29 +219,17 @@ def test_override_with_sub_main_depends(): app.dependency_overrides[common_parameters] = overrider_dependency_with_sub response = client.get("/main-depends/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "k"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "k"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "k"], + "msg": "Field required", + "input": None, + } + ] + } + app.dependency_overrides = {} @@ -302,29 +237,17 @@ def test_override_with_sub__main_depends_q_foo(): app.dependency_overrides[common_parameters] = overrider_dependency_with_sub response = client.get("/main-depends/?q=foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "k"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "k"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "k"], + "msg": "Field required", + "input": None, + } + ] + } + app.dependency_overrides = {} @@ -340,29 +263,17 @@ def test_override_with_sub_decorator_depends(): app.dependency_overrides[common_parameters] = overrider_dependency_with_sub response = client.get("/decorator-depends/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "k"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "k"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "k"], + "msg": "Field required", + "input": None, + } + ] + } + app.dependency_overrides = {} @@ -370,29 +281,17 @@ def test_override_with_sub_decorator_depends_q_foo(): app.dependency_overrides[common_parameters] = overrider_dependency_with_sub response = client.get("/decorator-depends/?q=foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "k"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "k"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "k"], + "msg": "Field required", + "input": None, + } + ] + } + app.dependency_overrides = {} @@ -408,29 +307,17 @@ def test_override_with_sub_router_depends(): app.dependency_overrides[common_parameters] = overrider_dependency_with_sub response = client.get("/router-depends/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "k"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "k"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "k"], + "msg": "Field required", + "input": None, + } + ] + } + app.dependency_overrides = {} @@ -438,29 +325,17 @@ def test_override_with_sub_router_depends_q_foo(): app.dependency_overrides[common_parameters] = overrider_dependency_with_sub response = client.get("/router-depends/?q=foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "k"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "k"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "k"], + "msg": "Field required", + "input": None, + } + ] + } + app.dependency_overrides = {} @@ -476,29 +351,17 @@ def test_override_with_sub_router_decorator_depends(): app.dependency_overrides[common_parameters] = overrider_dependency_with_sub response = client.get("/router-decorator-depends/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "k"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "k"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "k"], + "msg": "Field required", + "input": None, + } + ] + } + app.dependency_overrides = {} @@ -506,29 +369,17 @@ def test_override_with_sub_router_decorator_depends_q_foo(): app.dependency_overrides[common_parameters] = overrider_dependency_with_sub response = client.get("/router-decorator-depends/?q=foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "k"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "k"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "k"], + "msg": "Field required", + "input": None, + } + ] + } + app.dependency_overrides = {} diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py index bd16fe9254..45734ec28a 100644 --- a/tests/test_extra_routes.py +++ b/tests/test_extra_routes.py @@ -1,6 +1,5 @@ from typing import Optional -from dirty_equals import IsDict from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient @@ -328,14 +327,10 @@ def test_openapi_schema(): "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, - "price": IsDict( - { - "title": "Price", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - # TODO: remove when deprecating Pydantic v1 - | IsDict({"title": "Price", "type": "number"}), + "price": { + "title": "Price", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, }, }, "ValidationError": { diff --git a/tests/test_filter_pydantic_sub_model_pv2.py b/tests/test_filter_pydantic_sub_model_pv2.py index d70f530435..fc5876410d 100644 --- a/tests/test_filter_pydantic_sub_model_pv2.py +++ b/tests/test_filter_pydantic_sub_model_pv2.py @@ -1,7 +1,7 @@ from typing import Optional import pytest -from dirty_equals import HasRepr, IsDict, IsOneOf +from dirty_equals import HasRepr from fastapi import Depends, FastAPI from fastapi.exceptions import ResponseValidationError from fastapi.testclient import TestClient @@ -63,23 +63,13 @@ def test_validator_is_cloned(client: TestClient): with pytest.raises(ResponseValidationError) as err: client.get("/model/modelX") assert err.value.errors() == [ - IsDict( - { - "type": "value_error", - "loc": ("response", "name"), - "msg": "Value error, name must end in A", - "input": "modelX", - "ctx": {"error": HasRepr("ValueError('name must end in A')")}, - } - ) - | IsDict( - # TODO remove when deprecating Pydantic v1 - { - "loc": ("response", "name"), - "msg": "name must end in A", - "type": "value_error", - } - ) + { + "type": "value_error", + "loc": ("response", "name"), + "msg": "Value error, name must end in A", + "input": "modelX", + "ctx": {"error": HasRepr("ValueError('name must end in A')")}, + } ] @@ -145,23 +135,14 @@ def test_openapi_schema(client: TestClient): }, "ModelA": { "title": "ModelA", - "required": IsOneOf( - ["name", "description", "foo"], - # TODO remove when deprecating Pydantic v1 - ["name", "foo"], - ), + "required": ["name", "foo"], "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | - # TODO remove when deprecating Pydantic v1 - IsDict({"title": "Description", "type": "string"}), + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, "foo": {"$ref": "#/components/schemas/ModelB"}, "tags": { "additionalProperties": {"type": "string"}, diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py index c401cc9374..7d03d29572 100644 --- a/tests/test_forms_single_model.py +++ b/tests/test_forms_single_model.py @@ -1,6 +1,5 @@ from typing import Annotated, Optional -from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -79,68 +78,37 @@ def test_invalid_data(): }, ) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["body", "age"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "seventy", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "age"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["body", "age"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "seventy", + } + ] + } def test_no_data(): response = client.post("/form/") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {"tags": ["foo", "bar"], "with": "nothing"}, - }, - { - "type": "missing", - "loc": ["body", "lastname"], - "msg": "Field required", - "input": {"tags": ["foo", "bar"], "with": "nothing"}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "lastname"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": {"tags": ["foo", "bar"], "with": "nothing"}, + }, + { + "type": "missing", + "loc": ["body", "lastname"], + "msg": "Field required", + "input": {"tags": ["foo", "bar"], "with": "nothing"}, + }, + ] + } def test_extra_param_single(): diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py index e3d57bb428..147018996e 100644 --- a/tests/test_infer_param_optionality.py +++ b/tests/test_infer_param_optionality.py @@ -1,6 +1,5 @@ from typing import Optional -from dirty_equals import IsDict from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient @@ -163,16 +162,10 @@ def test_openapi_schema(): "required": False, "name": "user_id", "in": "query", - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "User Id", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User Id", + }, } ], "responses": { @@ -208,16 +201,10 @@ def test_openapi_schema(): "required": False, "name": "user_id", "in": "query", - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "User Id", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User Id", + }, }, ], "responses": { @@ -247,16 +234,10 @@ def test_openapi_schema(): "required": True, "name": "user_id", "in": "path", - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "User Id", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User Id", + }, } ], "responses": { @@ -292,16 +273,10 @@ def test_openapi_schema(): "required": True, "name": "user_id", "in": "path", - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "User Id", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User Id", + }, }, ], "responses": { diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py index 6ea405fe70..4418c77cb0 100644 --- a/tests/test_multi_body_errors.py +++ b/tests/test_multi_body_errors.py @@ -1,8 +1,9 @@ from decimal import Decimal -from dirty_equals import IsDict, IsOneOf +from dirty_equals import IsOneOf from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, condecimal app = FastAPI() @@ -24,109 +25,65 @@ client = TestClient(app) def test_put_correct_body(): response = client.post("/items/", json=[{"name": "Foo", "age": 5}]) assert response.status_code == 200, response.text - assert response.json() == { - "item": [ - { - "name": "Foo", - "age": IsOneOf( - 5, - # TODO: remove when deprecating Pydantic v1 - "5", - ), - } - ] - } + assert response.json() == snapshot( + { + "item": [ + { + "name": "Foo", + "age": "5", + } + ] + } + ) def test_jsonable_encoder_requiring_error(): response = client.post("/items/", json=[{"name": "Foo", "age": -1.0}]) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["body", 0, "age"], - "msg": "Input should be greater than 0", - "input": -1.0, - "ctx": {"gt": 0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"limit_value": 0.0}, - "loc": ["body", 0, "age"], - "msg": "ensure this value is greater than 0", - "type": "value_error.number.not_gt", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "greater_than", + "loc": ["body", 0, "age"], + "msg": "Input should be greater than 0", + "input": -1.0, + "ctx": {"gt": 0}, + } + ] + } def test_put_incorrect_body_multiple(): response = client.post("/items/", json=[{"age": "five"}, {"age": "six"}]) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", 0, "name"], - "msg": "Field required", - "input": {"age": "five"}, - }, - { - "type": "decimal_parsing", - "loc": ["body", 0, "age"], - "msg": "Input should be a valid decimal", - "input": "five", - }, - { - "type": "missing", - "loc": ["body", 1, "name"], - "msg": "Field required", - "input": {"age": "six"}, - }, - { - "type": "decimal_parsing", - "loc": ["body", 1, "age"], - "msg": "Input should be a valid decimal", - "input": "six", - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", 0, "name"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", 0, "age"], - "msg": "value is not a valid decimal", - "type": "type_error.decimal", - }, - { - "loc": ["body", 1, "name"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", 1, "age"], - "msg": "value is not a valid decimal", - "type": "type_error.decimal", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", 0, "name"], + "msg": "Field required", + "input": {"age": "five"}, + }, + { + "type": "decimal_parsing", + "loc": ["body", 0, "age"], + "msg": "Input should be a valid decimal", + "input": "five", + }, + { + "type": "missing", + "loc": ["body", 1, "name"], + "msg": "Field required", + "input": {"age": "six"}, + }, + { + "type": "decimal_parsing", + "loc": ["body", 1, "age"], + "msg": "Input should be a valid decimal", + "input": "six", + }, + ] + } def test_openapi_schema(): @@ -179,31 +136,21 @@ def test_openapi_schema(): "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, - "age": IsDict( - { - "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*$", - }, - ), - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Age", - "exclusiveMinimum": 0.0, - "type": "number", - } - ), + "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": { diff --git a/tests/test_multi_query_errors.py b/tests/test_multi_query_errors.py index 7387a81ddf..5df51ba185 100644 --- a/tests/test_multi_query_errors.py +++ b/tests/test_multi_query_errors.py @@ -1,4 +1,3 @@ -from dirty_equals import IsDict from fastapi import FastAPI, Query from fastapi.testclient import TestClient @@ -22,40 +21,22 @@ def test_multi_query(): def test_multi_query_incorrect(): response = client.get("/items/?q=five&q=six") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["query", "q", 0], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "five", - }, - { - "type": "int_parsing", - "loc": ["query", "q", 1], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "six", - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "q", 0], - "msg": "value is not a valid integer", - "type": "type_error.integer", - }, - { - "loc": ["query", "q", 1], - "msg": "value is not a valid integer", - "type": "type_error.integer", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["query", "q", 0], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "five", + }, + { + "type": "int_parsing", + "loc": ["query", "q", 1], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "six", + }, + ] + } def test_openapi_schema(): diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py index b3f83ae237..bd0d55452e 100644 --- a/tests/test_openapi_examples.py +++ b/tests/test_openapi_examples.py @@ -1,6 +1,5 @@ from typing import Union -from dirty_equals import IsDict from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi.testclient import TestClient from pydantic import BaseModel @@ -155,26 +154,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": IsDict( - { - "$ref": "#/components/schemas/Item", - "examples": [ - {"data": "Data in Body examples, example1"} - ], - } - ) - | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - "examples": [ - {"data": "Data in Body examples, example1"} - ], - } - ), + "schema": { + "$ref": "#/components/schemas/Item", + "examples": [ + {"data": "Data in Body examples, example1"} + ], + }, "examples": { "Example One": { "summary": "Example One Summary", @@ -265,27 +250,14 @@ def test_openapi_schema(): "name": "data", "in": "query", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "examples": [ - "json_schema_query1", - "json_schema_query2", - ], - "title": "Data", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "examples": [ - "json_schema_query1", - "json_schema_query2", - ], - "type": "string", - "title": "Data", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "examples": [ + "json_schema_query1", + "json_schema_query2", + ], + "title": "Data", + }, "examples": { "Query One": { "summary": "Query One Summary", @@ -323,27 +295,14 @@ def test_openapi_schema(): "name": "data", "in": "header", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "examples": [ - "json_schema_header1", - "json_schema_header2", - ], - "title": "Data", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "examples": [ - "json_schema_header1", - "json_schema_header2", - ], - "title": "Data", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "examples": [ + "json_schema_header1", + "json_schema_header2", + ], + "title": "Data", + }, "examples": { "Header One": { "summary": "Header One Summary", @@ -381,27 +340,14 @@ def test_openapi_schema(): "name": "data", "in": "cookie", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "examples": [ - "json_schema_cookie1", - "json_schema_cookie2", - ], - "title": "Data", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "examples": [ - "json_schema_cookie1", - "json_schema_cookie2", - ], - "title": "Data", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "examples": [ + "json_schema_cookie1", + "json_schema_cookie2", + ], + "title": "Data", + }, "examples": { "Cookie One": { "summary": "Cookie One Summary", diff --git a/tests/test_openapi_query_parameter_extension.py b/tests/test_openapi_query_parameter_extension.py index dc7147c712..084cb695d4 100644 --- a/tests/test_openapi_query_parameter_extension.py +++ b/tests/test_openapi_query_parameter_extension.py @@ -1,6 +1,5 @@ from typing import Optional -from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient @@ -53,21 +52,11 @@ def test_openapi(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "default": 50, - "title": "Standard Query Param", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Standard Query Param", - "type": "integer", - "default": 50, - } - ), + "schema": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "default": 50, + "title": "Standard Query Param", + }, "name": "standard_query_param", "in": "query", }, diff --git a/tests/test_openapi_servers.py b/tests/test_openapi_servers.py index 8697c8438b..33079e4b1d 100644 --- a/tests/test_openapi_servers.py +++ b/tests/test_openapi_servers.py @@ -1,6 +1,6 @@ -from dirty_equals import IsOneOf from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI( servers=[ @@ -30,39 +30,31 @@ def test_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"}, - "servers": [ - {"url": "/", "description": "Default, relative server"}, - { - "url": IsOneOf( - "http://staging.localhost.tiangolo.com:8000/", - # TODO: remove when deprecating Pydantic v1 - "http://staging.localhost.tiangolo.com:8000", - ), - "description": "Staging but actually localhost still", - }, - { - "url": IsOneOf( - "https://prod.example.com/", - # TODO: remove when deprecating Pydantic v1 - "https://prod.example.com", - ) - }, - ], - "paths": { - "/foo": { - "get": { - "summary": "Foo", - "operationId": "foo_foo_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"}, + "servers": [ + {"url": "/", "description": "Default, relative server"}, + { + "url": "http://staging.localhost.tiangolo.com:8000", + "description": "Staging but actually localhost still", + }, + {"url": "https://prod.example.com"}, + ], + "paths": { + "/foo": { + "get": { + "summary": "Foo", + "operationId": "foo_foo_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_params_repr.py b/tests/test_params_repr.py index 19c2e8d696..670e4f5ddf 100644 --- a/tests/test_params_repr.py +++ b/tests/test_params_repr.py @@ -1,6 +1,5 @@ from typing import Any -from dirty_equals import IsOneOf from fastapi.params import Body, Cookie, Header, Param, Path, Query test_data: list[Any] = ["teststr", None, ..., 1, []] @@ -19,11 +18,7 @@ def test_param_repr_none(): def test_param_repr_ellipsis(): - assert repr(Param(...)) == IsOneOf( - "Param(PydanticUndefined)", - # TODO: remove when deprecating Pydantic v1 - "Param(Ellipsis)", - ) + assert repr(Param(...)) == "Param(PydanticUndefined)" def test_param_repr_number(): @@ -35,16 +30,8 @@ def test_param_repr_list(): def test_path_repr(): - assert repr(Path()) == IsOneOf( - "Path(PydanticUndefined)", - # TODO: remove when deprecating Pydantic v1 - "Path(Ellipsis)", - ) - assert repr(Path(...)) == IsOneOf( - "Path(PydanticUndefined)", - # TODO: remove when deprecating Pydantic v1 - "Path(Ellipsis)", - ) + assert repr(Path()) == "Path(PydanticUndefined)" + assert repr(Path(...)) == "Path(PydanticUndefined)" def test_query_repr_str(): @@ -56,11 +43,7 @@ def test_query_repr_none(): def test_query_repr_ellipsis(): - assert repr(Query(...)) == IsOneOf( - "Query(PydanticUndefined)", - # TODO: remove when deprecating Pydantic v1 - "Query(Ellipsis)", - ) + assert repr(Query(...)) == "Query(PydanticUndefined)" def test_query_repr_number(): @@ -80,11 +63,7 @@ def test_header_repr_none(): def test_header_repr_ellipsis(): - assert repr(Header(...)) == IsOneOf( - "Header(PydanticUndefined)", - # TODO: remove when deprecating Pydantic v1 - "Header(Ellipsis)", - ) + assert repr(Header(...)) == "Header(PydanticUndefined)" def test_header_repr_number(): @@ -104,11 +83,7 @@ def test_cookie_repr_none(): def test_cookie_repr_ellipsis(): - assert repr(Cookie(...)) == IsOneOf( - "Cookie(PydanticUndefined)", - # TODO: remove when deprecating Pydantic v1 - "Cookie(Ellipsis)", - ) + assert repr(Cookie(...)) == "Cookie(PydanticUndefined)" def test_cookie_repr_number(): @@ -128,11 +103,7 @@ def test_body_repr_none(): def test_body_repr_ellipsis(): - assert repr(Body(...)) == IsOneOf( - "Body(PydanticUndefined)", - # TODO: remove when deprecating Pydantic v1 - "Body(Ellipsis)", - ) + assert repr(Body(...)) == "Body(PydanticUndefined)" def test_body_repr_number(): diff --git a/tests/test_path.py b/tests/test_path.py index 09c1f13fb1..47051b927c 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -1,4 +1,3 @@ -from dirty_equals import IsDict from fastapi.testclient import TestClient from .main import app @@ -45,57 +44,31 @@ def test_path_str_True(): def test_path_int_foobar(): response = client.get("/path/int/foobar") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foobar", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "foobar", + } + ] + } def test_path_int_True(): response = client.get("/path/int/True") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "True", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "True", + } + ] + } def test_path_int_42(): @@ -107,85 +80,46 @@ def test_path_int_42(): def test_path_int_42_5(): response = client.get("/path/int/42.5") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "42.5", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "42.5", + } + ] + } def test_path_float_foobar(): response = client.get("/path/float/foobar") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "float_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid number, unable to parse string as a number", - "input": "foobar", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid float", - "type": "type_error.float", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "float_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid number, unable to parse string as a number", + "input": "foobar", + } + ] + } def test_path_float_True(): response = client.get("/path/float/True") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "float_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid number, unable to parse string as a number", - "input": "True", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid float", - "type": "type_error.float", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "float_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid number, unable to parse string as a number", + "input": "True", + } + ] + } def test_path_float_42(): @@ -203,29 +137,16 @@ def test_path_float_42_5(): def test_path_bool_foobar(): response = client.get("/path/bool/foobar") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "bool_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid boolean, unable to interpret input", - "input": "foobar", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value could not be parsed to a boolean", - "type": "type_error.bool", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "bool_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid boolean, unable to interpret input", + "input": "foobar", + } + ] + } def test_path_bool_True(): @@ -237,57 +158,31 @@ def test_path_bool_True(): def test_path_bool_42(): response = client.get("/path/bool/42") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "bool_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid boolean, unable to interpret input", - "input": "42", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value could not be parsed to a boolean", - "type": "type_error.bool", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "bool_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid boolean, unable to interpret input", + "input": "42", + } + ] + } def test_path_bool_42_5(): response = client.get("/path/bool/42.5") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "bool_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid boolean, unable to interpret input", - "input": "42.5", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value could not be parsed to a boolean", - "type": "type_error.bool", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "bool_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid boolean, unable to interpret input", + "input": "42.5", + } + ] + } def test_path_bool_1(): @@ -335,31 +230,17 @@ def test_path_param_minlength_foo(): def test_path_param_minlength_fo(): response = client.get("/path/param-minlength/fo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_too_short", - "loc": ["path", "item_id"], - "msg": "String should have at least 3 characters", - "input": "fo", - "ctx": {"min_length": 3}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value has at least 3 characters", - "type": "value_error.any_str.min_length", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_too_short", + "loc": ["path", "item_id"], + "msg": "String should have at least 3 characters", + "input": "fo", + "ctx": {"min_length": 3}, + } + ] + } def test_path_param_maxlength_foo(): @@ -371,31 +252,17 @@ def test_path_param_maxlength_foo(): def test_path_param_maxlength_foobar(): response = client.get("/path/param-maxlength/foobar") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_too_long", - "loc": ["path", "item_id"], - "msg": "String should have at most 3 characters", - "input": "foobar", - "ctx": {"max_length": 3}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value has at most 3 characters", - "type": "value_error.any_str.max_length", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_too_long", + "loc": ["path", "item_id"], + "msg": "String should have at most 3 characters", + "input": "foobar", + "ctx": {"max_length": 3}, + } + ] + } def test_path_param_min_maxlength_foo(): @@ -407,60 +274,33 @@ def test_path_param_min_maxlength_foo(): def test_path_param_min_maxlength_foobar(): response = client.get("/path/param-min_maxlength/foobar") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_too_long", - "loc": ["path", "item_id"], - "msg": "String should have at most 3 characters", - "input": "foobar", - "ctx": {"max_length": 3}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value has at most 3 characters", - "type": "value_error.any_str.max_length", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_too_long", + "loc": ["path", "item_id"], + "msg": "String should have at most 3 characters", + "input": "foobar", + "ctx": {"max_length": 3}, + } + ] + } def test_path_param_min_maxlength_f(): response = client.get("/path/param-min_maxlength/f") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_too_short", - "loc": ["path", "item_id"], - "msg": "String should have at least 2 characters", - "input": "f", - "ctx": {"min_length": 2}, - } - ] - } - ) | IsDict( - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value has at least 2 characters", - "type": "value_error.any_str.min_length", - "ctx": {"limit_value": 2}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_too_short", + "loc": ["path", "item_id"], + "msg": "String should have at least 2 characters", + "input": "f", + "ctx": {"min_length": 2}, + } + ] + } def test_path_param_gt_42(): @@ -472,31 +312,17 @@ def test_path_param_gt_42(): def test_path_param_gt_2(): response = client.get("/path/param-gt/2") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["path", "item_id"], - "msg": "Input should be greater than 3", - "input": "2", - "ctx": {"gt": 3.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is greater than 3", - "type": "value_error.number.not_gt", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "greater_than", + "loc": ["path", "item_id"], + "msg": "Input should be greater than 3", + "input": "2", + "ctx": {"gt": 3.0}, + } + ] + } def test_path_param_gt0_0_05(): @@ -508,31 +334,17 @@ def test_path_param_gt0_0_05(): def test_path_param_gt0_0(): response = client.get("/path/param-gt0/0") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["path", "item_id"], - "msg": "Input should be greater than 0", - "input": "0", - "ctx": {"gt": 0.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is greater than 0", - "type": "value_error.number.not_gt", - "ctx": {"limit_value": 0}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "greater_than", + "loc": ["path", "item_id"], + "msg": "Input should be greater than 0", + "input": "0", + "ctx": {"gt": 0.0}, + } + ] + } def test_path_param_ge_42(): @@ -550,61 +362,33 @@ def test_path_param_ge_3(): def test_path_param_ge_2(): response = client.get("/path/param-ge/2") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than_equal", - "loc": ["path", "item_id"], - "msg": "Input should be greater than or equal to 3", - "input": "2", - "ctx": {"ge": 3.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is greater than or equal to 3", - "type": "value_error.number.not_ge", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "greater_than_equal", + "loc": ["path", "item_id"], + "msg": "Input should be greater than or equal to 3", + "input": "2", + "ctx": {"ge": 3.0}, + } + ] + } def test_path_param_lt_42(): response = client.get("/path/param-lt/42") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "less_than", - "loc": ["path", "item_id"], - "msg": "Input should be less than 3", - "input": "42", - "ctx": {"lt": 3.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is less than 3", - "type": "value_error.number.not_lt", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "less_than", + "loc": ["path", "item_id"], + "msg": "Input should be less than 3", + "input": "42", + "ctx": {"lt": 3.0}, + } + ] + } def test_path_param_lt_2(): @@ -622,61 +406,33 @@ def test_path_param_lt0__1(): def test_path_param_lt0_0(): response = client.get("/path/param-lt0/0") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "less_than", - "loc": ["path", "item_id"], - "msg": "Input should be less than 0", - "input": "0", - "ctx": {"lt": 0.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is less than 0", - "type": "value_error.number.not_lt", - "ctx": {"limit_value": 0}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "less_than", + "loc": ["path", "item_id"], + "msg": "Input should be less than 0", + "input": "0", + "ctx": {"lt": 0.0}, + } + ] + } def test_path_param_le_42(): response = client.get("/path/param-le/42") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "less_than_equal", - "loc": ["path", "item_id"], - "msg": "Input should be less than or equal to 3", - "input": "42", - "ctx": {"le": 3.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is less than or equal to 3", - "type": "value_error.number.not_le", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "less_than_equal", + "loc": ["path", "item_id"], + "msg": "Input should be less than or equal to 3", + "input": "42", + "ctx": {"le": 3.0}, + } + ] + } def test_path_param_le_3(): @@ -700,61 +456,33 @@ def test_path_param_lt_gt_2(): def test_path_param_lt_gt_4(): response = client.get("/path/param-lt-gt/4") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "less_than", - "loc": ["path", "item_id"], - "msg": "Input should be less than 3", - "input": "4", - "ctx": {"lt": 3.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is less than 3", - "type": "value_error.number.not_lt", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "less_than", + "loc": ["path", "item_id"], + "msg": "Input should be less than 3", + "input": "4", + "ctx": {"lt": 3.0}, + } + ] + } def test_path_param_lt_gt_0(): response = client.get("/path/param-lt-gt/0") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["path", "item_id"], - "msg": "Input should be greater than 1", - "input": "0", - "ctx": {"gt": 1.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is greater than 1", - "type": "value_error.number.not_gt", - "ctx": {"limit_value": 1}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "greater_than", + "loc": ["path", "item_id"], + "msg": "Input should be greater than 1", + "input": "0", + "ctx": {"gt": 1.0}, + } + ] + } def test_path_param_le_ge_2(): @@ -777,31 +505,17 @@ def test_path_param_le_ge_3(): def test_path_param_le_ge_4(): response = client.get("/path/param-le-ge/4") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "less_than_equal", - "loc": ["path", "item_id"], - "msg": "Input should be less than or equal to 3", - "input": "4", - "ctx": {"le": 3.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is less than or equal to 3", - "type": "value_error.number.not_le", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "less_than_equal", + "loc": ["path", "item_id"], + "msg": "Input should be less than or equal to 3", + "input": "4", + "ctx": {"le": 3.0}, + } + ] + } def test_path_param_lt_int_2(): @@ -813,59 +527,32 @@ def test_path_param_lt_int_2(): def test_path_param_lt_int_42(): response = client.get("/path/param-lt-int/42") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "less_than", - "loc": ["path", "item_id"], - "msg": "Input should be less than 3", - "input": "42", - "ctx": {"lt": 3}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is less than 3", - "type": "value_error.number.not_lt", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "less_than", + "loc": ["path", "item_id"], + "msg": "Input should be less than 3", + "input": "42", + "ctx": {"lt": 3}, + } + ] + } def test_path_param_lt_int_2_7(): response = client.get("/path/param-lt-int/2.7") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "2.7", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "2.7", + } + ] + } def test_path_param_gt_int_42(): @@ -877,89 +564,48 @@ def test_path_param_gt_int_42(): def test_path_param_gt_int_2(): response = client.get("/path/param-gt-int/2") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["path", "item_id"], - "msg": "Input should be greater than 3", - "input": "2", - "ctx": {"gt": 3}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is greater than 3", - "type": "value_error.number.not_gt", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "greater_than", + "loc": ["path", "item_id"], + "msg": "Input should be greater than 3", + "input": "2", + "ctx": {"gt": 3}, + } + ] + } def test_path_param_gt_int_2_7(): response = client.get("/path/param-gt-int/2.7") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "2.7", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "2.7", + } + ] + } def test_path_param_le_int_42(): response = client.get("/path/param-le-int/42") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "less_than_equal", - "loc": ["path", "item_id"], - "msg": "Input should be less than or equal to 3", - "input": "42", - "ctx": {"le": 3}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is less than or equal to 3", - "type": "value_error.number.not_le", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "less_than_equal", + "loc": ["path", "item_id"], + "msg": "Input should be less than or equal to 3", + "input": "42", + "ctx": {"le": 3}, + } + ] + } def test_path_param_le_int_3(): @@ -977,29 +623,16 @@ def test_path_param_le_int_2(): def test_path_param_le_int_2_7(): response = client.get("/path/param-le-int/2.7") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "2.7", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "2.7", + } + ] + } def test_path_param_ge_int_42(): @@ -1017,59 +650,32 @@ def test_path_param_ge_int_3(): def test_path_param_ge_int_2(): response = client.get("/path/param-ge-int/2") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than_equal", - "loc": ["path", "item_id"], - "msg": "Input should be greater than or equal to 3", - "input": "2", - "ctx": {"ge": 3}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is greater than or equal to 3", - "type": "value_error.number.not_ge", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "greater_than_equal", + "loc": ["path", "item_id"], + "msg": "Input should be greater than or equal to 3", + "input": "2", + "ctx": {"ge": 3}, + } + ] + } def test_path_param_ge_int_2_7(): response = client.get("/path/param-ge-int/2.7") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "2.7", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "2.7", + } + ] + } def test_path_param_lt_gt_int_2(): @@ -1081,89 +687,48 @@ def test_path_param_lt_gt_int_2(): def test_path_param_lt_gt_int_4(): response = client.get("/path/param-lt-gt-int/4") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "less_than", - "loc": ["path", "item_id"], - "msg": "Input should be less than 3", - "input": "4", - "ctx": {"lt": 3}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is less than 3", - "type": "value_error.number.not_lt", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "less_than", + "loc": ["path", "item_id"], + "msg": "Input should be less than 3", + "input": "4", + "ctx": {"lt": 3}, + } + ] + } def test_path_param_lt_gt_int_0(): response = client.get("/path/param-lt-gt-int/0") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["path", "item_id"], - "msg": "Input should be greater than 1", - "input": "0", - "ctx": {"gt": 1}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is greater than 1", - "type": "value_error.number.not_gt", - "ctx": {"limit_value": 1}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "greater_than", + "loc": ["path", "item_id"], + "msg": "Input should be greater than 1", + "input": "0", + "ctx": {"gt": 1}, + } + ] + } def test_path_param_lt_gt_int_2_7(): response = client.get("/path/param-lt-gt-int/2.7") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "2.7", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "2.7", + } + ] + } def test_path_param_le_ge_int_2(): @@ -1187,56 +752,29 @@ def test_path_param_le_ge_int_3(): def test_path_param_le_ge_int_4(): response = client.get("/path/param-le-ge-int/4") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "less_than_equal", - "loc": ["path", "item_id"], - "msg": "Input should be less than or equal to 3", - "input": "4", - "ctx": {"le": 3}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "ensure this value is less than or equal to 3", - "type": "value_error.number.not_le", - "ctx": {"limit_value": 3}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "less_than_equal", + "loc": ["path", "item_id"], + "msg": "Input should be less than or equal to 3", + "input": "4", + "ctx": {"le": 3}, + } + ] + } def test_path_param_le_ge_int_2_7(): response = client.get("/path/param-le-ge-int/2.7") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "2.7", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["path", "item_id"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "2.7", + } + ] + } diff --git a/tests/test_query.py b/tests/test_query.py index 57f551d2ab..c25960caca 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,4 +1,3 @@ -from dirty_equals import IsDict from fastapi.testclient import TestClient from .main import app @@ -9,29 +8,16 @@ client = TestClient(app) def test_query(): response = client.get("/query") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "query"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "query"], + "msg": "Field required", + "input": None, + } + ] + } def test_query_query_baz(): @@ -43,29 +29,16 @@ def test_query_query_baz(): def test_query_not_declared_baz(): response = client.get("/query?not_declared=baz") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "query"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "query"], + "msg": "Field required", + "input": None, + } + ] + } def test_query_optional(): @@ -89,29 +62,16 @@ def test_query_optional_not_declared_baz(): def test_query_int(): response = client.get("/query/int") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "query"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "query"], + "msg": "Field required", + "input": None, + } + ] + } def test_query_int_query_42(): @@ -123,85 +83,46 @@ def test_query_int_query_42(): def test_query_int_query_42_5(): response = client.get("/query/int?query=42.5") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["query", "query"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "42.5", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["query", "query"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "42.5", + } + ] + } def test_query_int_query_baz(): response = client.get("/query/int?query=baz") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["query", "query"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "baz", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["query", "query"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "baz", + } + ] + } def test_query_int_not_declared_baz(): response = client.get("/query/int?not_declared=baz") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "query"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "query"], + "msg": "Field required", + "input": None, + } + ] + } def test_query_int_optional(): @@ -219,29 +140,16 @@ def test_query_int_optional_query_50(): def test_query_int_optional_query_foo(): response = client.get("/query/int/optional?query=foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["query", "query"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["query", "query"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "foo", + } + ] + } def test_query_int_default(): @@ -259,29 +167,16 @@ def test_query_int_default_query_50(): def test_query_int_default_query_foo(): response = client.get("/query/int/default?query=foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["query", "query"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["query", "query"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "foo", + } + ] + } def test_query_param(): @@ -299,29 +194,16 @@ def test_query_param_query_50(): def test_query_param_required(): response = client.get("/query/param-required") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "query"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "query"], + "msg": "Field required", + "input": None, + } + ] + } def test_query_param_required_query_50(): @@ -333,29 +215,16 @@ def test_query_param_required_query_50(): def test_query_param_required_int(): response = client.get("/query/param-required/int") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "query"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "query"], + "msg": "Field required", + "input": None, + } + ] + } def test_query_param_required_int_query_50(): @@ -367,29 +236,16 @@ def test_query_param_required_int_query_50(): def test_query_param_required_int_query_foo(): response = client.get("/query/param-required/int?query=foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["query", "query"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "query"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["query", "query"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "foo", + } + ] + } def test_query_frozenset_query_1_query_1_query_2(): diff --git a/tests/test_regex_deprecated_body.py b/tests/test_regex_deprecated_body.py index 9d58c5dae1..5b4daa450f 100644 --- a/tests/test_regex_deprecated_body.py +++ b/tests/test_regex_deprecated_body.py @@ -1,10 +1,10 @@ from typing import Annotated import pytest -from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient +from inline_snapshot import snapshot from .utils import needs_py310 @@ -47,31 +47,17 @@ def test_query_nonregexquery(): client = get_client() response = client.post("/items/", data={"q": "nonregexquery"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["body", "q"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["body", "q"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_pattern_mismatch", + "loc": ["body", "q"], + "msg": "String should match pattern '^fixedquery$'", + "input": "nonregexquery", + "ctx": {"pattern": "^fixedquery$"}, + } + ] + } @needs_py310 @@ -79,104 +65,88 @@ 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/": { - "post": { - "summary": "Read Items", - "operationId": "read_items_items__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_read_items_items__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Read Items", + "operationId": "read_items_items__post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { "$ref": "#/components/schemas/Body_read_items_items__post" } - ) - } - } - }, - "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_read_items_items__post": { - "properties": { - "q": IsDict( - { + }, + "components": { + "schemas": { + "Body_read_items_items__post": { + "properties": { + "q": { "anyOf": [ {"type": "string", "pattern": "^fixedquery$"}, {"type": "null"}, ], "title": "Q", } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"type": "string", "pattern": "^fixedquery$", "title": "Q"} - ) - }, - "type": "object", - "title": "Body_read_items_items__post", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, + "type": "object", + "title": "Body_read_items_items__post", }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "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_regex_deprecated_params.py b/tests/test_regex_deprecated_params.py index 8973657a90..d6eaa45fb1 100644 --- a/tests/test_regex_deprecated_params.py +++ b/tests/test_regex_deprecated_params.py @@ -1,7 +1,6 @@ from typing import Annotated import pytest -from dirty_equals import IsDict from fastapi import FastAPI, Query from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient @@ -47,31 +46,17 @@ def test_query_params_str_validations_item_query_nonregexquery(): client = get_client() response = client.get("/items/", params={"q": "nonregexquery"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "q"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "q"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_pattern_mismatch", + "loc": ["query", "q"], + "msg": "String should match pattern '^fixedquery$'", + "input": "nonregexquery", + "ctx": {"pattern": "^fixedquery$"}, + } + ] + } @needs_py310 @@ -93,23 +78,13 @@ def test_openapi_schema(): "name": "q", "in": "query", "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "string", "pattern": "^fixedquery$"}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "pattern": "^fixedquery$", - "title": "Q", - } - ), + "schema": { + "anyOf": [ + {"type": "string", "pattern": "^fixedquery$"}, + {"type": "null"}, + ], + "title": "Q", + }, } ], "responses": { diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py index 0048da0f8b..50847335ce 100644 --- a/tests/test_request_params/test_body/test_list.py +++ b/tests/test_request_params/test_body/test_list.py @@ -148,29 +148,16 @@ def test_required_list_alias_missing(path: str, json: Union[dict, None]): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": IsOneOf(["body", "p_alias"], ["body"]), - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": IsOneOf(["body", "p_alias"], ["body"]), - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": IsOneOf(["body", "p_alias"], ["body"]), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -181,29 +168,16 @@ def test_required_list_alias_by_name(path: str): client = TestClient(app) response = client.post(path, json={"p": ["hello", "world"]}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {"p": ["hello", "world"]}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": ["hello", "world"]}), + } + ] + } @pytest.mark.parametrize( 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 960e8890f3..ba8ba9092e 100644 --- a/tests/test_request_params/test_body/test_optional_list.py +++ b/tests/test_request_params/test_body/test_optional_list.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -38,30 +37,19 @@ def test_optional_list_str_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p": {"items": {"type": "string"}, "type": "array", "title": "P"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } def test_optional_list_str_missing(): @@ -75,29 +63,16 @@ def test_model_optional_list_str_missing(): client = TestClient(app) response = client.post("/model-optional-list-str") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "input": None, - "loc": ["body"], - "msg": "Field required", - "type": "missing", - }, - ], - } - ) | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - }, - ], - } - ) + assert response.json() == { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } @pytest.mark.parametrize( @@ -153,34 +128,19 @@ def test_optional_list_str_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_alias": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_alias": { - "items": {"type": "string"}, - "type": "array", - "title": "P Alias", - }, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } def test_optional_list_alias_missing(): @@ -194,29 +154,16 @@ def test_model_optional_list_alias_missing(): client = TestClient(app) response = client.post("/model-optional-list-alias") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "input": None, - "loc": ["body"], - "msg": "Field required", - "type": "missing", - }, - ], - } - ) | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - }, - ], - } - ) + assert response.json() == { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } @pytest.mark.parametrize( @@ -289,34 +236,19 @@ def test_optional_list_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_val_alias": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Val Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_val_alias": { - "items": {"type": "string"}, - "type": "array", - "title": "P Val Alias", - }, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } def test_optional_list_validation_alias_missing(): @@ -330,29 +262,16 @@ def test_model_optional_list_validation_alias_missing(): client = TestClient(app) response = client.post("/model-optional-list-validation-alias") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "input": None, - "loc": ["body"], - "msg": "Field required", - "type": "missing", - }, - ], - } - ) | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - }, - ], - } - ) + assert response.json() == { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } @pytest.mark.parametrize( @@ -438,34 +357,19 @@ def test_optional_list_alias_and_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_val_alias": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Val Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_val_alias": { - "items": {"type": "string"}, - "type": "array", - "title": "P Val Alias", - }, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } def test_optional_list_alias_and_validation_alias_missing(): @@ -479,29 +383,16 @@ def test_model_optional_list_alias_and_validation_alias_missing(): client = TestClient(app) response = client.post("/model-optional-list-alias-and-validation-alias") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "input": None, - "loc": ["body"], - "msg": "Field required", - "type": "missing", - }, - ], - } - ) | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - }, - ], - } - ) + assert response.json() == { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } @pytest.mark.parametrize( 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 59732688a2..b9c18034da 100644 --- a/tests/test_request_params/test_body/test_optional_str.py +++ b/tests/test_request_params/test_body/test_optional_str.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -36,27 +35,16 @@ def test_optional_str_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p": {"type": "string", "title": "P"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } def test_optional_str_missing(): @@ -70,29 +58,16 @@ def test_model_optional_str_missing(): client = TestClient(app) response = client.post("/model-optional-str") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "input": None, - "loc": ["body"], - "msg": "Field required", - "type": "missing", - }, - ], - } - ) | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - }, - ], - } - ) + assert response.json() == { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } @pytest.mark.parametrize( @@ -148,27 +123,16 @@ def test_optional_str_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_alias": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_alias": {"type": "string", "title": "P Alias"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } def test_optional_alias_missing(): @@ -182,29 +146,16 @@ def test_model_optional_alias_missing(): client = TestClient(app) response = client.post("/model-optional-alias") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "input": None, - "loc": ["body"], - "msg": "Field required", - "type": "missing", - }, - ], - } - ) | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - }, - ], - } - ) + assert response.json() == { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } @pytest.mark.parametrize( @@ -274,27 +225,16 @@ def test_optional_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_val_alias": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_val_alias": {"type": "string", "title": "P Val Alias"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } def test_optional_validation_alias_missing(): @@ -308,29 +248,16 @@ def test_model_optional_validation_alias_missing(): client = TestClient(app) response = client.post("/model-optional-validation-alias") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "input": None, - "loc": ["body"], - "msg": "Field required", - "type": "missing", - }, - ], - } - ) | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - }, - ], - } - ) + assert response.json() == { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } @pytest.mark.parametrize( @@ -413,27 +340,16 @@ def test_optional_alias_and_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_val_alias": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_val_alias": {"type": "string", "title": "P Val Alias"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } def test_optional_alias_and_validation_alias_missing(): @@ -447,29 +363,16 @@ def test_model_optional_alias_and_validation_alias_missing(): client = TestClient(app) response = client.post("/model-optional-alias-and-validation-alias") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "input": None, - "loc": ["body"], - "msg": "Field required", - "type": "missing", - }, - ], - } - ) | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - }, - ], - } - ) + assert response.json() == { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } @pytest.mark.parametrize( 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 5571ba5d5a..5b434fa1db 100644 --- a/tests/test_request_params/test_body/test_required_str.py +++ b/tests/test_request_params/test_body/test_required_str.py @@ -1,7 +1,7 @@ from typing import Annotated, Any, Union import pytest -from dirty_equals import IsDict, IsOneOf +from dirty_equals import IsOneOf from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -55,29 +55,16 @@ def test_required_str_missing(path: str, json: Union[dict[str, Any], None]): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": IsOneOf(["body"], ["body", "p"]), - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": IsOneOf(["body"], ["body", "p"]), - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": IsOneOf(["body"], ["body", "p"]), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -141,29 +128,16 @@ def test_required_alias_missing(path: str, json: Union[dict[str, Any], None]): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": IsOneOf(["body", "p_alias"], ["body"]), - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": IsOneOf(["body", "p_alias"], ["body"]), - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": IsOneOf(["body", "p_alias"], ["body"]), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -174,29 +148,16 @@ def test_required_alias_by_name(path: str): client = TestClient(app) response = client.post(path, json={"p": "hello"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {"p": "hello"}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": IsOneOf(["body", "p_alias"], ["body"]), - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } @pytest.mark.parametrize( 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 b2f7f9cef5..6f381c8b86 100644 --- a/tests/test_request_params/test_cookie/test_optional_str.py +++ b/tests/test_request_params/test_cookie/test_optional_str.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import Cookie, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -32,26 +31,15 @@ async def read_model_optional_str(p: Annotated[CookieModelOptionalStr, Cookie()] ) def test_optional_str_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P", - }, - "name": "p", - "in": "cookie", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": {"title": "P", "type": "string"}, - "name": "p", - "in": "cookie", - } - ) + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + "name": "p", + "in": "cookie", + } ] @@ -104,26 +92,15 @@ async def read_model_optional_alias(p: Annotated[CookieModelOptionalAlias, Cooki ) def test_optional_str_alias_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Alias", - }, - "name": "p_alias", - "in": "cookie", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": {"title": "P Alias", "type": "string"}, - "name": "p_alias", - "in": "cookie", - } - ) + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + "name": "p_alias", + "in": "cookie", + } ] 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 58bb7af5b9..3e877b3e3d 100644 --- a/tests/test_request_params/test_cookie/test_required_str.py +++ b/tests/test_request_params/test_cookie/test_required_str.py @@ -1,7 +1,7 @@ from typing import Annotated import pytest -from dirty_equals import IsDict, IsOneOf +from dirty_equals import IsOneOf from fastapi import Cookie, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -49,29 +49,16 @@ def test_required_str_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["cookie", "p"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["cookie", "p"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -127,29 +114,16 @@ def test_required_alias_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["cookie", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["cookie", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -164,32 +138,19 @@ def test_required_alias_by_name(path: str): client.cookies.set("p", "hello") response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["cookie", "p_alias"], - "msg": "Field required", - "input": IsOneOf( - None, - {"p": "hello"}, - ), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["cookie", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "p_alias"], + "msg": "Field required", + "input": IsOneOf( + None, + {"p": "hello"}, + ), + } + ] + } @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py index 90f36e5f7c..f096532554 100644 --- a/tests/test_request_params/test_file/test_list.py +++ b/tests/test_request_params/test_file/test_list.py @@ -75,29 +75,16 @@ def test_list_missing(path: str): client = TestClient(app) response = client.post(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p"], + "msg": "Field required", + "input": None, + } + ] + } @pytest.mark.parametrize( @@ -182,29 +169,16 @@ def test_list_alias_missing(path: str): client = TestClient(app) response = client.post(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": None, + } + ] + } @pytest.mark.parametrize( @@ -218,29 +192,16 @@ def test_list_alias_by_name(path: str): client = TestClient(app) response = client.post(path, files=[("p", b"hello"), ("p", b"world")]) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": None, + } + ] + } @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py index 4e9564873c..45ef7bdec4 100644 --- a/tests/test_request_params/test_file/test_optional.py +++ b/tests/test_request_params/test_file/test_optional.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient @@ -36,21 +35,13 @@ def test_optional_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p": ( - IsDict( - { - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - "title": "P", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "P", "type": "string", "format": "binary"} - ) - ), + "p": { + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + "title": "P", + } }, "title": body_model_name, "type": "object", @@ -116,21 +107,13 @@ def test_optional_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_alias": ( - IsDict( - { - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - "title": "P Alias", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "P Alias", "type": "string", "format": "binary"} - ) - ), + "p_alias": { + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + "title": "P Alias", + } }, "title": body_model_name, "type": "object", @@ -215,21 +198,13 @@ def test_optional_validation_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_val_alias": ( - IsDict( - { - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - "title": "P Val Alias", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "P Val Alias", "type": "string", "format": "binary"} - ) - ), + "p_val_alias": { + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + "title": "P Val Alias", + } }, "title": body_model_name, "type": "object", @@ -319,21 +294,13 @@ def test_optional_alias_and_validation_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_val_alias": ( - IsDict( - { - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - "title": "P Val Alias", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "P Val Alias", "type": "string", "format": "binary"} - ) - ), + "p_val_alias": { + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + "title": "P Val Alias", + } }, "title": body_model_name, "type": "object", 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 e18f36e152..162fbe08ae 100644 --- a/tests/test_request_params/test_file/test_optional_list.py +++ b/tests/test_request_params/test_file/test_optional_list.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient @@ -38,28 +37,16 @@ def test_optional_list_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p": ( - IsDict( + "p": { + "anyOf": [ { - "anyOf": [ - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - }, - {"type": "null"}, - ], - "title": "P", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "P", "type": "array", "items": {"type": "string", "format": "binary"}, }, - ) - ), + {"type": "null"}, + ], + "title": "P", + } }, "title": body_model_name, "type": "object", @@ -125,28 +112,16 @@ def test_optional_list_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_alias": ( - IsDict( + "p_alias": { + "anyOf": [ { - "anyOf": [ - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - }, - {"type": "null"}, - ], - "title": "P Alias", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "P Alias", "type": "array", "items": {"type": "string", "format": "binary"}, - } - ) - ), + }, + {"type": "null"}, + ], + "title": "P Alias", + } }, "title": body_model_name, "type": "object", @@ -228,28 +203,16 @@ def test_optional_validation_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_val_alias": ( - IsDict( + "p_val_alias": { + "anyOf": [ { - "anyOf": [ - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - }, - {"type": "null"}, - ], - "title": "P Val Alias", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "P Val Alias", "type": "array", "items": {"type": "string", "format": "binary"}, - } - ) - ), + }, + {"type": "null"}, + ], + "title": "P Val Alias", + } }, "title": body_model_name, "type": "object", @@ -336,28 +299,16 @@ def test_optional_list_alias_and_validation_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_val_alias": ( - IsDict( + "p_val_alias": { + "anyOf": [ { - "anyOf": [ - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - }, - {"type": "null"}, - ], - "title": "P Val Alias", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "P Val Alias", "type": "array", "items": {"type": "string", "format": "binary"}, - } - ) - ), + }, + {"type": "null"}, + ], + "title": "P Val Alias", + } }, "title": body_model_name, "type": "object", diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py index 9783f4bceb..a0f9d23a6b 100644 --- a/tests/test_request_params/test_file/test_required.py +++ b/tests/test_request_params/test_file/test_required.py @@ -1,7 +1,6 @@ from typing import Annotated import pytest -from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient @@ -55,29 +54,16 @@ def test_required_missing(path: str): client = TestClient(app) response = client.post(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p"], + "msg": "Field required", + "input": None, + } + ] + } @pytest.mark.parametrize( @@ -142,29 +128,16 @@ def test_required_alias_missing(path: str): client = TestClient(app) response = client.post(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": None, + } + ] + } @pytest.mark.parametrize( @@ -178,29 +151,16 @@ def test_required_alias_by_name(path: str): client = TestClient(app) response = client.post(path, files=[("p", b"hello")]) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": None, + } + ] + } @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_form/test_list.py b/tests/test_request_params/test_form/test_list.py index 600dba4ae9..cfc42f523a 100644 --- a/tests/test_request_params/test_form/test_list.py +++ b/tests/test_request_params/test_form/test_list.py @@ -146,29 +146,16 @@ def test_required_list_alias_missing(path: str): client = TestClient(app) response = client.post(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -182,29 +169,16 @@ def test_required_list_alias_by_name(path: str): client = TestClient(app) response = client.post(path, data={"p": ["hello", "world"]}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {"p": ["hello", "world"]}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": ["hello", "world"]}), + } + ] + } @pytest.mark.parametrize( 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 4552623f51..6d1957a18c 100644 --- a/tests/test_request_params/test_form/test_optional_list.py +++ b/tests/test_request_params/test_form/test_optional_list.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -38,30 +37,19 @@ def test_optional_list_str_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p": {"items": {"type": "string"}, "type": "array", "title": "P"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } @pytest.mark.parametrize( @@ -119,34 +107,19 @@ def test_optional_list_str_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_alias": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_alias": { - "items": {"type": "string"}, - "type": "array", - "title": "P Alias", - }, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } @pytest.mark.parametrize( @@ -217,34 +190,19 @@ def test_optional_list_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_val_alias": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Val Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_val_alias": { - "items": {"type": "string"}, - "type": "array", - "title": "P Val Alias", - }, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } @pytest.mark.parametrize( @@ -326,34 +284,19 @@ def test_optional_list_alias_and_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_val_alias": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Val Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_val_alias": { - "items": {"type": "string"}, - "type": "array", - "title": "P Val Alias", - }, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } @pytest.mark.parametrize( 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 1b08299046..810e83caa3 100644 --- a/tests/test_request_params/test_form/test_optional_str.py +++ b/tests/test_request_params/test_form/test_optional_str.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -36,27 +35,16 @@ def test_optional_str_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p": {"type": "string", "title": "P"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } @pytest.mark.parametrize( @@ -112,27 +100,16 @@ def test_optional_str_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_alias": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_alias": {"type": "string", "title": "P Alias"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } @pytest.mark.parametrize( @@ -200,27 +177,16 @@ def test_optional_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_val_alias": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_val_alias": {"type": "string", "title": "P Val Alias"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } @pytest.mark.parametrize( @@ -303,27 +269,16 @@ def test_optional_alias_and_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( - { - "properties": { - "p_val_alias": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", }, - "title": body_model_name, - "type": "object", - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "properties": { - "p_val_alias": {"type": "string", "title": "P Val Alias"}, - }, - "title": body_model_name, - "type": "object", - } - ) + }, + "title": body_model_name, + "type": "object", + } @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_form/test_required_str.py b/tests/test_request_params/test_form/test_required_str.py index 1d2431b333..7c9523b308 100644 --- a/tests/test_request_params/test_form/test_required_str.py +++ b/tests/test_request_params/test_form/test_required_str.py @@ -1,7 +1,7 @@ from typing import Annotated import pytest -from dirty_equals import IsDict, IsOneOf +from dirty_equals import IsOneOf from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -54,29 +54,16 @@ def test_required_str_missing(path: str): client = TestClient(app) response = client.post(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -137,29 +124,16 @@ def test_required_alias_missing(path: str): client = TestClient(app) response = client.post(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -170,29 +144,16 @@ def test_required_alias_by_name(path: str): client = TestClient(app) response = client.post(path, data={"p": "hello"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {"p": "hello"}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } @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 2eba17559e..65510094af 100644 --- a/tests/test_request_params/test_header/test_list.py +++ b/tests/test_request_params/test_header/test_list.py @@ -135,29 +135,16 @@ def test_required_list_alias_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "p_alias"], - "msg": "Field required", - "input": AnyThing, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_alias"], + "msg": "Field required", + "input": AnyThing, + } + ] + } @pytest.mark.parametrize( @@ -171,29 +158,16 @@ def test_required_list_alias_by_name(path: str): client = TestClient(app) response = client.get(path, headers=[("p", "hello"), ("p", "world")]) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})), + } + ] + } @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 cd6167a183..5dd4ea9ade 100644 --- a/tests/test_request_params/test_header/test_optional_list.py +++ b/tests/test_request_params/test_header/test_optional_list.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -36,29 +35,18 @@ async def read_model_optional_list_str( ) def test_optional_list_str_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P", - }, - "name": "p", - "in": "header", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": {"items": {"type": "string"}, "type": "array", "title": "P"}, - "name": "p", - "in": "header", - } - ) + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", + }, + "name": "p", + "in": "header", + } ] @@ -112,33 +100,18 @@ async def read_model_optional_list_alias( ) def test_optional_list_str_alias_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Alias", - }, - "name": "p_alias", - "in": "header", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": { - "items": {"type": "string"}, - "type": "array", - "title": "P Alias", - }, - "name": "p_alias", - "in": "header", - } - ) + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", + }, + "name": "p_alias", + "in": "header", + } ] 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 d4f25cc1e7..0bd0eddc1b 100644 --- a/tests/test_request_params/test_header/test_optional_str.py +++ b/tests/test_request_params/test_header/test_optional_str.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -32,26 +31,15 @@ async def read_model_optional_str(p: Annotated[HeaderModelOptionalStr, Header()] ) def test_optional_str_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P", - }, - "name": "p", - "in": "header", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": {"title": "P", "type": "string"}, - "name": "p", - "in": "header", - } - ) + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + "name": "p", + "in": "header", + } ] @@ -103,26 +91,15 @@ async def read_model_optional_alias(p: Annotated[HeaderModelOptionalAlias, Heade ) def test_optional_str_alias_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Alias", - }, - "name": "p_alias", - "in": "header", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": {"title": "P Alias", "type": "string"}, - "name": "p_alias", - "in": "header", - } - ) + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + "name": "p_alias", + "in": "header", + } ] 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 85bb43d5a8..20dd296570 100644 --- a/tests/test_request_params/test_header/test_required_str.py +++ b/tests/test_request_params/test_header/test_required_str.py @@ -1,7 +1,7 @@ from typing import Annotated import pytest -from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict +from dirty_equals import AnyThing, IsOneOf, IsPartialDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -49,29 +49,16 @@ def test_required_str_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "p"], - "msg": "Field required", - "input": AnyThing, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "p"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p"], + "msg": "Field required", + "input": AnyThing, + } + ] + } @pytest.mark.parametrize( @@ -126,29 +113,16 @@ def test_required_alias_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "p_alias"], - "msg": "Field required", - "input": AnyThing, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_alias"], + "msg": "Field required", + "input": AnyThing, + } + ] + } @pytest.mark.parametrize( @@ -162,29 +136,16 @@ def test_required_alias_by_name(path: str): client = TestClient(app) response = client.get(path, headers={"p": "hello"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, IsPartialDict({"p": "hello"})), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, IsPartialDict({"p": "hello"})), + } + ] + } @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 dc21a85006..ed2ea6c809 100644 --- a/tests/test_request_params/test_query/test_list.py +++ b/tests/test_request_params/test_query/test_list.py @@ -135,29 +135,16 @@ def test_required_list_alias_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -171,29 +158,16 @@ def test_required_list_alias_by_name(path: str): client = TestClient(app) response = client.get(f"{path}?p=hello&p=world") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {"p": ["hello", "world"]}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": ["hello", "world"]}), + } + ] + } @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 2a8f63a36a..351e03a713 100644 --- a/tests/test_request_params/test_query/test_optional_list.py +++ b/tests/test_request_params/test_query/test_optional_list.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -36,29 +35,18 @@ async def read_model_optional_list_str( ) def test_optional_list_str_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P", - }, - "name": "p", - "in": "query", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": {"items": {"type": "string"}, "type": "array", "title": "P"}, - "name": "p", - "in": "query", - } - ) + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", + }, + "name": "p", + "in": "query", + } ] @@ -112,33 +100,18 @@ async def read_model_optional_list_alias( ) def test_optional_list_str_alias_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Alias", - }, - "name": "p_alias", - "in": "query", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": { - "items": {"type": "string"}, - "type": "array", - "title": "P Alias", - }, - "name": "p_alias", - "in": "query", - } - ) + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", + }, + "name": "p_alias", + "in": "query", + } ] 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 c6a70bc283..12e1b465a7 100644 --- a/tests/test_request_params/test_query/test_optional_str.py +++ b/tests/test_request_params/test_query/test_optional_str.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import pytest -from dirty_equals import IsDict from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -32,26 +31,15 @@ async def read_model_optional_str(p: Annotated[QueryModelOptionalStr, Query()]): ) def test_optional_str_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P", - }, - "name": "p", - "in": "query", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": {"title": "P", "type": "string"}, - "name": "p", - "in": "query", - } - ) + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + "name": "p", + "in": "query", + } ] @@ -103,26 +91,15 @@ async def read_model_optional_alias(p: Annotated[QueryModelOptionalAlias, Query( ) def test_optional_str_alias_schema(path: str): assert app.openapi()["paths"][path]["get"]["parameters"] == [ - IsDict( - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Alias", - }, - "name": "p_alias", - "in": "query", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "required": False, - "schema": {"title": "P Alias", "type": "string"}, - "name": "p_alias", - "in": "query", - } - ) + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + "name": "p_alias", + "in": "query", + } ] 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 2ef1b0373d..9e7b961453 100644 --- a/tests/test_request_params/test_query/test_required_str.py +++ b/tests/test_request_params/test_query/test_required_str.py @@ -1,7 +1,7 @@ from typing import Annotated import pytest -from dirty_equals import IsDict, IsOneOf +from dirty_equals import IsOneOf from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -49,29 +49,16 @@ def test_required_str_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "p"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "p"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -126,29 +113,16 @@ def test_required_alias_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "p_alias"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( @@ -162,32 +136,19 @@ def test_required_alias_by_name(path: str): client = TestClient(app) response = client.get(f"{path}?p=hello") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "p_alias"], - "msg": "Field required", - "input": IsOneOf( - None, - {"p": "hello"}, - ), - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "p_alias"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_alias"], + "msg": "Field required", + "input": IsOneOf( + None, + {"p": "hello"}, + ), + } + ] + } @pytest.mark.parametrize( diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py index 8f5195ba11..ac8999c90a 100644 --- a/tests/test_schema_extra_examples.py +++ b/tests/test_schema_extra_examples.py @@ -1,7 +1,6 @@ from typing import Union import pytest -from dirty_equals import IsDict from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient @@ -336,28 +335,13 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": IsDict( - { - "$ref": "#/components/schemas/Item", - "examples": [ - {"data": "Data in Body examples, example1"}, - {"data": "Data in Body examples, example2"}, - ], - } - ) - | IsDict( - # TODO: remove this when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - "examples": [ - {"data": "Data in Body examples, example1"}, - {"data": "Data in Body examples, example2"}, - ], - } - ) + "schema": { + "$ref": "#/components/schemas/Item", + "examples": [ + {"data": "Data in Body examples, example1"}, + {"data": "Data in Body examples, example2"}, + ], + } } }, "required": True, @@ -387,28 +371,13 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": IsDict( - { - "$ref": "#/components/schemas/Item", - "examples": [ - {"data": "examples example_examples 1"}, - {"data": "examples example_examples 2"}, - ], - } - ) - | IsDict( - # TODO: remove this when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - "examples": [ - {"data": "examples example_examples 1"}, - {"data": "examples example_examples 2"}, - ], - }, - ), + "schema": { + "$ref": "#/components/schemas/Item", + "examples": [ + {"data": "examples example_examples 1"}, + {"data": "examples example_examples 2"}, + ], + }, "example": {"data": "Overridden example"}, } }, @@ -539,16 +508,10 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - } - ) - | IsDict( - # TODO: Remove this when deprecating Pydantic v1 - {"title": "Data", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + }, "example": "query1", "name": "data", "in": "query", @@ -579,21 +542,11 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["query1", "query2"], - } - ) - | IsDict( - # TODO: Remove this when deprecating Pydantic v1 - { - "type": "string", - "title": "Data", - "examples": ["query1", "query2"], - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["query1", "query2"], + }, "name": "data", "in": "query", } @@ -623,21 +576,11 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["query1", "query2"], - } - ) - | IsDict( - # TODO: Remove this when deprecating Pydantic v1 - { - "type": "string", - "title": "Data", - "examples": ["query1", "query2"], - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["query1", "query2"], + }, "example": "query_overridden", "name": "data", "in": "query", @@ -668,16 +611,10 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - } - ) - | IsDict( - # TODO: Remove this when deprecating Pydantic v1 - {"title": "Data", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + }, "example": "header1", "name": "data", "in": "header", @@ -708,21 +645,11 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["header1", "header2"], - } - ) - | IsDict( - # TODO: Remove this when deprecating Pydantic v1 - { - "type": "string", - "title": "Data", - "examples": ["header1", "header2"], - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["header1", "header2"], + }, "name": "data", "in": "header", } @@ -752,21 +679,11 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["header1", "header2"], - } - ) - | IsDict( - # TODO: Remove this when deprecating Pydantic v1 - { - "title": "Data", - "type": "string", - "examples": ["header1", "header2"], - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["header1", "header2"], + }, "example": "header_overridden", "name": "data", "in": "header", @@ -797,16 +714,10 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - } - ) - | IsDict( - # TODO: Remove this when deprecating Pydantic v1 - {"title": "Data", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + }, "example": "cookie1", "name": "data", "in": "cookie", @@ -837,21 +748,11 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["cookie1", "cookie2"], - } - ) - | IsDict( - # TODO: Remove this when deprecating Pydantic v1 - { - "title": "Data", - "type": "string", - "examples": ["cookie1", "cookie2"], - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["cookie1", "cookie2"], + }, "name": "data", "in": "cookie", } @@ -881,21 +782,11 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["cookie1", "cookie2"], - } - ) - | IsDict( - # TODO: Remove this when deprecating Pydantic v1 - { - "title": "Data", - "type": "string", - "examples": ["cookie1", "cookie2"], - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["cookie1", "cookie2"], + }, "example": "cookie_overridden", "name": "data", "in": "cookie", diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py index 804e4152db..7ad9369956 100644 --- a/tests/test_security_oauth2.py +++ b/tests/test_security_oauth2.py @@ -1,5 +1,4 @@ import pytest -from dirty_equals import IsDict from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict from fastapi.testclient import TestClient @@ -64,79 +63,43 @@ def test_security_oauth2_password_bearer_no_header(): def test_strict_login_no_data(): response = client.post("/login") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "grant_type"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "grant_type"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "grant_type"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "password"], + "msg": "Field required", + "input": None, + }, + ] + } def test_strict_login_no_grant_type(): response = client.post("/login", data={"username": "johndoe", "password": "secret"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "grant_type"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "grant_type"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "grant_type"], + "msg": "Field required", + "input": None, + } + ] + } @pytest.mark.parametrize( @@ -153,31 +116,17 @@ def test_strict_login_incorrect_grant_type(grant_type: str): data={"username": "johndoe", "password": "secret", "grant_type": grant_type}, ) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["body", "grant_type"], - "msg": "String should match pattern '^password$'", - "input": grant_type, - "ctx": {"pattern": "^password$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "grant_type"], - "msg": 'string does not match regex "^password$"', - "type": "value_error.str.regex", - "ctx": {"pattern": "^password$"}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_pattern_mismatch", + "loc": ["body", "grant_type"], + "msg": "String should match pattern '^password$'", + "input": grant_type, + "ctx": {"pattern": "^password$"}, + } + ] + } def test_strict_login_correct_grant_type(): @@ -264,26 +213,14 @@ def test_openapi_schema(): "username": {"title": "Username", "type": "string"}, "password": {"title": "Password", "type": "string"}, "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Secret", "type": "string"} - ), + "client_id": { + "title": "Client Id", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "client_secret": { + "title": "Client Secret", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, }, }, "ValidationError": { diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py index 046ac57637..57c16058af 100644 --- a/tests/test_security_oauth2_optional.py +++ b/tests/test_security_oauth2_optional.py @@ -1,7 +1,6 @@ from typing import Optional import pytest -from dirty_equals import IsDict from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict from fastapi.testclient import TestClient @@ -67,79 +66,43 @@ def test_security_oauth2_password_bearer_no_header(): def test_strict_login_no_data(): response = client.post("/login") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "grant_type"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "grant_type"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "grant_type"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "password"], + "msg": "Field required", + "input": None, + }, + ] + } def test_strict_login_no_grant_type(): response = client.post("/login", data={"username": "johndoe", "password": "secret"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "grant_type"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "grant_type"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "grant_type"], + "msg": "Field required", + "input": None, + } + ] + } @pytest.mark.parametrize( @@ -156,31 +119,17 @@ def test_strict_login_incorrect_grant_type(grant_type: str): data={"username": "johndoe", "password": "secret", "grant_type": grant_type}, ) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["body", "grant_type"], - "msg": "String should match pattern '^password$'", - "input": grant_type, - "ctx": {"pattern": "^password$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "grant_type"], - "msg": 'string does not match regex "^password$"', - "type": "value_error.str.regex", - "ctx": {"pattern": "^password$"}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_pattern_mismatch", + "loc": ["body", "grant_type"], + "msg": "String should match pattern '^password$'", + "input": grant_type, + "ctx": {"pattern": "^password$"}, + } + ] + } def test_strict_login_correct_data(): @@ -267,26 +216,14 @@ def test_openapi_schema(): "username": {"title": "Username", "type": "string"}, "password": {"title": "Password", "type": "string"}, "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Secret", "type": "string"} - ), + "client_id": { + "title": "Client Id", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "client_secret": { + "title": "Client Secret", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, }, }, "ValidationError": { diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py index 629cddca2f..60c6c242e0 100644 --- a/tests/test_security_oauth2_optional_description.py +++ b/tests/test_security_oauth2_optional_description.py @@ -1,7 +1,6 @@ from typing import Optional import pytest -from dirty_equals import IsDict from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict from fastapi.testclient import TestClient @@ -68,79 +67,43 @@ def test_security_oauth2_password_bearer_no_header(): def test_strict_login_None(): response = client.post("/login", data=None) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "grant_type"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "grant_type"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "grant_type"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "password"], + "msg": "Field required", + "input": None, + }, + ] + } def test_strict_login_no_grant_type(): response = client.post("/login", data={"username": "johndoe", "password": "secret"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "grant_type"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "grant_type"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "grant_type"], + "msg": "Field required", + "input": None, + } + ] + } @pytest.mark.parametrize( @@ -157,31 +120,17 @@ def test_strict_login_incorrect_grant_type(grant_type: str): data={"username": "johndoe", "password": "secret", "grant_type": grant_type}, ) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["body", "grant_type"], - "msg": "String should match pattern '^password$'", - "input": grant_type, - "ctx": {"pattern": "^password$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "grant_type"], - "msg": 'string does not match regex "^password$"', - "type": "value_error.str.regex", - "ctx": {"pattern": "^password$"}, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_pattern_mismatch", + "loc": ["body", "grant_type"], + "msg": "String should match pattern '^password$'", + "input": grant_type, + "ctx": {"pattern": "^password$"}, + } + ] + } def test_strict_login_correct_correct_grant_type(): @@ -268,26 +217,14 @@ def test_openapi_schema(): "username": {"title": "Username", "type": "string"}, "password": {"title": "Password", "type": "string"}, "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Secret", "type": "string"} - ), + "client_id": { + "title": "Client Id", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "client_secret": { + "title": "Client Secret", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, }, }, "ValidationError": { diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py index ed7f4efe8a..cc7e5f5c6a 100644 --- a/tests/test_sub_callbacks.py +++ b/tests/test_sub_callbacks.py @@ -1,6 +1,5 @@ from typing import Optional -from dirty_equals import IsDict from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, HttpUrl @@ -99,30 +98,18 @@ def test_openapi_schema(): "parameters": [ { "required": False, - "schema": IsDict( - { - "title": "Callback Url", - "anyOf": [ - { - "type": "string", - "format": "uri", - "minLength": 1, - "maxLength": 2083, - }, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Callback Url", - "maxLength": 2083, - "minLength": 1, - "type": "string", - "format": "uri", - } - ), + "schema": { + "title": "Callback Url", + "anyOf": [ + { + "type": "string", + "format": "uri", + "minLength": 1, + "maxLength": 2083, + }, + {"type": "null"}, + ], + }, "name": "callback_url", "in": "query", } @@ -262,16 +249,10 @@ def test_openapi_schema(): "type": "object", "properties": { "id": {"title": "Id", "type": "string"}, - "title": IsDict( - { - "title": "Title", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Title", "type": "string"} - ), + "title": { + "title": "Title", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, "customer": {"title": "Customer", "type": "string"}, "total": {"title": "Total", "type": "number"}, }, diff --git a/tests/test_tuples.py b/tests/test_tuples.py index fbc69a6145..d3c89045b4 100644 --- a/tests/test_tuples.py +++ b/tests/test_tuples.py @@ -1,4 +1,3 @@ -from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel @@ -125,31 +124,16 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": IsDict( - { - "title": "Square", - "maxItems": 2, - "minItems": 2, - "type": "array", - "prefixItems": [ - {"$ref": "#/components/schemas/Coordinate"}, - {"$ref": "#/components/schemas/Coordinate"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Square", - "maxItems": 2, - "minItems": 2, - "type": "array", - "items": [ - {"$ref": "#/components/schemas/Coordinate"}, - {"$ref": "#/components/schemas/Coordinate"}, - ], - } - ) + "schema": { + "title": "Square", + "maxItems": 2, + "minItems": 2, + "type": "array", + "prefixItems": [ + {"$ref": "#/components/schemas/Coordinate"}, + {"$ref": "#/components/schemas/Coordinate"}, + ], + } } }, "required": True, @@ -212,28 +196,16 @@ def test_openapi_schema(): "required": ["values"], "type": "object", "properties": { - "values": IsDict( - { - "title": "Values", - "maxItems": 2, - "minItems": 2, - "type": "array", - "prefixItems": [ - {"type": "integer"}, - {"type": "integer"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Values", - "maxItems": 2, - "minItems": 2, - "type": "array", - "items": [{"type": "integer"}, {"type": "integer"}], - } - ) + "values": { + "title": "Values", + "maxItems": 2, + "minItems": 2, + "type": "array", + "prefixItems": [ + {"type": "integer"}, + {"type": "integer"}, + ], + } }, }, "Coordinate": { @@ -264,26 +236,15 @@ def test_openapi_schema(): "items": { "title": "Items", "type": "array", - "items": IsDict( - { - "maxItems": 2, - "minItems": 2, - "type": "array", - "prefixItems": [ - {"type": "string"}, - {"type": "string"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "maxItems": 2, - "minItems": 2, - "type": "array", - "items": [{"type": "string"}, {"type": "string"}], - } - ), + "items": { + "maxItems": 2, + "minItems": 2, + "type": "array", + "prefixItems": [ + {"type": "string"}, + {"type": "string"}, + ], + }, } }, }, diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py index bbcad8f294..8208605956 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py @@ -3,7 +3,6 @@ import os import shutil import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from tests.utils import needs_py310 @@ -80,16 +79,10 @@ def test_openapi_schema(client: TestClient): }, { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "boolean"}, {"type": "null"}], - "title": "Img", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Img", "type": "boolean"} - ), + "schema": { + "anyOf": [{"type": "boolean"}, {"type": "null"}], + "title": "Img", + }, "name": "img", "in": "query", }, diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py index cbd4fff7d6..c6abf5e466 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py @@ -3,7 +3,6 @@ import os import shutil import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from tests.utils import needs_py310 @@ -83,16 +82,10 @@ def test_openapi_schema(client: TestClient): }, { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "boolean"}, {"type": "null"}], - "title": "Img", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Img", "type": "boolean"} - ), + "schema": { + "anyOf": [{"type": "boolean"}, {"type": "null"}], + "title": "Img", + }, "name": "img", "in": "query", }, 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 2d1d1b03c8..a164bb80b5 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py @@ -1,5 +1,5 @@ -from dirty_equals import IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.behind_a_proxy.tutorial003_py39 import app @@ -15,40 +15,34 @@ def test_main(): 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"}, - "servers": [ - {"url": "/api/v1"}, - { - "url": IsOneOf( - "https://stag.example.com/", - # TODO: remove when deprecating Pydantic v1 - "https://stag.example.com", - ), - "description": "Staging environment", - }, - { - "url": IsOneOf( - "https://prod.example.com/", - # TODO: remove when deprecating Pydantic v1 - "https://prod.example.com", - ), - "description": "Production environment", - }, - ], - "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"}, + "servers": [ + {"url": "/api/v1"}, + { + "url": "https://stag.example.com", + "description": "Staging environment", + }, + { + "url": "https://prod.example.com", + "description": "Production environment", + }, + ], + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) 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 e8a03e8112..01bba9fedd 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py @@ -1,5 +1,5 @@ -from dirty_equals import IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.behind_a_proxy.tutorial004_py39 import app @@ -15,39 +15,33 @@ def test_main(): 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"}, - "servers": [ - { - "url": IsOneOf( - "https://stag.example.com/", - # TODO: remove when deprecating Pydantic v1 - "https://stag.example.com", - ), - "description": "Staging environment", - }, - { - "url": IsOneOf( - "https://prod.example.com/", - # TODO: remove when deprecating Pydantic v1 - "https://prod.example.com", - ), - "description": "Production environment", - }, - ], - "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"}, + "servers": [ + { + "url": "https://stag.example.com", + "description": "Staging environment", + }, + { + "url": "https://prod.example.com", + "description": "Production environment", + }, + ], + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py index 7493a9e661..f5e243b95a 100644 --- a/tests/test_tutorial/test_bigger_applications/test_main.py +++ b/tests/test_tutorial/test_bigger_applications/test_main.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient @@ -28,29 +27,16 @@ def test_users_token_jessica(client: TestClient): def test_users_with_no_token(client: TestClient): response = client.get("/users") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "token"], + "msg": "Field required", + "input": None, + } + ] + } def test_users_foo_token_jessica(client: TestClient): @@ -62,29 +48,16 @@ def test_users_foo_token_jessica(client: TestClient): def test_users_foo_with_no_token(client: TestClient): response = client.get("/users/foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "token"], + "msg": "Field required", + "input": None, + } + ] + } def test_users_me_token_jessica(client: TestClient): @@ -96,29 +69,16 @@ def test_users_me_token_jessica(client: TestClient): def test_users_me_with_no_token(client: TestClient): response = client.get("/users/me") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "token"], + "msg": "Field required", + "input": None, + } + ] + } def test_users_token_monica_with_no_jessica(client: TestClient): @@ -141,29 +101,16 @@ def test_items_token_jessica(client: TestClient): def test_items_with_no_token_jessica(client: TestClient): response = client.get("/items", headers={"X-Token": "fake-super-secret-token"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "token"], + "msg": "Field required", + "input": None, + } + ] + } def test_items_plumbus_token_jessica(client: TestClient): @@ -187,29 +134,16 @@ def test_items_plumbus_with_no_token(client: TestClient): "/items/plumbus", headers={"X-Token": "fake-super-secret-token"} ) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "token"], + "msg": "Field required", + "input": None, + } + ] + } def test_items_with_invalid_token(client: TestClient): @@ -227,57 +161,31 @@ def test_items_bar_with_invalid_token(client: TestClient): def test_items_with_missing_x_token_header(client: TestClient): response = client.get("/items?token=jessica") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "x-token"], + "msg": "Field required", + "input": None, + } + ] + } def test_items_plumbus_with_missing_x_token_header(client: TestClient): response = client.get("/items/plumbus?token=jessica") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "x-token"], + "msg": "Field required", + "input": None, + } + ] + } def test_root_token_jessica(client: TestClient): @@ -289,68 +197,37 @@ def test_root_token_jessica(client: TestClient): def test_root_with_no_token(client: TestClient): response = client.get("/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "token"], + "msg": "Field required", + "input": None, + } + ] + } def test_put_no_header(client: TestClient): response = client.put("/items/foo") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "token"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["header", "x-token"], + "msg": "Field required", + "input": None, + }, + ] + } def test_put_invalid_header(client: TestClient): diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py index 6aa9f2593e..5a7cae1603 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -2,7 +2,6 @@ import importlib from unittest.mock import patch import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -74,124 +73,67 @@ def test_post_with_str_float_description_tax(client: TestClient): def test_post_with_only_name(client: TestClient): response = client.post("/items/", json={"name": "Foo"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "price"], - "msg": "Field required", - "input": {"name": "Foo"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "price"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "price"], + "msg": "Field required", + "input": {"name": "Foo"}, + } + ] + } def test_post_with_only_name_price(client: TestClient): response = client.post("/items/", json={"name": "Foo", "price": "twenty"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "float_parsing", - "loc": ["body", "price"], - "msg": "Input should be a valid number, unable to parse string as a number", - "input": "twenty", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "price"], - "msg": "value is not a valid float", - "type": "type_error.float", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "float_parsing", + "loc": ["body", "price"], + "msg": "Input should be a valid number, unable to parse string as a number", + "input": "twenty", + } + ] + } def test_post_with_no_data(client: TestClient): response = client.post("/items/", json={}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "name"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "price"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "name"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "price"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "name"], + "msg": "Field required", + "input": {}, + }, + { + "type": "missing", + "loc": ["body", "price"], + "msg": "Field required", + "input": {}, + }, + ] + } def test_post_with_none(client: TestClient): response = client.post("/items/", json=None) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body"], + "msg": "Field required", + "input": None, + } + ] + } def test_post_broken_body(client: TestClient): @@ -201,67 +143,32 @@ def test_post_broken_body(client: TestClient): content="{some broken json}", ) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "json_invalid", - "loc": ["body", 1], - "msg": "JSON decode error", - "input": {}, - "ctx": { - "error": "Expecting property name enclosed in double quotes" - }, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", 1], - "msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)", - "type": "value_error.jsondecode", - "ctx": { - "msg": "Expecting property name enclosed in double quotes", - "doc": "{some broken json}", - "pos": 1, - "lineno": 1, - "colno": 2, - }, - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "json_invalid", + "loc": ["body", 1], + "msg": "JSON decode error", + "input": {}, + "ctx": {"error": "Expecting property name enclosed in double quotes"}, + } + ] + } def test_post_form_for_json(client: TestClient): response = client.post("/items/", data={"name": "Foo", "price": 50.5}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "model_attributes_type", - "loc": ["body"], - "msg": "Input should be a valid dictionary or object to extract fields from", - "input": "name=Foo&price=50.5", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "model_attributes_type", + "loc": ["body"], + "msg": "Input should be a valid dictionary or object to extract fields from", + "input": "name=Foo&price=50.5", + } + ] + } def test_explicit_content_type(client: TestClient): @@ -302,84 +209,46 @@ def test_wrong_headers(client: TestClient): "/items/", content=data, headers={"Content-Type": "text/plain"} ) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "model_attributes_type", - "loc": ["body"], - "msg": "Input should be a valid dictionary or object to extract fields from", - "input": '{"name": "Foo", "price": 50.5}', - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "model_attributes_type", + "loc": ["body"], + "msg": "Input should be a valid dictionary or object to extract fields from", + "input": '{"name": "Foo", "price": 50.5}', + } + ] + } response = client.post( "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"} ) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "model_attributes_type", - "loc": ["body"], - "msg": "Input should be a valid dictionary or object to extract fields from", - "input": '{"name": "Foo", "price": 50.5}', - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "model_attributes_type", + "loc": ["body"], + "msg": "Input should be a valid dictionary or object to extract fields from", + "input": '{"name": "Foo", "price": 50.5}', + } + ] + } + response = client.post( "/items/", content=data, headers={"Content-Type": "application/not-really-json"} ) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "model_attributes_type", - "loc": ["body"], - "msg": "Input should be a valid dictionary or object to extract fields from", - "input": '{"name": "Foo", "price": 50.5}', - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "model_attributes_type", + "loc": ["body"], + "msg": "Input should be a valid dictionary or object to extract fields from", + "input": '{"name": "Foo", "price": 50.5}', + } + ] + } def test_other_exceptions(client: TestClient): @@ -435,26 +304,14 @@ def test_openapi_schema(client: TestClient): "properties": { "name": {"title": "Name", "type": "string"}, "price": {"title": "Price", "type": "number"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, }, }, "ValidationError": { diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py index d54ec7191a..0ecadbb660 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -59,31 +58,17 @@ def test_items_6(client: TestClient): def test_invalid_price(client: TestClient): response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["body", "item", "price"], - "msg": "Input should be greater than 0", - "input": -3.0, - "ctx": {"gt": 0.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"limit_value": 0}, - "loc": ["body", "item", "price"], - "msg": "ensure this value is greater than 0", - "type": "value_error.number.not_gt", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "greater_than", + "loc": ["body", "item", "price"], + "msg": "Input should be greater than 0", + "input": -3.0, + "ctx": {"gt": 0.0}, + } + ] + } def test_openapi_schema(client: TestClient): @@ -142,39 +127,23 @@ def test_openapi_schema(client: TestClient): "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "The description of the item", - "anyOf": [ - {"maxLength": 300, "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "The description of the item", - "maxLength": 300, - "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": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, }, }, "Body_update_item_items__item_id__put": { 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 2035cf9448..63c9c16d62 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -53,29 +52,16 @@ def test_post_no_body(client: TestClient): def test_post_id_foo(client: TestClient): response = client.put("/items/foo", json=None) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.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", + } + ] + } def test_openapi_schema(client: TestClient): @@ -119,16 +105,10 @@ def test_openapi_schema(client: TestClient): }, { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, "name": "q", "in": "query", }, @@ -136,19 +116,13 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": IsDict( - { - "anyOf": [ - {"$ref": "#/components/schemas/Item"}, - {"type": "null"}, - ], - "title": "Item", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"$ref": "#/components/schemas/Item"} - ) + "schema": { + "anyOf": [ + {"$ref": "#/components/schemas/Item"}, + {"type": "null"}, + ], + "title": "Item", + } } } }, @@ -163,27 +137,15 @@ def test_openapi_schema(client: TestClient): "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, }, }, "ValidationError": { 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 d3e6401af2..76b7ff7099 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -49,101 +48,55 @@ def test_post_body_valid(client: TestClient): def test_post_body_no_data(client: TestClient): response = client.put("/items/5", json=None) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "item"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "user"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "importance"], + "msg": "Field required", + "input": None, + }, + ] + } def test_post_body_empty_list(client: TestClient): response = client.put("/items/5", json=[]) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "item"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "user"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "importance"], + "msg": "Field required", + "input": None, + }, + ] + } def test_openapi_schema(client: TestClient): @@ -202,27 +155,15 @@ def test_openapi_schema(client: TestClient): "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, }, }, "User": { @@ -231,16 +172,10 @@ def test_openapi_schema(client: TestClient): "type": "object", "properties": { "username": {"title": "Username", "type": "string"}, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), + "full_name": { + "title": "Full Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, }, }, "Body_update_item_items__item_id__put": { 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 db9f04546e..f2e56d40fb 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient @@ -29,29 +28,16 @@ def test_post_invalid_body(client: TestClient): data = {"foo": 2.2, "3": 3.3} response = client.post("/index-weights/", json=data) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["body", "foo", "[key]"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "__key__"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["body", "foo", "[key]"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "foo", + } + ] + } def test_openapi_schema(client: TestClient): 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 265dee944e..ac8e7bdae1 100644 --- a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -54,30 +53,16 @@ def test_cookie_param_model_invalid(client: TestClient): response = client.get("/items/") assert response.status_code == 422 assert response.json() == snapshot( - IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["cookie", "session_id"], - "msg": "Field required", - "input": {}, - } - ] - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "type": "value_error.missing", - "loc": ["cookie", "session_id"], - "msg": "field required", - } - ] - } - ) + { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "session_id"], + "msg": "Field required", + "input": {}, + } + ] + } ) @@ -115,37 +100,19 @@ def test_openapi_schema(client: TestClient): "name": "fatebook_tracker", "in": "cookie", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Fatebook Tracker", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "Fatebook Tracker", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Fatebook Tracker", + }, }, { "name": "googall_tracker", "in": "cookie", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Googall Tracker", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "Googall Tracker", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Googall Tracker", + }, }, ], "responses": { 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 0fbf141e05..d7c3d15f1b 100644 --- a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -72,30 +71,16 @@ def test_cookie_param_model_extra(client: TestClient): response = c.get("/items/") assert response.status_code == 422 assert response.json() == snapshot( - IsDict( - { - "detail": [ - { - "type": "extra_forbidden", - "loc": ["cookie", "extra"], - "msg": "Extra inputs are not permitted", - "input": "track-me-here-too", - } - ] - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "type": "value_error.extra", - "loc": ["cookie", "extra"], - "msg": "extra fields not permitted", - } - ] - } - ) + { + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "extra"], + "msg": "Extra inputs are not permitted", + "input": "track-me-here-too", + } + ] + } ) @@ -134,19 +119,10 @@ def test_openapi_schema(client: TestClient): "name": "googall_tracker", "in": "cookie", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Googall Tracker", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "Googall Tracker", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Googall Tracker", + }, }, ], "responses": { diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py index a65249d657..9b47cbc67a 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py @@ -2,7 +2,6 @@ import importlib from types import ModuleType import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -75,16 +74,10 @@ def test_openapi_schema(mod: ModuleType): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Ads Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Ads Id", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Ads Id", + }, "name": "ads_id", "in": "cookie", } 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 643011637e..a9c7ae638b 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 @@ -1,7 +1,7 @@ import importlib import pytest -from dirty_equals import IsDict, IsOneOf +from dirty_equals import IsOneOf from fastapi.testclient import TestClient from tests.utils import needs_py310 @@ -30,34 +30,17 @@ def test_endpoint_works(client: TestClient): def test_exception_handler_body_access(client: TestClient): response = client.post("/", json={"numbers": [1, 2, 3]}) - assert response.json() == IsDict( - { - "detail": { - "errors": [ - { - "type": "list_type", - "loc": ["body"], - "msg": "Input should be a valid list", - "input": {"numbers": [1, 2, 3]}, - } - ], - # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363 - "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'), - } + assert response.json() == { + "detail": { + "errors": [ + { + "type": "list_type", + "loc": ["body"], + "msg": "Input should be a valid list", + "input": {"numbers": [1, 2, 3]}, + } + ], + # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363 + "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'), } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": { - # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363 - "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'), - "errors": [ - { - "loc": ["body"], - "msg": "value is not a valid list", - "type": "type_error.list", - } - ], - } - } - ) + } diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py index bc407234a1..4683062f59 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from tests.utils import needs_py310 @@ -36,29 +35,16 @@ def test_post_item(client: TestClient): def test_post_invalid_item(client: TestClient): response = client.post("/items/", json={"name": "Foo", "price": "invalid price"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "float_parsing", - "loc": ["body", "price"], - "msg": "Input should be a valid number, unable to parse string as a number", - "input": "invalid price", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "price"], - "msg": "value is not a valid float", - "type": "type_error.float", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "float_parsing", + "loc": ["body", "price"], + "msg": "Input should be a valid number, unable to parse string as a number", + "input": "invalid price", + } + ] + } def test_openapi_schema(client: TestClient): @@ -119,26 +105,14 @@ def test_openapi_schema(client: TestClient): "properties": { "name": {"title": "Name", "type": "string"}, "price": {"title": "Price", "type": "number"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, }, }, "ValidationError": { diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py index 995d926752..210d743bb8 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py @@ -1,8 +1,8 @@ import importlib import pytest -from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from tests.utils import needs_py310 @@ -37,77 +37,53 @@ def test_get_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/next": { - "get": { - "summary": "Read Next Item", - "operationId": "read_next_item_items_next_get", - "responses": { - "200": { - "description": "Successful Response", - "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/next": { + "get": { + "summary": "Read Next Item", + "operationId": "read_next_item_items_next_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + } + }, + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": IsOneOf( - ["name", "price", "tags", "description", "tax"], - # TODO: remove when deprecating Pydantic v1 - ["name", "price"], - ), - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tags": IsDict( - { + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tags": { "title": "Tags", "type": "array", "items": {"type": "string"}, - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - } - ), - "description": IsDict( - { + }, + "description": { "title": "Description", "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "tax": IsDict( - { + }, + "tax": { "title": "Tax", "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, + }, + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py index 46f0066f9f..59202df3bf 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial006.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient @@ -22,40 +21,22 @@ def get_client(request: pytest.FixtureRequest): def test_get_no_headers(client: TestClient): response = client.get("/items/") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-key"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-key"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "x-token"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["header", "x-key"], + "msg": "Field required", + "input": None, + }, + ] + } def test_get_invalid_one_header(client: TestClient): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py index b791ee0aa1..d5599ac73a 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial012.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient @@ -22,79 +21,43 @@ def get_client(request: pytest.FixtureRequest): def test_get_no_headers_items(client: TestClient): response = client.get("/items/") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-key"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-key"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "x-token"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["header", "x-key"], + "msg": "Field required", + "input": None, + }, + ] + } def test_get_no_headers_users(client: TestClient): response = client.get("/users/") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-key"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-key"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "x-token"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["header", "x-key"], + "msg": "Field required", + "input": None, + }, + ] + } def test_get_invalid_one_header_items(client: TestClient): 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 e11f73fe35..5479e29252 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py @@ -1,8 +1,8 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -47,146 +47,117 @@ def test_extra_types(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": "Read Items", + "operationId": "read_items_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + "format": "uuid", + }, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "required": True, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_read_items_items__item_id__put" } } }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "type": "string", - "format": "uuid", - }, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ) - } - }, - }, + } } - } - }, - "components": { - "schemas": { - "Body_read_items_items__item_id__put": { - "title": "Body_read_items_items__item_id__put", - "type": "object", - "properties": { - "start_datetime": { - "title": "Start Datetime", - "type": "string", - "format": "date-time", - }, - "end_datetime": { - "title": "End Datetime", - "type": "string", - "format": "date-time", - }, - "repeat_at": IsDict( - { + }, + "components": { + "schemas": { + "Body_read_items_items__item_id__put": { + "title": "Body_read_items_items__item_id__put", + "type": "object", + "properties": { + "start_datetime": { + "title": "Start Datetime", + "type": "string", + "format": "date-time", + }, + "end_datetime": { + "title": "End Datetime", + "type": "string", + "format": "date-time", + }, + "repeat_at": { "title": "Repeat At", "anyOf": [ {"type": "string", "format": "time"}, {"type": "null"}, ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Repeat At", - "type": "string", - "format": "time", - } - ), - "process_after": IsDict( - { + }, + "process_after": { "title": "Process After", "type": "string", "format": "duration", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Process After", - "type": "number", - "format": "time-delta", - } - ), - }, - "required": ["start_datetime", "end_datetime", "process_after"], - }, - "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"}, + "required": ["start_datetime", "end_datetime", "process_after"], }, - }, - "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"}, + }, }, - }, - } - }, - } + "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_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py index 3aa83c0c40..872af53830 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial003.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py @@ -1,8 +1,8 @@ import importlib import pytest -from dirty_equals import IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -43,107 +43,115 @@ def test_get_plane(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", - "content": { - "application/json": { - "schema": { - "title": "Response Read Item Items Item Id Get", - "anyOf": [ - {"$ref": "#/components/schemas/PlaneItem"}, - {"$ref": "#/components/schemas/CarItem"}, - ], + 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": { + "title": "Response Read Item Items Item Id Get", + "anyOf": [ + { + "$ref": "#/components/schemas/PlaneItem" + }, + { + "$ref": "#/components/schemas/CarItem" + }, + ], + } } - } + }, + }, + "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", + } + ], + } } - } - }, - "components": { - "schemas": { - "PlaneItem": { - "title": "PlaneItem", - "required": IsOneOf( - ["description", "type", "size"], - # TODO: remove when deprecating Pydantic v1 - ["description", "size"], - ), - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "type": {"title": "Type", "type": "string", "default": "plane"}, - "size": {"title": "Size", "type": "integer"}, + }, + "components": { + "schemas": { + "PlaneItem": { + "title": "PlaneItem", + "required": ["description", "size"], + "type": "object", + "properties": { + "description": {"title": "Description", "type": "string"}, + "type": { + "title": "Type", + "type": "string", + "default": "plane", + }, + "size": {"title": "Size", "type": "integer"}, + }, }, - }, - "CarItem": { - "title": "CarItem", - "required": IsOneOf( - ["description", "type"], - # TODO: remove when deprecating Pydantic v1 - ["description"], - ), - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "type": {"title": "Type", "type": "string", "default": "car"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "CarItem": { + "title": "CarItem", + "required": ["description"], + "type": "object", + "properties": { + "description": {"title": "Description", "type": "string"}, + "type": { + "title": "Type", + "type": "string", + "default": "car", }, }, - "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"}, - } + "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"}, + }, }, - }, - } - }, - } + "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 d713c5d876..7bd947f194 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py @@ -1,4 +1,3 @@ -from dirty_equals import IsDict from fastapi.testclient import TestClient from docs_src.handling_errors.tutorial005_py39 import app @@ -9,31 +8,17 @@ client = TestClient(app) def test_post_validation_error(): response = client.post("/items/", json={"title": "towel", "size": "XL"}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["body", "size"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "XL", - } - ], - "body": {"title": "towel", "size": "XL"}, - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "size"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ], - "body": {"title": "towel", "size": "XL"}, - } - ) + assert response.json() == { + "detail": [ + { + "type": "int_parsing", + "loc": ["body", "size"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "XL", + } + ], + "body": {"title": "towel", "size": "XL"}, + } def test_post(): diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial006.py b/tests/test_tutorial/test_handling_errors/test_tutorial006.py index 491e461b3d..e95e53d5ed 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial006.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial006.py @@ -1,4 +1,3 @@ -from dirty_equals import IsDict from fastapi.testclient import TestClient from docs_src.handling_errors.tutorial006_py39 import app @@ -9,29 +8,16 @@ client = TestClient(app) def test_get_validation_error(): response = client.get("/items/foo") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) + assert response.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", + } + ] + } def test_get_http_error(): 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 f59d50762c..1fa8aee461 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial001.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -63,29 +62,19 @@ def test_header_param_model_invalid(client: TestClient): assert response.json() == snapshot( { "detail": [ - IsDict( - { - "type": "missing", - "loc": ["header", "save_data"], - "msg": "Field required", - "input": { - "x_tag": [], - "host": "testserver", - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "user-agent": "testclient", - }, - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "value_error.missing", - "loc": ["header", "save_data"], - "msg": "field required", - } - ) + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": { + "x_tag": [], + "host": "testserver", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "user-agent": "testclient", + }, + } ] } ) @@ -136,37 +125,19 @@ def test_openapi_schema(client: TestClient): "name": "if-modified-since", "in": "header", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "If Modified Since", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "If Modified Since", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "If Modified Since", + }, }, { "name": "traceparent", "in": "header", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Traceparent", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "Traceparent", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Traceparent", + }, }, { "name": "x-tag", 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 ed4743ebf9..079a8f5402 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial002.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -64,22 +63,12 @@ def test_header_param_model_invalid(client: TestClient): assert response.json() == snapshot( { "detail": [ - IsDict( - { - "type": "missing", - "loc": ["header", "save_data"], - "msg": "Field required", - "input": {"x_tag": [], "host": "testserver"}, - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "value_error.missing", - "loc": ["header", "save_data"], - "msg": "field required", - } - ) + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": {"x_tag": [], "host": "testserver"}, + } ] } ) @@ -93,22 +82,12 @@ def test_header_param_model_extra(client: TestClient): assert response.json() == snapshot( { "detail": [ - IsDict( - { - "type": "extra_forbidden", - "loc": ["header", "tool"], - "msg": "Extra inputs are not permitted", - "input": "plumbus", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "value_error.extra", - "loc": ["header", "tool"], - "msg": "extra fields not permitted", - } - ) + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } ] } ) @@ -143,37 +122,19 @@ def test_openapi_schema(client: TestClient): "name": "if-modified-since", "in": "header", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "If Modified Since", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "If Modified Since", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "If Modified Since", + }, }, { "name": "traceparent", "in": "header", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Traceparent", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "Traceparent", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Traceparent", + }, }, { "name": "x-tag", 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 947587504f..4c89d80ee2 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial003.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial003.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -60,33 +59,23 @@ def test_header_param_model_no_underscore(client: TestClient): assert response.json() == snapshot( { "detail": [ - IsDict( - { - "type": "missing", - "loc": ["header", "save_data"], - "msg": "Field required", - "input": { - "host": "testserver", - "traceparent": "123", - "x_tag": [], - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "user-agent": "testclient", - "save-data": "true", - "if-modified-since": "yesterday", - "x-tag": ["one", "two"], - }, - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "value_error.missing", - "loc": ["header", "save_data"], - "msg": "field required", - } - ) + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": { + "host": "testserver", + "traceparent": "123", + "x_tag": [], + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "user-agent": "testclient", + "save-data": "true", + "if-modified-since": "yesterday", + "x-tag": ["one", "two"], + }, + } ] } ) @@ -110,29 +99,19 @@ def test_header_param_model_invalid(client: TestClient): assert response.json() == snapshot( { "detail": [ - IsDict( - { - "type": "missing", - "loc": ["header", "save_data"], - "msg": "Field required", - "input": { - "x_tag": [], - "host": "testserver", - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "user-agent": "testclient", - }, - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "value_error.missing", - "loc": ["header", "save_data"], - "msg": "field required", - } - ) + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": { + "x_tag": [], + "host": "testserver", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "user-agent": "testclient", + }, + } ] } ) @@ -183,37 +162,19 @@ def test_openapi_schema(client: TestClient): "name": "if_modified_since", "in": "header", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "If Modified Since", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "If Modified Since", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "If Modified Since", + }, }, { "name": "traceparent", "in": "header", "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Traceparent", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "Traceparent", - } - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Traceparent", + }, }, { "name": "x_tag", diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py index beaf917f92..88591b8225 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -67,16 +66,10 @@ def test_openapi_schema(client: TestClient): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User-Agent", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "User-Agent", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User-Agent", + }, "name": "user-agent", "in": "header", } diff --git a/tests/test_tutorial/test_header_params/test_tutorial002.py b/tests/test_tutorial/test_header_params/test_tutorial002.py index b892ff905f..229f96c1f8 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -78,16 +77,10 @@ def test_openapi_schema(client: TestClient): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Strange Header", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Strange Header", "type": "string"} - ), + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Strange Header", + }, "name": "strange_header", "in": "header", } diff --git a/tests/test_tutorial/test_header_params/test_tutorial003.py b/tests/test_tutorial/test_header_params/test_tutorial003.py index ef76244159..cf067ccf9e 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -56,23 +55,13 @@ def test_openapi_schema(client: TestClient): "parameters": [ { "required": False, - "schema": IsDict( - { - "title": "X-Token", - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "X-Token", - "type": "array", - "items": {"type": "string"}, - } - ), + "schema": { + "title": "X-Token", + "anyOf": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "null"}, + ], + }, "name": "x-token", "in": "header", } diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py index 975e07cbdd..6fde96cb5b 100644 --- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py @@ -2,7 +2,6 @@ import importlib from types import ModuleType import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from tests.utils import needs_py310 @@ -55,30 +54,18 @@ def test_openapi_schema(client: TestClient): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "format": "uri", - "minLength": 1, - "maxLength": 2083, - }, - {"type": "null"}, - ], - "title": "Callback Url", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Callback Url", - "maxLength": 2083, - "minLength": 1, - "type": "string", - "format": "uri", - } - ), + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uri", + "minLength": 1, + "maxLength": 2083, + }, + {"type": "null"}, + ], + "title": "Callback Url", + }, "name": "callback_url", "in": "query", } @@ -171,16 +158,10 @@ def test_openapi_schema(client: TestClient): "type": "object", "properties": { "id": {"title": "Id", "type": "string"}, - "title": IsDict( - { - "title": "Title", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Title", "type": "string"} - ), + "title": { + "title": "Title", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, "customer": {"title": "Customer", "type": "string"}, "total": {"title": "Total", "type": "number"}, }, diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py index b3be70471a..86ccce7b6d 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial005.py +++ b/tests/test_tutorial/test_path_params/test_tutorial005.py @@ -1,4 +1,3 @@ -from dirty_equals import IsDict from fastapi.testclient import TestClient from docs_src.path_params.tutorial005_py39 import app @@ -27,31 +26,17 @@ def test_get_enums_resnet(): def test_get_enums_invalid(): response = client.get("/models/foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "enum", - "loc": ["path", "model_name"], - "msg": "Input should be 'alexnet', 'resnet' or 'lenet'", - "input": "foo", - "ctx": {"expected": "'alexnet', 'resnet' or 'lenet'"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"enum_values": ["alexnet", "resnet", "lenet"]}, - "loc": ["path", "model_name"], - "msg": "value is not a valid enumeration member; permitted: 'alexnet', 'resnet', 'lenet'", - "type": "type_error.enum", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "enum", + "loc": ["path", "model_name"], + "msg": "Input should be 'alexnet', 'resnet' or 'lenet'", + "input": "foo", + "ctx": {"expected": "'alexnet', 'resnet' or 'lenet'"}, + } + ] + } def test_openapi_schema(): @@ -106,22 +91,11 @@ def test_openapi_schema(): } }, }, - "ModelName": IsDict( - { - "title": "ModelName", - "enum": ["alexnet", "resnet", "lenet"], - "type": "string", - } - ) - | IsDict( - { - # TODO: remove when deprecating Pydantic v1 - "title": "ModelName", - "enum": ["alexnet", "resnet", "lenet"], - "type": "string", - "description": "An enumeration.", - } - ), + "ModelName": { + "title": "ModelName", + "enum": ["alexnet", "resnet", "lenet"], + "type": "string", + }, "ValidationError": { "title": "ValidationError", "required": ["loc", "msg", "type"], 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 86830b9341..d3ce57121d 100644 --- a/tests/test_tutorial/test_query_param_models/test_tutorial001.py +++ b/tests/test_tutorial/test_query_param_models/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -65,61 +64,31 @@ def test_query_param_model_invalid(client: TestClient): ) assert response.status_code == 422 assert response.json() == snapshot( - IsDict( - { - "detail": [ - { - "type": "less_than_equal", - "loc": ["query", "limit"], - "msg": "Input should be less than or equal to 100", - "input": "150", - "ctx": {"le": 100}, - }, - { - "type": "greater_than_equal", - "loc": ["query", "offset"], - "msg": "Input should be greater than or equal to 0", - "input": "-1", - "ctx": {"ge": 0}, - }, - { - "type": "literal_error", - "loc": ["query", "order_by"], - "msg": "Input should be 'created_at' or 'updated_at'", - "input": "invalid", - "ctx": {"expected": "'created_at' or 'updated_at'"}, - }, - ] - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "type": "value_error.number.not_le", - "loc": ["query", "limit"], - "msg": "ensure this value is less than or equal to 100", - "ctx": {"limit_value": 100}, - }, - { - "type": "value_error.number.not_ge", - "loc": ["query", "offset"], - "msg": "ensure this value is greater than or equal to 0", - "ctx": {"limit_value": 0}, - }, - { - "type": "value_error.const", - "loc": ["query", "order_by"], - "msg": "unexpected value; permitted: 'created_at', 'updated_at'", - "ctx": { - "given": "invalid", - "permitted": ["created_at", "updated_at"], - }, - }, - ] - } - ) + { + "detail": [ + { + "type": "less_than_equal", + "loc": ["query", "limit"], + "msg": "Input should be less than or equal to 100", + "input": "150", + "ctx": {"le": 100}, + }, + { + "type": "greater_than_equal", + "loc": ["query", "offset"], + "msg": "Input should be greater than or equal to 0", + "input": "-1", + "ctx": {"ge": 0}, + }, + { + "type": "literal_error", + "loc": ["query", "order_by"], + "msg": "Input should be 'created_at' or 'updated_at'", + "input": "invalid", + "ctx": {"expected": "'created_at' or 'updated_at'"}, + }, + ] + } ) 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 0e9c3351a0..96abce6ab9 100644 --- a/tests/test_tutorial/test_query_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_query_param_models/test_tutorial002.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -65,61 +64,31 @@ def test_query_param_model_invalid(client: TestClient): ) assert response.status_code == 422 assert response.json() == snapshot( - IsDict( - { - "detail": [ - { - "type": "less_than_equal", - "loc": ["query", "limit"], - "msg": "Input should be less than or equal to 100", - "input": "150", - "ctx": {"le": 100}, - }, - { - "type": "greater_than_equal", - "loc": ["query", "offset"], - "msg": "Input should be greater than or equal to 0", - "input": "-1", - "ctx": {"ge": 0}, - }, - { - "type": "literal_error", - "loc": ["query", "order_by"], - "msg": "Input should be 'created_at' or 'updated_at'", - "input": "invalid", - "ctx": {"expected": "'created_at' or 'updated_at'"}, - }, - ] - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "type": "value_error.number.not_le", - "loc": ["query", "limit"], - "msg": "ensure this value is less than or equal to 100", - "ctx": {"limit_value": 100}, - }, - { - "type": "value_error.number.not_ge", - "loc": ["query", "offset"], - "msg": "ensure this value is greater than or equal to 0", - "ctx": {"limit_value": 0}, - }, - { - "type": "value_error.const", - "loc": ["query", "order_by"], - "msg": "unexpected value; permitted: 'created_at', 'updated_at'", - "ctx": { - "given": "invalid", - "permitted": ["created_at", "updated_at"], - }, - }, - ] - } - ) + { + "detail": [ + { + "type": "less_than_equal", + "loc": ["query", "limit"], + "msg": "Input should be less than or equal to 100", + "input": "150", + "ctx": {"le": 100}, + }, + { + "type": "greater_than_equal", + "loc": ["query", "offset"], + "msg": "Input should be greater than or equal to 0", + "input": "-1", + "ctx": {"ge": 0}, + }, + { + "type": "literal_error", + "loc": ["query", "order_by"], + "msg": "Input should be 'created_at' or 'updated_at'", + "input": "invalid", + "ctx": {"expected": "'created_at' or 'updated_at'"}, + }, + ] + } ) @@ -138,22 +107,12 @@ def test_query_param_model_extra(client: TestClient): assert response.json() == snapshot( { "detail": [ - IsDict( - { - "type": "extra_forbidden", - "loc": ["query", "tool"], - "msg": "Extra inputs are not permitted", - "input": "plumbus", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "value_error.extra", - "loc": ["query", "tool"], - "msg": "extra fields not permitted", - } - ) + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } ] } ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py index ad4e4efa6b..1030781472 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial005.py +++ b/tests/test_tutorial/test_query_params/test_tutorial005.py @@ -1,4 +1,3 @@ -from dirty_equals import IsDict from fastapi.testclient import TestClient from docs_src.query_params.tutorial005_py39 import app @@ -15,29 +14,16 @@ def test_foo_needy_very(): def test_foo_no_needy(): response = client.get("/items/foo") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "needy"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "needy"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "needy"], + "msg": "Field required", + "input": None, + } + ] + } def test_openapi_schema(): diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py index 349f8dd223..157322c7e3 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial006.py +++ b/tests/test_tutorial/test_query_params/test_tutorial006.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -35,51 +34,28 @@ def test_foo_needy_very(client: TestClient): def test_foo_no_needy(client: TestClient): response = client.get("/items/foo?skip=a&limit=b") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "needy"], - "msg": "Field required", - "input": None, - }, - { - "type": "int_parsing", - "loc": ["query", "skip"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "a", - }, - { - "type": "int_parsing", - "loc": ["query", "limit"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "b", - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "needy"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["query", "skip"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - }, - { - "loc": ["query", "limit"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "needy"], + "msg": "Field required", + "input": None, + }, + { + "type": "int_parsing", + "loc": ["query", "skip"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "a", + }, + { + "type": "int_parsing", + "loc": ["query", "limit"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "b", + }, + ] + } def test_openapi_schema(client: TestClient): @@ -134,16 +110,10 @@ def test_openapi_schema(client: TestClient): }, { "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Limit", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Limit", "type": "integer"} - ), + "schema": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Limit", + }, "name": "limit", "in": "query", }, 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 de5dbbb2ee..00889c5bf7 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 @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE from fastapi.testclient import TestClient @@ -50,31 +49,17 @@ def test_query_params_str_validations_q_fixedquery(client: TestClient): def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): response = client.get("/items/", params={"item-query": "nonregexquery"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "string_pattern_mismatch", + "loc": ["query", "item-query"], + "msg": "String should match pattern '^fixedquery$'", + "input": "nonregexquery", + "ctx": {"pattern": "^fixedquery$"}, + } + ] + } def test_openapi_schema(client: TestClient): @@ -109,38 +94,25 @@ 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": IsDict( - { - "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 {} - ), - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), + "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", } 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 50b3c5683c..11de33ae14 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 @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -69,23 +68,13 @@ def test_openapi_schema(client: TestClient): "parameters": [ { "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), + "schema": { + "anyOf": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "null"}, + ], + "title": "Q", + }, "name": "q", "in": "query", } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index a16d951dc6..e0e1bbe639 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient @@ -22,57 +21,31 @@ def get_client(request: pytest.FixtureRequest): def test_post_form_no_body(client: TestClient): response = client.post("/files/") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "file"], + "msg": "Field required", + "input": None, + } + ] + } def test_post_body_json(client: TestClient): response = client.post("/files/", json={"file": "Foo"}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "file"], + "msg": "Field required", + "input": None, + } + ] + } def test_post_file(tmp_path, client: TestClient): 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 caea0d2e8f..18948c5444 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -2,8 +2,8 @@ import importlib from pathlib import Path import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -59,166 +59,132 @@ def test_post_upload_file(tmp_path: 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": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { + 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": { + "multipart/form-data": { + "schema": { "$ref": "#/components/schemas/Body_create_file_files__post" } - ) - } - } - }, - "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" + } + } + }, }, }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { + } + }, + "/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" } - ) - } - } - }, - "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_create_file_files__post": { + "title": "Body_create_file_files__post", + "type": "object", + "properties": { + "file": { + "title": "File", + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + } + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "type": "object", + "properties": { + "file": { + "title": "File", + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + } + }, + }, + "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"}, }, }, } }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "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"}, - }, - }, - } - }, - } + } + ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index 34dbbb985d..03772419ad 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient @@ -28,57 +27,31 @@ def get_client(app: FastAPI): def test_post_form_no_body(client: TestClient): response = client.post("/files/") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "files"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "files"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "files"], + "msg": "Field required", + "input": None, + } + ] + } def test_post_body_json(client: TestClient): response = client.post("/files/", json={"file": "Foo"}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "files"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "files"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "files"], + "msg": "Field required", + "input": None, + } + ] + } def test_post_files(tmp_path, app: FastAPI): 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 a54df8536c..0c43dd7b21 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial001.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient @@ -28,135 +27,73 @@ def test_post_body_form(client: TestClient): def test_post_body_form_no_password(client: TestClient): response = client.post("/login/", data={"username": "Foo"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {"username": "Foo"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "password"], + "msg": "Field required", + "input": {"username": "Foo"}, + } + ] + } def test_post_body_form_no_username(client: TestClient): response = client.post("/login/", data={"password": "secret"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {"password": "secret"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": {"password": "secret"}, + } + ] + } def test_post_body_form_no_data(client: TestClient): response = client.post("/login/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": {}, + }, + { + "type": "missing", + "loc": ["body", "password"], + "msg": "Field required", + "input": {}, + }, + ] + } def test_post_body_json(client: TestClient): response = client.post("/login/", json={"username": "Foo", "password": "secret"}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": {}, + }, + { + "type": "missing", + "loc": ["body", "password"], + "msg": "Field required", + "input": {}, + }, + ] + } def test_openapi_schema(client: TestClient): diff --git a/tests/test_tutorial/test_request_forms/test_tutorial001.py b/tests/test_tutorial/test_request_forms/test_tutorial001.py index da20535cf8..4276414fc2 100644 --- a/tests/test_tutorial/test_request_forms/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms/test_tutorial001.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient @@ -28,135 +27,73 @@ def test_post_body_form(client: TestClient): def test_post_body_form_no_password(client: TestClient): response = client.post("/login/", data={"username": "Foo"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "password"], + "msg": "Field required", + "input": None, + } + ] + } def test_post_body_form_no_username(client: TestClient): response = client.post("/login/", data={"password": "secret"}) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": None, + } + ] + } def test_post_body_form_no_data(client: TestClient): response = client.post("/login/") assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "password"], + "msg": "Field required", + "input": None, + }, + ] + } def test_post_body_json(client: TestClient): response = client.post("/login/", json={"username": "Foo", "password": "secret"}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "password"], + "msg": "Field required", + "input": None, + }, + ] + } def test_openapi_schema(client: TestClient): 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 f37ffad443..7fa4c3de57 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 @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient @@ -28,140 +27,76 @@ def get_client(app: FastAPI): def test_post_form_no_body(client: TestClient): response = client.post("/files/") assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "fileb"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "fileb"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "file"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "fileb"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "token"], + "msg": "Field required", + "input": None, + }, + ] + } def test_post_form_no_file(client: TestClient): response = client.post("/files/", data={"token": "foo"}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "fileb"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "fileb"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "file"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "fileb"], + "msg": "Field required", + "input": None, + }, + ] + } def test_post_body_json(client: TestClient): response = client.post("/files/", json={"file": "Foo", "token": "Bar"}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "fileb"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "fileb"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "file"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "fileb"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "token"], + "msg": "Field required", + "input": None, + }, + ] + } def test_post_file_no_token(tmp_path, app: FastAPI): @@ -172,40 +107,22 @@ def test_post_file_no_token(tmp_path, app: FastAPI): with path.open("rb") as file: response = client.post("/files/", files={"file": file}) assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "fileb"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "fileb"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "fileb"], + "msg": "Field required", + "input": None, + }, + { + "type": "missing", + "loc": ["body", "token"], + "msg": "Field required", + "input": None, + }, + ] + } def test_post_files_and_token(tmp_path, app: FastAPI): diff --git a/tests/test_tutorial/test_response_model/test_tutorial003.py b/tests/test_tutorial/test_response_model/test_tutorial003.py index 0f9eac890b..35ed5572dd 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003.py @@ -1,8 +1,8 @@ import importlib import pytest -from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -42,125 +42,115 @@ 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", - "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": { + "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"}], + }, }, }, - "summary": "Create User", - "operationId": "create_user_user__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserIn"} + "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"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "UserOut": { - "title": "UserOut", - "required": IsOneOf( - ["username", "email", "full_name"], - # TODO: remove when deprecating Pydantic v1 - ["username", "email"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": { - "title": "Email", - "type": "string", - "format": "email", - }, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "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": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full 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"}, - }, - }, - "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_01.py b/tests/test_tutorial/test_response_model/test_tutorial003_01.py index 1a7ce4c7a1..fa1eb62770 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_01.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_01.py @@ -1,8 +1,8 @@ import importlib import pytest -from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -42,125 +42,115 @@ 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": { - "summary": "Create User", - "operationId": "create_user_user__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserIn"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/user/": { + "post": { + "summary": "Create User", + "operationId": "create_user_user__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserIn"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseUser" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "BaseUser": { + "title": "BaseUser", + "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"}], + }, + }, + }, + "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/BaseUser"} - } + "UserIn": { + "title": "UserIn", + "required": ["username", "email", "password"], + "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"}], + }, + "password": {"title": "Password", "type": "string"}, }, - "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"}, }, }, } - } - }, - "components": { - "schemas": { - "BaseUser": { - "title": "BaseUser", - "required": IsOneOf( - ["username", "email", "full_name"], - # TODO: remove when deprecating Pydantic v1 - ["username", "email"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": { - "title": "Email", - "type": "string", - "format": "email", - }, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "UserIn": { - "title": "UserIn", - "required": ["username", "email", "password"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": { - "title": "Email", - "type": "string", - "format": "email", - }, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "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"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py index 19f6998f70..9c0d95ebd0 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004.py @@ -1,8 +1,8 @@ import importlib import pytest -from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -50,104 +50,98 @@ def test_get(url, 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}": { - "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" + 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", - } - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": IsOneOf( - ["name", "description", "price", "tax", "tags"], - # TODO: remove when deprecating Pydantic v1 - ["name", "price"], - ), - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": IsDict( + "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": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": { "title": "Description", "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "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"}] + }, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], }, }, - "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"}, - } + "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"}, + }, }, - }, - } - }, - } + "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_tutorial005.py b/tests/test_tutorial/test_response_model/test_tutorial005.py index 47d77dc498..63e8535db0 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial005.py +++ b/tests/test_tutorial/test_response_model/test_tutorial005.py @@ -1,8 +1,8 @@ import importlib import pytest -from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -40,132 +40,126 @@ def test_read_item_public_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}/name": { - "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" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}/name": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} } - } + }, }, - }, - }, - "summary": "Read Item Name", - "operationId": "read_item_name_items__item_id__name_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/items/{item_id}/public": { - "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" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Item Public Data", - "operationId": "read_item_public_data_items__item_id__public_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": IsOneOf( - ["name", "description", "price", "tax"], - # TODO: remove when deprecating Pydantic v1 - ["name", "price"], - ), - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": IsDict( + "summary": "Read Item Name", + "operationId": "read_item_name_items__item_id__name_get", + "parameters": [ { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/items/{item_id}/public": { + "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 Public Data", + "operationId": "read_item_public_data_items__item_id__public_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + }, + "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"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] }, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, }, - "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"}, - } + "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"}, + }, }, - }, - } - }, - } + "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_tutorial006.py b/tests/test_tutorial/test_response_model/test_tutorial006.py index a03aa41e8c..08ab659527 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial006.py +++ b/tests/test_tutorial/test_response_model/test_tutorial006.py @@ -1,8 +1,8 @@ import importlib import pytest -from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -40,132 +40,126 @@ def test_read_item_public_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}/name": { - "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" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}/name": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} } - } + }, }, - }, - }, - "summary": "Read Item Name", - "operationId": "read_item_name_items__item_id__name_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/items/{item_id}/public": { - "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" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Item Public Data", - "operationId": "read_item_public_data_items__item_id__public_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": IsOneOf( - ["name", "description", "price", "tax"], - # TODO: remove when deprecating Pydantic v1 - ["name", "price"], - ), - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": IsDict( + "summary": "Read Item Name", + "operationId": "read_item_name_items__item_id__name_get", + "parameters": [ { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/items/{item_id}/public": { + "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 Public Data", + "operationId": "read_item_public_data_items__item_id__public_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + }, + "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"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] }, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, }, - "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"}, - } + "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"}, + }, }, - }, - } - }, - } + "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_tutorial004.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py index 47ecb9ba73..9326e06290 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -58,46 +57,22 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": IsDict( - { - "$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", - }, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "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", - }, - ], - } - ) + "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", + }, + ], + } } }, "required": True, @@ -140,27 +115,15 @@ def test_openapi_schema(client: TestClient): "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, }, }, "ValidationError": { 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 1c964f3d15..2d0dee48ca 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -58,16 +57,7 @@ def test_openapi_schema(client: TestClient) -> None: "requestBody": { "content": { "application/json": { - "schema": IsDict({"$ref": "#/components/schemas/Item"}) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - } - ), + "schema": {"$ref": "#/components/schemas/Item"}, "examples": { "normal": { "summary": "A normal example", @@ -134,27 +124,15 @@ def test_openapi_schema(client: TestClient) -> None: "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, }, }, "ValidationError": { diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py index 000c8b2ac4..6a786348cf 100644 --- a/tests/test_tutorial/test_security/test_tutorial003.py +++ b/tests/test_tutorial/test_security/test_tutorial003.py @@ -1,7 +1,6 @@ import importlib import pytest -from dirty_equals import IsDict from fastapi.testclient import TestClient from ...utils import needs_py310 @@ -144,23 +143,13 @@ def test_openapi_schema(client: TestClient): "required": ["username", "password"], "type": "object", "properties": { - "grant_type": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "^password$", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Grant Type", - "pattern": "^password$", - "type": "string", - } - ), + "grant_type": { + "title": "Grant Type", + "anyOf": [ + {"pattern": "^password$", "type": "string"}, + {"type": "null"}, + ], + }, "username": {"title": "Username", "type": "string"}, "password": { "title": "Password", @@ -168,31 +157,15 @@ def test_openapi_schema(client: TestClient): "format": "password", }, "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - "format": "password", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Client Secret", - "type": "string", - "format": "password", - } - ), + "client_id": { + "title": "Client Id", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "client_secret": { + "title": "Client Secret", + "anyOf": [{"type": "string"}, {"type": "null"}], + "format": "password", + }, }, }, "ValidationError": { diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py index 7953e8e3f6..25b47f0adc 100644 --- a/tests/test_tutorial/test_security/test_tutorial005.py +++ b/tests/test_tutorial/test_security/test_tutorial005.py @@ -2,8 +2,8 @@ import importlib from types import ModuleType import pytest -from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -215,240 +215,200 @@ 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": ["me"]}], + } + }, + "/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": ["items", "me"]}], + } + }, + "/status/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read System Status", + "operationId": "read_system_status_status__get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, }, - "/users/me/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me__get", - "security": [{"OAuth2PasswordBearer": ["me"]}], - } - }, - "/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": ["items", "me"]}], - } - }, - "/status/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read System Status", - "operationId": "read_system_status_status__get", - "security": [{"OAuth2PasswordBearer": []}], - } - }, - }, - "components": { - "schemas": { - "User": { - "title": "User", - "required": IsOneOf( - ["username", "email", "full_name", "disabled"], - # TODO: remove when deprecating Pydantic v1 - ["username"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": IsDict( - { + "components": { + "schemas": { + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": { "title": "Email", "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Email", "type": "string"} - ), - "full_name": IsDict( - { + }, + "full_name": { "title": "Full Name", "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - "disabled": IsDict( - { + }, + "disabled": { "title": "Disabled", "anyOf": [{"type": "boolean"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Disabled", "type": "boolean"} - ), + }, + }, }, - }, - "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"}, + "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": IsDict( - { + "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"}, ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Grant Type", - "pattern": "^password$", + }, + "username": {"title": "Username", "type": "string"}, + "password": { + "title": "Password", "type": "string", - } - ), - "username": {"title": "Username", "type": "string"}, - "password": { - "title": "Password", - "type": "string", - "format": "password", - }, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": IsDict( - { + "format": "password", + }, + "scope": { + "title": "Scope", + "type": "string", + "default": "", + }, + "client_id": { "title": "Client Id", "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { + }, + "client_secret": { "title": "Client Secret", "anyOf": [{"type": "string"}, {"type": "null"}], "format": "password", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Client Secret", - "type": "string", - "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"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "me": "Read information about the current user.", - "items": "Read items.", + "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"}, + }, }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "me": "Read information about the current user.", + "items": "Read items.", + }, + "tokenUrl": "token", + } + }, + } + }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_sql_databases/test_tutorial001.py index e3e6bac128..2c628f5257 100644 --- a/tests/test_tutorial/test_sql_databases/test_tutorial001.py +++ b/tests/test_tutorial/test_sql_databases/test_tutorial001.py @@ -2,7 +2,7 @@ import importlib import warnings import pytest -from dirty_equals import IsDict, IsInt +from dirty_equals import IsInt from fastapi.testclient import TestClient from inline_snapshot import snapshot from sqlalchemy import StaticPool @@ -318,33 +318,15 @@ def test_openapi_schema(client: TestClient): }, "Hero": { "properties": { - "id": IsDict( - { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "integer", - "title": "Id", - } - ), + "id": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Id", + }, "name": {"type": "string", "title": "Name"}, - "age": IsDict( - { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Age", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "integer", - "title": "Age", - } - ), + "age": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + }, "secret_name": {"type": "string", "title": "Secret Name"}, }, "type": "object", diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial002.py b/tests/test_tutorial/test_sql_databases/test_tutorial002.py index e3b8c7f9e4..c72c16e9ae 100644 --- a/tests/test_tutorial/test_sql_databases/test_tutorial002.py +++ b/tests/test_tutorial/test_sql_databases/test_tutorial002.py @@ -2,7 +2,7 @@ import importlib import warnings import pytest -from dirty_equals import IsDict, IsInt +from dirty_equals import IsInt from fastapi.testclient import TestClient from inline_snapshot import Is, snapshot from sqlalchemy import StaticPool @@ -373,19 +373,10 @@ def test_openapi_schema(client: TestClient): "HeroCreate": { "properties": { "name": {"type": "string", "title": "Name"}, - "age": IsDict( - { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Age", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "integer", - "title": "Age", - } - ), + "age": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + }, "secret_name": {"type": "string", "title": "Secret Name"}, }, "type": "object", @@ -395,19 +386,10 @@ def test_openapi_schema(client: TestClient): "HeroPublic": { "properties": { "name": {"type": "string", "title": "Name"}, - "age": IsDict( - { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Age", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "integer", - "title": "Age", - } - ), + "age": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + }, "id": {"type": "integer", "title": "Id"}, }, "type": "object", @@ -416,45 +398,18 @@ def test_openapi_schema(client: TestClient): }, "HeroUpdate": { "properties": { - "name": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Name", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "Name", - } - ), - "age": IsDict( - { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Age", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "integer", - "title": "Age", - } - ), - "secret_name": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Secret Name", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "Secret Name", - } - ), + "name": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Name", + }, + "age": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + }, + "secret_name": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Secret Name", + }, }, "type": "object", "title": "HeroUpdate", diff --git a/tests/test_union_body.py b/tests/test_union_body.py index c15acacd18..ee7fcc4231 100644 --- a/tests/test_union_body.py +++ b/tests/test_union_body.py @@ -1,6 +1,5 @@ from typing import Optional, Union -from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -91,18 +90,12 @@ def test_openapi_schema(): "Item": { "title": "Item", "type": "object", - "properties": IsDict( - { - "name": { - "title": "Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } + "properties": { + "name": { + "title": "Name", + "anyOf": [{"type": "string"}, {"type": "null"}], } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"name": {"title": "Name", "type": "string"}} - ), + }, }, "ValidationError": { "title": "ValidationError", diff --git a/tests/test_union_body_discriminator.py b/tests/test_union_body_discriminator.py index 40fd0065a9..6c31649bcc 100644 --- a/tests/test_union_body_discriminator.py +++ b/tests/test_union_body_discriminator.py @@ -1,6 +1,5 @@ from typing import Annotated, Any, Union -from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -90,21 +89,11 @@ def test_discriminator_pydantic_v2() -> None: "description": "Successful Response", "content": { "application/json": { - "schema": IsDict( - { - # Pydantic 2.10, in Python 3.8 - # TODO: remove when dropping support for Python 3.8 - "type": "object", - "title": "Response Save Union Body Discriminator Items Post", - } - ) - | IsDict( - { - "type": "object", - "additionalProperties": True, - "title": "Response Save Union Body Discriminator Items Post", - } - ) + "schema": { + "type": "object", + "additionalProperties": True, + "title": "Response Save Union Body Discriminator Items Post", + } } }, }, diff --git a/tests/test_union_inherited_body.py b/tests/test_union_inherited_body.py index ef75d459ea..3c062e7f5a 100644 --- a/tests/test_union_inherited_body.py +++ b/tests/test_union_inherited_body.py @@ -1,6 +1,5 @@ from typing import Optional, Union -from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -86,16 +85,10 @@ def test_openapi_schema(): "title": "Item", "type": "object", "properties": { - "name": IsDict( - { - "title": "Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Name", "type": "string"} - ) + "name": { + "title": "Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + } }, }, "ExtendedItem": { @@ -103,16 +96,10 @@ def test_openapi_schema(): "required": ["age"], "type": "object", "properties": { - "name": IsDict( - { - "title": "Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Name", "type": "string"} - ), + "name": { + "title": "Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, "age": {"title": "Age", "type": "integer"}, }, }, From ded035a421a9eb11d23b51016b626bbe2433493f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 27 Dec 2025 18:19:33 +0000 Subject: [PATCH 024/110] =?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 ef7894037c..8d080d6c56 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* ๐Ÿ”ฅ Remove Pydantic v1 specific test variants. PR [#14611](https://github.com/fastapi/fastapi/pull/14611) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.0 ### Breaking Changes From 1b42639296b47f1344e24c1789f649caabbee67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 27 Dec 2025 10:31:34 -0800 Subject: [PATCH 025/110] =?UTF-8?q?=F0=9F=94=A5=20Remove=20test=20variants?= =?UTF-8?q?=20for=20Pydantic=20v1=20in=20test=5Frequest=5Fparams=20(#14612?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_body/test_list.py | 34 ++---- .../test_file/test_list.py | 105 ++++-------------- .../test_form/test_list.py | 34 ++---- .../test_header/test_list.py | 34 ++---- .../test_query/test_list.py | 34 ++---- 5 files changed, 64 insertions(+), 177 deletions(-) diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py index 50847335ce..970e6a6607 100644 --- a/tests/test_request_params/test_body/test_list.py +++ b/tests/test_request_params/test_body/test_list.py @@ -1,7 +1,7 @@ from typing import Annotated, Union import pytest -from dirty_equals import IsDict, IsOneOf, IsPartialDict +from dirty_equals import IsOneOf, IsPartialDict from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -59,28 +59,16 @@ def test_required_list_str_missing(path: str, json: Union[dict, None]): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": IsOneOf(["body", "p"], ["body"]), - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - { - "detail": [ - { - "loc": IsOneOf(["body", "p"], ["body"]), - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": IsOneOf(["body", "p"], ["body"]), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py index f096532554..68280fcf32 100644 --- a/tests/test_request_params/test_file/test_list.py +++ b/tests/test_request_params/test_file/test_list.py @@ -1,7 +1,6 @@ from typing import Annotated import pytest -from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient @@ -36,27 +35,11 @@ def test_list_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p": ( - IsDict( - { - "anyOf": [ - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - }, - {"type": "null"}, - ], - "title": "P", - }, - ) - | IsDict( - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - "title": "P", - }, - ) - ) + "p": { + "type": "array", + "items": {"type": "string", "format": "binary"}, + "title": "P", + }, }, "required": ["p"], "title": body_model_name, @@ -130,27 +113,11 @@ def test_list_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_alias": ( - IsDict( - { - "anyOf": [ - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - }, - {"type": "null"}, - ], - "title": "P Alias", - }, - ) - | IsDict( - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - "title": "P Alias", - }, - ) - ) + "p_alias": { + "type": "array", + "items": {"type": "string", "format": "binary"}, + "title": "P Alias", + }, }, "required": ["p_alias"], "title": body_model_name, @@ -252,27 +219,11 @@ def test_list_validation_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_val_alias": ( - IsDict( - { - "anyOf": [ - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - }, - {"type": "null"}, - ], - "title": "P Val Alias", - }, - ) - | IsDict( - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - "title": "P Val Alias", - }, - ) - ) + "p_val_alias": { + "type": "array", + "items": {"type": "string", "format": "binary"}, + "title": "P Val Alias", + }, }, "required": ["p_val_alias"], "title": body_model_name, @@ -385,27 +336,11 @@ def test_list_alias_and_validation_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_val_alias": ( - IsDict( - { - "anyOf": [ - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - }, - {"type": "null"}, - ], - "title": "P Val Alias", - }, - ) - | IsDict( - { - "type": "array", - "items": {"type": "string", "format": "binary"}, - "title": "P Val Alias", - }, - ) - ) + "p_val_alias": { + "type": "array", + "items": {"type": "string", "format": "binary"}, + "title": "P Val Alias", + }, }, "required": ["p_val_alias"], "title": body_model_name, diff --git a/tests/test_request_params/test_form/test_list.py b/tests/test_request_params/test_form/test_list.py index cfc42f523a..abe781c945 100644 --- a/tests/test_request_params/test_form/test_list.py +++ b/tests/test_request_params/test_form/test_list.py @@ -1,7 +1,7 @@ from typing import Annotated import pytest -from dirty_equals import IsDict, IsOneOf, IsPartialDict +from dirty_equals import IsOneOf, IsPartialDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -58,28 +58,16 @@ def test_required_list_str_missing(path: str): client = TestClient(app) response = client.post(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "p"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - { - "detail": [ - { - "loc": ["body", "p"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @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 65510094af..489a6b3e7d 100644 --- a/tests/test_request_params/test_header/test_list.py +++ b/tests/test_request_params/test_header/test_list.py @@ -1,7 +1,7 @@ from typing import Annotated import pytest -from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict +from dirty_equals import AnyThing, IsOneOf, IsPartialDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -53,28 +53,16 @@ def test_required_list_str_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "p"], - "msg": "Field required", - "input": AnyThing, - } - ] - } - ) | IsDict( - { - "detail": [ - { - "loc": ["header", "p"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p"], + "msg": "Field required", + "input": AnyThing, + } + ] + } @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 ed2ea6c809..e933da214d 100644 --- a/tests/test_request_params/test_query/test_list.py +++ b/tests/test_request_params/test_query/test_list.py @@ -1,7 +1,7 @@ from typing import Annotated import pytest -from dirty_equals import IsDict, IsOneOf +from dirty_equals import IsOneOf from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -53,28 +53,16 @@ def test_required_list_str_missing(path: str): client = TestClient(app) response = client.get(path) assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "p"], - "msg": "Field required", - "input": IsOneOf(None, {}), - } - ] - } - ) | IsDict( - { - "detail": [ - { - "loc": ["query", "p"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } @pytest.mark.parametrize( From a1735d6d119a7f8a33dd0f20d0cf0bb1b1f631c5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 27 Dec 2025 18:31:59 +0000 Subject: [PATCH 026/110] =?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 8d080d6c56..45a6d31d66 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* ๐Ÿ”ฅ Remove test variants for Pydantic v1 in test_request_params. PR [#14612](https://github.com/fastapi/fastapi/pull/14612) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ฅ Remove Pydantic v1 specific test variants. PR [#14611](https://github.com/fastapi/fastapi/pull/14611) by [@tiangolo](https://github.com/tiangolo). ## 0.128.0 From 4d4fb28f9f7331d758a0da908804348b5a0ba719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 27 Dec 2025 10:48:45 -0800 Subject: [PATCH 027/110] =?UTF-8?q?=F0=9F=91=B7=20Do=20not=20run=20transla?= =?UTF-8?q?tions=20on=20cron=20while=20finishing=20updating=20existing=20l?= =?UTF-8?q?anguages=20(#14613)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/translate.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index e681762ca3..f1267d21f5 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -1,8 +1,8 @@ name: Translate on: - schedule: - - cron: "0 5 15 * *" # Run at 05:00 on the 15 of every month + # schedule: + # - cron: "0 5 15 * *" # Run at 05:00 on the 15 of every month workflow_dispatch: inputs: From 52842fb8d371ad1517dbac48950d55e9cfa5f64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 27 Dec 2025 10:49:08 -0800 Subject: [PATCH 028/110] =?UTF-8?q?=F0=9F=94=A7=20Add=20LLM=20prompt=20fil?= =?UTF-8?q?e=20for=20Simplified=20Chinese,=20generated=20from=20the=20exis?= =?UTF-8?q?ting=20translations=20(#14549)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ๅฒ้›ฒๆ˜” (Vincy SHI) --- docs/zh/llm-prompt.md | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/zh/llm-prompt.md diff --git a/docs/zh/llm-prompt.md b/docs/zh/llm-prompt.md new file mode 100644 index 0000000000..1dfbe59162 --- /dev/null +++ b/docs/zh/llm-prompt.md @@ -0,0 +1,46 @@ +### Target language + +Translate to Simplified Chinese (็ฎ€ไฝ“ไธญๆ–‡). + +Language code: zh. + +### Grammar and tone + +1) Use clear, concise technical Chinese consistent with existing docs. +2) Address the reader naturally (commonly using โ€œไฝ /ไฝ ็š„โ€). + +### Headings + +1) Follow existing Simplified Chinese heading style (short and descriptive). +2) Do not add trailing punctuation to headings. +3) If a heading contains only the name of a FastAPI feature, do not translate it. + +### Quotes and punctuation + +1) Keep punctuation style consistent with existing Simplified Chinese docs (they often mix English terms like โ€œFastAPIโ€ with Chinese text). +2) Never change punctuation inside inline code, code blocks, URLs, or file paths. + +### Ellipsis + +1) Keep ellipsis style consistent within each document, prefer `...` over `โ€ฆโ€ฆ`. +2) Never change ellipsis in code, URLs, or CLI examples. + +### Preferred translations / glossary + +Use the following preferred translations when they apply in documentation prose: + +- request (HTTP): ่ฏทๆฑ‚ +- response (HTTP): ๅ“ๅบ” +- path operation: ่ทฏๅพ„ๆ“ไฝœ +- path operation function: ่ทฏๅพ„ๆ“ไฝœๅ‡ฝๆ•ฐ + +### `///` admonitions + +1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). +2) If a title is present, prefer these canonical titles: + +- `/// tip | ๆ็คบ` +- `/// note | ๆณจๆ„` +- `/// warning | ่ญฆๅ‘Š` +- `/// info | ไฟกๆฏ` +- `/// danger | ๅฑ้™ฉ` From 13743e115a2a842e0c0076041f750660305c6b77 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 27 Dec 2025 18:49:09 +0000 Subject: [PATCH 029/110] =?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 45a6d31d66..50a83da2ea 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* ๐Ÿ‘ท Do not run translations on cron while finishing updating existing languages. PR [#14613](https://github.com/fastapi/fastapi/pull/14613) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ฅ Remove test variants for Pydantic v1 in test_request_params. PR [#14612](https://github.com/fastapi/fastapi/pull/14612) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ฅ Remove Pydantic v1 specific test variants. PR [#14611](https://github.com/fastapi/fastapi/pull/14611) by [@tiangolo](https://github.com/tiangolo). From dbe83f3919c6de3438b572b8395e8bed9f0060d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 27 Dec 2025 10:49:46 -0800 Subject: [PATCH 030/110] =?UTF-8?q?=F0=9F=94=A7=20Add=20LLM=20prompt=20fil?= =?UTF-8?q?e=20for=20Traditional=20Chinese,=20generated=20from=20the=20exi?= =?UTF-8?q?sting=20translations=20(#14550)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: W. H. Wang --- docs/zh-hant/llm-prompt.md | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/zh-hant/llm-prompt.md diff --git a/docs/zh-hant/llm-prompt.md b/docs/zh-hant/llm-prompt.md new file mode 100644 index 0000000000..043501162c --- /dev/null +++ b/docs/zh-hant/llm-prompt.md @@ -0,0 +1,54 @@ +### Target language + +Translate to Traditional Chinese (็น้ซ”ไธญๆ–‡). + +Language code: zh-hant. + +### Grammar and tone + +1) Use clear, concise technical Traditional Chinese consistent with existing docs. +2) Address the reader naturally (commonly using โ€œไฝ /ไฝ ็š„โ€). + +### Headings + +1) Follow existing Traditional Chinese heading style (short and descriptive). +2) Do not add trailing punctuation to headings. + +### Quotes and punctuation + +1) Keep punctuation style consistent with existing Traditional Chinese docs (they often mix English terms like โ€œFastAPIโ€ with Chinese text). +2) Never change punctuation inside inline code, code blocks, URLs, or file paths. +3) For more details, please follow the [Chinese Copywriting Guidelines](https://github.com/sparanoid/chinese-copywriting-guidelines). + +### Ellipsis + +1) Keep ellipsis style consistent within each document, prefer `...` over `โ€ฆโ€ฆ`. +2) Never change ellipsis in code, URLs, or CLI examples. + +### Preferred translations / glossary + +1. Should avoid using simplified Chinese characters and terms. Always examine if the translation can be easily comprehended by the Traditional Chinese readers. +2. For some Python-specific terms like "pickle", "list", "dict" etc, we don't have to translate them. +3. Use the following preferred translations when they apply in documentation prose: + +- request (HTTP): ่ซ‹ๆฑ‚ +- response (HTTP): ๅ›žๆ‡‰ +- path operation: ่ทฏๅพ‘ๆ“ไฝœ +- path operation function: ่ทฏๅพ‘ๆ“ไฝœๅ‡ฝๅผ + +The translation can optionally include the original English text only in the first occurrence of each page (e.g. "่ทฏๅพ‘ๆ“ไฝœ (path operation)") if the translation is hard to be comprehended by most of the Chinese readers. + +### `///` admonitions + +1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). +2) Many Traditional Chinese docs currently omit titles in `///` blocks; that is OK. +3) If a generic title is present, prefer these canonical titles: + +- `/// note | ๆณจๆ„` + +Notes: + +- `details` blocks exist; keep `/// details` as-is and translate only the title after `|`. +- Example canonical titles used in existing docs: + - `/// details | ไธŠ่ฟฐๆŒ‡ไปค็š„ๅซ็พฉ` + - `/// details | ้—œๆ–ผ `requirements.txt`` From 4ce34686d98eae741f858f93f239c96baba83e01 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 27 Dec 2025 18:51:18 +0000 Subject: [PATCH 031/110] =?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 50a83da2ea..c4b88a4732 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Translations + +* ๐Ÿ”ง Add LLM prompt file for Simplified Chinese, generated from the existing translations. PR [#14549](https://github.com/fastapi/fastapi/pull/14549) by [@tiangolo](https://github.com/tiangolo). + ### Internal * ๐Ÿ‘ท Do not run translations on cron while finishing updating existing languages. PR [#14613](https://github.com/fastapi/fastapi/pull/14613) by [@tiangolo](https://github.com/tiangolo). From f362fdc2346d4c373c714263b9467a5edc1c420f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 27 Dec 2025 18:51:39 +0000 Subject: [PATCH 032/110] =?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 c4b88a4732..584137f5b3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* ๐Ÿ”ง Add LLM prompt file for Traditional Chinese, generated from the existing translations. PR [#14550](https://github.com/fastapi/fastapi/pull/14550) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add LLM prompt file for Simplified Chinese, generated from the existing translations. PR [#14549](https://github.com/fastapi/fastapi/pull/14549) by [@tiangolo](https://github.com/tiangolo). ### Internal From 3b1b4f034bebddb873d4bd8c1bb5f52aaae17240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 27 Dec 2025 11:05:53 -0800 Subject: [PATCH 033/110] =?UTF-8?q?=F0=9F=94=A8=20Update=20LLM=20translati?= =?UTF-8?q?on=20script=20to=20guide=20reviewers=20to=20change=20the=20prom?= =?UTF-8?q?pt=20(#14614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/translate.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/translate.py b/scripts/translate.py index 6ebd24f547..66da46a083 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -1036,9 +1036,13 @@ def make_pr( print("Creating PR") g = Github(github_token) gh_repo = g.get_repo(github_repository) - pr = gh_repo.create_pull( - title=message, body=message, base="master", head=branch_name + body = ( + message + + "\n\nThis PR was created automatically using LLMs." + + f"\n\nIt uses the prompt file https://github.com/fastapi/fastapi/blob/master/docs/{language}/llm-prompt.md." + + "\n\nIn most cases, it's better to make PRs updating that file so that the LLM can do a better job generating the translations than suggesting changes in this PR." ) + pr = gh_repo.create_pull(title=message, body=body, base="master", head=branch_name) print(f"Created PR: {pr.number}") print("Finished") From 47391ea8fbdfcedf6ff3f4a56666ccf928ca10c2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 27 Dec 2025 19:06:15 +0000 Subject: [PATCH 034/110] =?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 584137f5b3..1380d7ff6d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -14,6 +14,7 @@ hide: ### Internal +* ๐Ÿ”จ Update LLM translation script to guide reviewers to change the prompt. PR [#14614](https://github.com/fastapi/fastapi/pull/14614) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Do not run translations on cron while finishing updating existing languages. PR [#14613](https://github.com/fastapi/fastapi/pull/14613) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ฅ Remove test variants for Pydantic v1 in test_request_params. PR [#14612](https://github.com/fastapi/fastapi/pull/14612) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ฅ Remove Pydantic v1 specific test variants. PR [#14611](https://github.com/fastapi/fastapi/pull/14611) by [@tiangolo](https://github.com/tiangolo). From edf799577539de24b5ff52381dcc4830803fffbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 29 Dec 2025 04:38:11 -0800 Subject: [PATCH 035/110] =?UTF-8?q?=F0=9F=94=A7=20Add=20LLM=20prompt=20fil?= =?UTF-8?q?e=20for=20Turkish,=20generated=20from=20the=20existing=20transl?= =?UTF-8?q?ations=20(#14547)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tr/llm-prompt.md | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/tr/llm-prompt.md diff --git a/docs/tr/llm-prompt.md b/docs/tr/llm-prompt.md new file mode 100644 index 0000000000..38377f4f79 --- /dev/null +++ b/docs/tr/llm-prompt.md @@ -0,0 +1,52 @@ +### Target language + +Translate to Turkish (Tรผrkรงe). + +Language code: tr. + +### Grammar and tone + +1) Use instructional Turkish, consistent with existing Turkish docs. +2) Use imperative/guide language when appropriate (e.g. โ€œaรงalฤฑmโ€, โ€œgidinโ€, โ€œkopyalayalฤฑmโ€). + +### Headings + +1) Follow existing Turkish heading style (Title Case where used; no trailing period). + +### Quotes + +1) Keep quote style consistent with the existing Turkish docs (they frequently use ASCII quotes in prose). +2) Never change quotes inside inline code, code blocks, URLs, or file paths. + +### Ellipsis + +1) Keep ellipsis style consistent with existing Turkish docs. +2) Never change `...` in code, URLs, or CLI examples. + +### Preferred translations / glossary + +Do not translate technical terms like path, route, request, response, query, body, cookie, and header, keep them as is. + +- Suffixing is very important, when adding Turkish suffixes to the English words, do that based on the pronunciation of the word and with an apostrophe. + +- Suffixes also changes based on what word comes next in Turkish too, here is an example: + +"Server'a gelen request'leri intercept... " or this could have been "request'e", "request'i" etc. + +- Some words are tricky like "path'e" can't be used like "path'a" but it could have been "path'i" "path'leri" etc. + +- You can use a more instructional style, that is consistent with the document, you can add the Turkish version of the term in parenthesis if it is not something very obvious, or an advanced concept, but do not over do it, do it only the first time it is mentioned, but keep the English term as the primary word. + +### `///` admonitions + +1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). +2) If a title is present, prefer these canonical titles: + +- `/// note | Not` +- `/// note | Teknik Detaylar` +- `/// tip | ฤฐpucu` +- `/// warning | Uyarฤฑ` +- `/// info | Bilgi` +- `/// check | Ek bilgi` + +Prefer `ฤฐpucu` over `Ipucu`. From 9ed5f246ed97a5806f2ea2dd531f1c18b0bd17db Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 29 Dec 2025 12:38:35 +0000 Subject: [PATCH 036/110] =?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 1380d7ff6d..9c4fe05ea0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* ๐Ÿ”ง Add LLM prompt file for Turkish, generated from the existing translations. PR [#14547](https://github.com/fastapi/fastapi/pull/14547) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add LLM prompt file for Traditional Chinese, generated from the existing translations. PR [#14550](https://github.com/fastapi/fastapi/pull/14550) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add LLM prompt file for Simplified Chinese, generated from the existing translations. PR [#14549](https://github.com/fastapi/fastapi/pull/14549) by [@tiangolo](https://github.com/tiangolo). From d9b7b65b8139eca2a3a2b61c9a024ffd970d5c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 29 Dec 2025 10:54:20 -0800 Subject: [PATCH 037/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translation=20p?= =?UTF-8?q?rompts=20(#14619)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/de/llm-prompt.md | 477 ++++++++++++------------- docs/en/docs/_llm-test.md | 2 +- docs/ja/llm-prompt.md | 16 +- docs/ko/llm-prompt.md | 16 +- docs/missing-translation.md | 2 +- docs/pt/llm-prompt.md | 18 +- docs/tr/llm-prompt.md | 18 +- docs/uk/llm-prompt.md | 20 +- docs/zh-hant/llm-prompt.md | 34 +- docs/zh/llm-prompt.md | 22 +- scripts/general-llm-prompt.md | 528 +++++++++++++++++++++++++++ scripts/translate.py | 646 +--------------------------------- 12 files changed, 837 insertions(+), 962 deletions(-) create mode 100644 scripts/general-llm-prompt.md diff --git a/docs/de/llm-prompt.md b/docs/de/llm-prompt.md index 35ca9f0692..2d345bf6d1 100644 --- a/docs/de/llm-prompt.md +++ b/docs/de/llm-prompt.md @@ -4,213 +4,197 @@ Translate to German (Deutsch). Language code: de. - -### Definitions - -"hyphen" - The character ยซ-ยป - Unicode U+002D (HYPHEN-MINUS) - Alternative names: hyphen, dash, minus sign - -"dash" - The character ยซโ€“ยป - Unicode U+2013 (EN DASH) - German name: Halbgeviertstrich - - ### Grammar to use when talking to the reader -Use the formal grammar (use ยซSieยป instead of ยซDuยป). - +Use the formal grammar (use `Sie` instead of `Du`). ### Quotes -1) Convert neutral double quotes (ยซ"ยป) and English double typographic quotes (ยซโ€œยป and ยซโ€ยป) to German double typographic quotes (ยซโ€žยป and ยซโ€œยป). Convert neutral single quotes (ยซ'ยป) and English single typographic quotes (ยซโ€˜ยป and ยซโ€™ยป) to German single typographic quotes (ยซโ€šยป and ยซโ€˜ยป). Do NOT convert ยซ`"ยป to ยซโ€žยป, do NOT convert ยซ"`ยป to ยซโ€œยป. +1) Convert neutral double quotes (`"`) to German double typographic quotes (`โ€ž` and `โ€œ`). Convert neutral single quotes (`'`) to German single typographic quotes (`โ€š` and `โ€˜`). + +Do NOT convert quotes in code snippets and code blocks to their German typographic equivalents. Examples: - Source (English): +Source (English): - ยซยซยซ - "Hello world" - โ€œHello Universeโ€ - "He said: 'Hello'" - โ€œmy name is โ€˜Nilsโ€™โ€ - `"__main__"` - `"items"` - ยปยปยป +``` +"Hello world" +โ€œHello Universeโ€ +"He said: 'Hello'" +โ€œmy name is โ€˜Nilsโ€™โ€ +`"__main__"` +`"items"` +``` - Result (German): - - ยซยซยซ - โ€žHallo Weltโ€œ - โ€žHallo Universumโ€œ - โ€žEr sagte: โ€šHalloโ€˜โ€œ - โ€žMein Name ist โ€šNilsโ€˜โ€œ - `"__main__"` - `"items"` - ยปยปยป +Result (German): +``` +โ€žHallo Weltโ€œ +โ€žHallo Universumโ€œ +โ€žEr sagte: โ€šHalloโ€˜โ€œ +โ€žMein Name ist โ€šNilsโ€˜โ€œ +`"__main__"` +`"items"` +``` ### Ellipsis -1) Make sure there is a space between an ellipsis and a word following or preceding the ellipsis. +- Make sure there is a space between an ellipsis and a word following or preceding the ellipsis. Examples: - Source (English): +Source (English): - ยซยซยซ - ...as we intended. - ...this would work: - ...etc. - others... - More to come... - ยปยปยป +``` +...as we intended. +...this would work: +...etc. +others... +More to come... +``` - Result (German): +Result (German): - ยซยซยซ - ... wie wir es beabsichtigt hatten. - ... das wรผrde funktionieren: - ... usw. - Andere ... - Spรคter mehr ... - ยปยปยป - -2) This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there. +``` +... wie wir es beabsichtigt hatten. +... das wรผrde funktionieren: +... usw. +Andere ... +Spรคter mehr ... +``` +- This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there. ### Headings -1) Translate headings using the infinite form. +- Translate headings using the infinite form. Examples: - Source (English): +Source (English): - ยซยซยซ - ## Create a Project { #create-a-project } - ยปยปยป +``` +## Create a Project { #create-a-project } +``` - Translate with (German): +Result (German): - ยซยซยซ - ## Ein Projekt erstellen { #create-a-project } - ยปยปยป +``` +## Ein Projekt erstellen { #create-a-project } +``` - Do NOT translate with (German): +Do NOT translate with (German): - ยซยซยซ - ## Erstellen Sie ein Projekt { #create-a-project } - ยปยปยป +``` +## Erstellen Sie ein Projekt { #create-a-project } +``` - Source (English): +Source (English): - ยซยซยซ - # Install Packages { #install-packages } - ยปยปยป +``` +# Install Packages { #install-packages } +``` - Translate with (German): +Translate with (German): - ยซยซยซ - # Pakete installieren { #install-packages } - ยปยปยป +``` +# Pakete installieren { #install-packages } +``` - Do NOT translate with (German): +Do NOT translate with (German): - ยซยซยซ - # Installieren Sie Pakete { #install-packages } - ยปยปยป +``` +# Installieren Sie Pakete { #install-packages } +``` - Source (English): +Source (English): - ยซยซยซ - ### Run Your Program { #run-your-program } - ยปยปยป +``` +### Run Your Program { #run-your-program } +``` - Translate with (German): +Translate with (German): - ยซยซยซ - ### Ihr Programm ausfรผhren { #run-your-program } - ยปยปยป +``` +### Ihr Programm ausfรผhren { #run-your-program } +``` - Do NOT translate with (German): +Do NOT translate with (German): - ยซยซยซ - ### Fรผhren Sie Ihr Programm aus { #run-your-program } - ยปยปยป +``` +### Fรผhren Sie Ihr Programm aus { #run-your-program } +``` -2) Make sure that the translated part of the heading does not end with a period. +- Make sure that the translated part of the heading does not end with a period. Example: - Source (English): +Source (English): - ยซยซยซ - ## Another module with `APIRouter` { #another-module-with-apirouter } - ยปยปยป +``` +## Another module with `APIRouter` { #another-module-with-apirouter } +``` - Translate with (German): +Translate with (German): - ยซยซยซ - ## Ein weiteres Modul mit `APIRouter` { #another-module-with-apirouter } - ยปยปยป +``` +## Ein weiteres Modul mit `APIRouter` { #another-module-with-apirouter } +``` - Do NOT translate with (German) โ€“ notice the added period: +Do NOT translate with (German) โ€“ notice the added period: - ยซยซยซ - ## Ein weiteres Modul mit `APIRouter`. { #another-module-with-apirouter } - ยปยปยป +``` +## Ein weiteres Modul mit `APIRouter`. { #another-module-with-apirouter } +``` -3) Replace occurrences of literal ยซ - ยป (a space followed by a hyphen followed by a space) with ยซ โ€“ ยป (a space followed by a dash followed by a space) in the translated part of the heading. +- Replace occurrences of literal ` - ` (a space followed by a hyphen followed by a space) with ` โ€“ ` (a space followed by a dash followed by a space) in the translated part of the heading. Example: - Source (English): +Source (English): - ยซยซยซ - # FastAPI in Containers - Docker { #fastapi-in-containers-docker } - ยปยปยป +``` +# FastAPI in Containers - Docker { #fastapi-in-containers-docker } +``` - Translate with (German) โ€“ notice the dash: +Translate with (German) โ€“ notice the dash: - ยซยซยซ - # FastAPI in Containern โ€“ Docker { #fastapi-in-containers-docker } - ยปยปยป +``` +# FastAPI in Containern โ€“ Docker { #fastapi-in-containers-docker } +``` - Do NOT translate with (German) โ€“ notice the hyphen: +Do NOT translate with (German) โ€“ notice the hyphen: - ยซยซยซ - # FastAPI in Containern - Docker { #fastapi-in-containers-docker } - ยปยปยป +``` +# FastAPI in Containern - Docker { #fastapi-in-containers-docker } +``` -3.1) Do not apply rule 3 when there is no space before or no space after the hyphen. +- Do not apply rule 3 when there is no space before or no space after the hyphen. Example: - Source (English): +Source (English): - ยซยซยซ - ## Type hints and annotations { #type-hints-and-annotations } - ยปยปยป +``` +## Type hints and annotations { #type-hints-and-annotations } +``` - Translate with (German) โ€“ notice the hyphen: +Translate with (German) - notice the hyphen: - ยซยซยซ - ## Typhinweise und -annotationen { #type-hints-and-annotations } - ยปยปยป +``` +## Typhinweise und -annotationen { #type-hints-and-annotations } +``` - Do NOT translate with (German) โ€“ notice the dash: +Do NOT translate with (German) - notice the dash: - ยซยซยซ - ## Typhinweise und โ€“annotationen { #type-hints-and-annotations } - ยปยปยป +``` +## Typhinweise und โ€“annotationen { #type-hints-and-annotations } +``` -3.2) Do not apply rule 3 to the untranslated part of the heading inside curly brackets, which you shall not translate. +- Do not modify the hyphens in the content in headers inside of curly braces, which you shall not translate. - -### German instructions, when to use and when not to use hyphens in words (written in first person, which is you) +### German instructions, when to use and when not to use hyphens in words (written in first person, which is you). In der Regel versuche ich so weit wie mรถglich Worte zusammenzuschreiben, also ohne Bindestrich, es sei denn, es ist Konkretesding-Klassevondingen, etwa ยซPydantic-Modellยป (aber: ยซDatenbankmodellยป), ยซPython-Modulยป (aber: ยซStandardmodulยป). Ich setze auch einen Bindestrich, wenn er die gleichen Buchstaben verbindet, etwa ยซEnum-Memberยป, ยซCloud-Dienstยป, ยซTemplate-Engineยป. Oder wenn das Wort sonst einfach zu lang wird, etwa, ยซPerformance-Optimierungยป. Oder um etwas visuell besser zu dokumentieren, etwa ยซPfadoperation-Dekoratorยป, ยซPfadoperation-Funktionยป. @@ -219,123 +203,122 @@ In der Regel versuche ich so weit wie mรถglich Worte zusammenzuschreiben, also o Ich versuche nicht, alles einzudeutschen. Das bezieht sich besonders auf Begriffe aus dem Bereich der Programmierung. Ich wandele zwar korrekt in GroรŸschreibung um und setze Bindestriche, wo notwendig, aber ansonsten lasse ich solch ein Wort unverรคndert. Beispielsweise wird aus dem englischen Wort ยซstringยป in der deutschen รœbersetzung ยซStringยป, aber nicht ยซZeichenketteยป. Oder aus dem englischen Wort ยซrequest bodyยป wird in der deutschen รœbersetzung ยซRequestbodyยป, aber nicht ยซAnfragekรถrperยป. Oder aus dem englischen ยซresponseยป wird im Deutschen ยซResponseยป, aber nicht ยซAntwortยป. - ### List of English terms and their preferred German translations -Below is a list of English terms and their preferred German translations, separated by a colon (ยซ:ยป). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them. In the below list, a term or a translation may be followed by an explanation in brackets, which explains when to translate the term this way. If a translation is preceded by ยซNOTยป, then that means: do NOT use this translation for this term. English nouns, starting with the word ยซtheยป, have the German genus โ€“ ยซderยป, ยซdieยป, ยซdasยป โ€“ prepended to their German translation, to help you to grammatically decline them in the translation. They are given in singular case, unless they have ยซ(plural)ยป attached, which means they are given in plural case. Verbs are given in the full infinitive โ€“ starting with the word ยซtoยป. +Below is a list of English terms and their preferred German translations, separated by a colon (:). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them. In the below list, a term or a translation may be followed by an explanation in brackets, which explains when to translate the term this way. If a translation is preceded by `NOT`, then that means: do NOT use this translation for this term. English nouns, starting with the word `the`, have the German genus โ€“ `der`, `die`, `das` โ€“ prepended to their German translation, to help you to grammatically decline them in the translation. They are given in singular case, unless they have `(plural)` attached, which means they are given in plural case. Verbs are given in the full infinitive โ€“ starting with the word `to`. -* ยซ/// checkยป: ยซ/// check | Testenยป -* ยซ/// dangerยป: ยซ/// danger | Gefahrยป -* ยซ/// infoยป: ยซ/// info | Infoยป -* ยซ/// note | Technical Detailsยป: ยซ/// note | Technische Detailsยป -* ยซ/// noteยป: ยซ/// note | Hinweisยป -* ยซ/// tipยป: ยซ/// tip | Tippยป -* ยซ/// warningยป: ยซ/// warning | Achtungยป -* ยซyouยป: ยซSieยป -* ยซyourยป: ยซIhrยป -* ยซe.gยป: ยซz. B.ยป -* ยซetc.ยป: ยซusw.ยป -* ยซrefยป: ยซRef.ยป -* ยซthe Tutorial - User guideยป: ยซdas Tutorial โ€“ Benutzerhandbuchยป -* ยซthe Advanced User Guideยป: ยซdas Handbuch fรผr fortgeschrittene Benutzerยป -* ยซthe SQLModel docsยป: ยซdie SQLModel-Dokumentationยป -* ยซthe docsยป: ยซdie Dokumentationยป (use singular case) -* ยซthe env varยป: ยซdie Umgebungsvariableยป -* ยซthe `PATH` environment variableยป: ยซdie `PATH`-Umgebungsvariableยป -* ยซthe `PATH`ยป: ยซder `PATH`ยป -* ยซthe `requirements.txt`ยป: ยซdie `requirements.txt`ยป -* ยซthe API Routerยป: ยซder API-Routerยป -* ยซthe Authorization-Headerยป: ยซder Autorisierungsheaderยป -* ยซthe `Authorization`-Headerยป: ยซder `Authorization`-Headerยป -* ยซthe background taskยป: ยซder Hintergrundtaskยป -* ยซthe buttonยป: ยซder Buttonยป -* ยซthe cloud providerยป: ยซder Cloudanbieterยป -* ยซthe CLIยป: ยซDas CLIยป -* ยซthe coverageยป: ยซDie Testabdeckungยป -* ยซthe command line interfaceยป: ยซDas Kommandozeileninterfaceยป -* ยซthe default valueยป: ยซder Defaultwertยป -* ยซthe default valueยป: NOT ยซder Standardwertยป -* ยซthe default declarationยป: ยซdie Default-Deklarationยป -* ยซthe deploymentยป: ยซdas Deploymentยป -* ยซthe dictยป: ยซdas Dictยป -* ยซthe dictionaryยป: ยซdas Dictionaryยป -* ยซthe enumerationยป: ยซdie Enumerationยป -* ยซthe enumยป: ยซdas Enumยป -* ยซthe engineยป: ยซdie Engineยป -* ยซthe error responseยป: ยซdie Error-Responseยป -* ยซthe eventยป: ยซdas Eventยป -* ยซthe exceptionยป: ยซdie Exceptionยป -* ยซthe exception handlerยป: ยซder Exceptionhandlerยป -* ยซthe form modelยป: ยซdas Formularmodellยป -* ยซthe form bodyยป: ยซder Formularbodyยป -* ยซthe headerยป: ยซder Headerยป -* ยซthe headersยป (plural): ยซdie Headerยป -* ยซin headersยป (plural): ยซin Headernยป -* ยซthe forwarded headerยป: ยซder Forwarded-Headerยป -* ยซthe lifespan eventยป: ยซdas Lifespan-Eventยป -* ยซthe lockยป: ยซder Lockยป -* ยซthe lockingยป: ยซdas Lockingยป -* ยซthe mobile applicationยป: ยซdie Mobile-Anwendungยป -* ยซthe model objectยป: ยซdas Modellobjektยป -* ยซthe mountingยป: ยซdas Mountenยป -* ยซmountedยป: ยซgemountetยป -* ยซthe originยป: ยซdas Originยป -* ยซthe overrideยป: ยซDie รœberschreibungยป -* ยซthe parameterยป: ยซder Parameterยป -* ยซthe parametersยป (plural): ยซdie Parameterยป -* ยซthe function parameterยป: ยซder Funktionsparameterยป -* ยซthe default parameterยป: ยซder Defaultparameterยป -* ยซthe body parameterยป: ยซder Body-Parameterยป -* ยซthe request body parameterยป: ยซder Requestbody-Parameterยป -* ยซthe path parameterยป: ยซder Pfad-Parameterยป -* ยซthe query parameterยป: ยซder Query-Parameterยป -* ยซthe cookie parameterยป: ยซder Cookie-Parameterยป -* ยซthe header parameterยป: ยซder Header-Parameterยป -* ยซthe form parameterยป: ยซder Formular-Parameterยป -* ยซthe payloadยป: ยซdie Payloadยป -* ยซthe performanceยป: NOT ยซdie Performanceยป -* ยซthe queryยป: ยซdie Queryยป -* ยซthe recapยป: ยซdie Zusammenfassungยป -* ยซthe requestยป (what the client sends to the server): ยซder Requestยป -* ยซthe request bodyยป: ยซder Requestbodyยป -* ยซthe request bodiesยป (plural): ยซdie Requestbodysยป -* ยซthe responseยป (what the server sends back to the client): ยซdie Responseยป -* ยซthe return typeยป: ยซder Rรผckgabetypยป -* ยซthe return valueยป: ยซder Rรผckgabewertยป -* ยซthe startupยป (the event of the app): ยซder Startupยป -* ยซthe shutdownยป (the event of the app): ยซder Shutdownยป -* ยซthe startup eventยป: ยซdas Startup-Eventยป -* ยซthe shutdown eventยป: ยซdas Shutdown-Eventยป -* ยซthe startupยป (of the server): ยซdas Hochfahrenยป -* ยซthe startupยป (the company): ยซdas Startupยป -* ยซthe SDKยป: ยซdas SDKยป -* ยซthe tagยป: ยซder Tagยป -* ยซthe type annotationยป: ยซdie Typannotationยป -* ยซthe type hintยป: ยซder Typhinweisยป -* ยซthe wildcardยป: ยซdie Wildcardยป -* ยซthe worker classยป: ยซdie Workerklasseยป -* ยซthe worker classยป: NOT ยซdie Arbeiterklasseยป -* ยซthe worker processยป: ยซder Workerprozessยป -* ยซthe worker processยป: NOT ยซder Arbeiterprozessยป -* ยซto commitยป: ยซcommittenยป -* ยซto deployยป (in the cloud): ยซdeployenยป -* ยซto modifyยป: ยซรคndernยป -* ยซto serveยป (an application): ยซbereitstellenยป -* ยซto serveยป (a response): ยซausliefernยป -* ยซto serveยป: NOT ยซbedienenยป -* ยซto upgradeยป: ยซaktualisierenยป -* ยซto wrapยป: ยซwrappenยป -* ยซto wrapยป: NOT ยซhรผllenยป -* ยซ`foo` as a `type`ยป: ยซ`foo` vom Typ `type`ยป -* ยซ`foo` as a `type`ยป: ยซ`foo`, ein `type`ยป -* ยซFastAPI's Xยป: ยซFastAPIs Xยป -* ยซStarlette's Yยป: ยซStarlettes Yยป -* ยซX is case-sensitiveยป: ยซGroรŸ-/Kleinยญschreiยญbung ist relevant in Xยป -* ยซX is case-insensitiveยป: ยซGroรŸ-/Kleinยญschreiยญbung ist nicht relevant in Xยป -* ยซstandard Pythonยป: ยซStandard-Pythonยป -* ยซdeprecatedยป: ยซdeprecatetยป +* /// check: /// check | Testen +* /// danger: /// danger | Gefahr +* /// info: /// info | Info +* /// note | Technical Details: /// note | Technische Details +* /// note: /// note | Hinweis +* /// tip: /// tip | Tipp +* /// warning: /// warning | Achtung +* you: Sie +* your: Ihr +* e.g: z. B. +* etc.: usw. +* ref: Ref. +* the Tutorial - User guide: das Tutorial โ€“ Benutzerhandbuch +* the Advanced User Guide: das Handbuch fรผr fortgeschrittene Benutzer +* the SQLModel docs: die SQLModel-Dokumentation +* the docs: die Dokumentation (use singular case) +* the env var: die Umgebungsvariable +* the `PATH` environment variable: die `PATH`-Umgebungsvariable +* the `PATH`: der `PATH` +* the `requirements.txt`: die `requirements.txt` +* the API Router: der API-Router +* the Authorization-Header: der Autorisierungsheader +* the `Authorization`-Header: der `Authorization`-Header +* the background task: der Hintergrundtask +* the button: der Button +* the cloud provider: der Cloudanbieter +* the CLI: Das CLI +* the coverage: Die Testabdeckung +* the command line interface: Das Kommandozeileninterface +* the default value: der Defaultwert +* the default value: NOT der Standardwert +* the default declaration: die Default-Deklaration +* the deployment: das Deployment +* the dict: das Dict +* the dictionary: das Dictionary +* the enumeration: die Enumeration +* the enum: das Enum +* the engine: die Engine +* the error response: die Error-Response +* the event: das Event +* the exception: die Exception +* the exception handler: der Exceptionhandler +* the form model: das Formularmodell +* the form body: der Formularbody +* the header: der Header +* the headers (plural): die Header +* in headers (plural): in Headern +* the forwarded header: der Forwarded-Header +* the lifespan event: das Lifespan-Event +* the lock: der Lock +* the locking: das Locking +* the mobile application: die Mobile-Anwendung +* the model object: das Modellobjekt +* the mounting: das Mounten +* mounted: gemountet +* the origin: das Origin +* the override: Die รœberschreibung +* the parameter: der Parameter +* the parameters (plural): die Parameter +* the function parameter: der Funktionsparameter +* the default parameter: der Defaultparameter +* the body parameter: der Body-Parameter +* the request body parameter: der Requestbody-Parameter +* the path parameter: der Pfad-Parameter +* the query parameter: der Query-Parameter +* the cookie parameter: der Cookie-Parameter +* the header parameter: der Header-Parameter +* the form parameter: der Formular-Parameter +* the payload: die Payload +* the performance: NOT die Performance +* the query: die Query +* the recap: die Zusammenfassung +* the request (what the client sends to the server): der Request +* the request body: der Requestbody +* the request bodies (plural): die Requestbodys +* the response (what the server sends back to the client): die Response +* the return type: der Rรผckgabetyp +* the return value: der Rรผckgabewert +* the startup (the event of the app): der Startup +* the shutdown (the event of the app): der Shutdown +* the startup event: das Startup-Event +* the shutdown event: das Shutdown-Event +* the startup (of the server): das Hochfahren +* the startup (the company): das Startup +* the SDK: das SDK +* the tag: der Tag +* the type annotation: die Typannotation +* the type hint: der Typhinweis +* the wildcard: die Wildcard +* the worker class: die Workerklasse +* the worker class: NOT die Arbeiterklasse +* the worker process: der Workerprozess +* the worker process: NOT der Arbeiterprozess +* to commit: committen +* to deploy (in the cloud): deployen +* to modify: รคndern +* to serve (an application): bereitstellen +* to serve (a response): ausliefern +* to serve: NOT bedienen +* to upgrade: aktualisieren +* to wrap: wrappen +* to wrap: NOT hรผllen +* `foo` as a `type`: `foo` vom Typ `type` +* `foo` as a `type`: `foo`, ein `type` +* FastAPI's X: FastAPIs X +* Starlette's Y: Starlettes Y +* X is case-sensitive: GroรŸ-/Kleinยญschreiยญbung ist relevant in X +* X is case-insensitive: GroรŸ-/Kleinยญschreiยญbung ist nicht relevant in X +* standard Python: Standard-Python +* deprecated: deprecatet ### Other rules -Preserve indentation. Keep emoticons. Encode in utf-8. Use Linux line breaks (LF). +Preserve indentation. Keep emojis. Encode in utf-8. Use Linux line breaks (LF). diff --git a/docs/en/docs/_llm-test.md b/docs/en/docs/_llm-test.md index 9f216f9d79..d218f7c76f 100644 --- a/docs/en/docs/_llm-test.md +++ b/docs/en/docs/_llm-test.md @@ -6,7 +6,7 @@ Tests added here will be seen by all designers of language specific prompts. Use as follows: -* Have a language specific prompt โ€“ `docs/{language code}/llm-prompt.md`. +* Have a language specific prompt - `docs/{language code}/llm-prompt.md`. * Do a fresh translation of this document into your desired target language (see e.g. the `translate-page` command of the `translate.py`). This will create the translation under `docs/{language code}/docs/_llm-test.md`. * Check if things are okay in the translation. * If necessary, improve your language specific prompt, the general prompt, or the English document. diff --git a/docs/ja/llm-prompt.md b/docs/ja/llm-prompt.md index c47cc36df7..18909cd595 100644 --- a/docs/ja/llm-prompt.md +++ b/docs/ja/llm-prompt.md @@ -6,23 +6,23 @@ Language code: ja. ### Grammar and tone -1) Use polite, instructional Japanese (ใงใ™/ใพใ™่ชฟ). -2) Keep the tone concise and technical (match existing Japanese FastAPI docs). +- Use polite, instructional Japanese (ใงใ™/ใพใ™่ชฟ). +- Keep the tone concise and technical (match existing Japanese FastAPI docs). ### Headings -1) Follow the existing Japanese style: short, descriptive headings (often noun phrases), e.g. ใ€Œใƒใ‚งใƒƒใ‚ฏใ€. -2) Do not add a trailing period at the end of headings. +- Follow the existing Japanese style: short, descriptive headings (often noun phrases), e.g. ใ€Œใƒใ‚งใƒƒใ‚ฏใ€. +- Do not add a trailing period at the end of headings. ### Quotes -1) Prefer Japanese corner brackets ใ€Œใ€ in normal prose when quoting a term. -2) Do not change quotes inside inline code, code blocks, URLs, or file paths. +- Prefer Japanese corner brackets ใ€Œใ€ in normal prose when quoting a term. +- Do not change quotes inside inline code, code blocks, URLs, or file paths. ### Ellipsis -1) Keep ellipsis style consistent with existing Japanese docs (commonly `...`). -2) Never change `...` in code, URLs, or CLI examples. +- Keep ellipsis style consistent with existing Japanese docs (commonly `...`). +- Never change `...` in code, URLs, or CLI examples. ### Preferred translations / glossary diff --git a/docs/ko/llm-prompt.md b/docs/ko/llm-prompt.md index 008511a5b7..df807c9496 100644 --- a/docs/ko/llm-prompt.md +++ b/docs/ko/llm-prompt.md @@ -6,23 +6,23 @@ Language code: ko. ### Grammar and tone -1) Use polite, instructional Korean (e.g. ํ•ฉ๋‹ˆ๋‹ค/ํ•˜์„ธ์š” style). -2) Keep the tone consistent with the existing Korean FastAPI docs. +- Use polite, instructional Korean (e.g. ํ•ฉ๋‹ˆ๋‹ค/ํ•˜์„ธ์š” style). +- Keep the tone consistent with the existing Korean FastAPI docs. ### Headings -1) Follow existing Korean heading style (short, action-oriented headings like โ€œํ™•์ธํ•˜๊ธฐโ€). -2) Do not add trailing punctuation to headings. +- Follow existing Korean heading style (short, action-oriented headings like โ€œํ™•์ธํ•˜๊ธฐโ€). +- Do not add trailing punctuation to headings. ### Quotes -1) Keep quote style consistent with the existing Korean docs. -2) Never change quotes inside inline code, code blocks, URLs, or file paths. +- Keep quote style consistent with the existing Korean docs. +- Never change quotes inside inline code, code blocks, URLs, or file paths. ### Ellipsis -1) Keep ellipsis style consistent with existing Korean docs (often `...`). -2) Never change `...` in code, URLs, or CLI examples. +- Keep ellipsis style consistent with existing Korean docs (often `...`). +- Never change `...` in code, URLs, or CLI examples. ### Preferred translations / glossary diff --git a/docs/missing-translation.md b/docs/missing-translation.md index bfff847669..71c0925c5c 100644 --- a/docs/missing-translation.md +++ b/docs/missing-translation.md @@ -4,6 +4,6 @@ This page hasnโ€™t been translated into your language yet. ๐ŸŒ Weโ€™re currently switching to an automated translation system ๐Ÿค–, which will help keep all translations complete and up to date. -Learn more: [Contributing โ€“ Translations](https://fastapi.tiangolo.com/contributing/#translations){.internal-link target=_blank} +Learn more: [Contributing - Translations](https://fastapi.tiangolo.com/contributing/#translations){.internal-link target=_blank} /// diff --git a/docs/pt/llm-prompt.md b/docs/pt/llm-prompt.md index 2374070ced..3f5208e910 100644 --- a/docs/pt/llm-prompt.md +++ b/docs/pt/llm-prompt.md @@ -14,15 +14,15 @@ When translating documentation into Portuguese, use neutral and widely understan For the next terms, use the following translations: -* ยซ/// checkยป: ยซ/// check | Verifiqueยป -* ยซ/// dangerยป: ยซ/// danger | Cuidadoยป -* ยซ/// infoยป: ยซ/// info | Informaรงรฃoยป -* ยซ/// note | Technical Detailsยป: ยซ/// note | Detalhes Tรฉcnicosยป -* ยซ/// info | Very Technical Detailsยป: ยซ/// note | Detalhes Tรฉcnicos Avanรงadosยป -* ยซ/// noteยป: ยซ/// note | Notaยป -* ยซ/// tipยป: ยซ/// tip | Dicaยป -* ยซ/// warningยป: ยซ/// warning | Atenรงรฃoยป -* ยซ(you should)ยป: ยซ(vocรช deveria)ยป +* /// check: /// check | Verifique +* /// danger: /// danger | Cuidado +* /// info: /// info | Informaรงรฃo +* /// note | Technical Details: /// note | Detalhes Tรฉcnicos +* /// info | Very Technical Details: /// note | Detalhes Tรฉcnicos Avanรงados +* /// note: /// note | Nota +* /// tip: /// tip | Dica +* /// warning: /// warning | Atenรงรฃo +* (you should): (vocรช deveria) * async context manager: gerenciador de contexto assรญncrono * autocomplete: autocompletar * autocompletion: preenchimento automรกtico diff --git a/docs/tr/llm-prompt.md b/docs/tr/llm-prompt.md index 38377f4f79..297b0a0e6c 100644 --- a/docs/tr/llm-prompt.md +++ b/docs/tr/llm-prompt.md @@ -6,22 +6,22 @@ Language code: tr. ### Grammar and tone -1) Use instructional Turkish, consistent with existing Turkish docs. -2) Use imperative/guide language when appropriate (e.g. โ€œaรงalฤฑmโ€, โ€œgidinโ€, โ€œkopyalayalฤฑmโ€). +- Use instructional Turkish, consistent with existing Turkish docs. +- Use imperative/guide language when appropriate (e.g. โ€œaรงalฤฑmโ€, โ€œgidinโ€, โ€œkopyalayalฤฑmโ€). ### Headings -1) Follow existing Turkish heading style (Title Case where used; no trailing period). +- Follow existing Turkish heading style (Title Case where used; no trailing period). ### Quotes -1) Keep quote style consistent with the existing Turkish docs (they frequently use ASCII quotes in prose). -2) Never change quotes inside inline code, code blocks, URLs, or file paths. +- Alฤฑntฤฑ stili mevcut Tรผrkรงe dokรผmanlarla tutarlฤฑ tutun (genellikle metin iรงinde ASCII tฤฑrnak iลŸaretleri kullanฤฑlฤฑr). +- Satฤฑr iรงi kod, kod bloklarฤฑ, URL'ler veya dosya yollarฤฑ iรงindeki tฤฑrnak iลŸaretlerini asla deฤŸiลŸtirmeyin. ### Ellipsis -1) Keep ellipsis style consistent with existing Turkish docs. -2) Never change `...` in code, URLs, or CLI examples. +- รœรง nokta (...) stili mevcut Tรผrkรงe dokรผmanlarla tutarlฤฑ tutun. +- Kod, URL veya CLI รถrneklerindeki `...` ifadesini asla deฤŸiลŸtirmeyin. ### Preferred translations / glossary @@ -39,8 +39,8 @@ Do not translate technical terms like path, route, request, response, query, bod ### `///` admonitions -1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). -2) If a title is present, prefer these canonical titles: +- Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). +- If a title is present, prefer these canonical titles: - `/// note | Not` - `/// note | Teknik Detaylar` diff --git a/docs/uk/llm-prompt.md b/docs/uk/llm-prompt.md index d55d36ab5c..f1c5377a48 100644 --- a/docs/uk/llm-prompt.md +++ b/docs/uk/llm-prompt.md @@ -6,23 +6,23 @@ Language code: uk. ### Grammar and tone -1) Use polite/formal address consistent with existing Ukrainian docs (use โ€œะฒะธ/ะฒะฐัˆโ€). -2) Keep the tone concise and technical. +- Use polite/formal address consistent with existing Ukrainian docs (use โ€œะฒะธ/ะฒะฐัˆโ€). +- Keep the tone concise and technical. ### Headings -1) Follow existing Ukrainian heading style; keep headings short and instructional. -2) Do not add trailing punctuation to headings. +- Follow existing Ukrainian heading style; keep headings short and instructional. +- Do not add trailing punctuation to headings. ### Quotes -1) Prefer Ukrainian guillemets ยซโ€ฆยป for quoted terms in prose, matching existing Ukrainian docs. -2) Never change quotes inside inline code, code blocks, URLs, or file paths. +- Prefer Ukrainian guillemets ยซโ€ฆยป for quoted terms in prose, matching existing Ukrainian docs. +- Never change quotes inside inline code, code blocks, URLs, or file paths. ### Ellipsis -1) Keep ellipsis style consistent with existing Ukrainian docs. -2) Never change `...` in code, URLs, or CLI examples. +- Keep ellipsis style consistent with existing Ukrainian docs. +- Never change `...` in code, URLs, or CLI examples. ### Preferred translations / glossary @@ -35,8 +35,8 @@ Use the following preferred translations when they apply in documentation prose: ### `///` admonitions -1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). -2) If a title is present, prefer these canonical titles (choose one canonical form where variants exist): +- Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). +- If a title is present, prefer these canonical titles (choose one canonical form where variants exist): - `/// note | ะŸั€ะธะผั–ั‚ะบะฐ` - `/// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั–` diff --git a/docs/zh-hant/llm-prompt.md b/docs/zh-hant/llm-prompt.md index 043501162c..d44709015c 100644 --- a/docs/zh-hant/llm-prompt.md +++ b/docs/zh-hant/llm-prompt.md @@ -6,30 +6,30 @@ Language code: zh-hant. ### Grammar and tone -1) Use clear, concise technical Traditional Chinese consistent with existing docs. -2) Address the reader naturally (commonly using โ€œไฝ /ไฝ ็š„โ€). +- Use clear, concise technical Traditional Chinese consistent with existing docs. +- Address the reader naturally (commonly using โ€œไฝ /ไฝ ็š„โ€). ### Headings -1) Follow existing Traditional Chinese heading style (short and descriptive). -2) Do not add trailing punctuation to headings. +- Follow existing Traditional Chinese heading style (short and descriptive). +- Do not add trailing punctuation to headings. ### Quotes and punctuation -1) Keep punctuation style consistent with existing Traditional Chinese docs (they often mix English terms like โ€œFastAPIโ€ with Chinese text). -2) Never change punctuation inside inline code, code blocks, URLs, or file paths. -3) For more details, please follow the [Chinese Copywriting Guidelines](https://github.com/sparanoid/chinese-copywriting-guidelines). +- Keep punctuation style consistent with existing Traditional Chinese docs (they often mix English terms like โ€œFastAPIโ€ with Chinese text). +- Never change punctuation inside inline code, code blocks, URLs, or file paths. +- For more details, please follow the [Chinese Copywriting Guidelines](https://github.com/sparanoid/chinese-copywriting-guidelines). ### Ellipsis -1) Keep ellipsis style consistent within each document, prefer `...` over `โ€ฆโ€ฆ`. -2) Never change ellipsis in code, URLs, or CLI examples. +- Keep ellipsis style consistent within each document, prefer `...` over `โ€ฆโ€ฆ`. +- Never change ellipsis in code, URLs, or CLI examples. ### Preferred translations / glossary -1. Should avoid using simplified Chinese characters and terms. Always examine if the translation can be easily comprehended by the Traditional Chinese readers. -2. For some Python-specific terms like "pickle", "list", "dict" etc, we don't have to translate them. -3. Use the following preferred translations when they apply in documentation prose: +- Should avoid using simplified Chinese characters and terms. Always examine if the translation can be easily comprehended by the Traditional Chinese readers. +- For some Python-specific terms like "pickle", "list", "dict" etc, we don't have to translate them. +- Use the following preferred translations when they apply in documentation prose: - request (HTTP): ่ซ‹ๆฑ‚ - response (HTTP): ๅ›žๆ‡‰ @@ -50,5 +50,11 @@ Notes: - `details` blocks exist; keep `/// details` as-is and translate only the title after `|`. - Example canonical titles used in existing docs: - - `/// details | ไธŠ่ฟฐๆŒ‡ไปค็š„ๅซ็พฉ` - - `/// details | ้—œๆ–ผ `requirements.txt`` + +``` +/// details | ไธŠ่ฟฐๆŒ‡ไปค็š„ๅซ็พฉ +``` + +``` +/// details | ้—œๆ–ผ `requirements.txt` +``` diff --git a/docs/zh/llm-prompt.md b/docs/zh/llm-prompt.md index 1dfbe59162..7ce6f96a47 100644 --- a/docs/zh/llm-prompt.md +++ b/docs/zh/llm-prompt.md @@ -6,24 +6,24 @@ Language code: zh. ### Grammar and tone -1) Use clear, concise technical Chinese consistent with existing docs. -2) Address the reader naturally (commonly using โ€œไฝ /ไฝ ็š„โ€). +- Use clear, concise technical Chinese consistent with existing docs. +- Address the reader naturally (commonly using โ€œไฝ /ไฝ ็š„โ€). ### Headings -1) Follow existing Simplified Chinese heading style (short and descriptive). -2) Do not add trailing punctuation to headings. -3) If a heading contains only the name of a FastAPI feature, do not translate it. +- Follow existing Simplified Chinese heading style (short and descriptive). +- Do not add trailing punctuation to headings. +- If a heading contains only the name of a FastAPI feature, do not translate it. ### Quotes and punctuation -1) Keep punctuation style consistent with existing Simplified Chinese docs (they often mix English terms like โ€œFastAPIโ€ with Chinese text). -2) Never change punctuation inside inline code, code blocks, URLs, or file paths. +- Keep punctuation style consistent with existing Simplified Chinese docs (they often mix English terms like โ€œFastAPIโ€ with Chinese text). +- Never change punctuation inside inline code, code blocks, URLs, or file paths. ### Ellipsis -1) Keep ellipsis style consistent within each document, prefer `...` over `โ€ฆโ€ฆ`. -2) Never change ellipsis in code, URLs, or CLI examples. +- Keep ellipsis style consistent within each document, prefer `...` over `โ€ฆโ€ฆ`. +- Never change ellipsis in code, URLs, or CLI examples. ### Preferred translations / glossary @@ -36,8 +36,8 @@ Use the following preferred translations when they apply in documentation prose: ### `///` admonitions -1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). -2) If a title is present, prefer these canonical titles: +- Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). +- If a title is present, prefer these canonical titles: - `/// tip | ๆ็คบ` - `/// note | ๆณจๆ„` diff --git a/scripts/general-llm-prompt.md b/scripts/general-llm-prompt.md new file mode 100644 index 0000000000..d45ab8eb07 --- /dev/null +++ b/scripts/general-llm-prompt.md @@ -0,0 +1,528 @@ +### Your task + +Translate an English original content to a target language. + +The original content is written in Markdown, write the translation in Markdown as well. + +The original content will be surrounded by triple percentage signs (%%%). Do not include the triple percentage signs in the translation. + +### Technical terms in English + +For technical terms in English that don't have a common translation term, use the original term in English. + +### Content of code snippets + +Do not translate the content of code snippets, keep the original in English. For example, `list`, `dict`, keep them as is. + +### Content of code blocks + +Do not translate the content of code blocks, except for comments in the language which the code block uses. + +Examples: + +Source (English) - The code block is a bash code example with one comment: + +```bash +# Print greeting +echo "Hello, World!" +``` + +Result (German): + +```bash +# GruรŸ ausgeben +echo "Hello, World!" +``` + +Source (English) - The code block is a console example containing HTML tags. No comments, so nothing to change here: + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +Result (German): + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +Source (English) - The code block is a console example containing 5 comments: + + +```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 +``` + +Result (German): + +```console +// Gehe zum Home-Verzeichnis +$ cd +// Erstelle ein Verzeichnis fรผr alle Ihre Code-Projekte +$ mkdir code +// Gehe in dieses Code-Verzeichnis +$ cd code +// Erstelle ein Verzeichnis fรผr dieses Projekt +$ mkdir awesome-project +// Gehe in dieses Projektverzeichnis +$ cd awesome-project +``` + +If there is an existing translation and its Mermaid diagram is in sync with the Mermaid diagram in the English source, except a few translated words, then use the Mermaid diagram of the existing translation. The human editor of the translation translated these words in the Mermaid diagram. Keep these translations, do not revert them back to the English source. + +Example: + +Source (English): + +```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 +``` + +Existing translation (German) - has three translations: + +```mermaid +flowchart LR + subgraph global[globale Umgebung] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone-Projekt] + stone(philosophers-stone) -->|benรถtigt| harry-1 + end +``` + +Result (German) - you change nothing: + +```mermaid +flowchart LR + subgraph global[globale Umgebung] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone-Projekt] + stone(philosophers-stone) -->|benรถtigt| harry-1 + end +``` + +### Special blocks + +There are special blocks of notes, tips and others that look like: + +/// note +Here goes a note +/// + +To translate it, keep the same line and add the translation after a vertical bar. + +For example, if you were translating to Spanish, you would write: + +/// note | Nota + +Some examples in Spanish: + +Source (English): + +/// tip + +Result (Spanish): + +/// tip | Consejo + +Source (English): + +/// details | Preview + +Result (Spanish): + +/// details | Vista previa + +### Tab blocks + +There are special blocks surrounded by four slashes (////). They mark text, which will be rendered as part of a tab in the final document. The scheme is: + +//// tab | {tab title} +{tab content, may span many lines} +//// + +Keep everything before the vertical bar (|) as is, including the vertical bar. Translate the tab title. Translate the tab content, applying the rules you know. Keep the four block closing slashes as is. + +Examples: + +Source (English): + +//// tab | Python 3.8+ non-Annotated +Hello +//// + +Result (German): + +//// tab | Python 3.8+ nicht annotiert +Hallo +//// + +Source (English) - Here there is nothing to translate in the tab title: + +//// tab | Linux, macOS, Windows Bash +Hello again +//// + +Result (German): + +//// tab | Linux, macOS, Windows Bash +Hallo wieder +//// + +### Headings + +Every Markdown heading in the English text (all levels) ends with a part inside curly brackets. This part denotes the hash of this heading, which is used in links to this heading. In translations, translate the heading, but do not translate this hash part, so that links do not break. + +Examples of how to translate a heading: + +Source (English): + +``` +## Alternative API docs { #alternative-api-docs } +``` + +Result (Spanish): + +``` +## Documentaciรณn de la API alternativa { #alternative-api-docs } +``` + +Source (English): + +``` +### Example { #example } +``` + +Result (German): + +``` +### Beispiel { #example } +``` + +### Links + +Use the following rules for links (apply both to Markdown-style links ([text](url)) and to HTML-style text tags): + +- For relative URLs, only translate the link text. Do not translate the URL or its parts. + +Example: + +Source (English): + +``` +[One of the fastest Python frameworks available](#performance) +``` + +Result (German): + +``` +[Eines der schnellsten verfรผgbaren Python-Frameworks](#performance) +``` + +- For absolute URLs which DO NOT start EXACTLY with https://fastapi.tiangolo.com, only translate the link text and leave the URL unchanged. + +Example: + +Source (English): + +``` +SQLModel docs +``` + +Result (German): + +``` +SQLModel-Dokumentation +``` + +- For absolute URLs which DO start EXACTLY with https://fastapi.tiangolo.com, only translate the link text and change the URL by adding the language code (https://fastapi.tiangolo.com/{language_code}[rest part of the url]). + +Example: + +Source (English): + +``` +Documentation +``` + +Result (Spanish): + +``` +Documentaciรณn +``` + +- Do not add language codes for URLs that point to static assets (e.g., images, CSS, JavaScript). + +Example: + +Source (English): + +``` +Something +``` + +Result (Spanish): + +``` +Algo +``` + +- For internal links, only translate link text. + +Example: + +Source (English): + +``` +[Create Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} +``` + +Result (German): + +``` +[Pull Requests erzeugen](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} +``` + +- Do not translate anchor fragments in links (the part after `#`), as they must remain the same to work correctly. + +- If an existing translation has a link with an anchor fragment different to the anchor fragment in the English source, then this is an error. Fix this by using the anchor fragment of the English source. + +Example: + +Source (English): + +``` +[Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank} +``` + +Existing wrong translation (German) - notice the wrongly translated anchor fragment: + +``` +[Body - Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#einzelne-werte-im-body){.internal-link target=_blank}. +``` + +Result (German) - you fix the anchor fragment: + +``` +[Body - Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. +``` + +- Do not add anchor fragments at will, even if this makes sense. If the English source has no anchor, don't add one. + +Example: + +Source (English): + +``` +Create a [virtual environment](../virtual-environments.md){.internal-link target=_blank} +``` + +Wrong translation in German - Anchor added to the URL. + +``` +Erstelle eine [virtuelle Umgebung](../virtual-environments.md#create-a-virtual-environment){.internal-link target=_blank} +``` + +Good translation (German) - URL stays like in the English source. + +``` +Erstelle eine [Virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} +``` + +### HTML abbr elements + +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. + +Conversion scheme: + +Source (English): + +``` +{abbreviation} +``` + +Result: + +``` +{abbreviation} +``` + +Examples: + +Source (English): + +``` +IoT +CPU +TL;DR: +``` + +Result (German): + +``` +IoT +CPU +TL;DR: +``` + +- If the language to which you translate mostly uses the letters of the ASCII char set (for example Spanish, French, German, but not Russian, Chinese) and if the translation of the full phrase is identical to, or starts with the same letters as the original full phrase, then only give the translation of the full phrase. + +Conversion scheme: + +Source (English): + +``` +{abbreviation} +``` + +Result: + +``` +{abbreviation} +``` + +Examples: + +Source (English): + +``` +JWT +Enum +ASGI +``` + +Result (German): + +``` +JWT +Enum +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. + +Conversion scheme: + +Source (English): + +``` +{abbreviation} +``` + +Result: + +``` +{abbreviation} +``` + +Examples: + +Source (English): + +``` +I/O +CDN +IDE +``` + +Result (German): + +``` +I/O +CDN +IDE +``` + +- You can leave the original full phrase away, if the translated full phrase is identical or starts with the same letters as the original full phrase. + +Conversion scheme: + +Source (English): + +``` +{abbreviation} +``` + +Result: + +``` +{abbreviation} +``` + +Example: + +Source (English): + +``` +ORM +``` + +Result (German): + +``` +ORM +``` + +- 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. diff --git a/scripts/translate.py b/scripts/translate.py index 66da46a083..ffecfde07b 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -25,650 +25,8 @@ non_translated_sections = ( "contributing.md", ) - -general_prompt = """ -### About literal text in this prompt - -1) In the following instructions (after I say: `The above rules are in effect now`) the two characters `ยซ` and `ยป` will be used to surround LITERAL TEXT, which is text or characters you shall interpret literally. The `ยซ` and the `ยป` are not part of the literal text, they are the meta characters denoting it. - -2) Furthermore, text surrounded by `ยซยซยซ` and `ยปยปยป` is a BLOCK OF LITERAL TEXT which spans multiple lines. To get its content, dedent all lines of the block until the `ยซยซยซ` and `ยปยปยป` are at column zero, then remove the newline (`\n`) after the `ยซยซยซ` and the newline before the `ยปยปยป`. The `ยซยซยซ` and the `ยปยปยป` are not part of the literal text block, they are the meta characters denoting it. - -3) If you see backticks or any other quotes inside literal text โ€“ inside `ยซ` and `ยป` โ€“ or inside blocks of literal text โ€“ inside `ยซยซยซ` and `ยปยปยป` โ€“ then interpret them as literal characters, do NOT interpret them as meta characters. - -The above rules are in effect now. - - -### Definitions of terms used in this prompt - -"backtick" - - The character ยซ`ยป - Unicode U+0060 (GRAVE ACCENT) - -"single backtick" - - A single backtick โ€“ ยซ`ยป - -"triple backticks" - - Three backticks in a row โ€“ ยซ```ยป - -"neutral double quote" - - The character ยซ"ยป - Unicode U+0022 (QUOTATION MARK) - -"neutral single quote" - - The character ยซ'ยป - Unicode U+0027 (APOSTROPHE) - -"English double typographic quotes" - - The characters ยซโ€œยป and ยซโ€ยป - Unicode U+201C (LEFT DOUBLE QUOTATION MARK) and Unicode U+201D (RIGHT DOUBLE QUOTATION MARK) - -"English single typographic quotes" - - The characters ยซโ€˜ยป and ยซโ€™ยป - Unicode U+2018 (LEFT SINGLE QUOTATION MARK) and Unicode U+2019 (RIGHT SINGLE QUOTATION MARK) - -"code snippet" - - Also called "inline code". Text in a Markdown document which is surrounded by single backticks. A paragraph in a Markdown document can have a more than one code snippet. - - Example: - - ยซยซยซ - `i am a code snippet` - ยปยปยป - - Example: - - ยซยซยซ - `first code snippet` `second code snippet` `third code snippet` - ยปยปยป - -"code block" - - Text in a Markdown document which is surrounded by triple backticks. Spreads multiple lines. - - Example: - - ยซยซยซ - ``` - Hello - World - ``` - ยปยปยป - - Example: - - ยซยซยซ - ```python - print("hello World") - ``` - ยปยปยป - -"HTML element" - - a HTML opening tag โ€“ e.g. ยซ
ยป โ€“ and a HTML closing tag โ€“ e.g. ยซ
ยป โ€“ surrounding text or other HTML elements. - - -### Your task - -Translate an English text โ€“ the original content โ€“ to a target language. - -The original content is written in Markdown, write the translation in Markdown as well. - -The original content will be surrounded by triple percentage signs (ยซ%%%ยป). Do not include the triple percentage signs in the translation. - - -### Technical terms in English - -For technical terms in English that don't have a common translation term, use the original term in English. - - -### Content of code snippets - -Do not translate the content of code snippets, keep the original in English. For example, ยซ`list`ยป, ยซ`dict`ยป, keep them as is. - - -### Content of code blocks - -Do not translate the content of code blocks, except for comments in the language which the code block uses. - -Examples: - - Source (English) โ€“ The code block is a bash code example with one comment: - - ยซยซยซ - ```bash - # Print greeting - echo "Hello, World!" - ``` - ยปยปยป - - Result (German): - - ยซยซยซ - ```bash - # GruรŸ ausgeben - echo "Hello, World!" - ``` - ยปยปยป - - Source (English) โ€“ The code block is a console example containing HTML tags. No comments, so nothing to change here: - - ยซยซยซ - ```console - $ fastapi run main.py - FastAPI Starting server - Searching for package file structure - ``` - ยปยปยป - - Result (German): - - ยซยซยซ - ```console - $ fastapi run main.py - FastAPI Starting server - Searching for package file structure - ``` - ยปยปยป - - Source (English) โ€“ The code block is a console example containing 5 comments: - - ยซยซยซ - ```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 - ``` - ยปยปยป - - Result (German): - - ยซยซยซ - ```console - // Gehe zum Home-Verzeichnis - $ cd - // Erstelle ein Verzeichnis fรผr alle Ihre Code-Projekte - $ mkdir code - // Gehe in dieses Code-Verzeichnis - $ cd code - // Erstelle ein Verzeichnis fรผr dieses Projekt - $ mkdir awesome-project - // Gehe in dieses Projektverzeichnis - $ cd awesome-project - ``` - ยปยปยป - -If there is an existing translation and its Mermaid diagram is in sync with the Mermaid diagram in the English source, except a few translated words, then use the Mermaid diagram of the existing translation. The human editor of the translation translated these words in the Mermaid diagram. Keep these translations, do not revert them back to the English source. - -Example: - - Source (English): - - ยซยซยซ - ```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 - ``` - ยปยปยป - - Existing translation (German) โ€“ has three translations: - - ยซยซยซ - ```mermaid - flowchart LR - subgraph global[globale Umgebung] - harry-1[harry v1] - end - subgraph stone-project[philosophers-stone-Projekt] - stone(philosophers-stone) -->|benรถtigt| harry-1 - end - ``` - ยปยปยป - - Result (German) โ€“ you change nothing: - - ยซยซยซ - ```mermaid - flowchart LR - subgraph global[globale Umgebung] - harry-1[harry v1] - end - subgraph stone-project[philosophers-stone-Projekt] - stone(philosophers-stone) -->|benรถtigt| harry-1 - end - ``` - ยปยปยป - - -### Special blocks - -There are special blocks of notes, tips and others that look like: - - ยซยซยซ - /// note - ยปยปยป - -To translate it, keep the same line and add the translation after a vertical bar. - -For example, if you were translating to Spanish, you would write: - - ยซยซยซ - /// note | Nota - ยปยปยป - -Some examples in Spanish: - - Source: - - ยซยซยซ - /// tip - ยปยปยป - - Result: - - ยซยซยซ - /// tip | Consejo - ยปยปยป - - Source: - - ยซยซยซ - /// details | Preview - ยปยปยป - - Result: - - ยซยซยซ - /// details | Vista previa - ยปยปยป - - -### Tab blocks - -There are special blocks surrounded by four slashes (ยซ////ยป). They mark text, which will be rendered as part of a tab in the final document. The scheme is: - - //// tab | {tab title} - {tab content, may span many lines} - //// - -Keep everything before the vertical bar (ยซ|ยป) as is, including the vertical bar. Translate the tab title. Translate the tab content, applying the rules you know. Keep the four block closing slashes as is. - -Examples: - - Source (English): - - ยซยซยซ - //// tab | Python 3.8+ non-Annotated - Hello - //// - ยปยปยป - - Result (German): - - ยซยซยซ - //// tab | Python 3.8+ nicht annotiert - Hallo - //// - ยปยปยป - - Source (English) โ€“ Here there is nothing to translate in the tab title: - - ยซยซยซ - //// tab | Linux, macOS, Windows Bash - Hello again - //// - ยปยปยป - - Result (German): - - ยซยซยซ - //// tab | Linux, macOS, Windows Bash - Hallo wieder - //// - ยปยปยป - - -### Headings - -Every Markdown heading in the English text (all levels) ends with a part inside curly brackets. This part denotes the hash of this heading, which is used in links to this heading. In translations, translate the heading, but do not translate this hash part, so that links do not break. - -Examples of how to translate a heading: - - Source (English): - - ยซยซยซ - ## Alternative API docs { #alternative-api-docs } - ยปยปยป - - Result (Spanish): - - ยซยซยซ - ## Documentaciรณn de la API alternativa { #alternative-api-docs } - ยปยปยป - - Source (English): - - ยซยซยซ - ### Example { #example } - ยปยปยป - - Result (German): - - ยซยซยซ - ### Beispiel { #example } - ยปยปยป - - -### Links - -Use the following rules for links (apply both to Markdown-style links ([text](url)) and to HTML-style tags): - -1) For relative URLs, only translate link text. Do not translate the URL or its parts - -Example: - - Source (English): - - ยซยซยซ - [One of the fastest Python frameworks available](#performance) - ยปยปยป - - Result (German): - - ยซยซยซ - [Eines der schnellsten verfรผgbaren Python-Frameworks](#performance) - ยปยปยป - -2) For absolute URLs which DO NOT start EXACTLY with ยซhttps://fastapi.tiangolo.comยป, only translate link text and leave the URL unchanged. - -Example: - - Source (English): - - ยซยซยซ - SQLModel docs - ยปยปยป - - Result (German): - - ยซยซยซ - SQLModel-Dokumentation - ยปยปยป - -3) For absolute URLs which DO start EXACTLY with ยซhttps://fastapi.tiangolo.comยป, only translate link text and change the URL by adding language code (ยซhttps://fastapi.tiangolo.com/{language_code}[rest part of the url]ยป). - -Example: - - Source (English): - - ยซยซยซ - Documentation - ยปยปยป - - Result (Spanish): - - ยซยซยซ - Documentaciรณn - ยปยปยป - -3.1) Do not add language codes for URLs that point to static assets (e.g., images, CSS, JavaScript). - -Example: - - Source (English): - - ยซยซยซ - Something - ยปยปยป - - Result (Spanish): - - ยซยซยซ - Algo - ยปยปยป - -4) For internal links, only translate link text. - -Example: - - Source (English): - - ยซยซยซ - [Create Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} - ยปยปยป - - Result (German): - - ยซยซยซ - [Pull Requests erzeugen](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} - ยปยปยป - -5) Do not translate anchor fragments in links (the part after ยซ#ยป), as they must remain the same to work correctly. - -5.1) If an existing translation has a link with an anchor fragment different to the anchor fragment in the English source, then this is an error. Fix this by using the anchor fragment of the English source. - -Example: - - Source (English): - - ยซยซยซ - [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank} - ยปยปยป - - Existing wrong translation (German) โ€“ notice the wrongly translated anchor fragment: - - ยซยซยซ - [Body โ€“ Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#einzelne-werte-im-body){.internal-link target=_blank}. - ยปยปยป - - Result (German) โ€“ you fix the anchor fragment: - - ยซยซยซ - [Body โ€“ Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. - ยปยปยป - -5.2) Do not add anchor fragments at will, even if this makes sense. If the English source has no anchor, don't add one. - -Example: - - Source (English): - - ยซยซยซ - Create a [virtual environment](../virtual-environments.md){.internal-link target=_blank} - ยปยปยป - - Wrong translation (German) โ€“ Anchor added to the URL. - - ยซยซยซ - Erstelle eine [virtuelle Umgebung](../virtual-environments.md#create-a-virtual-environment){.internal-link target=_blank} - ยปยปยป - - Good translation (German) โ€“ URL stays like in the English source. - - ยซยซยซ - Erstelle eine [Virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} - ยปยปยป - - -### HTML abbr elements - -Translate HTML abbr elements (ยซtextยป) as follows: - -1) 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. - -Conversion scheme: - - Source (English): - - {abbreviation} - - Result: - - {abbreviation} - -Examples: - - Source (English): - - ยซยซยซ - IoT - CPU - TL;DR: - ยปยปยป - - Result (German): - - ยซยซยซ - IoT - CPU - TL;DR: - ยปยปยป - -1.1) If the language to which you translate mostly uses the letters of the ASCII char set (for example Spanish, French, German, but not Russian, Chinese) and if the translation of the full phrase is identical to, or starts with the same letters as the original full phrase, then only give the translation of the full phrase. - -Conversion scheme: - - Source (English): - - {abbreviation} - - Result: - - {abbreviation} - -Examples: - - Source (English): - - ยซยซยซ - JWT - Enum - ASGI - ยปยปยป - - Result (German): - - ยซยซยซ - JWT - Enum - ASGI - ยปยปยป - -2) 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 - ยปยปยป - - -3) 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. - -Conversion scheme: - - Source (English): - - {abbreviation} - - Result: - - {abbreviation} - -Examples: - - Source (English): - - ยซยซยซ - I/O - CDN - IDE - ยปยปยป - - Result (German): - - ยซยซยซ - I/O - CDN - IDE - ยปยปยป - -3.1) Like in rule 2.1, you can leave the original full phrase away, if the translated full phrase is identical or starts with the same letters as the original full phrase. - -Conversion scheme: - - Source (English): - - {abbreviation} - - Result: - - {abbreviation} - -Example: - - Source (English): - - ยซยซยซ - ORM - ยปยปยป - - Result (German): - - ยซยซยซ - ORM - ยปยปยป - -4) 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. - -5) 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 from rule 4 alone. Do not change their formatting or content. - -""" +general_prompt_path = Path(__file__).absolute().parent / "llm-general-prompt.md" +general_prompt = general_prompt_path.read_text(encoding="utf-8") app = typer.Typer() From 53d2453d1a77f3384a1648d717f8ddafb5e9e460 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 29 Dec 2025 18:54:43 +0000 Subject: [PATCH 038/110] =?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 9c4fe05ea0..2eb674624e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Internal +* ๐ŸŒ Update translation prompts. PR [#14619](https://github.com/fastapi/fastapi/pull/14619) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Update LLM translation script to guide reviewers to change the prompt. PR [#14614](https://github.com/fastapi/fastapi/pull/14614) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Do not run translations on cron while finishing updating existing languages. PR [#14613](https://github.com/fastapi/fastapi/pull/14613) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ฅ Remove test variants for Pydantic v1 in test_request_params. PR [#14612](https://github.com/fastapi/fastapi/pull/14612) by [@tiangolo](https://github.com/tiangolo). From 258deb925d9d65f2d75392a2b941d6b072e305b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 1 Jan 2026 22:40:15 -0800 Subject: [PATCH 039/110] =?UTF-8?q?=F0=9F=91=A5=20Update=20FastAPI=20Peopl?= =?UTF-8?q?e=20-=20Contributors=20and=20Translators=20(#14625)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/en/data/contributors.yml | 29 +++-- docs/en/data/translation_reviewers.yml | 146 +++++++++++++------------ docs/en/data/translators.yml | 26 ++--- 3 files changed, 108 insertions(+), 93 deletions(-) diff --git a/docs/en/data/contributors.yml b/docs/en/data/contributors.yml index 163dc68e37..0c144cd4ce 100644 --- a/docs/en/data/contributors.yml +++ b/docs/en/data/contributors.yml @@ -1,6 +1,6 @@ tiangolo: login: tiangolo - count: 808 + count: 857 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo dependabot: @@ -10,7 +10,7 @@ dependabot: url: https://github.com/apps/dependabot alejsdev: login: alejsdev - count: 52 + count: 53 avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=85ceac49fb87138aebe8d663912e359447329090&v=4 url: https://github.com/alejsdev pre-commit-ci: @@ -18,6 +18,11 @@ pre-commit-ci: count: 50 avatarUrl: https://avatars.githubusercontent.com/in/68672?v=4 url: https://github.com/apps/pre-commit-ci +YuriiMotov: + login: YuriiMotov + count: 36 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov github-actions: login: github-actions count: 26 @@ -28,26 +33,21 @@ Kludex: count: 25 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 url: https://github.com/Kludex -YuriiMotov: - login: YuriiMotov - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - url: https://github.com/YuriiMotov dmontagu: login: dmontagu count: 17 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 url: https://github.com/dmontagu +svlandeg: + login: svlandeg + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 + url: https://github.com/svlandeg nilslindemann: login: nilslindemann count: 15 avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 url: https://github.com/nilslindemann -svlandeg: - login: svlandeg - count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 - url: https://github.com/svlandeg euri10: login: euri10 count: 13 @@ -553,6 +553,11 @@ DanielKusyDev: count: 2 avatarUrl: https://avatars.githubusercontent.com/u/36250676?u=2ea6114ff751fc48b55f231987a0e2582c6b1bd2&v=4 url: https://github.com/DanielKusyDev +Viicos: + login: Viicos + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/65306057?u=fcd677dc1b9bef12aa103613e5ccb3f8ce305af9&v=4 + url: https://github.com/Viicos DanielYang59: login: DanielYang59 count: 2 diff --git a/docs/en/data/translation_reviewers.yml b/docs/en/data/translation_reviewers.yml index c3d3d0388d..62db8e805d 100644 --- a/docs/en/data/translation_reviewers.yml +++ b/docs/en/data/translation_reviewers.yml @@ -15,7 +15,7 @@ sodaMelon: url: https://github.com/sodaMelon ceb10n: login: ceb10n - count: 116 + count: 117 avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 url: https://github.com/ceb10n tokusumi: @@ -23,16 +23,16 @@ tokusumi: count: 104 avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 url: https://github.com/tokusumi +hard-coders: + login: hard-coders + count: 96 + avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=78d12d1acdf853c817700145e73de7fd9e5d068b&v=4 + url: https://github.com/hard-coders hasansezertasan: login: hasansezertasan count: 95 avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 url: https://github.com/hasansezertasan -hard-coders: - login: hard-coders - count: 93 - avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 - url: https://github.com/hard-coders alv2017: login: alv2017 count: 88 @@ -40,7 +40,7 @@ alv2017: url: https://github.com/alv2017 nazarepiedady: login: nazarepiedady - count: 86 + count: 87 avatarUrl: https://avatars.githubusercontent.com/u/31008635?u=f69ddc4ea8bda3bdfac7aa0e2ea38de282e6ee2d&v=4 url: https://github.com/nazarepiedady AlertRED: @@ -48,6 +48,11 @@ AlertRED: count: 81 avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4 url: https://github.com/AlertRED +tiangolo: + login: tiangolo + count: 73 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo Alexandrhub: login: Alexandrhub count: 68 @@ -63,21 +68,16 @@ cassiobotaro: count: 62 avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=a08022b191ddbd0a6159b2981d9d878b6d5bb71f&v=4 url: https://github.com/cassiobotaro +mattwang44: + login: mattwang44 + count: 61 + avatarUrl: https://avatars.githubusercontent.com/u/24987826?u=58e37fb3927b9124b458945ac4c97aa0f1062d85&v=4 + url: https://github.com/mattwang44 nilslindemann: login: nilslindemann count: 59 avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 url: https://github.com/nilslindemann -mattwang44: - login: mattwang44 - count: 59 - avatarUrl: https://avatars.githubusercontent.com/u/24987826?u=58e37fb3927b9124b458945ac4c97aa0f1062d85&v=4 - url: https://github.com/mattwang44 -tiangolo: - login: tiangolo - count: 56 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo Laineyzhang55: login: Laineyzhang55 count: 48 @@ -88,6 +88,11 @@ Kludex: count: 47 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 url: https://github.com/Kludex +YuriiMotov: + login: YuriiMotov + count: 46 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov komtaki: login: komtaki count: 45 @@ -118,11 +123,6 @@ Winand: count: 40 avatarUrl: https://avatars.githubusercontent.com/u/53390?u=bb0e71a2fc3910a8e0ee66da67c33de40ea695f8&v=4 url: https://github.com/Winand -YuriiMotov: - login: YuriiMotov - count: 40 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - url: https://github.com/YuriiMotov solomein-sv: login: solomein-sv count: 38 @@ -138,6 +138,11 @@ alejsdev: count: 37 avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=85ceac49fb87138aebe8d663912e359447329090&v=4 url: https://github.com/alejsdev +mezgoodle: + login: mezgoodle + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/41520940?u=4a9c765af688389d54296845d18b8f6cd6ddf09a&v=4 + url: https://github.com/mezgoodle stlucasgarcia: login: stlucasgarcia count: 36 @@ -153,11 +158,6 @@ timothy-jeong: count: 36 avatarUrl: https://avatars.githubusercontent.com/u/53824764?u=db3d0cea2f5fab64d810113c5039a369699a2774&v=4 url: https://github.com/timothy-jeong -mezgoodle: - login: mezgoodle - count: 35 - avatarUrl: https://avatars.githubusercontent.com/u/41520940?u=4a9c765af688389d54296845d18b8f6cd6ddf09a&v=4 - url: https://github.com/mezgoodle rjNemo: login: rjNemo count: 34 @@ -173,6 +173,11 @@ akarev0: count: 33 avatarUrl: https://avatars.githubusercontent.com/u/53393089?u=6e528bb4789d56af887ce6fe237bea4010885406&v=4 url: https://github.com/akarev0 +Vincy1230: + login: Vincy1230 + count: 33 + avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4 + url: https://github.com/Vincy1230 romashevchenko: login: romashevchenko count: 32 @@ -183,11 +188,6 @@ LorhanSohaky: count: 30 avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4 url: https://github.com/LorhanSohaky -Vincy1230: - login: Vincy1230 - count: 30 - avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4 - url: https://github.com/Vincy1230 black-redoc: login: black-redoc count: 29 @@ -250,7 +250,7 @@ mycaule: url: https://github.com/mycaule Aruelius: login: Aruelius - count: 24 + count: 25 avatarUrl: https://avatars.githubusercontent.com/u/25380989?u=574f8cfcda3ea77a3f81884f6b26a97068e36a9d&v=4 url: https://github.com/Aruelius wisderfin: @@ -263,6 +263,11 @@ OzgunCaglarArslan: count: 24 avatarUrl: https://avatars.githubusercontent.com/u/86166426?v=4 url: https://github.com/OzgunCaglarArslan +ycd: + login: ycd + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4 + url: https://github.com/ycd sh0nk: login: sh0nk count: 23 @@ -288,11 +293,6 @@ Attsun1031: count: 20 avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 url: https://github.com/Attsun1031 -ycd: - login: ycd - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4 - url: https://github.com/ycd delhi09: login: delhi09 count: 20 @@ -418,6 +418,11 @@ mattkoehne: count: 14 avatarUrl: https://avatars.githubusercontent.com/u/80362153?v=4 url: https://github.com/mattkoehne +maru0123-2004: + login: maru0123-2004 + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/43961566?u=16ed8603a4d6a4665cb6c53a7aece6f31379b769&v=4 + url: https://github.com/maru0123-2004 jovicon: login: jovicon count: 13 @@ -443,6 +448,11 @@ impocode: count: 13 avatarUrl: https://avatars.githubusercontent.com/u/109408819?u=9cdfc5ccb31a2094c520f41b6087012fa9048982&v=4 url: https://github.com/impocode +waketzheng: + login: waketzheng + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/35413830?u=df19e4fd5bb928e7d086e053ef26a46aad23bf84&v=4 + url: https://github.com/waketzheng wesinalves: login: wesinalves count: 13 @@ -538,21 +548,16 @@ Lufa1u: count: 11 avatarUrl: https://avatars.githubusercontent.com/u/112495876?u=087658920ed9e74311597bdd921d8d2de939d276&v=4 url: https://github.com/Lufa1u -waketzheng: - login: waketzheng - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/35413830?u=df19e4fd5bb928e7d086e053ef26a46aad23bf84&v=4 - url: https://github.com/waketzheng KNChiu: login: KNChiu count: 11 avatarUrl: https://avatars.githubusercontent.com/u/36751646?v=4 url: https://github.com/KNChiu -maru0123-2004: - login: maru0123-2004 +Zhongheng-Cheng: + login: Zhongheng-Cheng count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/43961566?u=16ed8603a4d6a4665cb6c53a7aece6f31379b769&v=4 - url: https://github.com/maru0123-2004 + avatarUrl: https://avatars.githubusercontent.com/u/95612344?u=a0f7730a3cc7486827965e01a119ad610bda4b0a&v=4 + url: https://github.com/Zhongheng-Cheng mariacamilagl: login: mariacamilagl count: 10 @@ -608,16 +613,16 @@ nick-cjyx9: count: 10 avatarUrl: https://avatars.githubusercontent.com/u/119087246?u=7227a2de948c68fb8396d5beff1ee5b0e057c42e&v=4 url: https://github.com/nick-cjyx9 +marcelomarkus: + login: marcelomarkus + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/20115018?u=dda090ce9160ef0cd2ff69b1e5ea741283425cba&v=4 + url: https://github.com/marcelomarkus lucasbalieiro: login: lucasbalieiro count: 10 - avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=dad91601ee4f40458d691774ec439aff308344d7&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=d144221c34c08adac8b20e1833d776ffa1c4b1d0&v=4 url: https://github.com/lucasbalieiro -Zhongheng-Cheng: - login: Zhongheng-Cheng - count: 10 - avatarUrl: https://avatars.githubusercontent.com/u/95612344?u=a0f7730a3cc7486827965e01a119ad610bda4b0a&v=4 - url: https://github.com/Zhongheng-Cheng RunningIkkyu: login: RunningIkkyu count: 9 @@ -668,11 +673,6 @@ yodai-yodai: count: 9 avatarUrl: https://avatars.githubusercontent.com/u/7031039?u=4f3593f5931892b931a745cfab846eff6e9332e7&v=4 url: https://github.com/yodai-yodai -marcelomarkus: - login: marcelomarkus - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/20115018?u=dda090ce9160ef0cd2ff69b1e5ea741283425cba&v=4 - url: https://github.com/marcelomarkus JoaoGustavoRogel: login: JoaoGustavoRogel count: 9 @@ -683,6 +683,11 @@ Yarous: count: 9 avatarUrl: https://avatars.githubusercontent.com/u/61277193?u=5b462347458a373b2d599c6f416d2b75eddbffad&v=4 url: https://github.com/Yarous +Pyth3rEx: + login: Pyth3rEx + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/26427764?u=087724f74d813c95925d51e354554bd4b6d6bb60&v=4 + url: https://github.com/Pyth3rEx dimaqq: login: dimaqq count: 8 @@ -1023,6 +1028,11 @@ devluisrodrigues: count: 5 avatarUrl: https://avatars.githubusercontent.com/u/21125286?v=4 url: https://github.com/11kkw +EdmilsonRodrigues: + login: EdmilsonRodrigues + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/62777025?u=217d6f3cd6cc750bb8818a3af7726c8d74eb7c2d&v=4 + url: https://github.com/EdmilsonRodrigues lpdswing: login: lpdswing count: 4 @@ -1163,6 +1173,11 @@ AbolfazlKameli: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/120686133?u=af8f025278cce0d489007071254e4055df60b78c&v=4 url: https://github.com/AbolfazlKameli +SBillion: + login: SBillion + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/1070649?u=3ab493dfc88b39da0eb1600e3b8e7df1c90a5dee&v=4 + url: https://github.com/SBillion tyronedamasceno: login: tyronedamasceno count: 3 @@ -1211,7 +1226,7 @@ phamquanganh31101998: peebbv6364: login: peebbv6364 count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/26784747?u=75583df215ee01a5cd2dc646aecb81e7dbd33d06&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/26784747?u=3bf07017eb4f4fa3639ba8d4ed19980a34bf8f90&v=4 url: https://github.com/peebbv6364 mrparalon: login: mrparalon @@ -1413,11 +1428,6 @@ Mohammad222PR: count: 3 avatarUrl: https://avatars.githubusercontent.com/u/116789737?u=25810a5fe049d2f1618e2e7417cea011cc353ce4&v=4 url: https://github.com/Mohammad222PR -EdmilsonRodrigues: - login: EdmilsonRodrigues - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/62777025?u=217d6f3cd6cc750bb8818a3af7726c8d74eb7c2d&v=4 - url: https://github.com/EdmilsonRodrigues blaisep: login: blaisep count: 2 @@ -1838,11 +1848,11 @@ NavesSapnis: count: 2 avatarUrl: https://avatars.githubusercontent.com/u/79222417?u=b5b10291b8e9130ca84fd20f0a641e04ed94b6b1&v=4 url: https://github.com/NavesSapnis -eqsdxr: - login: eqsdxr +isgin01: + login: isgin01 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=7927dc0366995334f9a18c3204a41d3a34d6d96f&v=4 - url: https://github.com/eqsdxr + avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=ddffde10876b50f35dc90d1337f507a630530a6a&v=4 + url: https://github.com/isgin01 syedasamina56: login: syedasamina56 count: 2 diff --git a/docs/en/data/translators.yml b/docs/en/data/translators.yml index c66eff4d42..940b128da2 100644 --- a/docs/en/data/translators.yml +++ b/docs/en/data/translators.yml @@ -1,6 +1,6 @@ nilslindemann: login: nilslindemann - count: 125 + count: 130 avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 url: https://github.com/nilslindemann jaystone776: @@ -28,6 +28,11 @@ SwftAlpc: count: 23 avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 url: https://github.com/SwftAlpc +tiangolo: + login: tiangolo + count: 22 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo hasansezertasan: login: hasansezertasan count: 22 @@ -46,7 +51,7 @@ AlertRED: hard-coders: login: hard-coders count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=78d12d1acdf853c817700145e73de7fd9e5d068b&v=4 url: https://github.com/hard-coders Joao-Pedro-P-Holanda: login: Joao-Pedro-P-Holanda @@ -103,11 +108,6 @@ pablocm83: count: 8 avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4 url: https://github.com/pablocm83 -tiangolo: - login: tiangolo - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo ptt3199: login: ptt3199 count: 7 @@ -126,13 +126,18 @@ batlopes: lucasbalieiro: login: lucasbalieiro count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=dad91601ee4f40458d691774ec439aff308344d7&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=d144221c34c08adac8b20e1833d776ffa1c4b1d0&v=4 url: https://github.com/lucasbalieiro Alexandrhub: login: Alexandrhub count: 6 avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 url: https://github.com/Alexandrhub +YuriiMotov: + login: YuriiMotov + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov Serrones: login: Serrones count: 5 @@ -358,11 +363,6 @@ ruzia: count: 3 avatarUrl: https://avatars.githubusercontent.com/u/24503?v=4 url: https://github.com/ruzia -YuriiMotov: - login: YuriiMotov - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - url: https://github.com/YuriiMotov izaguerreiro: login: izaguerreiro count: 2 From 6854be9ebcef23edf2bd34f6e4833e731031eccc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 2 Jan 2026 06:40:39 +0000 Subject: [PATCH 040/110] =?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 2eb674624e..34ed433ce7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Internal +* ๐Ÿ‘ฅ Update FastAPI People - Contributors and Translators. PR [#14625](https://github.com/fastapi/fastapi/pull/14625) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translation prompts. PR [#14619](https://github.com/fastapi/fastapi/pull/14619) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Update LLM translation script to guide reviewers to change the prompt. PR [#14614](https://github.com/fastapi/fastapi/pull/14614) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Do not run translations on cron while finishing updating existing languages. PR [#14613](https://github.com/fastapi/fastapi/pull/14613) by [@tiangolo](https://github.com/tiangolo). From 31c7ffcdfebe32ca6b2feb7b33260aff3880c268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 1 Jan 2026 22:46:34 -0800 Subject: [PATCH 041/110] =?UTF-8?q?=F0=9F=91=A5=20Update=20FastAPI=20GitHu?= =?UTF-8?q?b=20topic=20repositories=20(#14630)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/en/data/topic_repos.yml | 476 +++++++++++++++++------------------ 1 file changed, 238 insertions(+), 238 deletions(-) diff --git a/docs/en/data/topic_repos.yml b/docs/en/data/topic_repos.yml index cb7e3c033e..d089c7e5a7 100644 --- a/docs/en/data/topic_repos.yml +++ b/docs/en/data/topic_repos.yml @@ -1,495 +1,495 @@ - name: full-stack-fastapi-template html_url: https://github.com/fastapi/full-stack-fastapi-template - stars: 39475 + stars: 40334 owner_login: fastapi owner_html_url: https://github.com/fastapi - name: Hello-Python html_url: https://github.com/mouredev/Hello-Python - stars: 33090 + stars: 33628 owner_login: mouredev owner_html_url: https://github.com/mouredev - name: serve html_url: https://github.com/jina-ai/serve - stars: 21798 + stars: 21817 owner_login: jina-ai owner_html_url: https://github.com/jina-ai - name: HivisionIDPhotos html_url: https://github.com/Zeyi-Lin/HivisionIDPhotos - stars: 20258 + stars: 20409 owner_login: Zeyi-Lin owner_html_url: https://github.com/Zeyi-Lin - name: sqlmodel html_url: https://github.com/fastapi/sqlmodel - stars: 17212 + stars: 17415 owner_login: fastapi owner_html_url: https://github.com/fastapi -- name: Douyin_TikTok_Download_API - html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API - stars: 15145 - owner_login: Evil0ctal - owner_html_url: https://github.com/Evil0ctal - name: fastapi-best-practices html_url: https://github.com/zhanymkanov/fastapi-best-practices - stars: 14644 + stars: 15776 owner_login: zhanymkanov owner_html_url: https://github.com/zhanymkanov +- name: Douyin_TikTok_Download_API + html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API + stars: 15588 + owner_login: Evil0ctal + owner_html_url: https://github.com/Evil0ctal - name: machine-learning-zoomcamp html_url: https://github.com/DataTalksClub/machine-learning-zoomcamp - stars: 12320 + stars: 12447 owner_login: DataTalksClub owner_html_url: https://github.com/DataTalksClub -- name: fastapi_mcp - html_url: https://github.com/tadata-org/fastapi_mcp - stars: 11174 - owner_login: tadata-org - owner_html_url: https://github.com/tadata-org - name: SurfSense html_url: https://github.com/MODSetter/SurfSense - stars: 10858 + stars: 12128 owner_login: MODSetter owner_html_url: https://github.com/MODSetter +- name: fastapi_mcp + html_url: https://github.com/tadata-org/fastapi_mcp + stars: 11326 + owner_login: tadata-org + owner_html_url: https://github.com/tadata-org - name: awesome-fastapi html_url: https://github.com/mjhea0/awesome-fastapi - stars: 10758 + stars: 10901 owner_login: mjhea0 owner_html_url: https://github.com/mjhea0 - name: XHS-Downloader html_url: https://github.com/JoeanAmier/XHS-Downloader - stars: 9313 + stars: 9584 owner_login: JoeanAmier owner_html_url: https://github.com/JoeanAmier -- name: FastUI - html_url: https://github.com/pydantic/FastUI - stars: 8915 - owner_login: pydantic - owner_html_url: https://github.com/pydantic - name: polar html_url: https://github.com/polarsource/polar - stars: 8339 + stars: 8951 owner_login: polarsource owner_html_url: https://github.com/polarsource +- name: FastUI + html_url: https://github.com/pydantic/FastUI + stars: 8934 + owner_login: pydantic + owner_html_url: https://github.com/pydantic - name: FileCodeBox html_url: https://github.com/vastsa/FileCodeBox - stars: 7721 + stars: 7934 owner_login: vastsa owner_html_url: https://github.com/vastsa - name: nonebot2 html_url: https://github.com/nonebot/nonebot2 - stars: 7170 + stars: 7248 owner_login: nonebot owner_html_url: https://github.com/nonebot - name: hatchet html_url: https://github.com/hatchet-dev/hatchet - stars: 6253 + stars: 6392 owner_login: hatchet-dev owner_html_url: https://github.com/hatchet-dev - name: fastapi-users html_url: https://github.com/fastapi-users/fastapi-users - stars: 5849 + stars: 5899 owner_login: fastapi-users owner_html_url: https://github.com/fastapi-users - name: serge html_url: https://github.com/serge-chat/serge - stars: 5756 + stars: 5754 owner_login: serge-chat owner_html_url: https://github.com/serge-chat - name: strawberry html_url: https://github.com/strawberry-graphql/strawberry - stars: 4569 + stars: 4577 owner_login: strawberry-graphql owner_html_url: https://github.com/strawberry-graphql -- name: chatgpt-web-share - html_url: https://github.com/chatpire/chatgpt-web-share - stars: 4294 - owner_login: chatpire - owner_html_url: https://github.com/chatpire - name: poem html_url: https://github.com/poem-web/poem - stars: 4276 + stars: 4303 owner_login: poem-web owner_html_url: https://github.com/poem-web +- name: chatgpt-web-share + html_url: https://github.com/chatpire/chatgpt-web-share + stars: 4287 + owner_login: chatpire + owner_html_url: https://github.com/chatpire - name: dynaconf html_url: https://github.com/dynaconf/dynaconf - stars: 4202 + stars: 4221 owner_login: dynaconf owner_html_url: https://github.com/dynaconf -- name: atrilabs-engine - html_url: https://github.com/Atri-Labs/atrilabs-engine - stars: 4093 - owner_login: Atri-Labs - owner_html_url: https://github.com/Atri-Labs - name: Kokoro-FastAPI html_url: https://github.com/remsky/Kokoro-FastAPI - stars: 4019 + stars: 4181 owner_login: remsky owner_html_url: https://github.com/remsky +- name: atrilabs-engine + html_url: https://github.com/Atri-Labs/atrilabs-engine + stars: 4090 + owner_login: Atri-Labs + owner_html_url: https://github.com/Atri-Labs +- name: devpush + html_url: https://github.com/hunvreus/devpush + stars: 4037 + owner_login: hunvreus + owner_html_url: https://github.com/hunvreus - name: logfire html_url: https://github.com/pydantic/logfire - stars: 3805 + stars: 3896 owner_login: pydantic owner_html_url: https://github.com/pydantic - name: LitServe html_url: https://github.com/Lightning-AI/LitServe - stars: 3719 + stars: 3756 owner_login: Lightning-AI owner_html_url: https://github.com/Lightning-AI -- name: fastapi-admin - html_url: https://github.com/fastapi-admin/fastapi-admin - stars: 3632 - owner_login: fastapi-admin - owner_html_url: https://github.com/fastapi-admin -- name: datamodel-code-generator - html_url: https://github.com/koxudaxi/datamodel-code-generator - stars: 3609 - owner_login: koxudaxi - owner_html_url: https://github.com/koxudaxi - name: huma html_url: https://github.com/danielgtaylor/huma - stars: 3603 + stars: 3702 owner_login: danielgtaylor owner_html_url: https://github.com/danielgtaylor +- name: Yuxi-Know + html_url: https://github.com/xerrors/Yuxi-Know + stars: 3680 + owner_login: xerrors + owner_html_url: https://github.com/xerrors +- name: datamodel-code-generator + html_url: https://github.com/koxudaxi/datamodel-code-generator + stars: 3675 + owner_login: koxudaxi + owner_html_url: https://github.com/koxudaxi +- name: fastapi-admin + html_url: https://github.com/fastapi-admin/fastapi-admin + stars: 3659 + owner_login: fastapi-admin + owner_html_url: https://github.com/fastapi-admin - name: farfalle html_url: https://github.com/rashadphz/farfalle - stars: 3490 + stars: 3497 owner_login: rashadphz owner_html_url: https://github.com/rashadphz - name: tracecat html_url: https://github.com/TracecatHQ/tracecat - stars: 3379 + stars: 3421 owner_login: TracecatHQ owner_html_url: https://github.com/TracecatHQ - name: opyrator html_url: https://github.com/ml-tooling/opyrator - stars: 3135 + stars: 3136 owner_login: ml-tooling owner_html_url: https://github.com/ml-tooling - name: docarray html_url: https://github.com/docarray/docarray - stars: 3114 + stars: 3111 owner_login: docarray owner_html_url: https://github.com/docarray -- name: devpush - html_url: https://github.com/hunvreus/devpush - stars: 3097 - owner_login: hunvreus - owner_html_url: https://github.com/hunvreus - name: fastapi-realworld-example-app html_url: https://github.com/nsidnev/fastapi-realworld-example-app - stars: 3050 + stars: 3051 owner_login: nsidnev owner_html_url: https://github.com/nsidnev -- name: uvicorn-gunicorn-fastapi-docker - html_url: https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker - stars: 2911 - owner_login: tiangolo - owner_html_url: https://github.com/tiangolo - name: mcp-context-forge html_url: https://github.com/IBM/mcp-context-forge - stars: 2899 + stars: 3034 owner_login: IBM owner_html_url: https://github.com/IBM -- name: best-of-web-python - html_url: https://github.com/ml-tooling/best-of-web-python - stars: 2648 - owner_login: ml-tooling - owner_html_url: https://github.com/ml-tooling +- name: uvicorn-gunicorn-fastapi-docker + html_url: https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker + stars: 2904 + owner_login: tiangolo + owner_html_url: https://github.com/tiangolo - name: FastAPI-template html_url: https://github.com/s3rius/FastAPI-template - stars: 2637 + stars: 2680 owner_login: s3rius owner_html_url: https://github.com/s3rius +- name: best-of-web-python + html_url: https://github.com/ml-tooling/best-of-web-python + stars: 2662 + owner_login: ml-tooling + owner_html_url: https://github.com/ml-tooling - name: YC-Killer html_url: https://github.com/sahibzada-allahyar/YC-Killer - stars: 2599 + stars: 2614 owner_login: sahibzada-allahyar owner_html_url: https://github.com/sahibzada-allahyar -- name: fastapi-react - html_url: https://github.com/Buuntu/fastapi-react - stars: 2569 - owner_login: Buuntu - owner_html_url: https://github.com/Buuntu -- name: Yuxi-Know - html_url: https://github.com/xerrors/Yuxi-Know - stars: 2563 - owner_login: xerrors - owner_html_url: https://github.com/xerrors - name: sqladmin html_url: https://github.com/aminalaee/sqladmin - stars: 2558 + stars: 2587 owner_login: aminalaee owner_html_url: https://github.com/aminalaee +- name: fastapi-react + html_url: https://github.com/Buuntu/fastapi-react + stars: 2566 + owner_login: Buuntu + owner_html_url: https://github.com/Buuntu - name: RasaGPT html_url: https://github.com/paulpierre/RasaGPT - stars: 2451 + stars: 2456 owner_login: paulpierre owner_html_url: https://github.com/paulpierre - name: supabase-py html_url: https://github.com/supabase/supabase-py - stars: 2344 + stars: 2394 owner_login: supabase owner_html_url: https://github.com/supabase - name: nextpy html_url: https://github.com/dot-agent/nextpy - stars: 2335 + stars: 2338 owner_login: dot-agent owner_html_url: https://github.com/dot-agent - name: fastapi-utils html_url: https://github.com/fastapiutils/fastapi-utils - stars: 2291 + stars: 2289 owner_login: fastapiutils owner_html_url: https://github.com/fastapiutils -- name: 30-Days-of-Python - html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python - stars: 2220 - owner_login: codingforentrepreneurs - owner_html_url: https://github.com/codingforentrepreneurs - name: langserve html_url: https://github.com/langchain-ai/langserve - stars: 2215 + stars: 2234 owner_login: langchain-ai owner_html_url: https://github.com/langchain-ai +- name: 30-Days-of-Python + html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python + stars: 2232 + owner_login: codingforentrepreneurs + owner_html_url: https://github.com/codingforentrepreneurs - name: solara html_url: https://github.com/widgetti/solara - stars: 2122 + stars: 2141 owner_login: widgetti owner_html_url: https://github.com/widgetti - name: mangum html_url: https://github.com/Kludex/mangum - stars: 2029 + stars: 2046 owner_login: Kludex owner_html_url: https://github.com/Kludex +- name: fastapi_best_architecture + html_url: https://github.com/fastapi-practices/fastapi_best_architecture + stars: 1963 + owner_login: fastapi-practices + owner_html_url: https://github.com/fastapi-practices +- name: NoteDiscovery + html_url: https://github.com/gamosoft/NoteDiscovery + stars: 1943 + owner_login: gamosoft + owner_html_url: https://github.com/gamosoft - name: agentkit html_url: https://github.com/BCG-X-Official/agentkit - stars: 1912 + stars: 1936 owner_login: BCG-X-Official owner_html_url: https://github.com/BCG-X-Official +- name: vue-fastapi-admin + html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin + stars: 1909 + owner_login: mizhexiaoxiao + owner_html_url: https://github.com/mizhexiaoxiao - name: manage-fastapi html_url: https://github.com/ycd/manage-fastapi - stars: 1885 + stars: 1887 owner_login: ycd owner_html_url: https://github.com/ycd - name: openapi-python-client html_url: https://github.com/openapi-generators/openapi-python-client - stars: 1862 + stars: 1879 owner_login: openapi-generators owner_html_url: https://github.com/openapi-generators -- name: piccolo - html_url: https://github.com/piccolo-orm/piccolo - stars: 1836 - owner_login: piccolo-orm - owner_html_url: https://github.com/piccolo-orm -- name: vue-fastapi-admin - html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin - stars: 1831 - owner_login: mizhexiaoxiao - owner_html_url: https://github.com/mizhexiaoxiao -- name: python-week-2022 - html_url: https://github.com/rochacbruno/python-week-2022 - stars: 1817 - owner_login: rochacbruno - owner_html_url: https://github.com/rochacbruno - name: slowapi html_url: https://github.com/laurentS/slowapi - stars: 1798 + stars: 1845 owner_login: laurentS owner_html_url: https://github.com/laurentS +- name: piccolo + html_url: https://github.com/piccolo-orm/piccolo + stars: 1843 + owner_login: piccolo-orm + owner_html_url: https://github.com/piccolo-orm +- name: python-week-2022 + html_url: https://github.com/rochacbruno/python-week-2022 + stars: 1813 + owner_login: rochacbruno + owner_html_url: https://github.com/rochacbruno - name: fastapi-cache html_url: https://github.com/long2ice/fastapi-cache - stars: 1789 + stars: 1805 owner_login: long2ice owner_html_url: https://github.com/long2ice - name: ormar html_url: https://github.com/collerek/ormar - stars: 1783 + stars: 1785 owner_login: collerek owner_html_url: https://github.com/collerek -- name: termpair - html_url: https://github.com/cs01/termpair - stars: 1716 - owner_login: cs01 - owner_html_url: https://github.com/cs01 -- name: FastAPI-boilerplate - html_url: https://github.com/benavlabs/FastAPI-boilerplate - stars: 1660 - owner_login: benavlabs - owner_html_url: https://github.com/benavlabs - name: fastapi-langgraph-agent-production-ready-template html_url: https://github.com/wassim249/fastapi-langgraph-agent-production-ready-template - stars: 1638 + stars: 1780 owner_login: wassim249 owner_html_url: https://github.com/wassim249 -- name: langchain-serve - html_url: https://github.com/jina-ai/langchain-serve - stars: 1635 - owner_login: jina-ai - owner_html_url: https://github.com/jina-ai -- name: awesome-fastapi-projects - html_url: https://github.com/Kludex/awesome-fastapi-projects - stars: 1589 - owner_login: Kludex - owner_html_url: https://github.com/Kludex -- name: fastapi-pagination - html_url: https://github.com/uriyyo/fastapi-pagination - stars: 1585 - owner_login: uriyyo - owner_html_url: https://github.com/uriyyo -- name: coronavirus-tracker-api - html_url: https://github.com/ExpDev07/coronavirus-tracker-api - stars: 1574 - owner_login: ExpDev07 - owner_html_url: https://github.com/ExpDev07 +- name: FastAPI-boilerplate + html_url: https://github.com/benavlabs/FastAPI-boilerplate + stars: 1734 + owner_login: benavlabs + owner_html_url: https://github.com/benavlabs +- name: termpair + html_url: https://github.com/cs01/termpair + stars: 1724 + owner_login: cs01 + owner_html_url: https://github.com/cs01 - name: fastapi-crudrouter html_url: https://github.com/awtkns/fastapi-crudrouter - stars: 1559 + stars: 1671 owner_login: awtkns owner_html_url: https://github.com/awtkns +- name: langchain-serve + html_url: https://github.com/jina-ai/langchain-serve + stars: 1633 + owner_login: jina-ai + owner_html_url: https://github.com/jina-ai +- name: fastapi-pagination + html_url: https://github.com/uriyyo/fastapi-pagination + stars: 1588 + owner_login: uriyyo + owner_html_url: https://github.com/uriyyo +- name: awesome-fastapi-projects + html_url: https://github.com/Kludex/awesome-fastapi-projects + stars: 1583 + owner_login: Kludex + owner_html_url: https://github.com/Kludex +- name: coronavirus-tracker-api + html_url: https://github.com/ExpDev07/coronavirus-tracker-api + stars: 1571 + owner_login: ExpDev07 + owner_html_url: https://github.com/ExpDev07 - name: bracket html_url: https://github.com/evroon/bracket - stars: 1489 + stars: 1549 owner_login: evroon owner_html_url: https://github.com/evroon - name: fastapi-amis-admin html_url: https://github.com/amisadmin/fastapi-amis-admin - stars: 1475 + stars: 1491 owner_login: amisadmin owner_html_url: https://github.com/amisadmin - name: fastapi-boilerplate html_url: https://github.com/teamhide/fastapi-boilerplate - stars: 1436 + stars: 1452 owner_login: teamhide owner_html_url: https://github.com/teamhide -- name: awesome-python-resources - html_url: https://github.com/DjangoEx/awesome-python-resources - stars: 1426 - owner_login: DjangoEx - owner_html_url: https://github.com/DjangoEx - name: fastcrud html_url: https://github.com/benavlabs/fastcrud - stars: 1414 + stars: 1452 owner_login: benavlabs owner_html_url: https://github.com/benavlabs +- name: awesome-python-resources + html_url: https://github.com/DjangoEx/awesome-python-resources + stars: 1430 + owner_login: DjangoEx + owner_html_url: https://github.com/DjangoEx - name: prometheus-fastapi-instrumentator html_url: https://github.com/trallnag/prometheus-fastapi-instrumentator - stars: 1388 + stars: 1399 owner_login: trallnag owner_html_url: https://github.com/trallnag -- name: fastapi_best_architecture - html_url: https://github.com/fastapi-practices/fastapi_best_architecture - stars: 1378 - owner_login: fastapi-practices - owner_html_url: https://github.com/fastapi-practices - name: fastapi-code-generator html_url: https://github.com/koxudaxi/fastapi-code-generator - stars: 1375 + stars: 1371 owner_login: koxudaxi owner_html_url: https://github.com/koxudaxi +- name: fastapi-tutorial + html_url: https://github.com/liaogx/fastapi-tutorial + stars: 1346 + owner_login: liaogx + owner_html_url: https://github.com/liaogx - name: budgetml html_url: https://github.com/ebhy/budgetml stars: 1345 owner_login: ebhy owner_html_url: https://github.com/ebhy -- name: fastapi-tutorial - html_url: https://github.com/liaogx/fastapi-tutorial - stars: 1327 - owner_login: liaogx - owner_html_url: https://github.com/liaogx -- name: fastapi-alembic-sqlmodel-async - html_url: https://github.com/jonra1993/fastapi-alembic-sqlmodel-async - stars: 1259 - owner_login: jonra1993 - owner_html_url: https://github.com/jonra1993 - name: fastapi-scaff html_url: https://github.com/atpuxiner/fastapi-scaff - stars: 1255 + stars: 1331 owner_login: atpuxiner owner_html_url: https://github.com/atpuxiner -- name: bedrock-chat - html_url: https://github.com/aws-samples/bedrock-chat - stars: 1254 - owner_login: aws-samples - owner_html_url: https://github.com/aws-samples - name: bolt-python html_url: https://github.com/slackapi/bolt-python - stars: 1253 + stars: 1266 owner_login: slackapi owner_html_url: https://github.com/slackapi +- name: bedrock-chat + html_url: https://github.com/aws-samples/bedrock-chat + stars: 1266 + owner_login: aws-samples + owner_html_url: https://github.com/aws-samples +- name: fastapi-alembic-sqlmodel-async + html_url: https://github.com/jonra1993/fastapi-alembic-sqlmodel-async + stars: 1260 + owner_login: jonra1993 + owner_html_url: https://github.com/jonra1993 - name: fastapi_production_template html_url: https://github.com/zhanymkanov/fastapi_production_template - stars: 1217 + stars: 1222 owner_login: zhanymkanov owner_html_url: https://github.com/zhanymkanov - name: langchain-extract html_url: https://github.com/langchain-ai/langchain-extract - stars: 1176 + stars: 1179 owner_login: langchain-ai owner_html_url: https://github.com/langchain-ai - name: restish html_url: https://github.com/rest-sh/restish - stars: 1140 + stars: 1152 owner_login: rest-sh owner_html_url: https://github.com/rest-sh - name: odmantic html_url: https://github.com/art049/odmantic - stars: 1138 + stars: 1143 owner_login: art049 owner_html_url: https://github.com/art049 - name: authx html_url: https://github.com/yezz123/authx - stars: 1119 + stars: 1128 owner_login: yezz123 owner_html_url: https://github.com/yezz123 -- name: NoteDiscovery - html_url: https://github.com/gamosoft/NoteDiscovery - stars: 1107 - owner_login: gamosoft - owner_html_url: https://github.com/gamosoft -- name: flock - html_url: https://github.com/Onelevenvy/flock - stars: 1055 - owner_login: Onelevenvy - owner_html_url: https://github.com/Onelevenvy -- name: fastapi-observability - html_url: https://github.com/blueswen/fastapi-observability - stars: 1038 - owner_login: blueswen - owner_html_url: https://github.com/blueswen +- name: SAG + html_url: https://github.com/Zleap-AI/SAG + stars: 1104 + owner_login: Zleap-AI + owner_html_url: https://github.com/Zleap-AI - name: aktools html_url: https://github.com/akfamily/aktools - stars: 1027 + stars: 1072 owner_login: akfamily owner_html_url: https://github.com/akfamily - name: RuoYi-Vue3-FastAPI html_url: https://github.com/insistence/RuoYi-Vue3-FastAPI - stars: 1016 + stars: 1063 owner_login: insistence owner_html_url: https://github.com/insistence -- name: autollm - html_url: https://github.com/viddexa/autollm - stars: 1002 - owner_login: viddexa - owner_html_url: https://github.com/viddexa -- name: titiler - html_url: https://github.com/developmentseed/titiler - stars: 999 - owner_login: developmentseed - owner_html_url: https://github.com/developmentseed -- name: lanarky - html_url: https://github.com/ajndkr/lanarky - stars: 994 - owner_login: ajndkr - owner_html_url: https://github.com/ajndkr -- name: every-pdf - html_url: https://github.com/DDULDDUCK/every-pdf - stars: 985 - owner_login: DDULDDUCK - owner_html_url: https://github.com/DDULDDUCK +- name: flock + html_url: https://github.com/Onelevenvy/flock + stars: 1059 + owner_login: Onelevenvy + owner_html_url: https://github.com/Onelevenvy +- name: fastapi-observability + html_url: https://github.com/blueswen/fastapi-observability + stars: 1046 + owner_login: blueswen + owner_html_url: https://github.com/blueswen - name: enterprise-deep-research html_url: https://github.com/SalesforceAIResearch/enterprise-deep-research - stars: 973 + stars: 1019 owner_login: SalesforceAIResearch owner_html_url: https://github.com/SalesforceAIResearch -- name: fastapi-mail - html_url: https://github.com/sabuhish/fastapi-mail - stars: 964 - owner_login: sabuhish - owner_html_url: https://github.com/sabuhish +- name: titiler + html_url: https://github.com/developmentseed/titiler + stars: 1016 + owner_login: developmentseed + owner_html_url: https://github.com/developmentseed +- name: every-pdf + html_url: https://github.com/DDULDDUCK/every-pdf + stars: 1004 + owner_login: DDULDDUCK + owner_html_url: https://github.com/DDULDDUCK +- name: autollm + html_url: https://github.com/viddexa/autollm + stars: 1003 + owner_login: viddexa + owner_html_url: https://github.com/viddexa +- name: lanarky + html_url: https://github.com/ajndkr/lanarky + stars: 996 + owner_login: ajndkr + owner_html_url: https://github.com/ajndkr From 052d6e86c21dc83a7ad458a2b620145b91a1b4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 1 Jan 2026 22:46:57 -0800 Subject: [PATCH 042/110] =?UTF-8?q?=F0=9F=91=A5=20Update=20FastAPI=20Peopl?= =?UTF-8?q?e=20-=20Sponsors=20(#14626)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/en/data/github_sponsors.yml | 431 +++++++++++++++---------------- 1 file changed, 211 insertions(+), 220 deletions(-) diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml index 24780603de..971687d8a9 100644 --- a/docs/en/data/github_sponsors.yml +++ b/docs/en/data/github_sponsors.yml @@ -2,57 +2,51 @@ sponsors: - - login: renderinc avatarUrl: https://avatars.githubusercontent.com/u/36424661?v=4 url: https://github.com/renderinc - - login: andrew-propelauth - avatarUrl: https://avatars.githubusercontent.com/u/89474256?u=c98993dec8553c09d424ede67bbe86e5c35f48c9&v=4 - url: https://github.com/andrew-propelauth - - login: blockbee-io - avatarUrl: https://avatars.githubusercontent.com/u/115143449?u=1b8620c2d6567c4df2111a371b85a51f448f9b85&v=4 - url: https://github.com/blockbee-io - - login: zuplo - avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4 - url: https://github.com/zuplo - - login: coderabbitai - avatarUrl: https://avatars.githubusercontent.com/u/132028505?v=4 - url: https://github.com/coderabbitai - - login: greptileai - avatarUrl: https://avatars.githubusercontent.com/u/140149887?v=4 - url: https://github.com/greptileai - login: subtotal avatarUrl: https://avatars.githubusercontent.com/u/176449348?v=4 url: https://github.com/subtotal + - login: greptileai + avatarUrl: https://avatars.githubusercontent.com/u/140149887?v=4 + url: https://github.com/greptileai + - login: coderabbitai + avatarUrl: https://avatars.githubusercontent.com/u/132028505?v=4 + url: https://github.com/coderabbitai + - login: zuplo + avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4 + url: https://github.com/zuplo + - login: blockbee-io + avatarUrl: https://avatars.githubusercontent.com/u/115143449?u=1b8620c2d6567c4df2111a371b85a51f448f9b85&v=4 + url: https://github.com/blockbee-io + - login: andrew-propelauth + avatarUrl: https://avatars.githubusercontent.com/u/89474256?u=c98993dec8553c09d424ede67bbe86e5c35f48c9&v=4 + url: https://github.com/andrew-propelauth - login: railwayapp avatarUrl: https://avatars.githubusercontent.com/u/66716858?v=4 url: https://github.com/railwayapp -- - login: dribia - avatarUrl: https://avatars.githubusercontent.com/u/41189616?v=4 - url: https://github.com/dribia - - login: svix - avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4 - url: https://github.com/svix +- - login: speakeasy-api + avatarUrl: https://avatars.githubusercontent.com/u/91446104?v=4 + url: https://github.com/speakeasy-api - login: stainless-api avatarUrl: https://avatars.githubusercontent.com/u/88061651?v=4 url: https://github.com/stainless-api - - login: speakeasy-api - avatarUrl: https://avatars.githubusercontent.com/u/91446104?v=4 - url: https://github.com/speakeasy-api - - login: databento - avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4 - url: https://github.com/databento + - login: svix + avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4 + url: https://github.com/svix - login: permitio avatarUrl: https://avatars.githubusercontent.com/u/71775833?v=4 url: https://github.com/permitio -- - login: Ponte-Energy-Partners - avatarUrl: https://avatars.githubusercontent.com/u/114745848?v=4 - url: https://github.com/Ponte-Energy-Partners - - login: LambdaTest-Inc + - login: databento + avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4 + url: https://github.com/databento +- - login: LambdaTest-Inc avatarUrl: https://avatars.githubusercontent.com/u/171592363?u=96606606a45fa170427206199014f2a5a2a4920b&v=4 url: https://github.com/LambdaTest-Inc + - login: Ponte-Energy-Partners + avatarUrl: https://avatars.githubusercontent.com/u/114745848?v=4 + url: https://github.com/Ponte-Energy-Partners - login: BoostryJP avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4 url: https://github.com/BoostryJP - - login: requestly - avatarUrl: https://avatars.githubusercontent.com/u/12287519?v=4 - url: https://github.com/requestly - login: acsone avatarUrl: https://avatars.githubusercontent.com/u/7601056?v=4 url: https://github.com/acsone @@ -68,9 +62,6 @@ sponsors: - login: Doist avatarUrl: https://avatars.githubusercontent.com/u/2565372?v=4 url: https://github.com/Doist - - login: bholagabbar - avatarUrl: https://avatars.githubusercontent.com/u/11693595?v=4 - url: https://github.com/bholagabbar - - login: mainframeindustries avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4 url: https://github.com/mainframeindustries @@ -86,6 +77,9 @@ sponsors: - login: ChargeStorm avatarUrl: https://avatars.githubusercontent.com/u/26000165?v=4 url: https://github.com/ChargeStorm + - login: ibrahimpelumi6142 + avatarUrl: https://avatars.githubusercontent.com/u/113442282?v=4 + url: https://github.com/ibrahimpelumi6142 - login: nilslindemann avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 url: https://github.com/nilslindemann @@ -116,124 +110,127 @@ sponsors: - login: Leay15 avatarUrl: https://avatars.githubusercontent.com/u/32212558?u=c4aa9c1737e515959382a5515381757b1fd86c53&v=4 url: https://github.com/Leay15 - - login: Karine-Bauch - avatarUrl: https://avatars.githubusercontent.com/u/90465103?u=7feb1018abb1a5631cfd9a91fea723d1ceb5f49b&v=4 - url: https://github.com/Karine-Bauch - login: jugeeem avatarUrl: https://avatars.githubusercontent.com/u/116043716?u=ae590d79c38ac79c91b9c5caa6887d061e865a3d&v=4 url: https://github.com/jugeeem - - login: patsatsia - avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4 - url: https://github.com/patsatsia - - login: anthonycepeda - avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4 - url: https://github.com/anthonycepeda - - login: patricioperezv - avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4 - url: https://github.com/patricioperezv - - login: chickenandstats - avatarUrl: https://avatars.githubusercontent.com/u/79477966?u=ae2b894aa954070db1d7830dab99b49eba4e4567&v=4 - url: https://github.com/chickenandstats + - login: Karine-Bauch + avatarUrl: https://avatars.githubusercontent.com/u/90465103?u=7feb1018abb1a5631cfd9a91fea723d1ceb5f49b&v=4 + url: https://github.com/Karine-Bauch - login: kaoru0310 avatarUrl: https://avatars.githubusercontent.com/u/80977929?u=1b61d10142b490e56af932ddf08a390fae8ee94f&v=4 url: https://github.com/kaoru0310 - - login: jstanden - avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4 - url: https://github.com/jstanden - - login: knallgelb - avatarUrl: https://avatars.githubusercontent.com/u/2358812?u=c48cb6362b309d74cbf144bd6ad3aed3eb443e82&v=4 - url: https://github.com/knallgelb - - login: dblackrun - avatarUrl: https://avatars.githubusercontent.com/u/3528486?v=4 - url: https://github.com/dblackrun - - login: zsinx6 - avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4 - url: https://github.com/zsinx6 - - login: kennywakeland - avatarUrl: https://avatars.githubusercontent.com/u/3631417?u=7c8f743f1ae325dfadea7c62bbf1abd6a824fc55&v=4 - url: https://github.com/kennywakeland - - login: aacayaco - avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4 - url: https://github.com/aacayaco - - login: anomaly - avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4 - url: https://github.com/anomaly - - login: mj0331 - avatarUrl: https://avatars.githubusercontent.com/u/3890353?u=1c627ac1a024515b4871de5c3ebbfaa1a57f65d4&v=4 - url: https://github.com/mj0331 - - login: gorhack - avatarUrl: https://avatars.githubusercontent.com/u/4141690?u=ec119ebc4bdf00a7bc84657a71aa17834f4f27f3&v=4 - url: https://github.com/gorhack - - login: Ryandaydev - avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=679ff84cb7b988c5795a5fa583857f574a055763&v=4 - url: https://github.com/Ryandaydev - - login: jaredtrog - avatarUrl: https://avatars.githubusercontent.com/u/4381365?v=4 - url: https://github.com/jaredtrog + - login: chickenandstats + avatarUrl: https://avatars.githubusercontent.com/u/79477966?u=ae2b894aa954070db1d7830dab99b49eba4e4567&v=4 + url: https://github.com/chickenandstats + - login: patricioperezv + avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4 + url: https://github.com/patricioperezv + - login: anthonycepeda + avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4 + url: https://github.com/anthonycepeda + - login: AalbatrossGuy + avatarUrl: https://avatars.githubusercontent.com/u/68378354?u=0bdeea9356d24f638244131f6d8d1e2d2f3601ca&v=4 + url: https://github.com/AalbatrossGuy + - login: patsatsia + avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4 + url: https://github.com/patsatsia - login: oliverxchen avatarUrl: https://avatars.githubusercontent.com/u/4471774?u=534191f25e32eeaadda22dfab4b0a428733d5489&v=4 url: https://github.com/oliverxchen - - login: paulcwatts - avatarUrl: https://avatars.githubusercontent.com/u/150269?u=1819e145d573b44f0ad74b87206d21cd60331d4e&v=4 - url: https://github.com/paulcwatts - - login: robintw - avatarUrl: https://avatars.githubusercontent.com/u/296686?v=4 - url: https://github.com/robintw - - login: pamelafox - avatarUrl: https://avatars.githubusercontent.com/u/297042?v=4 - url: https://github.com/pamelafox - - login: wshayes - avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 - url: https://github.com/wshayes - - login: koxudaxi - avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4 - url: https://github.com/koxudaxi - - login: falkben - avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4 - url: https://github.com/falkben - - login: mintuhouse - avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4 - url: https://github.com/mintuhouse + - login: jaredtrog + avatarUrl: https://avatars.githubusercontent.com/u/4381365?v=4 + url: https://github.com/jaredtrog + - login: Ryandaydev + avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=679ff84cb7b988c5795a5fa583857f574a055763&v=4 + url: https://github.com/Ryandaydev + - login: gorhack + avatarUrl: https://avatars.githubusercontent.com/u/4141690?u=ec119ebc4bdf00a7bc84657a71aa17834f4f27f3&v=4 + url: https://github.com/gorhack + - login: mj0331 + avatarUrl: https://avatars.githubusercontent.com/u/3890353?u=1c627ac1a024515b4871de5c3ebbfaa1a57f65d4&v=4 + url: https://github.com/mj0331 + - login: anomaly + avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4 + url: https://github.com/anomaly + - login: aacayaco + avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4 + url: https://github.com/aacayaco + - login: kennywakeland + avatarUrl: https://avatars.githubusercontent.com/u/3631417?u=7c8f743f1ae325dfadea7c62bbf1abd6a824fc55&v=4 + url: https://github.com/kennywakeland + - login: zsinx6 + avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4 + url: https://github.com/zsinx6 + - login: dblackrun + avatarUrl: https://avatars.githubusercontent.com/u/3528486?v=4 + url: https://github.com/dblackrun + - login: knallgelb + avatarUrl: https://avatars.githubusercontent.com/u/2358812?u=c48cb6362b309d74cbf144bd6ad3aed3eb443e82&v=4 + url: https://github.com/knallgelb - login: dodo5522 avatarUrl: https://avatars.githubusercontent.com/u/1362607?u=9bf1e0e520cccc547c046610c468ce6115bbcf9f&v=4 url: https://github.com/dodo5522 - - login: wdwinslow - avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=371272f2c69e680e0559a7b0a57385e83a5dc728&v=4 - url: https://github.com/wdwinslow - - login: jsoques - avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4 - url: https://github.com/jsoques - - login: dannywade - avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4 - url: https://github.com/dannywade - - login: khadrawy - avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4 - url: https://github.com/khadrawy - - login: mjohnsey - avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4 - url: https://github.com/mjohnsey - - login: ashi-agrawal - avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4 - url: https://github.com/ashi-agrawal + - login: mintuhouse + avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4 + url: https://github.com/mintuhouse + - login: falkben + avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4 + url: https://github.com/falkben + - login: koxudaxi + avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4 + url: https://github.com/koxudaxi + - login: wshayes + avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 + url: https://github.com/wshayes + - login: pamelafox + avatarUrl: https://avatars.githubusercontent.com/u/297042?v=4 + url: https://github.com/pamelafox + - login: robintw + avatarUrl: https://avatars.githubusercontent.com/u/296686?v=4 + url: https://github.com/robintw + - login: jstanden + avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4 + url: https://github.com/jstanden - login: RaamEEIL avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4 url: https://github.com/RaamEEIL - - login: ternaus - avatarUrl: https://avatars.githubusercontent.com/u/5481618?u=513a26b02a39e7a28d587cd37c6cc877ea368e6e&v=4 - url: https://github.com/ternaus - - login: eseglem - avatarUrl: https://avatars.githubusercontent.com/u/5920492?u=208d419cf667b8ac594c82a8db01932c7e50d057&v=4 - url: https://github.com/eseglem - - login: FernandoCelmer - avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=58ba6d5888fa7f355934e52db19f950e20b38162&v=4 - url: https://github.com/FernandoCelmer - - login: Rehket - avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4 - url: https://github.com/Rehket + - login: ashi-agrawal + avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4 + url: https://github.com/ashi-agrawal + - login: mjohnsey + avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4 + url: https://github.com/mjohnsey + - login: khadrawy + avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4 + url: https://github.com/khadrawy + - login: dannywade + avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4 + url: https://github.com/dannywade + - login: jsoques + avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4 + url: https://github.com/jsoques + - login: wdwinslow + avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=371272f2c69e680e0559a7b0a57385e83a5dc728&v=4 + url: https://github.com/wdwinslow - login: hiancdtrsnm avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4 url: https://github.com/hiancdtrsnm -- - login: manoelpqueiroz + - login: Rehket + avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4 + url: https://github.com/Rehket + - login: FernandoCelmer + avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=58ba6d5888fa7f355934e52db19f950e20b38162&v=4 + url: https://github.com/FernandoCelmer + - login: eseglem + avatarUrl: https://avatars.githubusercontent.com/u/5920492?u=208d419cf667b8ac594c82a8db01932c7e50d057&v=4 + url: https://github.com/eseglem + - login: ternaus + avatarUrl: https://avatars.githubusercontent.com/u/5481618?u=513a26b02a39e7a28d587cd37c6cc877ea368e6e&v=4 + url: https://github.com/ternaus +- - login: Artur-Galstyan + avatarUrl: https://avatars.githubusercontent.com/u/63471891?u=e8691f386037e51a737cc0ba866cd8c89e5cf109&v=4 + url: https://github.com/Artur-Galstyan + - login: manoelpqueiroz avatarUrl: https://avatars.githubusercontent.com/u/23669137?u=b12e84b28a84369ab5b30bd5a79e5788df5a0756&v=4 url: https://github.com/manoelpqueiroz - - login: pawamoy @@ -254,9 +251,12 @@ sponsors: - login: hgalytoby avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=6cc9028f3db63f8f60ad21c17b1ce4b88c4e2e60&v=4 url: https://github.com/hgalytoby - - login: nisutec - avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4 - url: https://github.com/nisutec + - login: johnl28 + avatarUrl: https://avatars.githubusercontent.com/u/54412955?u=47dd06082d1c39caa90c752eb55566e4f3813957&v=4 + url: https://github.com/johnl28 + - login: danielunderwood + avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4 + url: https://github.com/danielunderwood - login: hoenie-ams avatarUrl: https://avatars.githubusercontent.com/u/25708487?u=cda07434f0509ac728d9edf5e681117c0f6b818b&v=4 url: https://github.com/hoenie-ams @@ -267,93 +267,87 @@ sponsors: avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4 url: https://github.com/engineerjoe440 - login: bnkc - avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=db5e6f4f87836cad26c2aa90ce390ce49041c5a9&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=4771ac4e64066f0847d40e5b29910adabd9b2372&v=4 url: https://github.com/bnkc - login: petercool avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=75aa8c6729e6e8f85a300561c4dbeef9d65c8797&v=4 url: https://github.com/petercool - - login: johnl28 - avatarUrl: https://avatars.githubusercontent.com/u/54412955?u=47dd06082d1c39caa90c752eb55566e4f3813957&v=4 - url: https://github.com/johnl28 - - login: PunRabbit - avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4 - url: https://github.com/PunRabbit - login: PelicanQ avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4 url: https://github.com/PelicanQ - - login: WillHogan - avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=8a80356e3e7d5a417157aba7ea565dabc8678327&v=4 - url: https://github.com/WillHogan + - login: PunRabbit + avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4 + url: https://github.com/PunRabbit - login: my3 avatarUrl: https://avatars.githubusercontent.com/u/1825270?v=4 url: https://github.com/my3 - - login: danielunderwood - avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4 - url: https://github.com/danielunderwood - - login: ddanier - avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4 - url: https://github.com/ddanier - - login: bryanculbertson - avatarUrl: https://avatars.githubusercontent.com/u/144028?u=defda4f90e93429221cc667500944abde60ebe4a&v=4 - url: https://github.com/bryanculbertson - - login: slafs - avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 - url: https://github.com/slafs - - login: ceb10n - avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 - url: https://github.com/ceb10n - - login: tochikuji - avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4 - url: https://github.com/tochikuji + - login: WillHogan + avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=8a80356e3e7d5a417157aba7ea565dabc8678327&v=4 + url: https://github.com/WillHogan - login: miguelgr avatarUrl: https://avatars.githubusercontent.com/u/1484589?u=54556072b8136efa12ae3b6902032ea2a39ace4b&v=4 url: https://github.com/miguelgr - - login: xncbf - avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=a80a7bb349555b277645632ed66639ff43400614&v=4 - url: https://github.com/xncbf - - login: DMantis - avatarUrl: https://avatars.githubusercontent.com/u/9536869?u=652dd0d49717803c0cbcbf44f7740e53cf2d4892&v=4 - url: https://github.com/DMantis - - login: hard-coders - avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 - url: https://github.com/hard-coders - - login: mntolia - avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4 - url: https://github.com/mntolia - - login: Zuzah - avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4 - url: https://github.com/Zuzah - - login: TheR1D - avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4 - url: https://github.com/TheR1D + - login: tochikuji + avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4 + url: https://github.com/tochikuji + - login: ceb10n + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n + - login: slafs + avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 + url: https://github.com/slafs + - login: bryanculbertson + avatarUrl: https://avatars.githubusercontent.com/u/144028?u=defda4f90e93429221cc667500944abde60ebe4a&v=4 + url: https://github.com/bryanculbertson + - login: ddanier + avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4 + url: https://github.com/ddanier + - login: nisutec + avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4 + url: https://github.com/nisutec - login: joshuatz avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4 url: https://github.com/joshuatz - - login: rangulvers - avatarUrl: https://avatars.githubusercontent.com/u/5235430?u=e254d4af4ace5a05fa58372ae677c7d26f0d5a53&v=4 - url: https://github.com/rangulvers - - login: sdevkota - avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4 - url: https://github.com/sdevkota - - login: Baghdady92 - avatarUrl: https://avatars.githubusercontent.com/u/5708590?v=4 - url: https://github.com/Baghdady92 - - login: KentShikama - avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4 - url: https://github.com/KentShikama - - login: katnoria - avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4 - url: https://github.com/katnoria - - login: harsh183 - avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4 - url: https://github.com/harsh183 + - login: TheR1D + avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4 + url: https://github.com/TheR1D + - login: Zuzah + avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4 + url: https://github.com/Zuzah + - login: mntolia + avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4 + url: https://github.com/mntolia + - login: hard-coders + avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=78d12d1acdf853c817700145e73de7fd9e5d068b&v=4 + url: https://github.com/hard-coders + - login: DMantis + avatarUrl: https://avatars.githubusercontent.com/u/9536869?u=652dd0d49717803c0cbcbf44f7740e53cf2d4892&v=4 + url: https://github.com/DMantis + - login: xncbf + avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=a80a7bb349555b277645632ed66639ff43400614&v=4 + url: https://github.com/xncbf - login: moonape1226 avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4 url: https://github.com/moonape1226 -- - login: andrecorumba - avatarUrl: https://avatars.githubusercontent.com/u/37807517?u=9b9be3b41da9bda60957da9ef37b50dbf65baa61&v=4 - url: https://github.com/andrecorumba - - login: KOZ39 + - login: harsh183 + avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4 + url: https://github.com/harsh183 + - login: katnoria + avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4 + url: https://github.com/katnoria + - login: KentShikama + avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4 + url: https://github.com/KentShikama + - login: Baghdady92 + avatarUrl: https://avatars.githubusercontent.com/u/5708590?v=4 + url: https://github.com/Baghdady92 + - login: sdevkota + avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4 + url: https://github.com/sdevkota + - login: rangulvers + avatarUrl: https://avatars.githubusercontent.com/u/5235430?u=e254d4af4ace5a05fa58372ae677c7d26f0d5a53&v=4 + url: https://github.com/rangulvers +- - login: KOZ39 avatarUrl: https://avatars.githubusercontent.com/u/38822500?u=9dfc0a697df1c9628f08e20dc3fb17b1afc4e5a7&v=4 url: https://github.com/KOZ39 - login: rwxd @@ -365,27 +359,24 @@ sponsors: - login: Olegt0rr avatarUrl: https://avatars.githubusercontent.com/u/25399456?u=3e87b5239a2f4600975ba13be73054f8567c6060&v=4 url: https://github.com/Olegt0rr - - login: dinoz0rg - avatarUrl: https://avatars.githubusercontent.com/u/32940067?u=739cda1eb123a2dd5e1db45c361396f239e23f8b&v=4 - url: https://github.com/dinoz0rg - login: larsyngvelundin avatarUrl: https://avatars.githubusercontent.com/u/34173819?u=74958599695bf83ac9f1addd935a51548a10c6b0&v=4 url: https://github.com/larsyngvelundin - - login: hippoley - avatarUrl: https://avatars.githubusercontent.com/u/135493401?u=1164ef48a645a7c12664fabc1638fbb7e1c459b0&v=4 - url: https://github.com/hippoley - - login: 4anklee - avatarUrl: https://avatars.githubusercontent.com/u/144109238?u=a79c0d581b2a3d8f3897e7ef4c012640a6c1eb3a&v=4 - url: https://github.com/4anklee + - login: andrecorumba + avatarUrl: https://avatars.githubusercontent.com/u/37807517?u=9b9be3b41da9bda60957da9ef37b50dbf65baa61&v=4 + url: https://github.com/andrecorumba - login: CoderDeltaLAN avatarUrl: https://avatars.githubusercontent.com/u/152043745?u=4ff541efffb7d134e60c5fcf2dd1e343f90bb782&v=4 url: https://github.com/CoderDeltaLAN - - login: onestn - avatarUrl: https://avatars.githubusercontent.com/u/62360849?u=746dd21c34e7e06eefb11b03e8bb01aaae3c2a4f&v=4 - url: https://github.com/onestn + - login: hippoley + avatarUrl: https://avatars.githubusercontent.com/u/135493401?u=1164ef48a645a7c12664fabc1638fbb7e1c459b0&v=4 + url: https://github.com/hippoley - login: nayasinghania avatarUrl: https://avatars.githubusercontent.com/u/74111380?u=752e99a5e139389fdc0a0677122adc08438eb076&v=4 url: https://github.com/nayasinghania + - login: onestn + avatarUrl: https://avatars.githubusercontent.com/u/62360849?u=746dd21c34e7e06eefb11b03e8bb01aaae3c2a4f&v=4 + url: https://github.com/onestn - login: Toothwitch avatarUrl: https://avatars.githubusercontent.com/u/1710406?u=5eebb23b46cd26e48643b9e5179536cad491c17a&v=4 url: https://github.com/Toothwitch From 862c3f4f94bcffdedb62589632fe650adce92f6a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 2 Jan 2026 06:46:59 +0000 Subject: [PATCH 043/110] =?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 34ed433ce7..520554e255 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Internal +* ๐Ÿ‘ฅ Update FastAPI GitHub topic repositories. PR [#14630](https://github.com/fastapi/fastapi/pull/14630) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ฅ Update FastAPI People - Contributors and Translators. PR [#14625](https://github.com/fastapi/fastapi/pull/14625) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translation prompts. PR [#14619](https://github.com/fastapi/fastapi/pull/14619) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Update LLM translation script to guide reviewers to change the prompt. PR [#14614](https://github.com/fastapi/fastapi/pull/14614) by [@tiangolo](https://github.com/tiangolo). From f2687dc1bb01f4cb62eee90e656b61c38c2aaf6a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 2 Jan 2026 06:47:25 +0000 Subject: [PATCH 044/110] =?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 520554e255..9bd794f03a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Internal +* ๐Ÿ‘ฅ Update FastAPI People - Sponsors. PR [#14626](https://github.com/fastapi/fastapi/pull/14626) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ฅ Update FastAPI GitHub topic repositories. PR [#14630](https://github.com/fastapi/fastapi/pull/14630) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ฅ Update FastAPI People - Contributors and Translators. PR [#14625](https://github.com/fastapi/fastapi/pull/14625) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translation prompts. PR [#14619](https://github.com/fastapi/fastapi/pull/14619) by [@tiangolo](https://github.com/tiangolo). From b1db1395b6847b84690966182356f99a5bb595ff Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:35:09 +0300 Subject: [PATCH 045/110] =?UTF-8?q?=F0=9F=93=9D=20Specify=20language=20cod?= =?UTF-8?q?e=20for=20code=20block=20(#14656)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/bigger-applications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/tutorial/bigger-applications.md b/docs/en/docs/tutorial/bigger-applications.md index 3cc9d7ecfb..f6cee8036b 100644 --- a/docs/en/docs/tutorial/bigger-applications.md +++ b/docs/en/docs/tutorial/bigger-applications.md @@ -56,7 +56,7 @@ from app.routers import items The same file structure with comments: -``` +```bash . โ”œโ”€โ”€ app # "app" is a Python package โ”‚ย ย  โ”œโ”€โ”€ __init__.py # this file makes "app" a "Python package" From 18762e38a9000b91564d938f7b38f8fc86d4b8bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 10 Jan 2026 21:35:32 +0000 Subject: [PATCH 046/110] =?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 9bd794f03a..f9b694e2e6 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* ๐Ÿ“ Specify language code for code block. PR [#14656](https://github.com/fastapi/fastapi/pull/14656) by [@YuriiMotov](https://github.com/YuriiMotov). + ### Translations * ๐Ÿ”ง Add LLM prompt file for Turkish, generated from the existing translations. PR [#14547](https://github.com/fastapi/fastapi/pull/14547) by [@tiangolo](https://github.com/tiangolo). From d1c67c0055af1c0d187a55cf8ba3f020a73303c1 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:48:08 +0300 Subject: [PATCH 047/110] =?UTF-8?q?=F0=9F=94=A8=20Add=20LLM=20translations?= =?UTF-8?q?=20tool=20fixer=20(#14652)?= 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 --- scripts/doc_parsing_utils.py | 729 ++++++++++++++++++ .../tests/test_translation_fixer/conftest.py | 32 + .../test_code_blocks/data/en_doc.md | 44 ++ .../data/translated_doc_lines_number_gt.md | 45 ++ .../data/translated_doc_lines_number_lt.md | 45 ++ .../translated_doc_mermaid_not_translated.md | 44 ++ .../data/translated_doc_mermaid_translated.md | 44 ++ .../data/translated_doc_number_gt.md | 50 ++ .../data/translated_doc_number_lt.md | 41 + .../data/translated_doc_wrong_lang_code.md | 46 ++ .../data/translated_doc_wrong_lang_code_2.md | 46 ++ .../test_code_blocks_lines_number_mismatch.py | 58 ++ .../test_code_blocks_mermaid.py | 59 ++ .../test_code_blocks_number_mismatch.py | 56 ++ .../test_code_blocks_wrong_lang_code.py | 58 ++ .../test_code_includes/data/en_doc.md | 13 + .../data/translated_doc_number_gt.md | 15 + .../data/translated_doc_number_lt.md | 13 + .../test_number_mismatch.py | 56 ++ .../test_complex_doc/data/en_doc.md | 244 ++++++ .../test_complex_doc/data/translated_doc.md | 240 ++++++ .../data/translated_doc_expected.md | 240 ++++++ .../test_complex_doc/test_compex_doc.py | 30 + .../test_header_permalinks/data/en_doc.md | 19 + .../data/translated_doc_level_mismatch_1.md | 19 + .../data/translated_doc_level_mismatch_2.md | 19 + .../data/translated_doc_number_gt.md | 19 + .../data/translated_doc_number_lt.md | 19 + .../test_header_level_mismatch.py | 60 ++ .../test_header_number_mismatch.py | 56 ++ .../test_html_links/data/en_doc.md | 19 + .../data/translated_doc_number_gt.md | 21 + .../data/translated_doc_number_lt.md | 19 + .../test_html_links_number_mismatch.py | 54 ++ .../test_markdown_links/data/en_doc.md | 19 + .../data/translated_doc.md | 19 + .../data/translated_doc_number_gt.md | 21 + .../data/translated_doc_number_lt.md | 19 + .../test_mkd_links_number_mismatch.py | 56 ++ scripts/translation_fixer.py | 132 ++++ 40 files changed, 2838 insertions(+) create mode 100644 scripts/doc_parsing_utils.py create mode 100644 scripts/tests/test_translation_fixer/conftest.py create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/data/en_doc.md create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_gt.md create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_lt.md create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_not_translated.md create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_translated.md create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_gt.md create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_lt.md create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code.md create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code_2.md create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py create mode 100644 scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py create mode 100644 scripts/tests/test_translation_fixer/test_code_includes/data/en_doc.md create mode 100644 scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_gt.md create mode 100644 scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_lt.md create mode 100644 scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py create mode 100644 scripts/tests/test_translation_fixer/test_complex_doc/data/en_doc.md create mode 100644 scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc.md create mode 100644 scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc_expected.md create mode 100644 scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py create mode 100644 scripts/tests/test_translation_fixer/test_header_permalinks/data/en_doc.md create mode 100644 scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_1.md create mode 100644 scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_2.md create mode 100644 scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_gt.md create mode 100644 scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_lt.md create mode 100644 scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py create mode 100644 scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py create mode 100644 scripts/tests/test_translation_fixer/test_html_links/data/en_doc.md create mode 100644 scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_gt.md create mode 100644 scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_lt.md create mode 100644 scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py create mode 100644 scripts/tests/test_translation_fixer/test_markdown_links/data/en_doc.md create mode 100644 scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc.md create mode 100644 scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_gt.md create mode 100644 scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_lt.md create mode 100644 scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py create mode 100644 scripts/translation_fixer.py diff --git a/scripts/doc_parsing_utils.py b/scripts/doc_parsing_utils.py new file mode 100644 index 0000000000..857d808aaa --- /dev/null +++ b/scripts/doc_parsing_utils.py @@ -0,0 +1,729 @@ +import re +from typing import TypedDict + +CODE_INCLUDE_RE = re.compile(r"^\{\*\s*(\S+)\s*(.*)\*\}$") +CODE_INCLUDE_PLACEHOLDER = "" + +HEADER_WITH_PERMALINK_RE = re.compile(r"^(#{1,6}) (.+?)(\s*\{\s*#.*\s*\})?\s*$") +HEADER_LINE_RE = re.compile(r"^(#{1,6}) (.+?)(?:\s*\{\s*(#.*)\s*\})?\s*$") + +TIANGOLO_COM = "https://fastapi.tiangolo.com" +ASSETS_URL_PREFIXES = ("/img/", "/css/", "/js/") + +MARKDOWN_LINK_RE = re.compile( + r"(?.*?)\]" # link text (non-greedy) + r"\(" + r"(?P[^)\s]+)" # url (no spaces and `)`) + r'(?:\s+["\'](?P.*?)["\'])?' # optional title in "" or '' + r"\)" + r"(?:\s*\{(?P<attrs>[^}]*)\})?" # optional attributes in {} +) + +HTML_LINK_RE = re.compile(r"<a\s+[^>]*>.*?</a>") +HTML_LINK_TEXT_RE = re.compile(r"<a\b([^>]*)>(.*?)</a>") +HTML_LINK_OPEN_TAG_RE = re.compile(r"<a\b([^>]*)>") +HTML_ATTR_RE = re.compile(r'(\w+)\s*=\s*([\'"])(.*?)\2') + +CODE_BLOCK_LANG_RE = re.compile(r"^`{3,4}([\w-]*)", re.MULTILINE) + +SLASHES_COMMENT_RE = re.compile( + r"^(?P<code>.*?)(?P<comment>(?:(?<= )// .*)|(?:^// .*))?$" +) + +HASH_COMMENT_RE = re.compile(r"^(?P<code>.*?)(?P<comment>(?:(?<= )# .*)|(?:^# .*))?$") + + +class CodeIncludeInfo(TypedDict): + line_no: int + line: str + + +class HeaderPermalinkInfo(TypedDict): + line_no: int + hashes: str + title: str + permalink: str + + +class MarkdownLinkInfo(TypedDict): + line_no: int + url: str + text: str + title: str | None + attributes: str | None + full_match: str + + +class HTMLLinkAttribute(TypedDict): + name: str + quote: str + value: str + + +class HtmlLinkInfo(TypedDict): + line_no: int + full_tag: str + attributes: list[HTMLLinkAttribute] + text: str + + +class MultilineCodeBlockInfo(TypedDict): + lang: str + start_line_no: int + content: list[str] + + +# Code includes +# -------------------------------------------------------------------------------------- + + +def extract_code_includes(lines: list[str]) -> list[CodeIncludeInfo]: + """ + Extract lines that contain code includes. + + Return list of CodeIncludeInfo, where each dict contains: + - `line_no` - line number (1-based) + - `line` - text of the line + """ + + includes: list[CodeIncludeInfo] = [] + for line_no, line in enumerate(lines, start=1): + if CODE_INCLUDE_RE.match(line): + includes.append(CodeIncludeInfo(line_no=line_no, line=line)) + return includes + + +def replace_code_includes_with_placeholders(text: list[str]) -> list[str]: + """ + Replace code includes with placeholders. + """ + + modified_text = text.copy() + includes = extract_code_includes(text) + for include in includes: + modified_text[include["line_no"] - 1] = CODE_INCLUDE_PLACEHOLDER + return modified_text + + +def replace_placeholders_with_code_includes( + text: list[str], original_includes: list[CodeIncludeInfo] +) -> list[str]: + """ + Replace code includes placeholders with actual code includes from the original (English) document. + Fail if the number of placeholders does not match the number of original includes. + """ + + code_include_lines = [ + line_no + for line_no, line in enumerate(text) + if line.strip() == CODE_INCLUDE_PLACEHOLDER + ] + + if len(code_include_lines) != len(original_includes): + raise ValueError( + "Number of code include placeholders does not match the number of code includes " + "in the original document " + f"({len(code_include_lines)} vs {len(original_includes)})" + ) + + modified_text = text.copy() + for i, line_no in enumerate(code_include_lines): + modified_text[line_no] = original_includes[i]["line"] + + return modified_text + + +# Header permalinks +# -------------------------------------------------------------------------------------- + + +def extract_header_permalinks(lines: list[str]) -> list[HeaderPermalinkInfo]: + """ + Extract list of header permalinks from the given lines. + + Return list of HeaderPermalinkInfo, where each dict contains: + - `line_no` - line number (1-based) + - `hashes` - string of hashes representing header level (e.g., "###") + - `permalink` - permalink string (e.g., "{#permalink}") + """ + + headers: list[HeaderPermalinkInfo] = [] + in_code_block3 = False + in_code_block4 = False + + for line_no, line in enumerate(lines, start=1): + if not (in_code_block3 or in_code_block4): + if line.startswith("```"): + count = len(line) - len(line.lstrip("`")) + if count == 3: + in_code_block3 = True + continue + elif count >= 4: + in_code_block4 = True + continue + + header_match = HEADER_WITH_PERMALINK_RE.match(line) + if header_match: + hashes, title, permalink = header_match.groups() + headers.append( + HeaderPermalinkInfo( + hashes=hashes, line_no=line_no, permalink=permalink, title=title + ) + ) + + elif in_code_block3: + if line.startswith("```"): + count = len(line) - len(line.lstrip("`")) + if count == 3: + in_code_block3 = False + continue + + elif in_code_block4: + if line.startswith("````"): + count = len(line) - len(line.lstrip("`")) + if count >= 4: + in_code_block4 = False + continue + + return headers + + +def remove_header_permalinks(lines: list[str]) -> list[str]: + """ + Remove permalinks from headers in the given lines. + """ + + modified_lines: list[str] = [] + for line in lines: + header_match = HEADER_WITH_PERMALINK_RE.match(line) + if header_match: + hashes, title, _permalink = header_match.groups() + modified_line = f"{hashes} {title}" + modified_lines.append(modified_line) + else: + modified_lines.append(line) + return modified_lines + + +def replace_header_permalinks( + text: list[str], + header_permalinks: list[HeaderPermalinkInfo], + original_header_permalinks: list[HeaderPermalinkInfo], +) -> list[str]: + """ + Replace permalinks in the given text with the permalinks from the original document. + + Fail if the number or level of headers does not match the original. + """ + + modified_text: list[str] = text.copy() + + if len(header_permalinks) != len(original_header_permalinks): + raise ValueError( + "Number of headers with permalinks does not match the number in the " + "original document " + f"({len(header_permalinks)} vs {len(original_header_permalinks)})" + ) + + for header_no in range(len(header_permalinks)): + header_info = header_permalinks[header_no] + original_header_info = original_header_permalinks[header_no] + + if header_info["hashes"] != original_header_info["hashes"]: + raise ValueError( + "Header levels do not match between document and original document" + f" (found {header_info['hashes']}, expected {original_header_info['hashes']})" + f" for header โ„–{header_no + 1} in line {header_info['line_no']}" + ) + line_no = header_info["line_no"] - 1 + hashes = header_info["hashes"] + title = header_info["title"] + permalink = original_header_info["permalink"] + modified_text[line_no] = f"{hashes} {title}{permalink}" + + return modified_text + + +# Markdown links +# -------------------------------------------------------------------------------------- + + +def extract_markdown_links(lines: list[str]) -> list[MarkdownLinkInfo]: + """ + Extract all markdown links from the given lines. + + Return list of MarkdownLinkInfo, where each dict contains: + - `line_no` - line number (1-based) + - `url` - link URL + - `text` - link text + - `title` - link title (if any) + """ + + links: list[MarkdownLinkInfo] = [] + for line_no, line in enumerate(lines, start=1): + for m in MARKDOWN_LINK_RE.finditer(line): + links.append( + MarkdownLinkInfo( + line_no=line_no, + url=m.group("url"), + text=m.group("text"), + title=m.group("title"), + attributes=m.group("attrs"), + full_match=m.group(0), + ) + ) + return links + + +def _add_lang_code_to_url(url: str, lang_code: str) -> str: + if url.startswith(TIANGOLO_COM): + rel_url = url[len(TIANGOLO_COM) :] + if not rel_url.startswith(ASSETS_URL_PREFIXES): + url = url.replace(TIANGOLO_COM, f"{TIANGOLO_COM}/{lang_code}") + return url + + +def _construct_markdown_link( + url: str, text: str, title: str | None, attributes: str | None, lang_code: str +) -> str: + """ + Construct a markdown link, adjusting the URL for the given language code if needed. + """ + url = _add_lang_code_to_url(url, lang_code) + + if title: + link = f'[{text}]({url} "{title}")' + else: + link = f"[{text}]({url})" + + if attributes: + link += f"{{{attributes}}}" + + return link + + +def replace_markdown_links( + text: list[str], + links: list[MarkdownLinkInfo], + original_links: list[MarkdownLinkInfo], + lang_code: str, +) -> list[str]: + """ + Replace markdown links in the given text with the original links. + + Fail if the number of links does not match the original. + """ + + if len(links) != len(original_links): + raise ValueError( + "Number of markdown links does not match the number in the " + "original document " + f"({len(links)} vs {len(original_links)})" + ) + + modified_text = text.copy() + for i, link_info in enumerate(links): + link_text = link_info["text"] + link_title = link_info["title"] + original_link_info = original_links[i] + + # Replace + replacement_link = _construct_markdown_link( + url=original_link_info["url"], + text=link_text, + title=link_title, + attributes=original_link_info["attributes"], + lang_code=lang_code, + ) + line_no = link_info["line_no"] - 1 + modified_line = modified_text[line_no] + modified_line = modified_line.replace( + link_info["full_match"], replacement_link, 1 + ) + modified_text[line_no] = modified_line + + return modified_text + + +# HTML links +# -------------------------------------------------------------------------------------- + + +def extract_html_links(lines: list[str]) -> list[HtmlLinkInfo]: + """ + Extract all HTML links from the given lines. + + Return list of HtmlLinkInfo, where each dict contains: + - `line_no` - line number (1-based) + - `full_tag` - full HTML link tag + - `attributes` - list of HTMLLinkAttribute (name, quote, value) + - `text` - link text + """ + + links = [] + for line_no, line in enumerate(lines, start=1): + for html_link in HTML_LINK_RE.finditer(line): + link_str = html_link.group(0) + + link_text_match = HTML_LINK_TEXT_RE.match(link_str) + assert link_text_match is not None + link_text = link_text_match.group(2) + assert isinstance(link_text, str) + + link_open_tag_match = HTML_LINK_OPEN_TAG_RE.match(link_str) + assert link_open_tag_match is not None + link_open_tag = link_open_tag_match.group(1) + assert isinstance(link_open_tag, str) + + attributes: list[HTMLLinkAttribute] = [] + for attr_name, attr_quote, attr_value in re.findall( + HTML_ATTR_RE, link_open_tag + ): + assert isinstance(attr_name, str) + assert isinstance(attr_quote, str) + assert isinstance(attr_value, str) + attributes.append( + HTMLLinkAttribute( + name=attr_name, quote=attr_quote, value=attr_value + ) + ) + links.append( + HtmlLinkInfo( + line_no=line_no, + full_tag=link_str, + attributes=attributes, + text=link_text, + ) + ) + return links + + +def _construct_html_link( + link_text: str, + attributes: list[HTMLLinkAttribute], + lang_code: str, +) -> str: + """ + Reconstruct HTML link, adjusting the URL for the given language code if needed. + """ + + attributes_upd: list[HTMLLinkAttribute] = [] + for attribute in attributes: + if attribute["name"] == "href": + original_url = attribute["value"] + url = _add_lang_code_to_url(original_url, lang_code) + attributes_upd.append( + HTMLLinkAttribute(name="href", quote=attribute["quote"], value=url) + ) + else: + attributes_upd.append(attribute) + + attrs_str = " ".join( + f"{attribute['name']}={attribute['quote']}{attribute['value']}{attribute['quote']}" + for attribute in attributes_upd + ) + return f"<a {attrs_str}>{link_text}</a>" + + +def replace_html_links( + text: list[str], + links: list[HtmlLinkInfo], + original_links: list[HtmlLinkInfo], + lang_code: str, +) -> list[str]: + """ + Replace HTML links in the given text with the links from the original document. + + Adjust URLs for the given language code. + Fail if the number of links does not match the original. + """ + + if len(links) != len(original_links): + raise ValueError( + "Number of HTML links does not match the number in the " + "original document " + f"({len(links)} vs {len(original_links)})" + ) + + modified_text = text.copy() + for link_index, link in enumerate(links): + original_link_info = original_links[link_index] + + # Replace in the document text + replacement_link = _construct_html_link( + link_text=link["text"], + attributes=original_link_info["attributes"], + lang_code=lang_code, + ) + line_no = link["line_no"] - 1 + modified_text[line_no] = modified_text[line_no].replace( + link["full_tag"], replacement_link, 1 + ) + + return modified_text + + +# Multiline code blocks +# -------------------------------------------------------------------------------------- + + +def get_code_block_lang(line: str) -> str: + match = CODE_BLOCK_LANG_RE.match(line) + if match: + return match.group(1) + return "" + + +def extract_multiline_code_blocks(text: list[str]) -> list[MultilineCodeBlockInfo]: + blocks: list[MultilineCodeBlockInfo] = [] + + in_code_block3 = False + in_code_block4 = False + current_block_lang = "" + current_block_start_line = -1 + current_block_lines = [] + + for line_no, line in enumerate(text, start=1): + stripped = line.lstrip() + + # --- Detect opening fence --- + if not (in_code_block3 or in_code_block4): + if stripped.startswith("```"): + current_block_start_line = line_no + count = len(stripped) - len(stripped.lstrip("`")) + if count == 3: + in_code_block3 = True + current_block_lang = get_code_block_lang(stripped) + current_block_lines = [line] + continue + elif count >= 4: + in_code_block4 = True + current_block_lang = get_code_block_lang(stripped) + current_block_lines = [line] + continue + + # --- Detect closing fence --- + elif in_code_block3: + if stripped.startswith("```"): + count = len(stripped) - len(stripped.lstrip("`")) + if count == 3: + current_block_lines.append(line) + blocks.append( + MultilineCodeBlockInfo( + lang=current_block_lang, + start_line_no=current_block_start_line, + content=current_block_lines, + ) + ) + in_code_block3 = False + current_block_lang = "" + current_block_start_line = -1 + current_block_lines = [] + continue + current_block_lines.append(line) + + elif in_code_block4: + if stripped.startswith("````"): + count = len(stripped) - len(stripped.lstrip("`")) + if count >= 4: + current_block_lines.append(line) + blocks.append( + MultilineCodeBlockInfo( + lang=current_block_lang, + start_line_no=current_block_start_line, + content=current_block_lines, + ) + ) + in_code_block4 = False + current_block_lang = "" + current_block_start_line = -1 + current_block_lines = [] + continue + current_block_lines.append(line) + + return blocks + + +def _split_hash_comment(line: str) -> tuple[str, str | None]: + match = HASH_COMMENT_RE.match(line) + if match: + code = match.group("code").rstrip() + comment = match.group("comment") + return code, comment + return line.rstrip(), None + + +def _split_slashes_comment(line: str) -> tuple[str, str | None]: + match = SLASHES_COMMENT_RE.match(line) + if match: + code = match.group("code").rstrip() + comment = match.group("comment") + return code, comment + return line, None + + +def replace_multiline_code_block( + block_a: MultilineCodeBlockInfo, block_b: MultilineCodeBlockInfo +) -> list[str]: + """ + Replace multiline code block `a` with block `b` leaving comments intact. + + Syntax of comments depends on the language of the code block. + Raises ValueError if the blocks are not compatible (different languages or different number of lines). + """ + + start_line = block_a["start_line_no"] + end_line_no = start_line + len(block_a["content"]) - 1 + + if block_a["lang"] != block_b["lang"]: + raise ValueError( + f"Code block (lines {start_line}-{end_line_no}) " + "has different language than the original block " + f"('{block_a['lang']}' vs '{block_b['lang']}')" + ) + if len(block_a["content"]) != len(block_b["content"]): + raise ValueError( + f"Code block (lines {start_line}-{end_line_no}) " + "has different number of lines than the original block " + f"({len(block_a['content'])} vs {len(block_b['content'])})" + ) + + block_language = block_a["lang"].lower() + if block_language in {"mermaid"}: + if block_a != block_b: + print( + f"Skipping mermaid code block replacement (lines {start_line}-{end_line_no}). " + "This should be checked manually." + ) + 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: str | None = None + line_b_comment: str | None = None + + # Handle comments based on language + if block_language in { + "python", + "py", + "sh", + "bash", + "dockerfile", + "requirements", + "gitignore", + "toml", + "yaml", + "yml", + "hash-style-comments", + }: + _line_a_code, line_a_comment = _split_hash_comment(line_a) + _line_b_code, line_b_comment = _split_hash_comment(line_b) + res_line = line_b + if line_b_comment: + res_line = res_line.replace(line_b_comment, line_a_comment, 1) + code_block.append(res_line) + elif block_language in {"console", "json", "slash-style-comments"}: + _line_a_code, line_a_comment = _split_slashes_comment(line_a) + _line_b_code, line_b_comment = _split_slashes_comment(line_b) + res_line = line_b + if line_b_comment: + res_line = res_line.replace(line_b_comment, line_a_comment, 1) + code_block.append(res_line) + else: + code_block.append(line_b) + + return code_block + + +def replace_multiline_code_blocks_in_text( + text: list[str], + code_blocks: list[MultilineCodeBlockInfo], + original_code_blocks: list[MultilineCodeBlockInfo], +) -> list[str]: + """ + Update each code block in `text` with the corresponding code block from + `original_code_blocks` with comments taken from `code_blocks`. + + Raises ValueError if the number, language, or shape of code blocks do not match. + """ + + if len(code_blocks) != len(original_code_blocks): + raise ValueError( + "Number of code blocks does not match the number in the original document " + f"({len(code_blocks)} vs {len(original_code_blocks)})" + ) + + modified_text = text.copy() + for block, original_block in zip(code_blocks, original_code_blocks): + updated_content = replace_multiline_code_block(block, original_block) + + start_line_index = block["start_line_no"] - 1 + for i, updated_line in enumerate(updated_content): + modified_text[start_line_index + i] = updated_line + + return modified_text + + +# All checks +# -------------------------------------------------------------------------------------- + + +def check_translation( + doc_lines: list[str], + en_doc_lines: list[str], + lang_code: str, + auto_fix: bool, + path: str, +) -> list[str]: + # Fix code includes + en_code_includes = extract_code_includes(en_doc_lines) + doc_lines_with_placeholders = replace_code_includes_with_placeholders(doc_lines) + fixed_doc_lines = replace_placeholders_with_code_includes( + doc_lines_with_placeholders, en_code_includes + ) + if auto_fix and (fixed_doc_lines != doc_lines): + print(f"Fixing code includes in: {path}") + doc_lines = fixed_doc_lines + + # Fix permalinks + en_permalinks = extract_header_permalinks(en_doc_lines) + doc_permalinks = extract_header_permalinks(doc_lines) + fixed_doc_lines = replace_header_permalinks( + doc_lines, doc_permalinks, en_permalinks + ) + if auto_fix and (fixed_doc_lines != doc_lines): + print(f"Fixing header permalinks in: {path}") + doc_lines = fixed_doc_lines + + # Fix markdown links + en_markdown_links = extract_markdown_links(en_doc_lines) + doc_markdown_links = extract_markdown_links(doc_lines) + fixed_doc_lines = replace_markdown_links( + doc_lines, doc_markdown_links, en_markdown_links, lang_code + ) + if auto_fix and (fixed_doc_lines != doc_lines): + print(f"Fixing markdown links in: {path}") + doc_lines = fixed_doc_lines + + # Fix HTML links + en_html_links = extract_html_links(en_doc_lines) + doc_html_links = extract_html_links(doc_lines) + fixed_doc_lines = replace_html_links( + doc_lines, doc_html_links, en_html_links, lang_code + ) + if auto_fix and (fixed_doc_lines != doc_lines): + print(f"Fixing HTML links in: {path}") + doc_lines = fixed_doc_lines + + # Fix multiline code blocks + en_code_blocks = extract_multiline_code_blocks(en_doc_lines) + doc_code_blocks = extract_multiline_code_blocks(doc_lines) + fixed_doc_lines = replace_multiline_code_blocks_in_text( + doc_lines, doc_code_blocks, en_code_blocks + ) + if auto_fix and (fixed_doc_lines != doc_lines): + print(f"Fixing multiline code blocks in: {path}") + doc_lines = fixed_doc_lines + + return doc_lines diff --git a/scripts/tests/test_translation_fixer/conftest.py b/scripts/tests/test_translation_fixer/conftest.py new file mode 100644 index 0000000000..b2c745de11 --- /dev/null +++ b/scripts/tests/test_translation_fixer/conftest.py @@ -0,0 +1,32 @@ +import shutil +from pathlib import Path + +import pytest +from typer.testing import CliRunner + + +@pytest.fixture(name="runner") +def get_runner(): + runner = CliRunner() + with runner.isolated_filesystem(): + yield runner + + +@pytest.fixture(name="root_dir") +def prepare_paths(runner): + docs_dir = Path("docs") + en_docs_dir = docs_dir / "en" / "docs" + lang_docs_dir = docs_dir / "lang" / "docs" + en_docs_dir.mkdir(parents=True, exist_ok=True) + lang_docs_dir.mkdir(parents=True, exist_ok=True) + yield Path.cwd() + + +@pytest.fixture +def copy_test_files(root_dir: Path, request: pytest.FixtureRequest): + en_file_path = Path(request.param[0]) + translation_file_path = Path(request.param[1]) + shutil.copy(str(en_file_path), str(root_dir / "docs" / "en" / "docs" / "doc.md")) + shutil.copy( + str(translation_file_path), str(root_dir / "docs" / "lang" / "docs" / "doc.md") + ) diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/en_doc.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/en_doc.md new file mode 100644 index 0000000000..cad20e2c70 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/en_doc.md @@ -0,0 +1,44 @@ +# Code blocks { #code-blocks } + +Some text + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +Some more text + +```toml +# This is a sample TOML code block +title = "TOML Example" # Title of the document +``` + +And more text + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +And even more text + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + +Mermaid diagram + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_gt.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_gt.md new file mode 100644 index 0000000000..f46070156b --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_gt.md @@ -0,0 +1,45 @@ +# Code blocks { #code-blocks } + +Some text + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +Some more text + +```toml +# Extra line +# This is a sample TOML code block +title = "TOML Example" # Title of the document +``` + +And more text + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +And even more text + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + +ะ”ะธะฐะณั€ะฐะผะฐ Mermaid + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_lt.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_lt.md new file mode 100644 index 0000000000..e08baa70b1 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_lt.md @@ -0,0 +1,45 @@ +# Code blocks { #code-blocks } + +Some text + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +Some more text + +The following block is missing first line: + +```toml +title = "TOML Example" # Title of the document +``` + +And more text + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +And even more text + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + +ะ”ะธะฐะณั€ะฐะผะฐ Mermaid + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_not_translated.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_not_translated.md new file mode 100644 index 0000000000..cacb9546d0 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_not_translated.md @@ -0,0 +1,44 @@ +# Code blocks { #code-blocks } + +Some text + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +Some more text + +```toml +# This is a sample TOML code block +title = "TOML Example" # Title of the document +``` + +And more text + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +And even more text + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + +ะ”ะธะฐะณั€ะฐะผะฐ Mermaid + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_translated.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_translated.md new file mode 100644 index 0000000000..d03dca53eb --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_translated.md @@ -0,0 +1,44 @@ +# Code blocks { #code-blocks } + +Some text + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +Some more text + +```toml +# This is a sample TOML code block +title = "TOML Example" # Title of the document +``` + +And more text + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +And even more text + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + +ะ”ะธะฐะณั€ะฐะผะฐ Mermaid + +```mermaid +flowchart LR + stone(philosophers-stone) -->|ั‚ั€ะตะฑัƒะตั‚| harry-1[harry v1] +``` diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_gt.md new file mode 100644 index 0000000000..e77050cc95 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_gt.md @@ -0,0 +1,50 @@ +# Code blocks { #code-blocks } + +Some text + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +Some more text + +```toml +# This is a sample TOML code block +title = "TOML Example" # Title of the document +``` + +Extra code block + +``` +$ cd my_project +``` + +And more text + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +And even more text + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + +ะ”ะธะฐะณั€ะฐะผะฐ Mermaid + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_lt.md new file mode 100644 index 0000000000..918cb883f4 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_lt.md @@ -0,0 +1,41 @@ +# Code blocks { #code-blocks } + +Some text + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +Some more text + +Missing code block... + +And more text + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +And even more text + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + +ะ”ะธะฐะณั€ะฐะผะฐ Mermaid + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code.md new file mode 100644 index 0000000000..88aed900d3 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code.md @@ -0,0 +1,46 @@ +# Code blocks { #code-blocks } + +Some text + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +Some more text + +The following block has wrong language code (should be TOML): + +```yaml +# This is a sample TOML code block +title = "TOML Example" # Title of the document +``` + +And more text + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +And even more text + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + +ะ”ะธะฐะณั€ะฐะผะฐ Mermaid + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code_2.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code_2.md new file mode 100644 index 0000000000..a7fbb39f54 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code_2.md @@ -0,0 +1,46 @@ +# Code blocks { #code-blocks } + +Some text + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +Some more text + +The following block has wrong language code (should be TOML): + +``` +# This is a sample TOML code block +title = "TOML Example" # Title of the document +``` + +And more text + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +And even more text + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + +ะ”ะธะฐะณั€ะฐะผะฐ Mermaid + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py new file mode 100644 index 0000000000..906c8a560a --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py @@ -0,0 +1,58 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path( + "scripts/tests/test_translation_fixer/test_code_blocks/data" +).absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_lines_number_gt.md")], + indirect=True, +) +def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path( + f"{data_path}/translated_doc_lines_number_gt.md" + ).read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Code block (lines 14-18) has different number of lines than the original block (5 vs 4)" + ) in result.output + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_lines_number_lt.md")], + indirect=True, +) +def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + # assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path( + f"{data_path}/translated_doc_lines_number_lt.md" + ).read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Code block (lines 16-18) has different number of lines than the original block (3 vs 4)" + ) in result.output diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py new file mode 100644 index 0000000000..75c589fb50 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py @@ -0,0 +1,59 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path( + "scripts/tests/test_translation_fixer/test_code_blocks/data" +).absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_mermaid_translated.md")], + indirect=True, +) +def test_translated(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 0, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path( + f"{data_path}/translated_doc_mermaid_translated.md" + ).read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert ( + "Skipping mermaid code block replacement (lines 41-44). This should be checked manually." + ) in result.output + + +@pytest.mark.parametrize( + "copy_test_files", + [ + ( + f"{data_path}/en_doc.md", + f"{data_path}/translated_doc_mermaid_not_translated.md", + ) + ], + indirect=True, +) +def test_not_translated(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 0, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path( + f"{data_path}/translated_doc_mermaid_not_translated.md" + ).read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert ("Skipping mermaid code block replacement") not in result.output diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py new file mode 100644 index 0000000000..b05dac900e --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py @@ -0,0 +1,56 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path( + "scripts/tests/test_translation_fixer/test_code_blocks/data" +).absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")], + indirect=True, +) +def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of code blocks does not match the number " + "in the original document (6 vs 5)" + ) in result.output + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")], + indirect=True, +) +def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + # assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of code blocks does not match the number " + "in the original document (4 vs 5)" + ) in result.output diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py new file mode 100644 index 0000000000..6c2b18c89a --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py @@ -0,0 +1,58 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path( + "scripts/tests/test_translation_fixer/test_code_blocks/data" +).absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_wrong_lang_code.md")], + indirect=True, +) +def test_wrong_lang_code_1(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path( + f"{data_path}/translated_doc_wrong_lang_code.md" + ).read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Code block (lines 16-19) has different language than the original block ('yaml' vs 'toml')" + ) in result.output + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_wrong_lang_code_2.md")], + indirect=True, +) +def test_wrong_lang_code_2(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path( + f"{data_path}/translated_doc_wrong_lang_code_2.md" + ).read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Code block (lines 16-19) has different language than the original block ('' vs 'toml')" + ) in result.output 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 new file mode 100644 index 0000000000..593da0b327 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_includes/data/en_doc.md @@ -0,0 +1,13 @@ +# Header + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +Some text + +{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +Some more text + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +And even 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 new file mode 100644 index 0000000000..c1ad94d276 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_gt.md @@ -0,0 +1,15 @@ +# Header + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +Some text + +{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +Some more text + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +And even more text + +{* ../../docs_src/python_types/tutorial001_py39.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 new file mode 100644 index 0000000000..07eaf2c23d --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_lt.md @@ -0,0 +1,13 @@ +# Header + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +Some text + +{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +Some more text + +... + +And even more text diff --git a/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py b/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py new file mode 100644 index 0000000000..5e3eee51af --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py @@ -0,0 +1,56 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path( + "scripts/tests/test_translation_fixer/test_code_includes/data" +).absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")], + indirect=True, +) +def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1 + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of code include placeholders does not match the number of code includes " + "in the original document (4 vs 3)" + ) in result.output + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")], + indirect=True, +) +def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1 + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of code include placeholders does not match the number of code includes " + "in the original document (2 vs 3)" + ) in result.output 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 new file mode 100644 index 0000000000..69cd3f3fd7 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_complex_doc/data/en_doc.md @@ -0,0 +1,244 @@ +# Test translation fixer tool { #test-translation-fixer } + +## Code blocks with and without comments { #code-blocks-with-and-without-comments } + +This is a test page for the translation fixer tool. + +### Code blocks with comments { #code-blocks-with-comments } + +The following code blocks include comments in different styles. +Fixer tool should fix content, but preserve comments correctly. + +```python +# This is a sample Python code block +def hello_world(): + # Comment with indentation + print("Hello, world!") # Print greeting +``` + +```toml +# This is a sample TOML code block +title = "TOML Example" # Title of the document +``` + +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +```json +{ + // This is a sample JSON code block + "greeting": "Hello, world!" // Greeting +} +``` + + +### Code blocks with comments where language uses different comment styles { #code-blocks-with-different-comment-styles } + +The following code blocks include comments in different styles based on the language. +Fixer tool will not preserve comments in these blocks. + +```json +{ + # This is a sample JSON code block + "greeting": "Hello, world!" # Print greeting +} +``` + +```console +# This is a sample console code block +$ echo "Hello, world!" # Print greeting +``` + +```toml +// This is a sample TOML code block +title = "TOML Example" // Title of the document +``` + + +### Code blocks with comments with unsupported languages or without language specified { #code-blocks-with-unsupported-languages } + +The following code blocks use unsupported languages for comment preservation. +Fixer tool will not preserve comments in these blocks. + +```javascript +// This is a sample JavaScript code block +console.log("Hello, world!"); // Print greeting +``` + +``` +# This is a sample console code block +$ echo "Hello, world!" # Print greeting +``` + +``` +// This is a sample console code block +$ echo "Hello, world!" // Print greeting +``` + + +### Code blocks with comments that don't follow pattern { #code-blocks-with-comments-without-pattern } + +Fixer tool expects comments that follow specific pattern: + +- For hash-style comments: comment starts with `# ` (hash following by whitespace) in the beginning of the string or after a whitespace. +- For slash-style comments: comment starts with `// ` (two slashes following by whitespace) in the beginning of the string or after a whitespace. + +If comment doesn't follow this pattern, fixer tool will not preserve it. + +```python +#Function declaration +def hello_world():# Print greeting + print("Hello, world!") #Print greeting without space after hash +``` + +```console +//Function declaration +def hello_world():// Print greeting + print("Hello, world!") //Print greeting without space after slashes +``` + +## Code blocks with quadruple backticks { #code-blocks-with-quadruple-backticks } + +The following code block uses quadruple backticks. + +````python +# Hello world function +def hello_world(): + print("Hello, world!") # Print greeting +```` + +### Backticks number mismatch is fixable { #backticks-number-mismatch-is-fixable } + +The following code block has triple backticks in the original document, but quadruple backticks in the translated document. +It will be fixed by the fixer tool (will convert to triple backticks). + +```Python +# Some Python code +``` + +### Triple backticks inside quadruple backticks { #triple-backticks-inside-quadruple-backticks } + +Comments inside nested code block will NOT be preserved. + +```` +Here is a code block with quadruple backticks that contains triple backticks inside: + +```python +# This is a sample Python code block +def hello_world(): + print("Hello, world!") # Print greeting +``` + +```` + +# Code includes { #code-includes } + +## Simple code includes { #simple-code-includes } + +{* ../../docs_src/python_types/tutorial001_py39.py *} + +{* ../../docs_src/python_types/tutorial002_py39.py *} + + +## Code includes with highlighting { #code-includes-with-highlighting } + +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} + +{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *} + + +## Code includes with line ranges { #code-includes-with-line-ranges } + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] *} + + +## Code includes with line ranges and highlighting { #code-includes-with-line-ranges-and-highlighting } + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +{* ../../docs_src/dependencies/tutorial015_an_py310.py ln[10:15] hl[12:14] *} + + +## 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_py39/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] *} + +## 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/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + + + +# Links { #links } + +## Markdown-style links { #markdown-style-links } + +This is a [Markdown link](https://example.com) to an external site. + +This is a link with attributes: [**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank} + +This is a link to the main FastAPI site: [FastAPI](https://fastapi.tiangolo.com) - tool should add language code to the URL. + +This is a link to one of the pages on FastAPI site: [How to](https://fastapi.tiangolo.com/how-to/) - tool should add language code to the URL. + +Link to test wrong attribute: [**FastAPI** Project Generators](project-generation.md){.internal-link} - tool should fix the attribute. + +Link with a title: [Example](https://example.com "Example site") - URL will be fixed, title preserved. + +### Markdown link to static assets { #markdown-link-to-static-assets } + +These are links to static assets: + +* [FastAPI Logo](https://fastapi.tiangolo.com/img/fastapi-logo.png) +* [FastAPI CSS](https://fastapi.tiangolo.com/css/fastapi.css) +* [FastAPI JS](https://fastapi.tiangolo.com/js/fastapi.js) + +Tool should NOT add language code to their URLs. + +## HTML-style links { #html-style-links } + +This is an <a href="https://example.com" target="_blank" class="external-link">HTML link</a> to an external site. + +This is an <a href="https://fastapi.tiangolo.com">link to the main FastAPI site</a> - tool should add language code to the URL. + +This is an <a href="https://fastapi.tiangolo.com/how-to/">link to one of the pages on FastAPI site</a> - tool should add language code to the URL. + +Link to test wrong attribute: <a href="project-generation.md" class="internal-link">**FastAPI** Project Generators</a> - tool should fix the attribute. + +### HTML links to static assets { #html-links-to-static-assets } + +These are links to static assets: + +* <a href="https://fastapi.tiangolo.com/img/fastapi-logo.png">FastAPI Logo</a> +* <a href="https://fastapi.tiangolo.com/css/fastapi.css">FastAPI CSS</a> +* <a href="https://fastapi.tiangolo.com/js/fastapi.js">FastAPI JS</a> + +Tool should NOT add language code to their URLs. + +# Header (with HTML link to <a href="https://tiangolo.com">tiangolo.com</a>) { #header-with-html-link-to-tiangolo-com } + +#Not a header + +```Python +# Also not a header +``` + +Some text 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 new file mode 100644 index 0000000000..c922d7b133 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc.md @@ -0,0 +1,240 @@ +# ะขะตัั‚ะพะฒั‹ะน ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฟะตั€ะตะฒะพะดะพะฒ { #test-translation-fixer } + +## ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ ะธ ะฑะตะท ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ { #code-blocks-with-and-without-comments } + +ะญั‚ะพ ั‚ะตัั‚ะพะฒะฐั ัั‚ั€ะฐะฝะธั†ะฐ ะดะปั ะธะฝัั‚ั€ัƒะผะตะฝั‚ะฐ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฟะตั€ะตะฒะพะดะพะฒ. + +### ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ { #code-blocks-with-comments } + +ะกะปะตะดัƒัŽั‰ะธะต ะฑะปะพะบะธ ะบะพะดะฐ ัะพะดะตั€ะถะฐั‚ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒ ั€ะฐะทะฝั‹ั… ัั‚ะธะปัั…. +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะดะพะปะถะตะฝ ะธัะฟั€ะฐะฒะปัั‚ัŒ ัะพะดะตั€ะถะธะผะพะต, ะฝะพ ะบะพั€ั€ะตะบั‚ะฝะพ ัะพั…ั€ะฐะฝัั‚ัŒ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ. + +```python +# ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะฝะฐ Python +def hello_world(): + # ะšะพะผะผะตะฝั‚ะฐั€ะธะน ั ะพั‚ัั‚ัƒะฟะพะผ + print("Hello, world!") # ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +``` + +```toml +# ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะฝะฐ TOML +title = "TOML Example" # ะ—ะฐะณะพะปะพะฒะพะบ ะดะพะบัƒะผะตะฝั‚ะฐ +``` + +```console +// ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะบะพะผะฐะฝะดัƒ "live" ะธ ะฟะตั€ะตะดะฐะนั‚ะต ะบะพะด ัะทั‹ะบะฐ ะฒ ะบะฐั‡ะตัั‚ะฒะต ะฐั€ะณัƒะผะตะฝั‚ะฐ CLI +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +```json +{ + // ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะฝะฐ JSON + "greeting": "Hello, world!" // ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +} +``` + + +### ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ, ะณะดะต ัะทั‹ะบ ะธัะฟะพะปัŒะทัƒะตั‚ ะดั€ัƒะณะธะต ัั‚ะธะปะธ ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ { #code-blocks-with-different-comment-styles } + +ะกะปะตะดัƒัŽั‰ะธะต ะฑะปะพะบะธ ะบะพะดะฐ ัะพะดะตั€ะถะฐั‚ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒ ั€ะฐะทะฝั‹ั… ัั‚ะธะปัั… ะฒ ะทะฐะฒะธัะธะผะพัั‚ะธ ะพั‚ ัะทั‹ะบะฐ. +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฝะต ะฑัƒะดะตั‚ ัะพั…ั€ะฐะฝัั‚ัŒ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒ ัั‚ะธั… ะฑะปะพะบะฐั…. + +```json +{ + # ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะฝะฐ JSON + "greeting": "Hello, world!" # ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +} +``` + +```console +# ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะบะพะฝัะพะปะธ +$ echo "Hello, world!" # ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +``` + +```toml +// ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะฝะฐ TOML +title = "TOML Example" // ะ—ะฐะณะพะปะพะฒะพะบ ะดะพะบัƒะผะตะฝั‚ะฐ +``` + +### ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ ะฝะฐ ะฝะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผั‹ั… ัะทั‹ะบะฐั… ะธะปะธ ะฑะตะท ัƒะบะฐะทะฐะฝะธั ัะทั‹ะบะฐ { #code-blocks-with-unsupported-languages } + +ะกะปะตะดัƒัŽั‰ะธะต ะฑะปะพะบะธ ะบะพะดะฐ ะธัะฟะพะปัŒะทัƒัŽั‚ ะฝะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผั‹ะต ัะทั‹ะบะธ ะดะปั ัะพั…ั€ะฐะฝะตะฝะธั ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ. +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฝะต ะฑัƒะดะตั‚ ัะพั…ั€ะฐะฝัั‚ัŒ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒ ัั‚ะธั… ะฑะปะพะบะฐั…. + +```javascript +// ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะฝะฐ JavaScript +console.log("Hello, world!"); // ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +``` + +``` +# ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะบะพะฝัะพะปะธ +$ echo "Hello, world!" # ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +``` + +``` +// ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะบะพะฝัะพะปะธ +$ echo "Hello, world!" // ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +``` + +### ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ, ะบะพั‚ะพั€ั‹ะต ะฝะต ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‚ ัˆะฐะฑะปะพะฝัƒ { #code-blocks-with-comments-without-pattern } + +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะพะถะธะดะฐะตั‚ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ, ะบะพั‚ะพั€ั‹ะต ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‚ ะพะฟั€ะตะดะตะปั‘ะฝะฝะพะผัƒ ัˆะฐะฑะปะพะฝัƒ: + +- ะ”ะปั ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ ะฒ ัั‚ะธะปะต ั ั€ะตัˆั‘ั‚ะบะพะน: ะบะพะผะผะตะฝั‚ะฐั€ะธะน ะฝะฐั‡ะธะฝะฐะตั‚ัั ั `# ` (ั€ะตัˆั‘ั‚ะบะฐ, ะทะฐั‚ะตะผ ะฟั€ะพะฑะตะป) ะฒ ะฝะฐั‡ะฐะปะต ัั‚ั€ะพะบะธ ะธะปะธ ะฟะพัะปะต ะฟั€ะพะฑะตะปะฐ. +- ะ”ะปั ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ ะฒ ัั‚ะธะปะต ัะพ ัะปะตัˆะฐะผะธ: ะบะพะผะผะตะฝั‚ะฐั€ะธะน ะฝะฐั‡ะธะฝะฐะตั‚ัั ั `// ` (ะดะฒะฐ ัะปะตัˆะฐ, ะทะฐั‚ะตะผ ะฟั€ะพะฑะตะป) ะฒ ะฝะฐั‡ะฐะปะต ัั‚ั€ะพะบะธ ะธะปะธ ะฟะพัะปะต ะฟั€ะพะฑะตะปะฐ. + +ะ•ัะปะธ ะบะพะผะผะตะฝั‚ะฐั€ะธะน ะฝะต ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒะตั‚ ัั‚ะพะผัƒ ัˆะฐะฑะปะพะฝัƒ, ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฝะต ะฑัƒะดะตั‚ ะตะณะพ ัะพั…ั€ะฐะฝัั‚ัŒ. + +```python +#ะžะฑัŠัะฒะปะตะฝะธะต ั„ัƒะฝะบั†ะธะธ +def hello_world():# ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั + print("Hello, world!") #ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั ะฑะตะท ะฟั€ะพะฑะตะปะฐ ะฟะพัะปะต ั€ะตัˆั‘ั‚ะบะธ +``` + +```console +//ะžะฑัŠัะฒะปะตะฝะธะต ั„ัƒะฝะบั†ะธะธ +def hello_world():// ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั + print("Hello, world!") //ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั ะฑะตะท ะฟั€ะพะฑะตะปะฐ ะฟะพัะปะต ัะปะตัˆะตะน +``` + +## ะ‘ะปะพะบ ะบะพะดะฐ ั ั‡ะตั‚ั‹ั€ั‘ั…ะบั€ะฐั‚ะฝั‹ะผะธ ะพะฑั€ะฐั‚ะฝั‹ะผะธ ะบะฐะฒั‹ั‡ะบะฐะผะธ { #code-blocks-with-quadruple-backticks } + +ะกะปะตะดัƒัŽั‰ะธะน ะฑะปะพะบ ะบะพะดะฐ ัะพะดะตั€ะถะธั‚ ั‡ะตั‚ั‹ั€ั‘ั…ะบั€ะฐั‚ะฝั‹ะต ะพะฑั€ะฐั‚ะฝั‹ะต ะบะฐะฒั‹ั‡ะบะธ. + +````python +# ะคัƒะฝะบั†ะธั ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +def hello_world(): + print("Hello, world") # ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +```` + +### ะะตัะพะพั‚ะฒะตั‚ัั‚ะฒะธะต ะพะฑั€ะฐั‚ะฝั‹ั… ะบะฐะฒั‹ั‡ะตะบ ั„ะธะบัะธั‚ัั { #backticks-number-mismatch-is-fixable } + +ะกะปะตะดัƒัŽั‰ะธะน ะฑะปะพะบ ะบะพะดะฐ ะธะผะตะตั‚ ั‚ั€ะพะนะฝั‹ะต ะพะฑั€ะฐั‚ะฝั‹ะต ะบะฐะฒั‹ั‡ะบะธ ะฒ ะพั€ะธะณะธะฝะฐะปัŒะฝะพะผ ะดะพะบัƒะผะตะฝั‚ะต, ะฝะพ ั‡ะตั‚ั‹ั€ั‘ั…ะบั€ะฐั‚ะฝั‹ะต ะพะฑั€ะฐั‚ะฝั‹ะต ะบะฐะฒั‹ั‡ะบะธ ะฒ ะฟะตั€ะตะฒะตะดั‘ะฝะฝะพะผ ะดะพะบัƒะผะตะฝั‚ะต. +ะญั‚ะพ ะฑัƒะดะตั‚ ะธัะฟั€ะฐะฒะปะตะฝะพ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะผ ะธัะฟั€ะฐะฒะปะตะฝะธั (ะฑัƒะดะตั‚ ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐะฝะพ ะฒ ั‚ั€ะพะนะฝั‹ะต ะพะฑั€ะฐั‚ะฝั‹ะต ะบะฐะฒั‹ั‡ะบะธ). + +````Python +# ะะตะผะฝะพะณะพ ะบะพะดะฐ ะฝะฐ Python +```` + +### ะ‘ะปะพะบ ะบะพะดะฐ ะฒ ั‚ั€ะพะนะฝั‹ั… ะพะฑั€ะฐั‚ะฝั‹ั… ะบะฐะฒั‹ั‡ะบะฐ ะฒะฝัƒั‚ั€ะธ ะฑะปะพะบะฐ ะบะพะดะฐ ะฒ ั‡ะตั‚ั‹ั€ั‘ั…ะบั€ะฐั‚ะฝั‹ั… ะพะฑั€ะฐั‚ะฝั‹ั… ะบะฐะฒั‹ั‡ะบะฐั… { #triple-backticks-inside-quadruple-backticks } + +ะšะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒะฝัƒั‚ั€ะธ ะฒะปะพะถะตะฝะฝะพะณะพ ะฑะปะพะบะฐ ะบะพะดะฐ ะฒ ั‚ั€ะพะนะฝั‹ั… ะพะฑั€ะฐั‚ะฝั‹ั… ะบะฐะฒั‹ั‡ะบะฐั… ะะ• ะ‘ะฃะ”ะฃะข ัะพั…ั€ะฐะฝะตะฝั‹. + +```` +Here is a code block with quadruple backticks that contains triple backticks inside: + +```python +# ะญั‚ะพั‚ ะบะพะผะผะตะฝั‚ะฐั€ะธะน ะะ• ะฑัƒะดะตั‚ ัะพั…ั€ะฐะฝั‘ะฝ +def hello_world(): + print("Hello, world") # ะšะฐะบ ะธ ัั‚ะพั‚ ะบะพะผะผะตะฝั‚ะฐั€ะธะน +``` + +```` + +# ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ { #code-includes } + +## ะŸั€ะพัั‚ั‹ะต ะฒะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ { #simple-code-includes } + +{* ../../docs_src/python_types/tutorial001_py39.py *} + +{* ../../docs_src/python_types/tutorial002_py39.py *} + + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะฟะพะดัะฒะตั‚ะบะพะน { #code-includes-with-highlighting } + +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} + +{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *} + + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะดะธะฐะฟะฐะทะพะฝะฐะผะธ ัั‚ั€ะพะบ { #code-includes-with-line-ranges } + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] *} + + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะดะธะฐะฟะฐะทะพะฝะฐะผะธ ัั‚ั€ะพะบ ะธ ะฟะพะดัะฒะตั‚ะบะพะน { #code-includes-with-line-ranges-and-highlighting } + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +{* ../../docs_src/dependencies/tutorial015_an_py310.py ln[10:15] hl[12:14] *} + + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะทะฐะณะพะปะพะฒะบะพะผ { #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_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะฝะตะธะทะฒะตัั‚ะฝั‹ะผะธ ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ { #code-includes-with-unknown-attributes } + +{* ../../docs_src/python_types/tutorial001_py39.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/dependencies/tutorial013_an_py310.py ln[1:30] hl[1:10] *} + +# ะกัั‹ะปะบะธ { #links } + +## ะกัั‹ะปะบะธ ะฒ ัั‚ะธะปะต Markdown { #markdown-style-links } + +ะญั‚ะพ [Markdown-ััั‹ะปะบะฐ](https://example.com) ะฝะฐ ะฒะฝะตัˆะฝะธะน ัะฐะนั‚. + +ะญั‚ะพ ััั‹ะปะบะฐ ั ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ: [**FastAPI** ะณะตะฝะตั€ะฐั‚ะพั€ั‹ ะฟั€ะพะตะบั‚ะพะฒ](project-generation.md){.internal-link target=_blank} + +ะญั‚ะพ ััั‹ะปะบะฐ ะฝะฐ ะพัะฝะพะฒะฝะพะน ัะฐะนั‚ FastAPI: [FastAPI](https://fastapi.tiangolo.com) โ€” ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะธั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ URL. + +ะญั‚ะพ ััั‹ะปะบะฐ ะฝะฐ ะพะดะฝัƒ ะธะท ัั‚ั€ะฐะฝะธั† ะฝะฐ ัะฐะนั‚ะต FastAPI: [How to](https://fastapi.tiangolo.com/how-to) โ€” ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะธั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ URL. + +ะกัั‹ะปะบะฐ ะดะปั ั‚ะตัั‚ะธั€ะพะฒะฐะฝะธั ะฝะตะฟั€ะฐะฒะธะปัŒะฝะพะณะพ ะฐั‚ั€ะธะฑัƒั‚ะฐ: [**FastAPI** ะณะตะฝะตั€ะฐั‚ะพั€ั‹ ะฟั€ะพะตะบั‚ะพะฒ](project-generation.md){.external-link} - ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะธัะฟั€ะฐะฒะธั‚ัŒ ะฐั‚ั€ะธะฑัƒั‚. + +ะกัั‹ะปะบะฐ ั ะทะฐะณะพะปะพะฒะบะพะผ: [ะŸั€ะธะผะตั€](http://example.com/ "ะกะฐะนั‚ ะดะปั ะฟั€ะธะผะตั€ะฐ") - URL ะฑัƒะดะตั‚ ะธัะฟั€ะฐะฒะปะตะฝ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะผ, ะทะฐะณะพะปะพะฒะพะบ ัะพั…ั€ะฐะฝะธั‚ัั. + +### Markdown ััั‹ะปะบะธ ะฝะฐ ัั‚ะฐั‚ะธั‡ะตัะบะธะต ั€ะตััƒั€ัั‹ { #markdown-link-to-static-assets } + +ะญั‚ะพ ััั‹ะปะบะธ ะฝะฐ ัั‚ะฐั‚ะธั‡ะตัะบะธะต ั€ะตััƒั€ัั‹: + +* [FastAPI Logo](https://fastapi.tiangolo.com/img/fastapi-logo.png) +* [FastAPI CSS](https://fastapi.tiangolo.com/css/fastapi.css) +* [FastAPI JS](https://fastapi.tiangolo.com/js/fastapi.js) + +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะะ• ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะปัั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ ะธั… URL. + +## ะกัั‹ะปะบะธ ะฒ ัั‚ะธะปะต HTML { #html-style-links } + +ะญั‚ะพ <a href="https://example.com" target="_blank" class="external-link">HTML-ััั‹ะปะบะฐ</a> ะฝะฐ ะฒะฝะตัˆะฝะธะน ัะฐะนั‚. + +ะญั‚ะพ <a href="https://fastapi.tiangolo.com">ััั‹ะปะบะฐ ะฝะฐ ะพัะฝะพะฒะฝะพะน ัะฐะนั‚ FastAPI</a> โ€” ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะธั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ URL. + +ะญั‚ะพ <a href="https://fastapi.tiangolo.com/how-to/">ััั‹ะปะบะฐ ะฝะฐ ะพะดะฝัƒ ะธะท ัั‚ั€ะฐะฝะธั† ะฝะฐ ัะฐะนั‚ะต FastAPI</a> โ€” ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะธั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ URL. + +ะกัั‹ะปะบะฐ ะดะปั ั‚ะตัั‚ะธั€ะพะฒะฐะฝะธั ะฝะตะฟั€ะฐะฒะธะปัŒะฝะพะณะพ ะฐั‚ั€ะธะฑัƒั‚ะฐ: <a href="project-generation.md" class="external-link">**FastAPI** ะณะตะฝะตั€ะฐั‚ะพั€ั‹ ะฟั€ะพะตะบั‚ะพะฒ</a> - ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะธัะฟั€ะฐะฒะธั‚ัŒ ะฐั‚ั€ะธะฑัƒั‚. + +### HTML ััั‹ะปะบะธ ะฝะฐ ัั‚ะฐั‚ะธั‡ะตัะบะธะต ั€ะตััƒั€ัั‹ { #html-links-to-static-assets } + +ะญั‚ะพ ััั‹ะปะบะธ ะฝะฐ ัั‚ะฐั‚ะธั‡ะตัะบะธะต ั€ะตััƒั€ัั‹: + +* <a href="https://fastapi.tiangolo.com/img/fastapi-logo.png">FastAPI Logo</a> +* <a href="https://fastapi.tiangolo.com/css/fastapi.css">FastAPI CSS</a> +* <a href="https://fastapi.tiangolo.com/js/fastapi.js">FastAPI JS</a> + +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะะ• ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะปัั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ ะธั… URL. + +# ะ—ะฐะณะพะปะพะฒะพะบ (ั HTML ััั‹ะปะบะพะน ะฝะฐ <a href="https://tiangolo.com">tiangolo.com</a>) { #header-5 } + +#ะะต ะทะฐะณะพะปะพะฒะพะบ + +```Python +# ะขะฐะบะถะต ะฝะต ะทะฐะณะพะปะพะฒะพะบ +``` + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ 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 new file mode 100644 index 0000000000..b33f36e772 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc_expected.md @@ -0,0 +1,240 @@ +# ะขะตัั‚ะพะฒั‹ะน ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฟะตั€ะตะฒะพะดะพะฒ { #test-translation-fixer } + +## ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ ะธ ะฑะตะท ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ { #code-blocks-with-and-without-comments } + +ะญั‚ะพ ั‚ะตัั‚ะพะฒะฐั ัั‚ั€ะฐะฝะธั†ะฐ ะดะปั ะธะฝัั‚ั€ัƒะผะตะฝั‚ะฐ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฟะตั€ะตะฒะพะดะพะฒ. + +### ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ { #code-blocks-with-comments } + +ะกะปะตะดัƒัŽั‰ะธะต ะฑะปะพะบะธ ะบะพะดะฐ ัะพะดะตั€ะถะฐั‚ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒ ั€ะฐะทะฝั‹ั… ัั‚ะธะปัั…. +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะดะพะปะถะตะฝ ะธัะฟั€ะฐะฒะปัั‚ัŒ ัะพะดะตั€ะถะธะผะพะต, ะฝะพ ะบะพั€ั€ะตะบั‚ะฝะพ ัะพั…ั€ะฐะฝัั‚ัŒ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ. + +```python +# ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะฝะฐ Python +def hello_world(): + # ะšะพะผะผะตะฝั‚ะฐั€ะธะน ั ะพั‚ัั‚ัƒะฟะพะผ + print("Hello, world!") # ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +``` + +```toml +# ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะฝะฐ TOML +title = "TOML Example" # ะ—ะฐะณะพะปะพะฒะพะบ ะดะพะบัƒะผะตะฝั‚ะฐ +``` + +```console +// ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะบะพะผะฐะฝะดัƒ "live" ะธ ะฟะตั€ะตะดะฐะนั‚ะต ะบะพะด ัะทั‹ะบะฐ ะฒ ะบะฐั‡ะตัั‚ะฒะต ะฐั€ะณัƒะผะตะฝั‚ะฐ CLI +$ python ./scripts/docs.py live es + +<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 +<span style="color: green;">[INFO]</span> Start watching changes +<span style="color: green;">[INFO]</span> Start detecting changes +``` + +```json +{ + // ะญั‚ะพ ะฟั€ะธะผะตั€ ะฑะปะพะบะฐ ะบะพะดะฐ ะฝะฐ JSON + "greeting": "Hello, world!" // ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +} +``` + + +### ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ, ะณะดะต ัะทั‹ะบ ะธัะฟะพะปัŒะทัƒะตั‚ ะดั€ัƒะณะธะต ัั‚ะธะปะธ ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ { #code-blocks-with-different-comment-styles } + +ะกะปะตะดัƒัŽั‰ะธะต ะฑะปะพะบะธ ะบะพะดะฐ ัะพะดะตั€ะถะฐั‚ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒ ั€ะฐะทะฝั‹ั… ัั‚ะธะปัั… ะฒ ะทะฐะฒะธัะธะผะพัั‚ะธ ะพั‚ ัะทั‹ะบะฐ. +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฝะต ะฑัƒะดะตั‚ ัะพั…ั€ะฐะฝัั‚ัŒ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒ ัั‚ะธั… ะฑะปะพะบะฐั…. + +```json +{ + # This is a sample JSON code block + "greeting": "Hello, world!" # Print greeting +} +``` + +```console +# This is a sample console code block +$ echo "Hello, world!" # Print greeting +``` + +```toml +// This is a sample TOML code block +title = "TOML Example" // Title of the document +``` + +### ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ ะฝะฐ ะฝะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผั‹ั… ัะทั‹ะบะฐั… ะธะปะธ ะฑะตะท ัƒะบะฐะทะฐะฝะธั ัะทั‹ะบะฐ { #code-blocks-with-unsupported-languages } + +ะกะปะตะดัƒัŽั‰ะธะต ะฑะปะพะบะธ ะบะพะดะฐ ะธัะฟะพะปัŒะทัƒัŽั‚ ะฝะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผั‹ะต ัะทั‹ะบะธ ะดะปั ัะพั…ั€ะฐะฝะตะฝะธั ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ. +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฝะต ะฑัƒะดะตั‚ ัะพั…ั€ะฐะฝัั‚ัŒ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒ ัั‚ะธั… ะฑะปะพะบะฐั…. + +```javascript +// This is a sample JavaScript code block +console.log("Hello, world!"); // Print greeting +``` + +``` +# This is a sample console code block +$ echo "Hello, world!" # Print greeting +``` + +``` +// This is a sample console code block +$ echo "Hello, world!" // Print greeting +``` + +### ะ‘ะปะพะบะธ ะบะพะดะฐ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ, ะบะพั‚ะพั€ั‹ะต ะฝะต ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‚ ัˆะฐะฑะปะพะฝัƒ { #code-blocks-with-comments-without-pattern } + +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะพะถะธะดะฐะตั‚ ะบะพะผะผะตะฝั‚ะฐั€ะธะธ, ะบะพั‚ะพั€ั‹ะต ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‚ ะพะฟั€ะตะดะตะปั‘ะฝะฝะพะผัƒ ัˆะฐะฑะปะพะฝัƒ: + +- ะ”ะปั ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ ะฒ ัั‚ะธะปะต ั ั€ะตัˆั‘ั‚ะบะพะน: ะบะพะผะผะตะฝั‚ะฐั€ะธะน ะฝะฐั‡ะธะฝะฐะตั‚ัั ั `# ` (ั€ะตัˆั‘ั‚ะบะฐ, ะทะฐั‚ะตะผ ะฟั€ะพะฑะตะป) ะฒ ะฝะฐั‡ะฐะปะต ัั‚ั€ะพะบะธ ะธะปะธ ะฟะพัะปะต ะฟั€ะพะฑะตะปะฐ. +- ะ”ะปั ะบะพะผะผะตะฝั‚ะฐั€ะธะตะฒ ะฒ ัั‚ะธะปะต ัะพ ัะปะตัˆะฐะผะธ: ะบะพะผะผะตะฝั‚ะฐั€ะธะน ะฝะฐั‡ะธะฝะฐะตั‚ัั ั `// ` (ะดะฒะฐ ัะปะตัˆะฐ, ะทะฐั‚ะตะผ ะฟั€ะพะฑะตะป) ะฒ ะฝะฐั‡ะฐะปะต ัั‚ั€ะพะบะธ ะธะปะธ ะฟะพัะปะต ะฟั€ะพะฑะตะปะฐ. + +ะ•ัะปะธ ะบะพะผะผะตะฝั‚ะฐั€ะธะน ะฝะต ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒะตั‚ ัั‚ะพะผัƒ ัˆะฐะฑะปะพะฝัƒ, ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะธัะฟั€ะฐะฒะปะตะฝะธั ะฝะต ะฑัƒะดะตั‚ ะตะณะพ ัะพั…ั€ะฐะฝัั‚ัŒ. + +```python +#Function declaration +def hello_world():# Print greeting + print("Hello, world!") #Print greeting without space after hash +``` + +```console +//Function declaration +def hello_world():// Print greeting + print("Hello, world!") //Print greeting without space after slashes +``` + +## ะ‘ะปะพะบ ะบะพะดะฐ ั ั‡ะตั‚ั‹ั€ั‘ั…ะบั€ะฐั‚ะฝั‹ะผะธ ะพะฑั€ะฐั‚ะฝั‹ะผะธ ะบะฐะฒั‹ั‡ะบะฐะผะธ { #code-blocks-with-quadruple-backticks } + +ะกะปะตะดัƒัŽั‰ะธะน ะฑะปะพะบ ะบะพะดะฐ ัะพะดะตั€ะถะธั‚ ั‡ะตั‚ั‹ั€ั‘ั…ะบั€ะฐั‚ะฝั‹ะต ะพะฑั€ะฐั‚ะฝั‹ะต ะบะฐะฒั‹ั‡ะบะธ. + +````python +# ะคัƒะฝะบั†ะธั ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +def hello_world(): + print("Hello, world!") # ะŸะตั‡ะฐั‚ัŒ ะฟั€ะธะฒะตั‚ัั‚ะฒะธั +```` + +### ะะตัะพะพั‚ะฒะตั‚ัั‚ะฒะธะต ะพะฑั€ะฐั‚ะฝั‹ั… ะบะฐะฒั‹ั‡ะตะบ ั„ะธะบัะธั‚ัั { #backticks-number-mismatch-is-fixable } + +ะกะปะตะดัƒัŽั‰ะธะน ะฑะปะพะบ ะบะพะดะฐ ะธะผะตะตั‚ ั‚ั€ะพะนะฝั‹ะต ะพะฑั€ะฐั‚ะฝั‹ะต ะบะฐะฒั‹ั‡ะบะธ ะฒ ะพั€ะธะณะธะฝะฐะปัŒะฝะพะผ ะดะพะบัƒะผะตะฝั‚ะต, ะฝะพ ั‡ะตั‚ั‹ั€ั‘ั…ะบั€ะฐั‚ะฝั‹ะต ะพะฑั€ะฐั‚ะฝั‹ะต ะบะฐะฒั‹ั‡ะบะธ ะฒ ะฟะตั€ะตะฒะตะดั‘ะฝะฝะพะผ ะดะพะบัƒะผะตะฝั‚ะต. +ะญั‚ะพ ะฑัƒะดะตั‚ ะธัะฟั€ะฐะฒะปะตะฝะพ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะผ ะธัะฟั€ะฐะฒะปะตะฝะธั (ะฑัƒะดะตั‚ ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐะฝะพ ะฒ ั‚ั€ะพะนะฝั‹ะต ะพะฑั€ะฐั‚ะฝั‹ะต ะบะฐะฒั‹ั‡ะบะธ). + +```Python +# ะะตะผะฝะพะณะพ ะบะพะดะฐ ะฝะฐ Python +``` + +### ะ‘ะปะพะบ ะบะพะดะฐ ะฒ ั‚ั€ะพะนะฝั‹ั… ะพะฑั€ะฐั‚ะฝั‹ั… ะบะฐะฒั‹ั‡ะบะฐ ะฒะฝัƒั‚ั€ะธ ะฑะปะพะบะฐ ะบะพะดะฐ ะฒ ั‡ะตั‚ั‹ั€ั‘ั…ะบั€ะฐั‚ะฝั‹ั… ะพะฑั€ะฐั‚ะฝั‹ั… ะบะฐะฒั‹ั‡ะบะฐั… { #triple-backticks-inside-quadruple-backticks } + +ะšะพะผะผะตะฝั‚ะฐั€ะธะธ ะฒะฝัƒั‚ั€ะธ ะฒะปะพะถะตะฝะฝะพะณะพ ะฑะปะพะบะฐ ะบะพะดะฐ ะฒ ั‚ั€ะพะนะฝั‹ั… ะพะฑั€ะฐั‚ะฝั‹ั… ะบะฐะฒั‹ั‡ะบะฐั… ะะ• ะ‘ะฃะ”ะฃะข ัะพั…ั€ะฐะฝะตะฝั‹. + +```` +Here is a code block with quadruple backticks that contains triple backticks inside: + +```python +# This is a sample Python code block +def hello_world(): + print("Hello, world!") # Print greeting +``` + +```` + +# ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ { #code-includes } + +## ะŸั€ะพัั‚ั‹ะต ะฒะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ { #simple-code-includes } + +{* ../../docs_src/python_types/tutorial001_py39.py *} + +{* ../../docs_src/python_types/tutorial002_py39.py *} + + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะฟะพะดัะฒะตั‚ะบะพะน { #code-includes-with-highlighting } + +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} + +{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *} + + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะดะธะฐะฟะฐะทะพะฝะฐะผะธ ัั‚ั€ะพะบ { #code-includes-with-line-ranges } + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] *} + + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะดะธะฐะฟะฐะทะพะฝะฐะผะธ ัั‚ั€ะพะบ ะธ ะฟะพะดัะฒะตั‚ะบะพะน { #code-includes-with-line-ranges-and-highlighting } + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +{* ../../docs_src/dependencies/tutorial015_an_py310.py ln[10:15] hl[12:14] *} + + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะทะฐะณะพะปะพะฒะบะพะผ { #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_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +## ะ’ะบะปัŽั‡ะตะฝะธั ะบะพะดะฐ ั ะฝะตะธะทะฒะตัั‚ะฝั‹ะผะธ ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ { #code-includes-with-unknown-attributes } + +{* ../../docs_src/python_types/tutorial001_py39.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/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +# ะกัั‹ะปะบะธ { #links } + +## ะกัั‹ะปะบะธ ะฒ ัั‚ะธะปะต Markdown { #markdown-style-links } + +ะญั‚ะพ [Markdown-ััั‹ะปะบะฐ](https://example.com) ะฝะฐ ะฒะฝะตัˆะฝะธะน ัะฐะนั‚. + +ะญั‚ะพ ััั‹ะปะบะฐ ั ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ: [**FastAPI** ะณะตะฝะตั€ะฐั‚ะพั€ั‹ ะฟั€ะพะตะบั‚ะพะฒ](project-generation.md){.internal-link target=_blank} + +ะญั‚ะพ ััั‹ะปะบะฐ ะฝะฐ ะพัะฝะพะฒะฝะพะน ัะฐะนั‚ FastAPI: [FastAPI](https://fastapi.tiangolo.com/lang) โ€” ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะธั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ URL. + +ะญั‚ะพ ััั‹ะปะบะฐ ะฝะฐ ะพะดะฝัƒ ะธะท ัั‚ั€ะฐะฝะธั† ะฝะฐ ัะฐะนั‚ะต FastAPI: [How to](https://fastapi.tiangolo.com/lang/how-to/) โ€” ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะธั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ URL. + +ะกัั‹ะปะบะฐ ะดะปั ั‚ะตัั‚ะธั€ะพะฒะฐะฝะธั ะฝะตะฟั€ะฐะฒะธะปัŒะฝะพะณะพ ะฐั‚ั€ะธะฑัƒั‚ะฐ: [**FastAPI** ะณะตะฝะตั€ะฐั‚ะพั€ั‹ ะฟั€ะพะตะบั‚ะพะฒ](project-generation.md){.internal-link} - ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะธัะฟั€ะฐะฒะธั‚ัŒ ะฐั‚ั€ะธะฑัƒั‚. + +ะกัั‹ะปะบะฐ ั ะทะฐะณะพะปะพะฒะบะพะผ: [ะŸั€ะธะผะตั€](https://example.com "ะกะฐะนั‚ ะดะปั ะฟั€ะธะผะตั€ะฐ") - URL ะฑัƒะดะตั‚ ะธัะฟั€ะฐะฒะปะตะฝ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะผ, ะทะฐะณะพะปะพะฒะพะบ ัะพั…ั€ะฐะฝะธั‚ัั. + +### Markdown ััั‹ะปะบะธ ะฝะฐ ัั‚ะฐั‚ะธั‡ะตัะบะธะต ั€ะตััƒั€ัั‹ { #markdown-link-to-static-assets } + +ะญั‚ะพ ััั‹ะปะบะธ ะฝะฐ ัั‚ะฐั‚ะธั‡ะตัะบะธะต ั€ะตััƒั€ัั‹: + +* [FastAPI Logo](https://fastapi.tiangolo.com/img/fastapi-logo.png) +* [FastAPI CSS](https://fastapi.tiangolo.com/css/fastapi.css) +* [FastAPI JS](https://fastapi.tiangolo.com/js/fastapi.js) + +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะะ• ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะปัั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ ะธั… URL. + +## ะกัั‹ะปะบะธ ะฒ ัั‚ะธะปะต HTML { #html-style-links } + +ะญั‚ะพ <a href="https://example.com" target="_blank" class="external-link">HTML-ััั‹ะปะบะฐ</a> ะฝะฐ ะฒะฝะตัˆะฝะธะน ัะฐะนั‚. + +ะญั‚ะพ <a href="https://fastapi.tiangolo.com/lang">ััั‹ะปะบะฐ ะฝะฐ ะพัะฝะพะฒะฝะพะน ัะฐะนั‚ FastAPI</a> โ€” ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะธั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ URL. + +ะญั‚ะพ <a href="https://fastapi.tiangolo.com/lang/how-to/">ััั‹ะปะบะฐ ะฝะฐ ะพะดะฝัƒ ะธะท ัั‚ั€ะฐะฝะธั† ะฝะฐ ัะฐะนั‚ะต FastAPI</a> โ€” ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะธั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ URL. + +ะกัั‹ะปะบะฐ ะดะปั ั‚ะตัั‚ะธั€ะพะฒะฐะฝะธั ะฝะตะฟั€ะฐะฒะธะปัŒะฝะพะณะพ ะฐั‚ั€ะธะฑัƒั‚ะฐ: <a href="project-generation.md" class="internal-link">**FastAPI** ะณะตะฝะตั€ะฐั‚ะพั€ั‹ ะฟั€ะพะตะบั‚ะพะฒ</a> - ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะดะพะปะถะตะฝ ะธัะฟั€ะฐะฒะธั‚ัŒ ะฐั‚ั€ะธะฑัƒั‚. + +### HTML ััั‹ะปะบะธ ะฝะฐ ัั‚ะฐั‚ะธั‡ะตัะบะธะต ั€ะตััƒั€ัั‹ { #html-links-to-static-assets } + +ะญั‚ะพ ััั‹ะปะบะธ ะฝะฐ ัั‚ะฐั‚ะธั‡ะตัะบะธะต ั€ะตััƒั€ัั‹: + +* <a href="https://fastapi.tiangolo.com/img/fastapi-logo.png">FastAPI Logo</a> +* <a href="https://fastapi.tiangolo.com/css/fastapi.css">FastAPI CSS</a> +* <a href="https://fastapi.tiangolo.com/js/fastapi.js">FastAPI JS</a> + +ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ ะะ• ะดะพะปะถะตะฝ ะดะพะฑะฐะฒะปัั‚ัŒ ะบะพะด ัะทั‹ะบะฐ ะฒ ะธั… URL. + +# ะ—ะฐะณะพะปะพะฒะพะบ (ั HTML ััั‹ะปะบะพะน ะฝะฐ <a href="https://tiangolo.com">tiangolo.com</a>) { #header-with-html-link-to-tiangolo-com } + +#ะะต ะทะฐะณะพะปะพะฒะพะบ + +```Python +# ะขะฐะบะถะต ะฝะต ะทะฐะณะพะปะพะฒะพะบ +``` + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ diff --git a/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py b/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py new file mode 100644 index 0000000000..86cc4d5eb4 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py @@ -0,0 +1,30 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path( + "scripts/tests/test_translation_fixer/test_complex_doc/data" +).absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc.md")], + indirect=True, +) +def test_fix(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 0, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = (data_path / "translated_doc_expected.md").read_text() + assert fixed_content == expected_content + + assert "Fixing multiline code blocks in" in result.output + assert "Fixing markdown links in" in result.output diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/en_doc.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/en_doc.md new file mode 100644 index 0000000000..878e16fb5d --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/en_doc.md @@ -0,0 +1,19 @@ +# Header 1 { #header-1 } + +Some text + +## Header 2 { #header-2 } + +Some more text + +### Header 3 { #header-3 } + +Even more text + +# Header 4 { #header-4 } + +A bit more text + +#Not a header + +Final portion of text diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_1.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_1.md new file mode 100644 index 0000000000..feefd77705 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_1.md @@ -0,0 +1,19 @@ +# Header 1 { #header-1 } + +Some text + +# Header 2 { #header-2 } + +Some more text + +### Header 3 { #header-3 } + +Even more text + +# Header 4 { #header-4 } + +A bit more text + +#Not a header + +Final portion of text diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_2.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_2.md new file mode 100644 index 0000000000..ad53a660cd --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_2.md @@ -0,0 +1,19 @@ +# Header 1 { #header-1 } + +Some text + +## Header 2 { #header-2 } + +Some more text + +### Header 3 { #header-3 } + +Even more text + +## Header 4 { #header-4 } + +A bit more text + +#Not a header + +Final portion of text diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_gt.md new file mode 100644 index 0000000000..9c517c9bb0 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_gt.md @@ -0,0 +1,19 @@ +# Header 1 { #header-1 } + +Some text + +## Header 2 { #header-2 } + +Some more text + +### Header 3 { #header-3 } + +Even more text + +# Header 4 { #header-4 } + +A bit more text + +# Extra header + +Final portion of text diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_lt.md new file mode 100644 index 0000000000..8a308728b2 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_lt.md @@ -0,0 +1,19 @@ +# Header 1 { #header-1 } + +Some text + +## Header 2 { #header-2 } + +Some more text + +### Header 3 { #header-3 } + +Even more text + +Header 4 is missing + +A bit more text + +#Not a header + +Final portion of text diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py new file mode 100644 index 0000000000..9fe2f7ba70 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py @@ -0,0 +1,60 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path( + "scripts/tests/test_translation_fixer/test_header_permalinks/data" +).absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_level_mismatch_1.md")], + indirect=True, +) +def test_level_mismatch_1(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1 + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path( + f"{data_path}/translated_doc_level_mismatch_1.md" + ).read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Header levels do not match between document and original document" + " (found #, expected ##) for header โ„–2 in line 5" + ) in result.output + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_level_mismatch_2.md")], + indirect=True, +) +def test_level_mismatch_2(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1 + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path( + f"{data_path}/translated_doc_level_mismatch_2.md" + ).read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Header levels do not match between document and original document" + " (found ##, expected #) for header โ„–4 in line 13" + ) in result.output diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py new file mode 100644 index 0000000000..c0e78d0302 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py @@ -0,0 +1,56 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path( + "scripts/tests/test_translation_fixer/test_header_permalinks/data" +).absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")], + indirect=True, +) +def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1 + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of headers with permalinks does not match the number " + "in the original document (5 vs 4)" + ) in result.output + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")], + indirect=True, +) +def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1 + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of headers with permalinks does not match the number " + "in the original document (3 vs 4)" + ) in result.output diff --git a/scripts/tests/test_translation_fixer/test_html_links/data/en_doc.md b/scripts/tests/test_translation_fixer/test_html_links/data/en_doc.md new file mode 100644 index 0000000000..4c4d104cf2 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_html_links/data/en_doc.md @@ -0,0 +1,19 @@ +# Header 1 { #header-1 } + +Some text with a link to <a href="https://fastapi.tiangolo.com">FastAPI</a>. + +## Header 2 { #header-2 } + +Two links here: <a href="https://fastapi.tiangolo.com/how-to/">How to</a> and <a href="project-generation.md" class="internal-link" target="_blank">Project Generators</a>. + +### Header 3 { #header-3 } + +Another link: <a href="project-generation.md" class="internal-link" target="_blank" title="Link title">**FastAPI** Project Generators</a> with title. + +# Header 4 { #header-4 } + +Link to anchor: <a href="#header-2">Header 2</a> + +# Header with <a href="http://example.com">link</a> { #header-with-link } + +Some text diff --git a/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_gt.md new file mode 100644 index 0000000000..bac40242be --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_gt.md @@ -0,0 +1,21 @@ +# ะ—ะฐะณะพะปะพะฒะพะบ 1 { #header-1 } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ ัะพ ััั‹ะปะบะพะน ะฝะฐ <a href="https://fastapi.tiangolo.com">FastAPI</a>. + +## ะ—ะฐะณะพะปะพะฒะพะบ 2 { #header-2 } + +ะ”ะฒะต ััั‹ะปะบะธ ะทะดะตััŒ: <a href="https://fastapi.tiangolo.com/how-to/">How to</a> ะธ <a href="project-generation.md" class="internal-link" target="_blank">Project Generators</a>. + +### ะ—ะฐะณะพะปะพะฒะพะบ 3 { #header-3 } + +ะ•ั‰ั‘ ััั‹ะปะบะฐ: <a href="project-generation.md" class="internal-link" target="_blank" title="ะขะฐะนั‚ะป">**FastAPI** ะ“ะตะฝะตั€ะฐั‚ะพั€ั‹ ะŸั€ะพะตะบั‚ะพะฒ</a> ั ั‚ะฐะนั‚ะปะพะผ. + +ะ˜ ะตั‰ั‘ ะพะดะฝะฐ <a href="https://github.com">ัะบัั‚ั€ะฐ ััั‹ะปะบะฐ</a>. + +# ะ—ะฐะณะพะปะพะฒะพะบ 4 { #header-4 } + +ะกัั‹ะปะบะฐ ะฝะฐ ัะบะพั€ัŒ: <a href="#header-2">ะ—ะฐะณะพะปะพะฒะพะบ 2</a> + +# ะ—ะฐะณะพะปะพะฒะพะบ ัะพ <a href="http://example.com">ััั‹ะปะบะพะน</a> { #header-with-link } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ diff --git a/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_lt.md new file mode 100644 index 0000000000..e2b36b6887 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_lt.md @@ -0,0 +1,19 @@ +# ะ—ะฐะณะพะปะพะฒะพะบ 1 { #header-1 } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ ัะพ ััั‹ะปะบะพะน ะฝะฐ <a href="https://fastapi.tiangolo.com">FastAPI</a>. + +## ะ—ะฐะณะพะปะพะฒะพะบ 2 { #header-2 } + +ะ”ะฒะต ััั‹ะปะบะธ ะทะดะตััŒ: <a href="https://fastapi.tiangolo.com/how-to/">How to</a> ะธ <a href="project-generation.md" class="internal-link" target="_blank">Project Generators</a>. + +### ะ—ะฐะณะพะปะพะฒะพะบ 3 { #header-3 } + +ะ•ั‰ั‘ ััั‹ะปะบะฐ: <a href="project-generation.md" class="internal-link" target="_blank" title="ะขะฐะนั‚ะป">**FastAPI** ะ“ะตะฝะตั€ะฐั‚ะพั€ั‹ ะŸั€ะพะตะบั‚ะพะฒ</a> ั ั‚ะฐะนั‚ะปะพะผ. + +# ะ—ะฐะณะพะปะพะฒะพะบ 4 { #header-4 } + +ะกัั‹ะปะบะฐ ะฝะฐ ัะบะพั€ัŒ: <a href="#header-2">ะ—ะฐะณะพะปะพะฒะพะบ 2</a> + +# ะ—ะฐะณะพะปะพะฒะพะบ ั ะฟะพั‚ะตั€ัะฝะฝะพะน ััั‹ะปะบะพะน { #header-with-link } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ diff --git a/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py b/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py new file mode 100644 index 0000000000..271e5d2058 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py @@ -0,0 +1,54 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path("scripts/tests/test_translation_fixer/test_html_links/data").absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")], + indirect=True, +) +def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of HTML links does not match the number " + "in the original document (7 vs 6)" + ) in result.output + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")], + indirect=True, +) +def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + # assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of HTML links does not match the number " + "in the original document (5 vs 6)" + ) in result.output diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/data/en_doc.md b/scripts/tests/test_translation_fixer/test_markdown_links/data/en_doc.md new file mode 100644 index 0000000000..9d965b0d68 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_markdown_links/data/en_doc.md @@ -0,0 +1,19 @@ +# Header 1 { #header-1 } + +Some text with a link to [FastAPI](https://fastapi.tiangolo.com). + +## Header 2 { #header-2 } + +Two links here: [How to](https://fastapi.tiangolo.com/how-to/) and [Project Generators](project-generation.md){.internal-link target=_blank}. + +### Header 3 { #header-3 } + +Another link: [**FastAPI** Project Generators](project-generation.md "Link title"){.internal-link target=_blank} with title. + +# Header 4 { #header-4 } + +Link to anchor: [Header 2](#header-2) + +# Header with [link](http://example.com) { #header-with-link } + +Some text diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc.md b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc.md new file mode 100644 index 0000000000..804a7e60a6 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc.md @@ -0,0 +1,19 @@ +# ะ—ะฐะณะพะปะพะฒะพะบ 1 { #header-1 } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ ัะพ ััั‹ะปะบะพะน ะฝะฐ [FastAPI](https://fastapi.tiangolo.com). + +## ะ—ะฐะณะพะปะพะฒะพะบ 2 { #header-2 } + +ะ”ะฒะต ััั‹ะปะบะธ ะทะดะตััŒ: [How to](https://fastapi.tiangolo.com/how-to/) ะธ [Project Generators](project-generation.md){.internal-link target=_blank}. + +### ะ—ะฐะณะพะปะพะฒะพะบ 3 { #header-3 } + +ะ•ั‰ั‘ ััั‹ะปะบะฐ: [**FastAPI** ะ“ะตะฝะตั€ะฐั‚ะพั€ั‹ ะŸั€ะพะตะบั‚ะพะฒ](project-generation.md "ะขะฐะนั‚ะป"){.internal-link target=_blank} ั ั‚ะฐะนั‚ะปะพะผ. + +# ะ—ะฐะณะพะปะพะฒะพะบ 4 { #header-4 } + +ะกัั‹ะปะบะฐ ะฝะฐ ัะบะพั€ัŒ: [ะ—ะฐะณะพะปะพะฒะพะบ 2](#header-2) + +# ะ—ะฐะณะพะปะพะฒะพะบ ัะพ [ััั‹ะปะบะพะน](http://example.com) { #header-with-link } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_gt.md new file mode 100644 index 0000000000..9cbedb676e --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_gt.md @@ -0,0 +1,21 @@ +# ะ—ะฐะณะพะปะพะฒะพะบ 1 { #header-1 } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ ัะพ ััั‹ะปะบะพะน ะฝะฐ [FastAPI](https://fastapi.tiangolo.com). + +## ะ—ะฐะณะพะปะพะฒะพะบ 2 { #header-2 } + +ะ”ะฒะต ััั‹ะปะบะธ ะทะดะตััŒ: [How to](https://fastapi.tiangolo.com/how-to/) ะธ [Project Generators](project-generation.md){.internal-link target=_blank}. + +### ะ—ะฐะณะพะปะพะฒะพะบ 3 { #header-3 } + +ะ•ั‰ั‘ ััั‹ะปะบะฐ: [**FastAPI** ะ“ะตะฝะตั€ะฐั‚ะพั€ั‹ ะŸั€ะพะตะบั‚ะพะฒ](project-generation.md "ะขะฐะนั‚ะป"){.internal-link target=_blank} ั ั‚ะฐะนั‚ะปะพะผ. + +ะ˜ ะตั‰ั‘ ะพะดะฝะฐ [ัะบัั‚ั€ะฐ ััั‹ะปะบะฐ](https://github.com). + +# ะ—ะฐะณะพะปะพะฒะพะบ 4 { #header-4 } + +ะกัั‹ะปะบะฐ ะฝะฐ ัะบะพั€ัŒ: [ะ—ะฐะณะพะปะพะฒะพะบ 2](#header-2) + +# ะ—ะฐะณะพะปะพะฒะพะบ ัะพ [ััั‹ะปะบะพะน](http://example.com) { #header-with-link } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_lt.md new file mode 100644 index 0000000000..4e9e6ccf78 --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_lt.md @@ -0,0 +1,19 @@ +# ะ—ะฐะณะพะปะพะฒะพะบ 1 { #header-1 } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ ัะพ ััั‹ะปะบะพะน ะฝะฐ [FastAPI](https://fastapi.tiangolo.com). + +## ะ—ะฐะณะพะปะพะฒะพะบ 2 { #header-2 } + +ะ”ะฒะต ััั‹ะปะบะธ ะทะดะตััŒ: [How to](https://fastapi.tiangolo.com/how-to/) ะธ [Project Generators](project-generation.md){.internal-link target=_blank}. + +### ะ—ะฐะณะพะปะพะฒะพะบ 3 { #header-3 } + +ะ•ั‰ั‘ ััั‹ะปะบะฐ: [**FastAPI** ะ“ะตะฝะตั€ะฐั‚ะพั€ั‹ ะŸั€ะพะตะบั‚ะพะฒ](project-generation.md "ะขะฐะนั‚ะป"){.internal-link target=_blank} ั ั‚ะฐะนั‚ะปะพะผ. + +# ะ—ะฐะณะพะปะพะฒะพะบ 4 { #header-4 } + +ะกัั‹ะปะบะฐ ะฝะฐ ัะบะพั€ัŒ: [ะ—ะฐะณะพะปะพะฒะพะบ 2](#header-2) + +# ะ—ะฐะณะพะปะพะฒะพะบ ั ะฟะพั‚ะตั€ัะฝะฝะพะน ััั‹ะปะบะพะน { #header-with-link } + +ะะตะผะฝะพะณะพ ั‚ะตะบัั‚ะฐ diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py b/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py new file mode 100644 index 0000000000..0f4952f35d --- /dev/null +++ b/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py @@ -0,0 +1,56 @@ +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.translation_fixer import cli + +data_path = Path( + "scripts/tests/test_translation_fixer/test_markdown_links/data" +).absolute() + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")], + indirect=True, +) +def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of markdown links does not match the number " + "in the original document (7 vs 6)" + ) in result.output + + +@pytest.mark.parametrize( + "copy_test_files", + [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")], + indirect=True, +) +def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): + result = runner.invoke( + cli, + ["fix-pages", "docs/lang/docs/doc.md"], + ) + # assert result.exit_code == 1, result.output + + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + + assert fixed_content == expected_content # Translated doc remains unchanged + assert "Error processing docs/lang/docs/doc.md" in result.output + assert ( + "Number of markdown links does not match the number " + "in the original document (5 vs 6)" + ) in result.output diff --git a/scripts/translation_fixer.py b/scripts/translation_fixer.py new file mode 100644 index 0000000000..3e1f42d514 --- /dev/null +++ b/scripts/translation_fixer.py @@ -0,0 +1,132 @@ +import os +from collections.abc import Iterable +from pathlib import Path +from typing import Annotated + +import typer + +from scripts.doc_parsing_utils import check_translation + +non_translated_sections = ( + f"reference{os.sep}", + "release-notes.md", + "fastapi-people.md", + "external-links.md", + "newsletter.md", + "management-tasks.md", + "management.md", + "contributing.md", +) + + +cli = typer.Typer() + + +@cli.callback() +def callback(): + pass + + +def iter_all_lang_paths(lang_path_root: Path) -> Iterable[Path]: + """ + Iterate on the markdown files to translate in order of priority. + """ + + first_dirs = [ + lang_path_root / "learn", + lang_path_root / "tutorial", + lang_path_root / "advanced", + lang_path_root / "about", + lang_path_root / "how-to", + ] + first_parent = lang_path_root + yield from first_parent.glob("*.md") + for dir_path in first_dirs: + yield from dir_path.rglob("*.md") + first_dirs_str = tuple(str(d) for d in first_dirs) + for path in lang_path_root.rglob("*.md"): + if str(path).startswith(first_dirs_str): + continue + if path.parent == first_parent: + continue + yield path + + +def get_all_paths(lang: str): + res: list[str] = [] + lang_docs_root = Path("docs") / lang / "docs" + for path in iter_all_lang_paths(lang_docs_root): + relpath = path.relative_to(lang_docs_root) + if not str(relpath).startswith(non_translated_sections): + res.append(str(relpath)) + return res + + +def process_one_page(path: Path) -> bool: + """ + Fix one translated document by comparing it to the English version. + + Returns True if processed successfully, False otherwise. + """ + + try: + lang_code = path.parts[1] + if lang_code == "en": + print(f"Skipping English document: {path}") + return True + + en_doc_path = Path("docs") / "en" / Path(*path.parts[2:]) + + doc_lines = path.read_text(encoding="utf-8").splitlines() + en_doc_lines = en_doc_path.read_text(encoding="utf-8").splitlines() + + doc_lines = check_translation( + doc_lines=doc_lines, + en_doc_lines=en_doc_lines, + lang_code=lang_code, + auto_fix=True, + path=str(path), + ) + + # Write back the fixed document + doc_lines.append("") # Ensure file ends with a newline + path.write_text("\n".join(doc_lines), encoding="utf-8") + + except ValueError as e: + print(f"Error processing {path}: {e}") + return False + return True + + +@cli.command() +def fix_all(ctx: typer.Context, language: str): + docs = get_all_paths(language) + + all_good = True + for page in docs: + doc_path = Path("docs") / language / "docs" / page + res = process_one_page(doc_path) + all_good = all_good and res + + if not all_good: + raise typer.Exit(code=1) + + +@cli.command() +def fix_pages( + doc_paths: Annotated[ + list[Path], + typer.Argument(help="List of paths to documents."), + ], +): + all_good = True + for path in doc_paths: + res = process_one_page(path) + all_good = all_good and res + + if not all_good: + raise typer.Exit(code=1) + + +if __name__ == "__main__": + cli() From c35e1fd4b42c7937e5414e33bba60991f750f1d1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 21:48:32 +0000 Subject: [PATCH 048/110] =?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 f9b694e2e6..c1d24561c8 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -19,6 +19,7 @@ hide: ### Internal +* ๐Ÿ”จ Add LLM translations tool fixer. PR [#14652](https://github.com/fastapi/fastapi/pull/14652) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐Ÿ‘ฅ Update FastAPI People - Sponsors. PR [#14626](https://github.com/fastapi/fastapi/pull/14626) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ฅ Update FastAPI GitHub topic repositories. PR [#14630](https://github.com/fastapi/fastapi/pull/14630) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ฅ Update FastAPI People - Contributors and Translators. PR [#14625](https://github.com/fastapi/fastapi/pull/14625) by [@tiangolo](https://github.com/tiangolo). From b4ba7f46522352cf7ae7c74a9030a35679da3491 Mon Sep 17 00:00:00 2001 From: Jonathan Ehwald <github@ehwald.info> Date: Sat, 10 Jan 2026 23:02:57 +0100 Subject: [PATCH 049/110] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20=20Migrate=20to=20?= =?UTF-8?q?uv=20(#14676)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/dependabot.yml | 2 +- .github/workflows/build-docs.yml | 25 +- .github/workflows/contributors.yml | 12 +- .github/workflows/deploy-docs.yml | 16 +- .github/workflows/label-approved.yml | 12 +- .github/workflows/notify-translations.yml | 12 +- .github/workflows/people.yml | 12 +- .github/workflows/pre-commit.yml | 7 +- .github/workflows/publish.yml | 15 +- .github/workflows/smokeshow.yml | 11 +- .github/workflows/sponsors.yml | 12 +- .github/workflows/test-redistribute.yml | 4 +- .github/workflows/test.yml | 26 +- .github/workflows/topic-repos.yml | 12 +- .github/workflows/translate.yml | 23 +- .gitignore | 3 - .pre-commit-config.yaml | 1 + .python-version | 1 + docs/en/docs/contributing.md | 32 +- pyproject.toml | 65 +- requirements-docs-tests.txt | 4 - requirements-docs.txt | 21 - requirements-github-actions.txt | 6 - requirements-tests.txt | 18 - requirements-translations.txt | 3 - requirements.txt | 7 - uv.lock | 5339 +++++++++++++++++++++ 27 files changed, 5486 insertions(+), 215 deletions(-) create mode 100644 .python-version delete mode 100644 requirements-docs-tests.txt delete mode 100644 requirements-docs.txt delete mode 100644 requirements-github-actions.txt delete mode 100644 requirements-tests.txt delete mode 100644 requirements-translations.txt delete mode 100644 requirements.txt create mode 100644 uv.lock diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8979aabf8b..fdca003877 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,7 @@ updates: commit-message: prefix: โฌ† # Python - - package-ecosystem: "pip" + - package-ecosystem: "uv" directory: "/" schedule: interval: "monthly" diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index cd27179f57..2198fe6685 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -8,9 +8,6 @@ on: - opened - synchronize -env: - UV_SYSTEM_PYTHON: 1 - jobs: changes: runs-on: ubuntu-latest @@ -31,8 +28,8 @@ jobs: - README.md - docs/** - docs_src/** - - requirements-docs.txt - pyproject.toml + - uv.lock - mkdocs.yml - mkdocs.env.yml - .github/workflows/build-docs.yml @@ -49,21 +46,20 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install docs extras - run: uv pip install -r requirements-docs.txt + run: uv sync --locked --no-dev --group docs - name: Export Language Codes id: show-langs run: | - echo "langs=$(python ./scripts/docs.py langs-json)" >> $GITHUB_OUTPUT + echo "langs=$(uv run ./scripts/docs.py langs-json)" >> $GITHUB_OUTPUT build-docs: needs: @@ -83,25 +79,24 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install docs extras - run: uv pip install -r requirements-docs.txt + run: uv sync --locked --no-dev --group docs - name: Update Languages - run: python ./scripts/docs.py update-languages + run: uv run ./scripts/docs.py update-languages - uses: actions/cache@v4 with: key: mkdocs-cards-${{ matrix.lang }}-${{ github.ref }} path: docs/${{ matrix.lang }}/.cache - name: Build Docs - run: python ./scripts/docs.py build-lang ${{ matrix.lang }} + run: uv run ./scripts/docs.py build-lang ${{ matrix.lang }} - uses: actions/upload-artifact@v5 with: name: docs-site-${{ matrix.lang }} diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index 2abd2fdcf8..f3ced6aa30 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -10,9 +10,6 @@ on: required: false default: "false" -env: - UV_SYSTEM_PYTHON: 1 - jobs: job: if: github.repository_owner == 'fastapi' @@ -28,17 +25,16 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install Dependencies - run: uv pip install -r requirements-github-actions.txt + run: uv sync --locked --no-dev --group github-actions # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 @@ -48,6 +44,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} - name: FastAPI People Contributors - run: python ./scripts/contributors.py + run: uv run ./scripts/contributors.py env: GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 50662a1900..79d7391c46 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -12,9 +12,6 @@ permissions: pull-requests: write statuses: write -env: - UV_SYSTEM_PYTHON: 1 - jobs: deploy-docs: runs-on: ubuntu-latest @@ -27,19 +24,18 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install GitHub Actions dependencies - run: uv pip install -r requirements-github-actions.txt + run: uv sync --locked --no-dev --group github-actions - name: Deploy Docs Status Pending - run: python ./scripts/deploy_docs_status.py + run: uv run ./scripts/deploy_docs_status.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} @@ -70,14 +66,14 @@ jobs: command: pages deploy ./site --project-name=${{ env.PROJECT_NAME }} --branch=${{ env.BRANCH }} - name: Deploy Docs Status Error if: failure() - run: python ./scripts/deploy_docs_status.py + run: uv run ./scripts/deploy_docs_status.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} RUN_ID: ${{ github.run_id }} STATE: "error" - name: Comment Deploy - run: python ./scripts/deploy_docs_status.py + run: uv run ./scripts/deploy_docs_status.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }} diff --git a/.github/workflows/label-approved.yml b/.github/workflows/label-approved.yml index 7f16254dbb..1307fb8c23 100644 --- a/.github/workflows/label-approved.yml +++ b/.github/workflows/label-approved.yml @@ -8,9 +8,6 @@ on: permissions: pull-requests: write -env: - UV_SYSTEM_PYTHON: 1 - jobs: label-approved: if: github.repository_owner == 'fastapi' @@ -24,19 +21,18 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install GitHub Actions dependencies - run: uv pip install -r requirements-github-actions.txt + run: uv sync --locked --no-dev --group github-actions - name: Label Approved - run: python ./scripts/label_approved.py + run: uv run ./scripts/label_approved.py env: TOKEN: ${{ secrets.GITHUB_TOKEN }} CONFIG: > diff --git a/.github/workflows/notify-translations.yml b/.github/workflows/notify-translations.yml index 971e6bbd89..31f3eb4021 100644 --- a/.github/workflows/notify-translations.yml +++ b/.github/workflows/notify-translations.yml @@ -15,9 +15,6 @@ on: required: false default: 'false' -env: - UV_SYSTEM_PYTHON: 1 - jobs: job: runs-on: ubuntu-latest @@ -32,17 +29,16 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install Dependencies - run: uv pip install -r requirements-github-actions.txt + run: uv sync --locked --no-dev --group github-actions # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 @@ -52,7 +48,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Notify Translations - run: python ./scripts/notify_translations.py + run: uv run ./scripts/notify_translations.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NUMBER: ${{ github.event.inputs.number || null }} diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml index 9b35a3d7e0..cb3b742788 100644 --- a/.github/workflows/people.yml +++ b/.github/workflows/people.yml @@ -10,9 +10,6 @@ on: required: false default: "false" -env: - UV_SYSTEM_PYTHON: 1 - jobs: job: if: github.repository_owner == 'fastapi' @@ -28,17 +25,16 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install Dependencies - run: uv pip install -r requirements-github-actions.txt + run: uv sync --locked --no-dev --group github-actions # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 @@ -48,7 +44,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} - name: FastAPI People Experts - run: python ./scripts/people.py + run: uv run ./scripts/people.py env: GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} SLEEP_INTERVAL: ${{ vars.PEOPLE_SLEEP_INTERVAL }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index b397912e67..f027140ed7 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -40,18 +40,15 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.14" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: cache-dependency-glob: | - requirements**.txt pyproject.toml uv.lock - name: Install Dependencies - run: | - uv venv - uv pip install -r requirements.txt + run: uv sync --locked --extra all - name: Run prek - pre-commit id: precommit run: uvx prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6d9a00b49a..2232498cb1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,6 +15,7 @@ jobs: - fastapi-slim permissions: id-token: write + contents: read steps: - name: Dump GitHub context env: @@ -24,19 +25,15 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.10" + python-version-file: ".python-version" # Issue ref: https://github.com/actions/setup-python/issues/436 # cache: "pip" # cache-dependency-path: pyproject.toml - - name: Install build dependencies - run: pip install build + - name: Install uv + uses: astral-sh/setup-uv@v7 - name: Build distribution + run: uv build env: TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} - run: python -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.13.0 - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" + run: uv publish diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index 2ae7ef233d..1a45174dce 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -8,9 +8,6 @@ on: permissions: statuses: write -env: - UV_SYSTEM_PYTHON: 1 - jobs: smokeshow: runs-on: ubuntu-latest @@ -23,14 +20,14 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: cache-dependency-glob: | - requirements**.txt pyproject.toml - - run: uv pip install -r requirements-github-actions.txt + uv.lock + - run: uv sync --locked --no-dev --group github-actions - uses: actions/download-artifact@v6 with: name: coverage-html @@ -41,7 +38,7 @@ jobs: - name: Upload coverage to Smokeshow run: | for i in 1 2 3 4 5; do - if smokeshow upload htmlcov; then + if uv run smokeshow upload htmlcov; then echo "Smokeshow upload success!" break fi diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml index 8b02490011..88590ffa6c 100644 --- a/.github/workflows/sponsors.yml +++ b/.github/workflows/sponsors.yml @@ -10,9 +10,6 @@ on: required: false default: "false" -env: - UV_SYSTEM_PYTHON: 1 - jobs: job: if: github.repository_owner == 'fastapi' @@ -28,17 +25,16 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install Dependencies - run: uv pip install -r requirements-github-actions.txt + run: uv sync --locked --no-dev --group github-actions # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 @@ -46,7 +42,7 @@ jobs: with: limit-access-to-actor: true - name: FastAPI People Sponsors - run: python ./scripts/sponsors.py + run: uv run ./scripts/sponsors.py env: SPONSORS_TOKEN: ${{ secrets.SPONSORS_TOKEN }} PR_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} diff --git a/.github/workflows/test-redistribute.yml b/.github/workflows/test-redistribute.yml index 653ab2a748..0491d33ba7 100644 --- a/.github/workflows/test-redistribute.yml +++ b/.github/workflows/test-redistribute.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.10" + python-version-file: ".python-version" - name: Install build dependencies run: pip install build - name: Build source distribution @@ -40,7 +40,7 @@ jobs: - name: Install test dependencies run: | cd dist/fastapi*/ - pip install -r requirements-tests.txt + pip install --group tests --editable .[all] env: TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} - name: Run source distribution tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ad630d94b..4d45c20eb1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ on: - cron: "0 0 * * 1" env: - UV_SYSTEM_PYTHON: 1 + UV_NO_SYNC: true jobs: test: @@ -44,6 +44,8 @@ jobs: coverage: coverage fail-fast: false runs-on: ${{ matrix.os }} + env: + UV_PYTHON: ${{ matrix.python-version }} steps: - name: Dump GitHub context env: @@ -57,17 +59,16 @@ jobs: - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install Dependencies - run: uv pip install -r requirements-tests.txt + run: uv sync --locked --no-dev --group tests --extra all - run: mkdir coverage - name: Test if: matrix.codspeed != 'codspeed' - run: bash scripts/test.sh + run: uv run bash scripts/test.sh env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} @@ -79,7 +80,7 @@ jobs: CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} with: mode: simulation - run: coverage run -m pytest tests/ --codspeed + run: uv run 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' @@ -100,17 +101,16 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install Dependencies - run: uv pip install -r requirements-tests.txt + run: uv sync --locked --no-dev --group tests --extra all - name: Get coverage files uses: actions/download-artifact@v6 with: @@ -118,15 +118,15 @@ jobs: path: coverage merge-multiple: true - run: ls -la coverage - - run: coverage combine coverage - - run: coverage html --title "Coverage for ${{ github.sha }}" + - run: uv run coverage combine coverage + - run: uv run coverage html --title "Coverage for ${{ github.sha }}" - name: Store coverage HTML uses: actions/upload-artifact@v5 with: name: coverage-html path: htmlcov include-hidden-files: true - - run: coverage report --fail-under=100 + - run: uv run coverage report --fail-under=100 # https://github.com/marketplace/actions/alls-green#why check: # This job does nothing and is only used for the branch protection diff --git a/.github/workflows/topic-repos.yml b/.github/workflows/topic-repos.yml index 41dabee1e5..46f6d60847 100644 --- a/.github/workflows/topic-repos.yml +++ b/.github/workflows/topic-repos.yml @@ -5,9 +5,6 @@ on: - cron: "0 12 1 * *" workflow_dispatch: -env: - UV_SYSTEM_PYTHON: 1 - jobs: topic-repos: if: github.repository_owner == 'fastapi' @@ -23,18 +20,17 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" enable-cache: true cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install GitHub Actions dependencies - run: uv pip install -r requirements-github-actions.txt + run: uv sync --locked --no-dev --group github-actions - name: Update Topic Repos - run: python ./scripts/topic_repos.py + run: uv run ./scripts/topic_repos.py env: GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index f1267d21f5..e6edc5aaba 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -31,9 +31,6 @@ on: required: false default: "" -env: - UV_SYSTEM_PYTHON: 1 - jobs: langs: runs-on: ubuntu-latest @@ -45,20 +42,20 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install Dependencies - run: uv pip install -r requirements-github-actions.txt -r requirements-translations.txt + run: uv sync --locked --no-dev --group github-actions --group translations - name: Export Language Codes id: show-langs run: | - echo "langs=$(python ./scripts/translate.py llm-translatable-json)" >> $GITHUB_OUTPUT - echo "commands=$(python ./scripts/translate.py commands-json)" >> $GITHUB_OUTPUT + echo "langs=$(uv run ./scripts/translate.py llm-translatable-json)" >> $GITHUB_OUTPUT + echo "commands=$(uv run ./scripts/translate.py commands-json)" >> $GITHUB_OUTPUT env: LANGUAGE: ${{ github.event.inputs.language }} COMMAND: ${{ github.event.inputs.command }} @@ -84,15 +81,15 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Setup uv uses: astral-sh/setup-uv@v7 with: cache-dependency-glob: | - requirements**.txt pyproject.toml + uv.lock - name: Install Dependencies - run: uv pip install -r requirements-github-actions.txt -r requirements-translations.txt + run: uv sync --locked --no-dev --group github-actions --group translations # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 @@ -104,8 +101,8 @@ jobs: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: FastAPI Translate run: | - python ./scripts/translate.py ${{ matrix.command }} - python ./scripts/translate.py make-pr + uv run ./scripts/translate.py ${{ matrix.command }} + uv run ./scripts/translate.py make-pr env: GITHUB_TOKEN: ${{ secrets.FASTAPI_TRANSLATIONS }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} diff --git a/.gitignore b/.gitignore index 3dc12ca951..243cdb93a5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,4 @@ archive.zip # macOS .DS_Store -# Ignore while the setup still depends on requirements.txt files -uv.lock - .codspeed diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10a0949e48..5da3cccf1e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,7 @@ repos: hooks: - id: check-added-large-files args: ['--maxkb=750'] + exclude: ^uv.lock$ - id: check-toml - id: check-yaml args: diff --git a/.python-version b/.python-version new file mode 100644 index 0000000000..2c0733315e --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/docs/en/docs/contributing.md b/docs/en/docs/contributing.md index ae99059f41..18b7f60f72 100644 --- a/docs/en/docs/contributing.md +++ b/docs/en/docs/contributing.md @@ -6,44 +6,20 @@ First, you might want to see the basic ways to [help FastAPI and get help](help- If you already cloned the <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">fastapi repository</a> and you want to deep dive in the code, here are some guidelines to set up your environment. -### Virtual environment - -Follow the instructions to create and activate a [virtual environment](virtual-environments.md){.internal-link target=_blank} for the internal code of `fastapi`. - ### Install requirements -After activating the environment, install the required packages: - -//// tab | `pip` +Create a virtual environment and install the required packages with <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>: <div class="termy"> ```console -$ pip install -r requirements.txt +$ uv sync ---> 100% ``` </div> -//// - -//// tab | `uv` - -If you have <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>: - -<div class="termy"> - -```console -$ uv pip install -r requirements.txt - ----> 100% -``` - -</div> - -//// - It will install all the dependencies and your local FastAPI in your local environment. ### Using your local FastAPI @@ -56,9 +32,9 @@ That way, you don't have to "install" your local version to be able to test ever /// note | Technical Details -This only happens when you install using this included `requirements.txt` instead of running `pip install fastapi` directly. +This only happens when you install using `uv sync` instead of running `pip install fastapi` directly. -That is because inside the `requirements.txt` file, the local version of FastAPI is marked to be installed in "editable" mode, with the `-e` option. +That is because `uv sync` will install the local version of FastAPI in "editable" mode by default. /// diff --git a/pyproject.toml b/pyproject.toml index 9c2c35a9f5..fe9b3a7ea1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,6 +123,68 @@ all = [ [project.scripts] fastapi = "fastapi.cli:main" +[dependency-groups] +dev = [ + { include-group = "tests" }, + { include-group = "docs" }, + { include-group = "translations" }, + "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.4.1", + "mkdocs-material==9.7.0", + "mkdocs-redirects>=1.2.1,<1.3.0", + "mkdocstrings[python]==0.30.1", + "pillow==11.3.0", + "python-slugify==8.0.4", + "pyyaml>=5.3.1,<7.0.0", + "typer==0.16.0", +] +docs-tests = [ + "httpx>=0.23.0,<1.0.0", + "ruff==0.14.3", +] +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", +] +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>=1.1.2,<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.27", + "strawberry-graphql>=0.200.0,<1.0.0", + "types-orjson==3.6.2", + "types-ujson==5.10.0.20240515", +] +translations = [ + "gitpython==3.1.45", + "pydantic-ai==0.4.10", + "pygithub==2.8.1", +] + [tool.pdm] version = { source = "file", path = "fastapi/__init__.py" } distribution = true @@ -131,11 +193,10 @@ distribution = true source-includes = [ "tests/", "docs_src/", - "requirements*.txt", "scripts/", # For a test "docs/en/docs/img/favicon.png", - ] +] [tool.tiangolo._internal-slim-build.packages.fastapi-slim.project] name = "fastapi-slim" diff --git a/requirements-docs-tests.txt b/requirements-docs-tests.txt deleted file mode 100644 index 9350f3ee44..0000000000 --- a/requirements-docs-tests.txt +++ /dev/null @@ -1,4 +0,0 @@ -# For mkdocstrings and tests -httpx >=0.23.0,<1.0.0 -# For linting and generating docs versions -ruff ==0.14.3 diff --git a/requirements-docs.txt b/requirements-docs.txt deleted file mode 100644 index beefc654f2..0000000000 --- a/requirements-docs.txt +++ /dev/null @@ -1,21 +0,0 @@ --e . --r requirements-docs-tests.txt -mkdocs-material==9.7.0 -mdx-include >=1.4.1,<2.0.0 -mkdocs-redirects>=1.2.1,<1.3.0 -typer == 0.16.0 -pyyaml >=5.3.1,<7.0.0 -# For Material for MkDocs, Chinese search -jieba==0.42.1 -# For image processing by Material for MkDocs -pillow==11.3.0 -# For image processing by Material for MkDocs -cairosvg==2.8.2 -mkdocstrings[python]==0.30.1 -griffe-typingdoc==0.3.0 -griffe-warnings-deprecated==1.1.0 -# For griffe, it formats with black -black==25.1.0 -mkdocs-macros-plugin==1.4.1 -markdown-include-variants==0.0.8 -python-slugify==8.0.4 diff --git a/requirements-github-actions.txt b/requirements-github-actions.txt deleted file mode 100644 index a6a733447d..0000000000 --- a/requirements-github-actions.txt +++ /dev/null @@ -1,6 +0,0 @@ -PyGithub>=2.3.0,<3.0.0 -pydantic>=2.5.3,<3.0.0 -pydantic-settings>=2.1.0,<3.0.0 -httpx>=0.27.0,<1.0.0 -pyyaml >=5.3.1,<7.0.0 -smokeshow diff --git a/requirements-tests.txt b/requirements-tests.txt deleted file mode 100644 index 1604a2858c..0000000000 --- a/requirements-tests.txt +++ /dev/null @@ -1,18 +0,0 @@ --e .[all] --r requirements-docs-tests.txt -pytest >=7.1.3,<9.0.0 -coverage[toml] >= 6.5.0,< 8.0 -mypy ==1.14.1 -dirty-equals ==0.9.0 -sqlmodel==0.0.27 -flask >=1.1.2,<4.0.0 -strawberry-graphql >=0.200.0,< 1.0.0 -anyio[trio] >=3.2.1,<5.0.0 -PyJWT==2.9.0 -pyyaml >=5.3.1,<7.0.0 -pwdlib[argon2] >=0.2.1 -inline-snapshot>=0.21.1 -pytest-codspeed==4.2.0 -# types -types-ujson ==5.10.0.20240515 -types-orjson ==3.6.2 diff --git a/requirements-translations.txt b/requirements-translations.txt deleted file mode 100644 index 76bfae1670..0000000000 --- a/requirements-translations.txt +++ /dev/null @@ -1,3 +0,0 @@ -pydantic-ai==0.4.10 -GitPython==3.1.45 -pygithub==2.8.1 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 3bbab64a42..0000000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ --e .[all] --r requirements-tests.txt --r requirements-docs.txt --r requirements-translations.txt -prek==0.2.22 -# For generating screenshots -playwright diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000000..9ae2220e0c --- /dev/null +++ b/uv.lock @@ -0,0 +1,5339 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", + "python_full_version < '3.10'", +] + +[[package]] +name = "ag-ui-protocol" +version = "0.1.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/bb/5a5ec893eea5805fb9a3db76a9888c3429710dfb6f24bbb37568f2cf7320/ag_ui_protocol-0.1.10.tar.gz", hash = "sha256:3213991c6b2eb24bb1a8c362ee270c16705a07a4c5962267a083d0959ed894f4", size = 6945, upload-time = "2025-11-06T15:17:17.068Z" } +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 = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anthropic" +version = "0.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { 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" } +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" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[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'" }, +] + +[[package]] +name = "argcomplete" +version = "3.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } +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'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/ba4e4ca8d149f8dcc0d952ac0967089e1d759c7e5fcf0865a317eb680fbb/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e", size = 24549, upload-time = "2025-07-30T10:02:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/9b2386cc75ac0bd3210e12a44bfc7fd1632065ed8b80d573036eecb10442/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d", size = 25539, upload-time = "2025-07-30T10:02:00.929Z" }, + { url = "https://files.pythonhosted.org/packages/31/db/740de99a37aa727623730c90d92c22c9e12585b3c98c54b7960f7810289f/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584", size = 28467, upload-time = "2025-07-30T10:02:02.08Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/47c4509ea18d755f44e2b92b7178914f0c113946d11e16e626df8eaa2b0b/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690", size = 27355, upload-time = "2025-07-30T10:02:02.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/82/82745642d3c46e7cea25e1885b014b033f4693346ce46b7f47483cf5d448/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520", size = 29187, upload-time = "2025-07-30T10:02:03.674Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +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 = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +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 = "babel" +version = "2.17.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" } +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" }, +] + +[[package]] +name = "backrefs" +version = "6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, + { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, + { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" }, + { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, +] + +[[package]] +name = "black" +version = "25.1.0" +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 = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { 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'" }, +] +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" } +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" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "boto3" +version = "1.42.24" +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" } +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" }, +] + +[[package]] +name = "botocore" +version = "1.42.24" +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'" }, +] +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" } +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" }, +] + +[[package]] +name = "cairocffi" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/c5/1a4dc131459e68a173cbdab5fad6b524f53f9c1ef7861b7698e998b837cc/cairocffi-1.7.1.tar.gz", hash = "sha256:2e48ee864884ec4a3a34bfa8c9ab9999f688286eb714a15a43ec9d068c36557b", size = 88096, upload-time = "2024-06-18T10:56:06.741Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d8/ba13451aa6b745c49536e87b6bf8f629b950e84bd0e8308f7dc6883b67e2/cairocffi-1.7.1-py3-none-any.whl", hash = "sha256:9803a0e11f6c962f3b0ae2ec8ba6ae45e957a146a004697a1ac1bbf16b073b3f", size = 75611, upload-time = "2024-06-18T10:55:59.489Z" }, +] + +[[package]] +name = "cairosvg" +version = "2.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cairocffi" }, + { name = "cssselect2" }, + { name = "defusedxml" }, + { name = "pillow" }, + { 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'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/67/48/816bd4aaae93dbf9e408c58598bc32f4a8c65f4b86ab560864cb3ee60adb/cairosvg-2.8.2-py3-none-any.whl", hash = "sha256:eab46dad4674f33267a671dce39b64be245911c901c70d65d2b7b0821e852bf5", size = 45773, upload-time = "2025-05-15T06:56:28.552Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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 = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { 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]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { 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'" }, +] +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 = [ + { 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 = "cohere" +version = "5.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastavro" }, + { name = "httpx" }, + { name = "pydantic" }, + { 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 = "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" } +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" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +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.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/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } +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" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, +] + +[[package]] +name = "cross-web" +version = "0.4.0" +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" } +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" }, +] + +[[package]] +name = "cryptography" +version = "46.0.3" +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" } +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" }, +] + +[[package]] +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 = "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" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/e7/aa315e6a749d9b96c2504a1ba0ba031ba2d0517e972ce22682e3fccecb09/cssselect2-0.8.0-py3-none-any.whl", hash = "sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e", size = 15454, upload-time = "2025-03-05T14:46:06.463Z" }, +] + +[[package]] +name = "cyclic" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/9f/becc4fea44301f232e4eba17752001bd708e3c042fef37a72b9af7ddf4b5/cyclic-1.0.0.tar.gz", hash = "sha256:ecddd56cb831ee3e6b79f61ecb0ad71caee606c507136867782911aa01c3e5eb", size = 2167, upload-time = "2018-09-26T16:47:07.285Z" } +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 = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "dirty-equals" +version = "0.9.0" +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" } +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" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +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" }, +] + +[[package]] +name = "docstring-parser" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +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 = "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 = "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" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + +[[package]] +name = "eval-type-backport" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/a3/cafafb4558fd638aadfe4121dc6cefb8d743368c085acb2f521df0f3d9d7/eval_type_backport-0.3.1.tar.gz", hash = "sha256:57e993f7b5b69d271e37482e62f74e76a0276c82490cf8e4f0dffeb6b332d5ed", size = 9445, upload-time = "2025-12-02T11:51:42.987Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/22/fdc2e30d43ff853720042fa15baa3e6122722be1a7950a98233ebb55cd71/eval_type_backport-0.3.1-py3-none-any.whl", hash = "sha256:279ab641905e9f11129f56a8a78f493518515b83402b860f6f06dd7c011fdfa8", size = 6063, upload-time = "2025-12-02T11:51:41.665Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +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 = "fastapi" +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.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions" }, +] + +[package.optional-dependencies] +all = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["standard"] }, + { name = "httpx" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { 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.21", 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'" }, + { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, +] +standard = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["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.21", 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'" }, +] +standard-no-fastapi-cloud-cli = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["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.21", 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'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "anyio", extra = ["trio"] }, + { 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 = "dirty-equals" }, + { name = "flask" }, + { name = "gitpython" }, + { name = "griffe-typingdoc" }, + { name = "griffe-warnings-deprecated" }, + { name = "httpx" }, + { name = "inline-snapshot" }, + { name = "jieba" }, + { name = "markdown-include-variants" }, + { name = "mdx-include" }, + { name = "mkdocs-macros-plugin" }, + { name = "mkdocs-material" }, + { name = "mkdocs-redirects" }, + { name = "mkdocstrings", extra = ["python"] }, + { name = "mypy" }, + { 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" }, + { name = "pygithub" }, + { name = "pyjwt" }, + { name = "pytest" }, + { name = "pytest-codspeed" }, + { name = "python-slugify" }, + { 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.288.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 = "cairosvg" }, + { name = "griffe-typingdoc" }, + { name = "griffe-warnings-deprecated" }, + { name = "httpx" }, + { name = "jieba" }, + { name = "markdown-include-variants" }, + { name = "mdx-include" }, + { name = "mkdocs-macros-plugin" }, + { name = "mkdocs-material" }, + { name = "mkdocs-redirects" }, + { name = "mkdocstrings", extra = ["python"] }, + { name = "pillow" }, + { name = "python-slugify" }, + { name = "pyyaml" }, + { name = "ruff" }, + { name = "typer" }, +] +docs-tests = [ + { name = "httpx" }, + { name = "ruff" }, +] +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 = "pygithub" }, + { name = "pyyaml" }, + { name = "smokeshow" }, +] +tests = [ + { 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 = "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 = "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.288.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 = "pygithub" }, +] + +[package.metadata] +requires-dist = [ + { name = "annotated-doc", specifier = ">=0.0.2" }, + { name = "email-validator", marker = "extra == 'all'", specifier = ">=2.0.0" }, + { name = "email-validator", marker = "extra == 'standard'", specifier = ">=2.0.0" }, + { name = "email-validator", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=2.0.0" }, + { name = "fastapi-cli", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.0.8" }, + { name = "fastapi-cli", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.0.8" }, + { name = "fastapi-cli", extras = ["standard-no-fastapi-cloud-cli"], marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.0.8" }, + { name = "httpx", marker = "extra == 'all'", specifier = ">=0.23.0,<1.0.0" }, + { name = "httpx", marker = "extra == 'standard'", specifier = ">=0.23.0,<1.0.0" }, + { name = "httpx", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.23.0,<1.0.0" }, + { name = "itsdangerous", marker = "extra == 'all'", specifier = ">=1.1.0" }, + { 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 = "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" }, + { name = "pydantic-extra-types", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=2.0.0" }, + { name = "pydantic-settings", marker = "extra == 'all'", specifier = ">=2.0.0" }, + { name = "pydantic-settings", marker = "extra == 'standard'", specifier = ">=2.0.0" }, + { name = "pydantic-settings", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=2.0.0" }, + { name = "python-multipart", marker = "extra == 'all'", specifier = ">=0.0.18" }, + { 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 = "typing-extensions", specifier = ">=4.8.0" }, + { 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" }, + { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.12.0" }, +] +provides-extras = ["standard", "standard-no-fastapi-cloud-cli", "all"] + +[package.metadata.requires-dev] +dev = [ + { name = "anyio", extras = ["trio"], specifier = ">=3.2.1,<5.0.0" }, + { name = "black", specifier = "==25.1.0" }, + { name = "cairosvg", specifier = "==2.8.2" }, + { name = "coverage", extras = ["toml"], specifier = ">=6.5.0,<8.0" }, + { name = "dirty-equals", specifier = "==0.9.0" }, + { name = "flask", specifier = ">=1.1.2,<4.0.0" }, + { name = "gitpython", specifier = "==3.1.45" }, + { name = "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 = "mdx-include", specifier = ">=1.4.1,<2.0.0" }, + { name = "mkdocs-macros-plugin", specifier = "==1.4.1" }, + { 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 = "playwright", specifier = ">=1.57.0" }, + { 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 = "pytest", specifier = ">=7.1.3,<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" }, + { name = "ruff", specifier = "==0.14.3" }, + { name = "sqlmodel", specifier = "==0.0.27" }, + { name = "strawberry-graphql", specifier = ">=0.200.0,<1.0.0" }, + { name = "typer", specifier = "==0.16.0" }, + { 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 = "httpx", specifier = ">=0.23.0,<1.0.0" }, + { name = "jieba", specifier = "==0.42.1" }, + { name = "markdown-include-variants", specifier = "==0.0.8" }, + { name = "mdx-include", specifier = ">=1.4.1,<2.0.0" }, + { name = "mkdocs-macros-plugin", specifier = "==1.4.1" }, + { name = "mkdocs-material", specifier = "==9.7.0" }, + { name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" }, + { name = "mkdocstrings", extras = ["python"], specifier = "==0.30.1" }, + { name = "pillow", specifier = "==11.3.0" }, + { name = "python-slugify", specifier = "==8.0.4" }, + { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" }, + { name = "ruff", specifier = "==0.14.3" }, + { name = "typer", specifier = "==0.16.0" }, +] +docs-tests = [ + { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, + { name = "ruff", specifier = "==0.14.3" }, +] +github-actions = [ + { name = "httpx", specifier = ">=0.27.0,<1.0.0" }, + { name = "pydantic", specifier = ">=2.5.3,<3.0.0" }, + { name = "pydantic-settings", specifier = ">=2.1.0,<3.0.0" }, + { name = "pygithub", specifier = ">=2.3.0,<3.0.0" }, + { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" }, + { name = "smokeshow", specifier = ">=0.5.0" }, +] +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 = "httpx", specifier = ">=0.23.0,<1.0.0" }, + { name = "inline-snapshot", specifier = ">=0.21.1" }, + { 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-codspeed", specifier = "==4.2.0" }, + { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" }, + { name = "ruff", specifier = "==0.14.3" }, + { name = "sqlmodel", specifier = "==0.0.27" }, + { name = "strawberry-graphql", specifier = ">=0.200.0,<1.0.0" }, + { name = "types-orjson", specifier = "==3.6.2" }, + { name = "types-ujson", specifier = "==5.10.0.20240515" }, +] +translations = [ + { name = "gitpython", specifier = "==3.1.45" }, + { name = "pydantic-ai", specifier = "==0.4.10" }, + { name = "pygithub", specifier = "==2.8.1" }, +] + +[[package]] +name = "fastapi-cli" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +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'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/08/89/5c4eef60524d0fd704eb0706885b82cd5623a43396b94e4a5b17d3a3f516/fastapi_cli-0.0.20-py3-none-any.whl", hash = "sha256:e58b6a0038c0b1532b7a0af690656093dee666201b6b19d3c87175b358e9f783", size = 12390, upload-time = "2025-12-22T17:13:31.708Z" }, +] + +[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'" }, +] +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'" }, +] + +[[package]] +name = "fastapi-cloud-cli" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastar" }, + { name = "httpx" }, + { name = "pydantic", extra = ["email"] }, + { name = "rich-toolkit" }, + { 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'" }, +] +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" } +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" }, +] + +[[package]] +name = "fastar" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/e7/f89d54fb04104114dd0552836dc2b47914f416cc0e200b409dd04a33de5e/fastar-0.8.0.tar.gz", hash = "sha256:f4d4d68dbf1c4c2808f0e730fac5843493fc849f70fe3ad3af60dfbaf68b9a12", size = 68524, upload-time = "2025-11-26T02:36:00.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/e2/51d9ee443aabcd5aa581d45b18b6198ced364b5cd97e5504c5d782ceb82c/fastar-0.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c9f930cff014cf79d396d0541bd9f3a3f170c9b5e45d10d634d98f9ed08788c3", size = 708536, upload-time = "2025-11-26T02:34:35.236Z" }, + { url = "https://files.pythonhosted.org/packages/07/2a/edfc6274768b8a3859a5ca4f8c29cb7f614d7f27d2378e2c88aa91cda54e/fastar-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07b70f712d20622346531a4b46bb332569bea621f61314c0b7e80903a16d14cf", size = 632235, upload-time = "2025-11-26T02:34:19.367Z" }, + { url = "https://files.pythonhosted.org/packages/ef/1e/3cfbaaec464caef196700ee2ffae1c03f94f7c5e2a85d0ec0ea9cdd1da81/fastar-0.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:330639db3bfba4c6d132421a2a4aeb81e7bea8ce9159cdb6e247fbc5fae97686", size = 871386, upload-time = "2025-11-26T02:33:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/82/50/224a674ad541054179e4e6e0b54bb6e162f04f698a2512b42a8085fc6b6f/fastar-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ea7ceb6231e48d7bb0d7dc13e946baa29c7f6873eaf4afb69725d6da349033", size = 764955, upload-time = "2025-11-26T02:32:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/4d/5e/4608184aa57cb6a54f62c1eb3e5133ba8d461fc7f13193c0255effbec12a/fastar-0.8.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a90695a601a78bbca910fdf2efcdf3103c55d0de5a5c6e93556d707bf886250b", size = 765987, upload-time = "2025-11-26T02:32:59.701Z" }, + { url = "https://files.pythonhosted.org/packages/e0/53/6afd2b680dddfa10df9a16bbcf6cabfee0d92435d5c7e3f4cfe3b1712662/fastar-0.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d0bf655ff4c9320b0ca8a5b128063d5093c0c8c1645a2b5f7167143fd8531aa", size = 930900, upload-time = "2025-11-26T02:33:16.059Z" }, + { url = "https://files.pythonhosted.org/packages/ef/1e/b7a304bfcc1d06845cbfa4b464516f6fff9c8c6692f6ef80a3a86b04e199/fastar-0.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8df22cdd8d58e7689aa89b2e4a07e8e5fa4f88d2d9c2621f0e88a49be97ccea", size = 821523, upload-time = "2025-11-26T02:33:30.897Z" }, + { url = "https://files.pythonhosted.org/packages/1d/da/9ef8605c6d233cd6ca3a95f7f518ac22aa064903afe6afa57733bfb7c31b/fastar-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a5e6ad722685128521c8fb44cf25bd38669650ba3a4b466b8903e5aa28e1a0", size = 821268, upload-time = "2025-11-26T02:34:04.003Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/ed37c78a6b4420de1677d82e79742787975c34847229c33dc376334c7283/fastar-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:31cd541231a2456e32104da891cf9962c3b40234d0465cbf9322a6bc8a1b05d5", size = 986286, upload-time = "2025-11-26T02:34:50.279Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a6/366b15f432d85d4089e6e4b52a09cc2a2bcf4d7a1f0771e3d3194deccb1e/fastar-0.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:175db2a98d67ced106468e8987975484f8bbbd5ad99201da823b38bafb565ed5", size = 1041921, upload-time = "2025-11-26T02:35:07.292Z" }, + { url = "https://files.pythonhosted.org/packages/f4/45/45f8e6991e3ce9f8aeefdc8d4c200daada41097a36808643d1703464c3e2/fastar-0.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ada877ab1c65197d772ce1b1c2e244d4799680d8b3f136a4308360f3d8661b23", size = 1047302, upload-time = "2025-11-26T02:35:24.995Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e2/a587796111a3cd4b78cd61ec3fc1252d8517d81f763f4164ed5680f84810/fastar-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:01084cb75f13ca6a8e80bd41584322523189f8e81b472053743d6e6c3062b5a6", size = 995141, upload-time = "2025-11-26T02:35:42.449Z" }, + { url = "https://files.pythonhosted.org/packages/89/c0/7a8ec86695b0b77168e220cf2af1aa30592f5ecdbd0ce6d641d29c4a8bae/fastar-0.8.0-cp310-cp310-win32.whl", hash = "sha256:ca639b9909805e44364ea13cca2682b487e74826e4ad75957115ec693228d6b6", size = 456544, upload-time = "2025-11-26T02:36:23.801Z" }, + { url = "https://files.pythonhosted.org/packages/be/a9/8da4deb840121c59deabd939ce2dca3d6beec85576f3743d1144441938b5/fastar-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:fbc0f2ed0f4add7fb58034c576584d44d7eaaf93dee721dfb26dbed6e222dbac", size = 490701, upload-time = "2025-11-26T02:36:09.625Z" }, + { url = "https://files.pythonhosted.org/packages/cd/15/1c764530b81b266f6d27d78d49b6bef22a73b3300cd83a280bfd244908c5/fastar-0.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cd9c0d3ebf7a0a6f642f771cf41b79f7c98d40a3072a8abe1174fbd9bd615bd3", size = 708427, upload-time = "2025-11-26T02:34:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/41/fc/75d42c008516543219e4293e4d8ac55da57a5c63147484f10468bd1bc24e/fastar-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2875a077340fe4f8099bd3ed8fa90d9595e1ac3cd62ae19ab690d5bf550eeb35", size = 631740, upload-time = "2025-11-26T02:34:20.718Z" }, + { url = "https://files.pythonhosted.org/packages/50/8d/9632984f7824ed2210157dcebd8e9821ef6d4f2b28510d0516db6625ff9b/fastar-0.8.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a999263d9f87184bf2801833b2ecf105e03c0dd91cac78685673b70da564fd64", size = 871628, upload-time = "2025-11-26T02:33:49.279Z" }, + { url = "https://files.pythonhosted.org/packages/05/97/3eb6ea71b7544d45cd29cacb764ca23cde8ce0aed1a6a02251caa4c0a818/fastar-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c41111da56430f638cbfc498ebdcc7d30f63416e904b27b7695c29bd4889cb8", size = 765005, upload-time = "2025-11-26T02:32:45.833Z" }, + { url = "https://files.pythonhosted.org/packages/d6/45/3eb0ee945a0b5d5f9df7e7c25c037ce7fa441cd0b4d44f76d286e2f4396a/fastar-0.8.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3719541a12bb09ab1eae91d2c987a9b2b7d7149c52e7109ba6e15b74aabc49b1", size = 765587, upload-time = "2025-11-26T02:33:01.174Z" }, + { url = "https://files.pythonhosted.org/packages/51/bb/7defd6ec0d9570b1987d8ebde52d07d97f3f26e10b592fb3e12738eba39a/fastar-0.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9b0fff8079b18acdface7ef1b7f522fd9a589f65ca4a1a0dd7c92a0886c2a2", size = 931150, upload-time = "2025-11-26T02:33:17.374Z" }, + { url = "https://files.pythonhosted.org/packages/28/54/62e51e684dab347c61878afbf09e177029c1a91eb1e39ef244e6b3ef9efa/fastar-0.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac073576c1931959191cb20df38bab21dd152f66c940aa3ca8b22e39f753b2f3", size = 821354, upload-time = "2025-11-26T02:33:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/53/a8/12708ea4d21e3cf9f485b2a67d44ce84d949a6eddcc9aa5b3d324585ab43/fastar-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:003b59a7c3e405b6a7bff8fab17d31e0ccbc7f06730a8f8ca1694eeea75f3c76", size = 821626, upload-time = "2025-11-26T02:34:05.685Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/1b4d3347c7a759853f963410bf6baf42fe014d587c50c39c8e145f4bf1a0/fastar-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a7b96748425efd9fc155cd920d65088a1b0d754421962418ea73413d02ff515a", size = 986187, upload-time = "2025-11-26T02:34:52.047Z" }, + { url = "https://files.pythonhosted.org/packages/dc/59/2dbe0dc2570764475e60030403738faa261a9d3bff16b08629c378ab939a/fastar-0.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:90957a30e64418b02df5b4d525bea50403d98a4b1f29143ce5914ddfa7e54ee4", size = 1041536, upload-time = "2025-11-26T02:35:08.926Z" }, + { url = "https://files.pythonhosted.org/packages/d9/0f/639b295669c7ca6fbc2b4be2a7832aaeac1a5e06923f15a8a6d6daecbc7d/fastar-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f6e784a8015623fbb7ccca1af372fd82cb511b408ddd2348dc929fc6e415df73", size = 1047149, upload-time = "2025-11-26T02:35:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e7/23e3a19e06d261d1894f98eca9458f98c090c505a0c712dafc0ff1fc2965/fastar-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a03eaf287bbc93064688a1220580ce261e7557c8898f687f4d0b281c85b28d3c", size = 994992, upload-time = "2025-11-26T02:35:44.009Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7a/3ea4726bae3ac9358d02107ae48f3e10ee186dbed554af79e00b7b498c44/fastar-0.8.0-cp311-cp311-win32.whl", hash = "sha256:661a47ed90762f419406c47e802f46af63a08254ba96abd1c8191e4ce967b665", size = 456449, upload-time = "2025-11-26T02:36:25.291Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3c/0142bee993c431ee91cf5535e6e4b079ad491f620c215fcd79b7e5ffeb2b/fastar-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:b48abd6056fef7bc3d414aafb453c5b07fdf06d2df5a2841d650288a3aa1e9d3", size = 490863, upload-time = "2025-11-26T02:36:11.114Z" }, + { url = "https://files.pythonhosted.org/packages/3b/18/d119944f6bdbf6e722e204e36db86390ea45684a1bf6be6e3aa42abd471f/fastar-0.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:50c18788b3c6ffb85e176dcb8548bb8e54616a0519dcdbbfba66f6bbc4316933", size = 462230, upload-time = "2025-11-26T02:36:01.917Z" }, + { url = "https://files.pythonhosted.org/packages/58/f1/5b2ff898abac7f1a418284aad285e3a4f68d189c572ab2db0f6c9079dd16/fastar-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f10d2adfe40f47ff228f4efaa32d409d732ded98580e03ed37c9535b5fc923d", size = 706369, upload-time = "2025-11-26T02:34:37.783Z" }, + { url = "https://files.pythonhosted.org/packages/23/60/8046a386dca39154f80c927cbbeeb4b1c1267a3271bffe61552eb9995757/fastar-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b930da9d598e3bc69513d131f397e6d6be4643926ef3de5d33d1e826631eb036", size = 629097, upload-time = "2025-11-26T02:34:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/22/7e/1ae005addc789924a9268da2394d3bb5c6f96836f7e37b7e3d23c2362675/fastar-0.8.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9d210da2de733ca801de83e931012349d209f38b92d9630ccaa94bd445bdc9b8", size = 868938, upload-time = "2025-11-26T02:33:51.119Z" }, + { url = "https://files.pythonhosted.org/packages/a6/77/290a892b073b84bf82e6b2259708dfe79c54f356e252c2dd40180b16fe07/fastar-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa02270721517078a5bd61a38719070ac2537a4aa6b6c48cf369cf2abc59174a", size = 765204, upload-time = "2025-11-26T02:32:47.02Z" }, + { url = "https://files.pythonhosted.org/packages/d0/00/c3155171b976003af3281f5258189f1935b15d1221bfc7467b478c631216/fastar-0.8.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83c391e5b789a720e4d0029b9559f5d6dee3226693c5b39c0eab8eaece997e0f", size = 764717, upload-time = "2025-11-26T02:33:02.453Z" }, + { url = "https://files.pythonhosted.org/packages/b7/43/405b7ad76207b2c11b7b59335b70eac19e4a2653977f5588a1ac8fed54f4/fastar-0.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3258d7a78a72793cdd081545da61cabe85b1f37634a1d0b97ffee0ff11d105ef", size = 931502, upload-time = "2025-11-26T02:33:18.619Z" }, + { url = "https://files.pythonhosted.org/packages/da/8a/a3dde6d37cc3da4453f2845cdf16675b5686b73b164f37e2cc579b057c2c/fastar-0.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6eab95dd985cdb6a50666cbeb9e4814676e59cfe52039c880b69d67cfd44767", size = 821454, upload-time = "2025-11-26T02:33:33.427Z" }, + { url = "https://files.pythonhosted.org/packages/da/c1/904fe2468609c8990dce9fe654df3fbc7324a8d8e80d8240ae2c89757064/fastar-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:829b1854166141860887273c116c94e31357213fa8e9fe8baeb18bd6c38aa8d9", size = 821647, upload-time = "2025-11-26T02:34:07Z" }, + { url = "https://files.pythonhosted.org/packages/c8/73/a0642ab7a400bc07528091785e868ace598fde06fcd139b8f865ec1b6f3c/fastar-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1667eae13f9457a3c737f4376d68e8c3e548353538b28f7e4273a30cb3965cd", size = 986342, upload-time = "2025-11-26T02:34:53.371Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/60c1bfa6edab72366461a95f053d0f5f7ab1825fe65ca2ca367432cd8629/fastar-0.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b864a95229a7db0814cd9ef7987cb713fd43dce1b0d809dd17d9cd6f02fdde3e", size = 1040207, upload-time = "2025-11-26T02:35:10.65Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a0/0d624290dec622e7fa084b6881f456809f68777d54a314f5dde932714506/fastar-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c05fbc5618ce17675a42576fa49858d79734627f0a0c74c0875ab45ee8de340c", size = 1045031, upload-time = "2025-11-26T02:35:28.108Z" }, + { url = "https://files.pythonhosted.org/packages/a7/74/cf663af53c4706ba88e6b4af44a6b0c3bd7d7ca09f079dc40647a8f06585/fastar-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7f41c51ee96f338662ee3c3df4840511ba3f9969606840f1b10b7cb633a3c716", size = 994877, upload-time = "2025-11-26T02:35:45.797Z" }, + { url = "https://files.pythonhosted.org/packages/52/17/444c8be6e77206050e350da7c338102b6cab384be937fa0b1d6d1f9ede73/fastar-0.8.0-cp312-cp312-win32.whl", hash = "sha256:d949a1a2ea7968b734632c009df0571c94636a5e1622c87a6e2bf712a7334f47", size = 455996, upload-time = "2025-11-26T02:36:26.938Z" }, + { url = "https://files.pythonhosted.org/packages/dc/34/fc3b5e56d71a17b1904800003d9251716e8fd65f662e1b10a26881698a74/fastar-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:fc645994d5b927d769121094e8a649b09923b3c13a8b0b98696d8f853f23c532", size = 490429, upload-time = "2025-11-26T02:36:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/35/a8/5608cc837417107c594e2e7be850b9365bcb05e99645966a5d6a156285fe/fastar-0.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:d81ee82e8dc78a0adb81728383bd39611177d642a8fa2d601d4ad5ad59e5f3bd", size = 461297, upload-time = "2025-11-26T02:36:03.546Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a5/79ecba3646e22d03eef1a66fb7fc156567213e2e4ab9faab3bbd4489e483/fastar-0.8.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a3253a06845462ca2196024c7a18f5c0ba4de1532ab1c4bad23a40b332a06a6a", size = 706112, upload-time = "2025-11-26T02:34:39.237Z" }, + { url = "https://files.pythonhosted.org/packages/0a/03/4f883bce878218a8676c2d7ca09b50c856a5470bb3b7f63baf9521ea6995/fastar-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5cbeb3ebfa0980c68ff8b126295cc6b208ccd81b638aebc5a723d810a7a0e5d2", size = 628954, upload-time = "2025-11-26T02:34:23.705Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f1/892e471f156b03d10ba48ace9384f5a896702a54506137462545f38e40b8/fastar-0.8.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1c0d5956b917daac77d333d48b3f0f3ff927b8039d5b32d8125462782369f761", size = 868685, upload-time = "2025-11-26T02:33:53.077Z" }, + { url = "https://files.pythonhosted.org/packages/39/ba/e24915045852e30014ec6840446975c03f4234d1c9270394b51d3ad18394/fastar-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27b404db2b786b65912927ce7f3790964a4bcbde42cdd13091b82a89cd655e1c", size = 765044, upload-time = "2025-11-26T02:32:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/1aa11ac21a99984864c2fca4994e094319ff3a2046e7a0343c39317bd5b9/fastar-0.8.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0902fc89dcf1e7f07b8563032a4159fe2b835e4c16942c76fd63451d0e5f76a3", size = 764322, upload-time = "2025-11-26T02:33:03.859Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f0/4b91902af39fe2d3bae7c85c6d789586b9fbcf618d7fdb3d37323915906d/fastar-0.8.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:069347e2f0f7a8b99bbac8cd1bc0e06c7b4a31dc964fc60d84b95eab3d869dc1", size = 931016, upload-time = "2025-11-26T02:33:19.902Z" }, + { url = "https://files.pythonhosted.org/packages/c9/97/8fc43a5a9c0a2dc195730f6f7a0f367d171282cd8be2511d0e87c6d2dad0/fastar-0.8.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd135306f6bfe9a835918280e0eb440b70ab303e0187d90ab51ca86e143f70d", size = 821308, upload-time = "2025-11-26T02:33:34.664Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e9/058615b63a7fd27965e8c5966f393ed0c169f7ff5012e1674f21684de3ba/fastar-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d06d6897f43c27154b5f2d0eb930a43a81b7eec73f6f0b0114814d4a10ab38", size = 821171, upload-time = "2025-11-26T02:34:08.498Z" }, + { url = "https://files.pythonhosted.org/packages/ca/cf/69e16a17961570a755c37ffb5b5aa7610d2e77807625f537989da66f2a9d/fastar-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a922f8439231fa0c32b15e8d70ff6d415619b9d40492029dabbc14a0c53b5f18", size = 986227, upload-time = "2025-11-26T02:34:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/fb/83/2100192372e59b56f4ace37d7d9cabda511afd71b5febad1643d1c334271/fastar-0.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a739abd51eb766384b4caff83050888e80cd75bbcfec61e6d1e64875f94e4a40", size = 1039395, upload-time = "2025-11-26T02:35:12.166Z" }, + { url = "https://files.pythonhosted.org/packages/75/15/cdd03aca972f55872efbb7cf7540c3fa7b97a75d626303a3ea46932163dc/fastar-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a65f419d808b23ac89d5cd1b13a2f340f15bc5d1d9af79f39fdb77bba48ff1b", size = 1044766, upload-time = "2025-11-26T02:35:29.62Z" }, + { url = "https://files.pythonhosted.org/packages/3d/29/945e69e4e2652329ace545999334ec31f1431fbae3abb0105587e11af2ae/fastar-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7bb2ae6c0cce58f0db1c9f20495e7557cca2c1ee9c69bbd90eafd54f139171c5", size = 994740, upload-time = "2025-11-26T02:35:47.887Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5d/dbfe28f8cd1eb484bba0c62e5259b2cf6fea229d6ef43e05c06b5a78c034/fastar-0.8.0-cp313-cp313-win32.whl", hash = "sha256:b28753e0d18a643272597cb16d39f1053842aa43131ad3e260c03a2417d38401", size = 455990, upload-time = "2025-11-26T02:36:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/e1/01/e965740bd36e60ef4c5aa2cbe42b6c4eb1dc3551009238a97c2e5e96bd23/fastar-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:620e5d737dce8321d49a5ebb7997f1fd0047cde3512082c27dc66d6ac8c1927a", size = 490227, upload-time = "2025-11-26T02:36:14.363Z" }, + { url = "https://files.pythonhosted.org/packages/dd/10/c99202719b83e5249f26902ae53a05aea67d840eeb242019322f20fc171c/fastar-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:c4c4bd08df563120cd33e854fe0a93b81579e8571b11f9b7da9e84c37da2d6b6", size = 461078, upload-time = "2025-11-26T02:36:04.94Z" }, + { url = "https://files.pythonhosted.org/packages/96/4a/9573b87a0ef07580ed111e7230259aec31bb33ca3667963ebee77022ec61/fastar-0.8.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:50b36ce654ba44b0e13fae607ae17ee6e1597b69f71df1bee64bb8328d881dfc", size = 706041, upload-time = "2025-11-26T02:34:40.638Z" }, + { url = "https://files.pythonhosted.org/packages/4a/19/f95444a1d4f375333af49300aa75ee93afa3335c0e40fda528e460ed859c/fastar-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63a892762683d7ab00df0227d5ea9677c62ff2cde9b875e666c0be569ed940f3", size = 628617, upload-time = "2025-11-26T02:34:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c9/b51481b38b7e3f16ef2b9e233b1a3623386c939d745d6e41bbd389eaae30/fastar-0.8.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4ae6a145c1bff592644bde13f2115e0239f4b7babaf506d14e7d208483cf01a5", size = 869299, upload-time = "2025-11-26T02:33:54.274Z" }, + { url = "https://files.pythonhosted.org/packages/bf/02/3ba1267ee5ba7314e29c431cf82eaa68586f2c40cdfa08be3632b7d07619/fastar-0.8.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ae0ff7c0a1c7e1428404b81faee8aebef466bfd0be25bfe4dabf5d535c68741", size = 764667, upload-time = "2025-11-26T02:32:49.606Z" }, + { url = "https://files.pythonhosted.org/packages/1b/84/bf33530fd015b5d7c2cc69e0bce4a38d736754a6955487005aab1af6adcd/fastar-0.8.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dbfd87dbd217b45c898b2dbcd0169aae534b2c1c5cbe3119510881f6a5ac8ef5", size = 763993, upload-time = "2025-11-26T02:33:05.782Z" }, + { url = "https://files.pythonhosted.org/packages/da/e0/9564d24e7cea6321a8d921c6d2a457044a476ef197aa4708e179d3d97f0d/fastar-0.8.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5abd99fcba83ef28c8fe6ae2927edc79053db43a0457a962ed85c9bf150d37", size = 930153, upload-time = "2025-11-26T02:33:21.53Z" }, + { url = "https://files.pythonhosted.org/packages/35/b1/6f57fcd8d6e192cfebf97e58eb27751640ad93784c857b79039e84387b51/fastar-0.8.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91d4c685620c3a9d6b5ae091dbabab4f98b20049b7ecc7976e19cc9016c0d5d6", size = 821177, upload-time = "2025-11-26T02:33:35.839Z" }, + { url = "https://files.pythonhosted.org/packages/b3/78/9e004ea9f3aa7466f5ddb6f9518780e1d2f0ed3ca55f093632982598bace/fastar-0.8.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f77c2f2cad76e9dc7b6701297adb1eba87d0485944b416fc2ccf5516c01219a3", size = 820652, upload-time = "2025-11-26T02:34:09.776Z" }, + { url = "https://files.pythonhosted.org/packages/42/95/b604ed536544005c9f1aee7c4c74b00150db3d8d535cd8232dc20f947063/fastar-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e7f07c4a3dada7757a8fc430a5b4a29e6ef696d2212747213f57086ffd970316", size = 985961, upload-time = "2025-11-26T02:34:56.401Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7b/fa9d4d96a5d494bdb8699363bb9de8178c0c21a02e1d89cd6f913d127018/fastar-0.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:90c0c3fe55105c0aed8a83135dbdeb31e683455dbd326a1c48fa44c378b85616", size = 1039316, upload-time = "2025-11-26T02:35:13.807Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f9/8462789243bc3f33e8401378ec6d54de4e20cfa60c96a0e15e3e9d1389bb/fastar-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fb9ee51e5bffe0dab3d3126d3a4fac8d8f7235cedcb4b8e74936087ce1c157f3", size = 1045028, upload-time = "2025-11-26T02:35:31.079Z" }, + { url = "https://files.pythonhosted.org/packages/a5/71/9abb128777e616127194b509e98fcda3db797d76288c1a8c23dd22afc14f/fastar-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e380b1e8d30317f52406c43b11e98d11e1d68723bbd031e18049ea3497b59a6d", size = 994677, upload-time = "2025-11-26T02:35:49.391Z" }, + { url = "https://files.pythonhosted.org/packages/de/c1/b81b3f194853d7ad232a67a1d768f5f51a016f165cfb56cb31b31bbc6177/fastar-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1c4ffc06e9c4a8ca498c07e094670d8d8c0d25b17ca6465b9774da44ea997ab1", size = 456687, upload-time = "2025-11-26T02:36:30.205Z" }, + { url = "https://files.pythonhosted.org/packages/cb/87/9e0cd4768a98181d56f0cdbab2363404cc15deb93f4aad3b99cd2761bbaa/fastar-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:5517a8ad4726267c57a3e0e2a44430b782e00b230bf51c55b5728e758bb3a692", size = 490578, upload-time = "2025-11-26T02:36:16.218Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1e/580a76cf91847654f2ad6520e956e93218f778540975bc4190d363f709e2/fastar-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:58030551046ff4a8616931e52a36c83545ff05996db5beb6e0cd2b7e748aa309", size = 461473, upload-time = "2025-11-26T02:36:06.373Z" }, + { url = "https://files.pythonhosted.org/packages/58/4c/bdb5c6efe934f68708529c8c9d4055ebef5c4be370621966438f658b29bd/fastar-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:1e7d29b6bfecb29db126a08baf3c04a5ab667f6cea2b7067d3e623a67729c4a6", size = 705570, upload-time = "2025-11-26T02:34:42.01Z" }, + { url = "https://files.pythonhosted.org/packages/6d/78/f01ac7e71d5a37621bd13598a26e948a12b85ca8042f7ee1a0a8c9f59cda/fastar-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05eb7b96940f9526b485f1d0b02393839f0f61cac4b1f60024984f8b326d2640", size = 627761, upload-time = "2025-11-26T02:34:26.152Z" }, + { url = "https://files.pythonhosted.org/packages/06/45/6df0ecda86ea9d2e95053c1a655d153dee55fc121b6e13ea6d1e246a50b6/fastar-0.8.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:619352d8ac011794e2345c462189dc02ba634750d23cd9d86a9267dd71b1f278", size = 869414, upload-time = "2025-11-26T02:33:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b2/72/486421f5a8c0c377cc82e7a50c8a8ea899a6ec2aa72bde8f09fb667a2dc8/fastar-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74ebfecef3fe6d7a90355fac1402fd30636988332a1d33f3e80019a10782bb24", size = 763863, upload-time = "2025-11-26T02:32:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/39f654dbb41a3867fb1f2c8081c014d8f1d32ea10585d84cacbef0b32995/fastar-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2975aca5a639e26a3ab0d23b4b0628d6dd6d521146c3c11486d782be621a35aa", size = 763065, upload-time = "2025-11-26T02:33:07.274Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bd/c011a34fb3534c4c3301f7c87c4ffd7e47f6113c904c092ddc8a59a303ea/fastar-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afc438eaed8ff0dcdd9308268be5cb38c1db7e94c3ccca7c498ca13a4a4535a3", size = 930530, upload-time = "2025-11-26T02:33:23.117Z" }, + { url = "https://files.pythonhosted.org/packages/55/9d/aa6e887a7033c571b1064429222bbe09adc9a3c1e04f3d1788ba5838ebd5/fastar-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ced0a5399cc0a84a858ef0a31ca2d0c24d3bbec4bcda506a9192d8119f3590a", size = 820572, upload-time = "2025-11-26T02:33:37.542Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9c/7a3a2278a1052e1a5d98646de7c095a00cffd2492b3b84ce730e2f1cd93a/fastar-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec9b23da8c4c039da3fe2e358973c66976a0c8508aa06d6626b4403cb5666c19", size = 820649, upload-time = "2025-11-26T02:34:11.108Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d38edc1f4438cd047e56137c26d94783ffade42e1b3bde620ccf17b771ef/fastar-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dfba078fcd53478032fd0ceed56960ec6b7ff0511cfc013a8a3a4307e3a7bac4", size = 985653, upload-time = "2025-11-26T02:34:57.884Z" }, + { url = "https://files.pythonhosted.org/packages/69/d9/2147d0c19757e165cd62d41cec3f7b38fad2ad68ab784978b5f81716c7ea/fastar-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ade56c94c14be356d295fecb47a3fcd473dd43a8803ead2e2b5b9e58feb6dcfa", size = 1038140, upload-time = "2025-11-26T02:35:15.778Z" }, + { url = "https://files.pythonhosted.org/packages/7f/1d/ec4c717ffb8a308871e9602ec3197d957e238dc0227127ac573ec9bca952/fastar-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e48d938f9366db5e59441728f70b7f6c1ccfab7eff84f96f9b7e689b07786c52", size = 1045195, upload-time = "2025-11-26T02:35:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/637334dc8c8f3bb391388b064ae13f0ad9402bc5a6c3e77b8887d0c31921/fastar-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:79c441dc1482ff51a54fb3f57ae6f7bb3d2cff88fa2cc5d196c519f8aab64a56", size = 994686, upload-time = "2025-11-26T02:35:51.392Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/eb/79/4124c54260f7ee5cb7034bfe499eff2f8512b052d54be4671e59d4f25a4f/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e54bfdee6c81a0005e147319e93d8797f442308032c92fa28d03ef8fda076", size = 766779, upload-time = "2025-11-26T02:32:55.109Z" }, + { url = "https://files.pythonhosted.org/packages/36/b6/043b263c4126bf6557c942d099503989af9c5c7ee5cca9a04e00f754816f/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a78e5221b94a80800930b7fd0d0e797ae73aadf7044c05ed46cb9bdf870f022", size = 766755, upload-time = "2025-11-26T02:33:11.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/ff/29a5dc06f2940439ebf98661ecc98d48d3f22fed8d6a2d5dc985d1e8da24/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997092d31ff451de8d0568f6773f3517cb87dcd0bc76184edb65d7154390a6f8", size = 932732, upload-time = "2025-11-26T02:33:27.122Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e8/2218830f422b37aad52c24b53cb84b5d88bd6fd6ad411bd6689b1a32500d/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:558e8fcf8fe574541df5db14a46cd98bfbed14a811b7014a54f2b714c0cfac42", size = 822571, upload-time = "2025-11-26T02:33:42.986Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fd/ba6dfeff77cddfe58d85c490b1735c002b81c0d6f826916a8b6c4f8818bc/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1d2a54f87e2908cc19e1a6ee249620174fbefc54a219aba1eaa6f31657683c3", size = 822440, upload-time = "2025-11-26T02:34:15.439Z" }, + { url = "https://files.pythonhosted.org/packages/a7/57/54d5740c84b35de0eb12975397ecc16785b5ad8bed2dbac38b8c8a7c1edd/fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ef94901537be277f9ec59db939eb817960496c6351afede5b102699b5098604d", size = 987424, upload-time = "2025-11-26T02:35:02.742Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c7/18115927f16deb1ddffdbd4ae992e7e33064bc6defa2b92a147948f8bc0c/fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:0afbb92f78bf29d5e9db76fb46cbabc429e49015cddf72ab9e761afbe88ac100", size = 1042675, upload-time = "2025-11-26T02:35:20.252Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1a/ca884fc7973ec6d765e87af23a4dd25784fb0a36ac2df825f18c3630bbab/fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:fb59c7925e7710ad178d9e1a3e65edf295d9a042a0cdcb673b4040949eb8ad0a", size = 1047098, upload-time = "2025-11-26T02:35:37.643Z" }, + { url = "https://files.pythonhosted.org/packages/44/ee/25cd645db749b206bb95e1512e57e75d56ccbbb8ec3536f52a7979deab6b/fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e6c4d6329da568ec36b1347b0c09c4d27f9dfdeddf9f438ddb16799ecf170098", size = 997397, upload-time = "2025-11-26T02:35:56.215Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/6c46aa7f8c8734e7f96ee5141acd3877667ce66f34eea10703aa7571d191/fastar-0.8.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:998e3fa4b555b63eb134e6758437ed739ad1652fdd2a61dfe1dacbfddc35fe66", size = 710662, upload-time = "2025-11-26T02:34:47.593Z" }, + { url = "https://files.pythonhosted.org/packages/70/27/fd622442f2fbd4ff5459677987481ef1c60e077cb4e63a2ed4d8dce6f869/fastar-0.8.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5f83e60d845091f3a12bc37f412774264d161576eaf810ed8b43567eb934b7e5", size = 634049, upload-time = "2025-11-26T02:34:32.365Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ee/aa4d08aea25b5419a7277132e738ab1cd775f26aebddce11413b07e2fdff/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:299672e1c74d8b73c61684fac9159cfc063d35f4b165996a88facb0e26862cb5", size = 872055, upload-time = "2025-11-26T02:34:01.377Z" }, + { url = "https://files.pythonhosted.org/packages/92/9a/2bf2f77aade575e67997e0c759fd55cb1c66b7a5b437b1cd0e97d8b241bc/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3d3a27066b84d015deab5faee78565509bb33b137896443e4144cb1be1a5f90", size = 766787, upload-time = "2025-11-26T02:32:57.161Z" }, + { url = "https://files.pythonhosted.org/packages/0b/90/23a3f6c252f11b10c70f854bce09abc61f71b5a0e6a4b0eac2bcb9a2c583/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef0bcf4385bbdd3c1acecce2d9ea7dab7cc9b8ee0581bbccb7ab11908a7ce288", size = 766861, upload-time = "2025-11-26T02:33:12.824Z" }, + { url = "https://files.pythonhosted.org/packages/76/bb/beeb9078380acd4484db5c957d066171695d9340e3526398eb230127b0c2/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f10ef62b6eda6cb6fd9ba8e1fe08a07d7b2bdcc8eaa00eb91566143b92ed7eee", size = 932667, upload-time = "2025-11-26T02:33:28.405Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6d/b034cc637bd0ee638d5a85d08e941b0b8ffd44cf391fb751ba98233734f7/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4f6c82a8ee98c17aa48585ee73b51c89c1b010e5c951af83e07c3436180e3fc", size = 822712, upload-time = "2025-11-26T02:33:44.27Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2b/7d183c63f59227c4689792042d6647f2586a5e7273b55e81745063088d81/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6129067fcb86276635b5857010f4e9b9c7d5d15dd571bb03c6c1ed73c40fd92", size = 822659, upload-time = "2025-11-26T02:34:16.815Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f9/716e0cd9de2427fdf766bc68176f76226cd01fffef3a56c5046fa863f5f0/fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4cc9e77019e489f1ddac446b6a5b9dfb5c3d9abd142652c22a1d9415dbcc0e47", size = 987412, upload-time = "2025-11-26T02:35:04.259Z" }, + { 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]] +name = "fastavro" +version = "1.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/8b/fa2d3287fd2267be6261d0177c6809a7fa12c5600ddb33490c8dc29e77b2/fastavro-1.12.1.tar.gz", hash = "sha256:2f285be49e45bc047ab2f6bed040bb349da85db3f3c87880e4b92595ea093b2b", size = 1025661, upload-time = "2025-10-10T15:40:55.41Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/a0/077fd7cbfc143152cb96780cb592ed6cb6696667d8bc1b977745eb2255a8/fastavro-1.12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:00650ca533907361edda22e6ffe8cf87ab2091c5d8aee5c8000b0f2dcdda7ed3", size = 1000335, upload-time = "2025-10-10T15:40:59.834Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ae/a115e027f3a75df237609701b03ecba0b7f0aa3d77fe0161df533fde1eb7/fastavro-1.12.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac76d6d95f909c72ee70d314b460b7e711d928845771531d823eb96a10952d26", size = 3221067, upload-time = "2025-10-10T15:41:04.399Z" }, + { url = "https://files.pythonhosted.org/packages/94/4e/c4991c3eec0175af9a8a0c161b88089cb7bf7fe353b3e3be1bc4cf9036b2/fastavro-1.12.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55eef18c41d4476bd32a82ed5dd86aabc3f614e1b66bdb09ffa291612e1670", size = 3228979, upload-time = "2025-10-10T15:41:06.738Z" }, + { url = "https://files.pythonhosted.org/packages/21/0c/f2afb8eaea38799ccb1ed07d68bf2659f2e313f1902bbd36774cf6a1bef9/fastavro-1.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81563e1f93570e6565487cdb01ba241a36a00e58cff9c5a0614af819d1155d8f", size = 3160740, upload-time = "2025-10-10T15:41:08.731Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1a/f4d367924b40b86857862c1fa65f2afba94ddadf298b611e610a676a29e5/fastavro-1.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bec207360f76f0b3de540758a297193c5390e8e081c43c3317f610b1414d8c8f", size = 3235787, upload-time = "2025-10-10T15:41:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/8db9331896e3dfe4f71b2b3c23f2e97fbbfd90129777467ca9f8bafccb74/fastavro-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:c0390bfe4a9f8056a75ac6785fbbff8f5e317f5356481d2e29ec980877d2314b", size = 449350, upload-time = "2025-10-10T15:41:12.104Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e9/31c64b47cefc0951099e7c0c8c8ea1c931edd1350f34d55c27cbfbb08df1/fastavro-1.12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b632b713bc5d03928a87d811fa4a11d5f25cd43e79c161e291c7d3f7aa740fd", size = 1016585, upload-time = "2025-10-10T15:41:13.717Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/111560775b548f5d8d828c1b5285ff90e2d2745643fb80ecbf115344eea4/fastavro-1.12.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa7ab3769beadcebb60f0539054c7755f63bd9cf7666e2c15e615ab605f89a8", size = 3404629, upload-time = "2025-10-10T15:41:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/b0/07/6bb93cb963932146c2b6c5c765903a0a547ad9f0f8b769a4a9aad8c06369/fastavro-1.12.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123fb221df3164abd93f2d042c82f538a1d5a43ce41375f12c91ce1355a9141e", size = 3428594, upload-time = "2025-10-10T15:41:17.779Z" }, + { url = "https://files.pythonhosted.org/packages/d1/67/8115ec36b584197ea737ec79e3499e1f1b640b288d6c6ee295edd13b80f6/fastavro-1.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:632a4e3ff223f834ddb746baae0cc7cee1068eb12c32e4d982c2fee8a5b483d0", size = 3344145, upload-time = "2025-10-10T15:41:19.89Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9e/a7cebb3af967e62539539897c10138fa0821668ec92525d1be88a9cd3ee6/fastavro-1.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e6caf4e7a8717d932a3b1ff31595ad169289bbe1128a216be070d3a8391671", size = 3431942, upload-time = "2025-10-10T15:41:22.076Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d1/7774ddfb8781c5224294c01a593ebce2ad3289b948061c9701bd1903264d/fastavro-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:b91a0fe5a173679a6c02d53ca22dcaad0a2c726b74507e0c1c2e71a7c3f79ef9", size = 450542, upload-time = "2025-10-10T15:41:23.333Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f0/10bd1a3d08667fa0739e2b451fe90e06df575ec8b8ba5d3135c70555c9bd/fastavro-1.12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:509818cb24b98a804fc80be9c5fed90f660310ae3d59382fc811bfa187122167", size = 1009057, upload-time = "2025-10-10T15:41:24.556Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/0d985bc99e1fa9e74c636658000ba38a5cd7f5ab2708e9c62eaf736ecf1a/fastavro-1.12.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089e155c0c76e0d418d7e79144ce000524dd345eab3bc1e9c5ae69d500f71b14", size = 3391866, upload-time = "2025-10-10T15:41:26.882Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9e/b4951dc84ebc34aac69afcbfbb22ea4a91080422ec2bfd2c06076ff1d419/fastavro-1.12.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44cbff7518901c91a82aab476fcab13d102e4999499df219d481b9e15f61af34", size = 3458005, upload-time = "2025-10-10T15:41:29.017Z" }, + { url = "https://files.pythonhosted.org/packages/af/f8/5a8df450a9f55ca8441f22ea0351d8c77809fc121498b6970daaaf667a21/fastavro-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a275e48df0b1701bb764b18a8a21900b24cf882263cb03d35ecdba636bbc830b", size = 3295258, upload-time = "2025-10-10T15:41:31.564Z" }, + { url = "https://files.pythonhosted.org/packages/99/b2/40f25299111d737e58b85696e91138a66c25b7334f5357e7ac2b0e8966f8/fastavro-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2de72d786eb38be6b16d556b27232b1bf1b2797ea09599507938cdb7a9fe3e7c", size = 3430328, upload-time = "2025-10-10T15:41:33.689Z" }, + { url = "https://files.pythonhosted.org/packages/e0/07/85157a7c57c5f8b95507d7829b5946561e5ee656ff80e9dd9a757f53ddaf/fastavro-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:9090f0dee63fe022ee9cc5147483366cc4171c821644c22da020d6b48f576b4f", size = 444140, upload-time = "2025-10-10T15:41:34.902Z" }, + { url = "https://files.pythonhosted.org/packages/bb/57/26d5efef9182392d5ac9f253953c856ccb66e4c549fd3176a1e94efb05c9/fastavro-1.12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:78df838351e4dff9edd10a1c41d1324131ffecbadefb9c297d612ef5363c049a", size = 1000599, upload-time = "2025-10-10T15:41:36.554Z" }, + { url = "https://files.pythonhosted.org/packages/33/cb/8ab55b21d018178eb126007a56bde14fd01c0afc11d20b5f2624fe01e698/fastavro-1.12.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:780476c23175d2ae457c52f45b9ffa9d504593499a36cd3c1929662bf5b7b14b", size = 3335933, upload-time = "2025-10-10T15:41:39.07Z" }, + { url = "https://files.pythonhosted.org/packages/fe/03/9c94ec9bf873eb1ffb0aa694f4e71940154e6e9728ddfdc46046d7e8ced4/fastavro-1.12.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0714b285160fcd515eb0455540f40dd6dac93bdeacdb03f24e8eac3d8aa51f8d", size = 3402066, upload-time = "2025-10-10T15:41:41.608Z" }, + { url = "https://files.pythonhosted.org/packages/75/c8/cb472347c5a584ccb8777a649ebb28278fccea39d005fc7df19996f41df8/fastavro-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8bc2dcec5843d499f2489bfe0747999108f78c5b29295d877379f1972a3d41a", size = 3240038, upload-time = "2025-10-10T15:41:43.743Z" }, + { url = "https://files.pythonhosted.org/packages/e1/77/569ce9474c40304b3a09e109494e020462b83e405545b78069ddba5f614e/fastavro-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b1921ac35f3d89090a5816b626cf46e67dbecf3f054131f84d56b4e70496f45", size = 3369398, upload-time = "2025-10-10T15:41:45.719Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1f/9589e35e9ea68035385db7bdbf500d36b8891db474063fb1ccc8215ee37c/fastavro-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:5aa777b8ee595b50aa084104cd70670bf25a7bbb9fd8bb5d07524b0785ee1699", size = 444220, upload-time = "2025-10-10T15:41:47.39Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d2/78435fe737df94bd8db2234b2100f5453737cffd29adee2504a2b013de84/fastavro-1.12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c3d67c47f177e486640404a56f2f50b165fe892cc343ac3a34673b80cc7f1dd6", size = 1086611, upload-time = "2025-10-10T15:41:48.818Z" }, + { url = "https://files.pythonhosted.org/packages/b6/be/428f99b10157230ddac77ec8cc167005b29e2bd5cbe228345192bb645f30/fastavro-1.12.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5217f773492bac43dae15ff2931432bce2d7a80be7039685a78d3fab7df910bd", size = 3541001, upload-time = "2025-10-10T15:41:50.871Z" }, + { url = "https://files.pythonhosted.org/packages/16/08/a2eea4f20b85897740efe44887e1ac08f30dfa4bfc3de8962bdcbb21a5a1/fastavro-1.12.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:469fecb25cba07f2e1bfa4c8d008477cd6b5b34a59d48715e1b1a73f6160097d", size = 3432217, upload-time = "2025-10-10T15:41:53.149Z" }, + { url = "https://files.pythonhosted.org/packages/87/bb/b4c620b9eb6e9838c7f7e4b7be0762834443adf9daeb252a214e9ad3178c/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d71c8aa841ef65cfab709a22bb887955f42934bced3ddb571e98fdbdade4c609", size = 3366742, upload-time = "2025-10-10T15:41:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d1/e69534ccdd5368350646fea7d93be39e5f77c614cca825c990bd9ca58f67/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b81fc04e85dfccf7c028e0580c606e33aa8472370b767ef058aae2c674a90746", size = 3383743, upload-time = "2025-10-10T15:41:57.68Z" }, + { url = "https://files.pythonhosted.org/packages/58/54/b7b4a0c3fb5fcba38128542da1b26c4e6d69933c923f493548bdfd63ab6a/fastavro-1.12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9445da127751ba65975d8e4bdabf36bfcfdad70fc35b2d988e3950cce0ec0e7c", size = 1001377, upload-time = "2025-10-10T15:41:59.241Z" }, + { url = "https://files.pythonhosted.org/packages/1e/4f/0e589089c7df0d8f57d7e5293fdc34efec9a3b758a0d4d0c99a7937e2492/fastavro-1.12.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed924233272719b5d5a6a0b4d80ef3345fc7e84fc7a382b6232192a9112d38a6", size = 3320401, upload-time = "2025-10-10T15:42:01.682Z" }, + { url = "https://files.pythonhosted.org/packages/f9/19/260110d56194ae29d7e423a336fccea8bcd103196d00f0b364b732bdb84e/fastavro-1.12.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3616e2f0e1c9265e92954fa099db79c6e7817356d3ff34f4bcc92699ae99697c", size = 3350894, upload-time = "2025-10-10T15:42:04.073Z" }, + { url = "https://files.pythonhosted.org/packages/d0/96/58b0411e8be9694d5972bee3167d6c1fd1fdfdf7ce253c1a19a327208f4f/fastavro-1.12.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb0337b42fd3c047fcf0e9b7597bd6ad25868de719f29da81eabb6343f08d399", size = 3229644, upload-time = "2025-10-10T15:42:06.221Z" }, + { url = "https://files.pythonhosted.org/packages/5b/db/38660660eac82c30471d9101f45b3acfdcbadfe42d8f7cdb129459a45050/fastavro-1.12.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:64961ab15b74b7c168717bbece5660e0f3d457837c3cc9d9145181d011199fa7", size = 3329704, upload-time = "2025-10-10T15:42:08.384Z" }, + { url = "https://files.pythonhosted.org/packages/9d/a9/1672910f458ecb30b596c9e59e41b7c00309b602a0494341451e92e62747/fastavro-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:792356d320f6e757e89f7ac9c22f481e546c886454a6709247f43c0dd7058004", size = 452911, upload-time = "2025-10-10T15:42:09.795Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/2e15d0938ded1891b33eff252e8500605508b799c2e57188a933f0bd744c/fastavro-1.12.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120aaf82ac19d60a1016afe410935fe94728752d9c2d684e267e5b7f0e70f6d9", size = 3541999, upload-time = "2025-10-10T15:42:11.794Z" }, + { 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]] +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.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'", +] +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" } +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" }, +] + +[[package]] +name = "flask" +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 = "itsdangerous" }, + { name = "jinja2" }, + { 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" } +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 = "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 = "2025.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'", +] +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" } +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" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, +] + +[[package]] +name = "google-auth" +version = "2.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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" } +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" }, +] + +[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", 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" }, +] + +[[package]] +name = "google-genai" +version = "1.57.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", 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" } +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" }, +] + +[[package]] +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.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/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } +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" }, +] + +[[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'" }, +] +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" }, +] + +[[package]] +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 = "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" } +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" }, +] + +[[package]] +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'" }, +] +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 = [ + { 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" }, +] + +[[package]] +name = "groq" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/12/f4099a141677fcd2ed79dcc1fcec431e60c52e0e90c9c5d935f0ffaf8c0e/groq-1.0.0.tar.gz", hash = "sha256:66cb7bb729e6eb644daac7ce8efe945e99e4eb33657f733ee6f13059ef0c25a9", size = 146068, upload-time = "2025-12-17T23:34:23.115Z" } +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 = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/a5/85ef910a0aa034a2abcfadc360ab5ac6f6bc4e9112349bd40ca97551cff0/hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649", size = 2861870, upload-time = "2025-10-24T19:04:11.422Z" }, + { url = "https://files.pythonhosted.org/packages/ea/40/e2e0a7eb9a51fe8828ba2d47fe22a7e74914ea8a0db68a18c3aa7449c767/hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813", size = 2717584, upload-time = "2025-10-24T19:04:09.586Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7d/daf7f8bc4594fdd59a8a596f9e3886133fdc68e675292218a5e4c1b7e834/hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc", size = 3315004, upload-time = "2025-10-24T19:04:00.314Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/45ea2f605fbf6d81c8b21e4d970b168b18a53515923010c312c06cd83164/hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5", size = 3222636, upload-time = "2025-10-24T19:03:58.111Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1d/04513e3cab8f29ab8c109d309ddd21a2705afab9d52f2ba1151e0c14f086/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f", size = 3408448, upload-time = "2025-10-24T19:04:20.951Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7c/60a2756d7feec7387db3a1176c632357632fbe7849fce576c5559d4520c7/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832", size = 3503401, upload-time = "2025-10-24T19:04:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/4e/64/48fffbd67fb418ab07451e4ce641a70de1c40c10a13e25325e24858ebe5a/hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382", size = 2900866, upload-time = "2025-10-24T19:04:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/e2/51/f7e2caae42f80af886db414d4e9885fac959330509089f97cccb339c6b87/hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e", size = 2861861, upload-time = "2025-10-24T19:04:19.01Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1d/a641a88b69994f9371bd347f1dd35e5d1e2e2460a2e350c8d5165fc62005/hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8", size = 2717699, upload-time = "2025-10-24T19:04:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/df/e0/e5e9bba7d15f0318955f7ec3f4af13f92e773fbb368c0b8008a5acbcb12f/hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0", size = 3314885, upload-time = "2025-10-24T19:04:07.642Z" }, + { url = "https://files.pythonhosted.org/packages/21/90/b7fe5ff6f2b7b8cbdf1bd56145f863c90a5807d9758a549bf3d916aa4dec/hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090", size = 3221550, upload-time = "2025-10-24T19:04:05.55Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cb/73f276f0a7ce46cc6a6ec7d6c7d61cbfe5f2e107123d9bbd0193c355f106/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a", size = 3408010, upload-time = "2025-10-24T19:04:28.598Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1e/d642a12caa78171f4be64f7cd9c40e3ca5279d055d0873188a58c0f5fbb9/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f", size = 3503264, upload-time = "2025-10-24T19:04:30.397Z" }, + { url = "https://files.pythonhosted.org/packages/17/b5/33764714923fa1ff922770f7ed18c2daae034d21ae6e10dbf4347c854154/hf_xet-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc", size = 2901071, upload-time = "2025-10-24T19:04:37.463Z" }, + { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" }, + { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" }, + { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" }, +] + +[[package]] +name = "hjson" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/e5/0b56d723a76ca67abadbf7fb71609fb0ea7e6926e94fcca6c65a85b36a0e/hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75", size = 40541, upload-time = "2022-08-13T02:53:01.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/7f/13cd798d180af4bf4c0ceddeefba2b864a63c71645abc0308b768d67bb81/hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89", size = 54018, upload-time = "2022-08-13T02:52:59.899Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78", size = 204531, upload-time = "2025-10-10T03:54:20.887Z" }, + { url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4", size = 109408, upload-time = "2025-10-10T03:54:22.455Z" }, + { url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05", size = 440889, upload-time = "2025-10-10T03:54:23.753Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed", size = 440460, upload-time = "2025-10-10T03:54:25.313Z" }, + { url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a", size = 425267, upload-time = "2025-10-10T03:54:26.81Z" }, + { url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b", size = 424429, upload-time = "2025-10-10T03:54:28.174Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568", size = 86173, upload-time = "2025-10-10T03:54:29.5Z" }, + { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657", size = 206521, upload-time = "2025-10-10T03:54:31.002Z" }, + { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70", size = 110375, upload-time = "2025-10-10T03:54:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df", size = 456621, upload-time = "2025-10-10T03:54:33.176Z" }, + { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e", size = 454954, upload-time = "2025-10-10T03:54:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274", size = 440175, upload-time = "2025-10-10T03:54:35.942Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec", size = 440310, upload-time = "2025-10-10T03:54:37.1Z" }, + { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb", size = 86875, upload-time = "2025-10-10T03:54:38.421Z" }, + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { 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]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.3.0" +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 = "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 = "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" }, + { name = "pyyaml" }, + { name = "shellingham" }, + { name = "tqdm" }, + { 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" } +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" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +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" }, +] + +[[package]] +name = "inline-snapshot" +version = "0.31.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pytest" }, + { name = "rich" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/b1/52b5ee59f73ed31d5fe21b10881bf2d121d07d54b23c0b6b74186792e620/inline_snapshot-0.31.1.tar.gz", hash = "sha256:4ea5ed70aa1d652713bbfd750606b94bd8a42483f7d3680433b3e92994495f64", size = 2606338, upload-time = "2025-11-07T07:36:18.932Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/52/945db420380efbda8c69a7a4a16c53df9d7ac50d8217286b9d41e5d825ff/inline_snapshot-0.31.1-py3-none-any.whl", hash = "sha256:7875a73c986a03388c7e758fb5cb8a43d2c3a20328aa1d851bfb4ed536c4496f", size = 71965, upload-time = "2025-11-07T07:36:16.836Z" }, +] + +[[package]] +name = "invoke" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/bd/b461d3424a24c80490313fd77feeb666ca4f6a28c7e72713e3d9095719b4/invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707", size = 304762, upload-time = "2025-10-11T00:36:35.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/4b/b99e37f88336009971405cbb7630610322ed6fbfa31e1d7ab3fbf3049a2d/invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8", size = 160287, upload-time = "2025-10-11T00:36:33.703Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +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 = "jieba" +version = "0.42.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/cb/18eeb235f833b726522d7ebed54f2278ce28ba9438e3135ab0278d9792a2/jieba-0.42.1.tar.gz", hash = "sha256:055ca12f62674fafed09427f176506079bc135638a14e23e25be909131928db2", size = 19214172, upload-time = "2020-01-20T14:27:23.5Z" } + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.12.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" } +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" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +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" } +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" }, +] + +[[package]] +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'" }, +] +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 = [ + { 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-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing", marker = "python_full_version >= '3.10'" }, +] +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 = [ + { 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 = "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 = "logfire-api" +version = "4.17.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" } +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" }, +] + +[[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" +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" } +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" }, +] + +[[package]] +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", 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 = [ + { 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'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { 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]] +name = "mcp" +version = "1.19.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 = "python-multipart", version = "0.0.21", 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 = "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" } +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" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mdx-include" +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", 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" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/40/6844997dee251103c5a4c4eb0d1d2f2162b7c29ffc4e86de3cd68d269be2/mdx_include-1.4.2-py3-none-any.whl", hash = "sha256:cfbeadd59985f27a9b70cb7ab0a3d209892fe1bb1aa342df055e0b135b3c9f34", size = 11591, upload-time = "2022-07-26T05:46:11.518Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mistralai" +version = "1.9.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "httpx" }, + { name = "invoke" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/8d/d8b7af67a966b6f227024e1cb7287fc19901a434f87a5a391dcfe635d338/mistralai-1.9.11.tar.gz", hash = "sha256:3df9e403c31a756ec79e78df25ee73cea3eb15f86693773e16b16adaf59c9b8a", size = 208051, upload-time = "2025-10-02T15:53:40.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/76/4ce12563aea5a76016f8643eff30ab731e6656c845e9e4d090ef10c7b925/mistralai-1.9.11-py3-none-any.whl", hash = "sha256:7a3dc2b8ef3fceaa3582220234261b5c4e3e03a972563b07afa150e44a25a6d3", size = 442796, upload-time = "2025-10-02T15:53:39.134Z" }, +] + +[[package]] +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 = "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", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +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", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, +] + +[[package]] +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 = "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" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, +] + +[[package]] +name = "mkdocs-macros-plugin" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hjson" }, + { name = "jinja2" }, + { name = "mkdocs" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "python-dateutil" }, + { 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'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/42/bb2ceed148c77f82b57c6d3b7f584f0f34ababf7a9a8ff85809380d1f400/mkdocs_macros_plugin-1.4.1.tar.gz", hash = "sha256:55a9c93871e3744cdeb0736316783d60830a6d5d97b1132364e6b491607f2332", size = 35094, upload-time = "2025-10-25T12:37:20.689Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/ec/e6e96a7ae8df414f03f43681821234b0d3b86666f7b91f70ab26775a8809/mkdocs_macros_plugin-1.4.1-py3-none-any.whl", hash = "sha256:5a9e483f6056fe7ad0923802affe699233ca468672e20a9640dba237165b3240", size = 40155, upload-time = "2025-10-25T12:37:19.417Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { 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", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { 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" } +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" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocs-redirects" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/a8/6d44a6cf07e969c7420cb36ab287b0669da636a2044de38a7d2208d5a758/mkdocs_redirects-1.2.2.tar.gz", hash = "sha256:3094981b42ffab29313c2c1b8ac3969861109f58b2dd58c45fc81cd44bfa0095", size = 7162, upload-time = "2024-11-07T14:57:21.109Z" } +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" } +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 = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +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'" }, + { 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", 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" }, +] + +[[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", 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" } +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" }, +] + +[[package]] +name = "mypy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { 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" } +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" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +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 = "openai" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { 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" } +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" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } +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 = "orjson" +version = "3.11.5" +source = { registry = "https://pypi.org/simple" } +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 = "outcome" +version = "1.3.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060, upload-time = "2023-10-26T04:26:04.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" }, +] + +[[package]] +name = "packaging" +version = "25.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" } +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" }, +] + +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +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 = "pathspec" +version = "1.0.2" +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" } +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" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +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 = "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" }, +] + +[[package]] +name = "playwright" +version = "1.57.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 = "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" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prek" +version = "0.2.22" +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" } +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" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +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 = "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" }, +] + +[package.optional-dependencies] +argon2 = [ + { name = "argon2-cffi", version = "25.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +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" } +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 = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-ai" +version = "0.4.10" +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"] }, +] +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" } +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" }, +] + +[[package]] +name = "pydantic-ai-slim" +version = "0.4.10" +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" }, +] +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" } +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" }, +] + +[package.optional-dependencies] +ag-ui = [ + { name = "ag-ui-protocol" }, + { 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" }, +] +bedrock = [ + { name = "boto3" }, +] +cli = [ + { name = "argcomplete" }, + { name = "prompt-toolkit" }, + { name = "rich" }, +] +cohere = [ + { name = "cohere", marker = "sys_platform != 'emscripten'" }, +] +evals = [ + { name = "pydantic-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'" }, +] +groq = [ + { name = "groq" }, +] +huggingface = [ + { name = "huggingface-hub" }, +] +mcp = [ + { name = "mcp", marker = "python_full_version >= '3.10'" }, +] +mistral = [ + { name = "mistralai" }, +] +openai = [ + { name = "openai" }, +] +retries = [ + { name = "tenacity" }, +] +vertexai = [ + { name = "google-auth" }, + { name = "requests" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { 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.4.10" +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" }, +] +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" } +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" }, +] + +[[package]] +name = "pydantic-extra-types" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/35/2fee58b1316a73e025728583d3b1447218a97e621933fc776fb8c0f2ebdd/pydantic_extra_types-2.11.0.tar.gz", hash = "sha256:4e9991959d045b75feb775683437a97991d02c138e00b59176571db9ce634f0e", size = 157226, upload-time = "2025-12-31T16:18:27.944Z" } +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.4.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "typing-inspection" }, +] +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" } +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" }, +] + +[[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'" }, +] +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 = [ + { 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 = "pyee" +version = "13.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, +] + +[[package]] +name = "pygithub" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyjwt", extra = ["crypto"] }, + { 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'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/07/ba/7049ce39f653f6140aac4beb53a5aaf08b4407b6a3019aae394c1c5244ff/pygithub-2.8.1-py3-none-any.whl", hash = "sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0", size = 432709, upload-time = "2025-09-02T17:41:52.947Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.9.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" } +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" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.20" +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 = "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" } +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" }, +] + +[[package]] +name = "pynacl" +version = "1.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" }, + { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" }, + { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" }, + { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, + { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, + { 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 = "pytest" +version = "8.4.2" +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 = "packaging" }, + { name = "pluggy" }, + { 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" } +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" }, +] + +[[package]] +name = "pytest-codspeed" +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" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/e8/27fcbe6516a1c956614a4b61a7fccbf3791ea0b992e07416e8948184327d/pytest_codspeed-4.2.0.tar.gz", hash = "sha256:04b5d0bc5a1851ba1504d46bf9d7dbb355222a69f2cd440d54295db721b331f7", size = 113263, upload-time = "2025-10-24T09:02:55.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/b8/d599a466c50af3f04001877ae8b17c12b803f3b358235736b91a0769de0d/pytest_codspeed-4.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:609828b03972966b75b9b7416fa2570c4a0f6124f67e02d35cd3658e64312a7b", size = 261943, upload-time = "2025-10-24T09:02:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/74/19/ccc1a2fcd28357a8db08ba6b60f381832088a3850abc262c8e0b3406491a/pytest_codspeed-4.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23a0c0fbf8bb4de93a3454fd9e5efcdca164c778aaef0a9da4f233d85cb7f5b8", size = 250782, upload-time = "2025-10-24T09:02:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2d/f0083a2f14ecf008d961d40439a71da0ae0d568e5f8dc2fccd3e8a2ab3e4/pytest_codspeed-4.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2de87bde9fbc6fd53f0fd21dcf2599c89e0b8948d49f9bad224edce51c47e26b", size = 261960, upload-time = "2025-10-24T09:02:40.665Z" }, + { url = "https://files.pythonhosted.org/packages/5f/0c/1f514c553db4ea5a69dfbe2706734129acd0eca8d5101ec16f1dd00dbc0f/pytest_codspeed-4.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95aeb2479ca383f6b18e2cc9ebcd3b03ab184980a59a232aea6f370bbf59a1e3", size = 250808, upload-time = "2025-10-24T09:02:42.07Z" }, + { url = "https://files.pythonhosted.org/packages/81/04/479905bd6653bc981c0554fcce6df52d7ae1594e1eefd53e6cf31810ec7f/pytest_codspeed-4.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d4fefbd4ae401e2c60f6be920a0be50eef0c3e4a1f0a1c83962efd45be38b39", size = 262084, upload-time = "2025-10-24T09:02:43.155Z" }, + { url = "https://files.pythonhosted.org/packages/d2/46/d6f345d7907bac6cbb6224bd697ecbc11cf7427acc9e843c3618f19e3476/pytest_codspeed-4.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:309b4227f57fcbb9df21e889ea1ae191d0d1cd8b903b698fdb9ea0461dbf1dfe", size = 251100, upload-time = "2025-10-24T09:02:44.168Z" }, + { url = "https://files.pythonhosted.org/packages/de/dc/e864f45e994a50390ff49792256f1bdcbf42f170e3bc0470ee1a7d2403f3/pytest_codspeed-4.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72aab8278452a6d020798b9e4f82780966adb00f80d27a25d1274272c54630d5", size = 262057, upload-time = "2025-10-24T09:02:45.791Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1c/f1d2599784486879cf6579d8d94a3e22108f0e1f130033dab8feefd29249/pytest_codspeed-4.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:684fcd9491d810ded653a8d38de4835daa2d001645f4a23942862950664273f8", size = 251013, upload-time = "2025-10-24T09:02:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fd/eafd24db5652a94b4d00fe9b309b607de81add0f55f073afb68a378a24b6/pytest_codspeed-4.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50794dabea6ec90d4288904452051e2febace93e7edf4ca9f2bce8019dd8cd37", size = 262065, upload-time = "2025-10-24T09:02:48.018Z" }, + { 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" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +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-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.21" +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" } +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" }, +] + +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +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 = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, + { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { 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]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { 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]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "rcslice" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/3e/abe47d91d5340b77b003baf96fdf8966c946eb4c5a704a844b5d03e6e578/rcslice-1.1.0.tar.gz", hash = "sha256:a2ce70a60690eb63e52b722e046b334c3aaec5e900b28578f529878782ee5c6e", size = 4414, upload-time = "2018-09-27T12:44:06.601Z" } +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 = "referencing" +version = "0.37.0" +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" } +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" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +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'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rich" +version = "14.2.0" +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" } +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" }, +] + +[[package]] +name = "rich-toolkit" +version = "0.17.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 = "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" } +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" }, +] + +[[package]] +name = "rignore" +version = "0.7.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/f5/8bed2310abe4ae04b67a38374a4d311dd85220f5d8da56f47ae9361be0b0/rignore-0.7.6.tar.gz", hash = "sha256:00d3546cd793c30cb17921ce674d2c8f3a4b00501cb0e3dd0e82217dbeba2671", size = 57140, upload-time = "2025-11-05T21:41:21.968Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/7a/b970cd0138b0ece72eb28f086e933f9ed75b795716ad3de5ab22994b3b54/rignore-0.7.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f3c74a7e5ee77aea669c95fdb3933f2a6c7549893700082e759128a29cf67e45", size = 884999, upload-time = "2025-11-05T20:42:38.373Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/23faca29616d8966ada63fb0e13c214107811fa9a0aba2275e4c7ca63bd5/rignore-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7202404958f5fe3474bac91f65350f0b1dde1a5e05089f2946549b7e91e79ec", size = 824824, upload-time = "2025-11-05T20:42:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/05a1e61f04cf2548524224f0b5f21ca19ea58f7273a863bac10846b8ff69/rignore-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bde7c5835fa3905bfb7e329a4f1d7eccb676de63da7a3f934ddd5c06df20597", size = 899121, upload-time = "2025-11-05T20:40:48.94Z" }, + { url = "https://files.pythonhosted.org/packages/ff/35/71518847e10bdbf359badad8800e4681757a01f4777b3c5e03dbde8a42d8/rignore-0.7.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:626c3d4ba03af266694d25101bc1d8d16eda49c5feb86cedfec31c614fceca7d", size = 873813, upload-time = "2025-11-05T20:41:04.71Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c8/32ae405d3e7fd4d9f9b7838f2fcca0a5005bb87fa514b83f83fd81c0df22/rignore-0.7.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a43841e651e7a05a4274b9026cc408d1912e64016ede8cd4c145dae5d0635be", size = 1168019, upload-time = "2025-11-05T20:41:20.723Z" }, + { url = "https://files.pythonhosted.org/packages/25/98/013c955982bc5b4719bf9a5bea58be317eea28aa12bfd004025e3cd7c000/rignore-0.7.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7978c498dbf7f74d30cdb8859fe612167d8247f0acd377ae85180e34490725da", size = 942822, upload-time = "2025-11-05T20:41:36.99Z" }, + { url = "https://files.pythonhosted.org/packages/90/fb/9a3f3156c6ed30bcd597e63690353edac1fcffe9d382ad517722b56ac195/rignore-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d22f72ab695c07d2d96d2a645208daff17084441b5d58c07378c9dd6f9c4c87", size = 959820, upload-time = "2025-11-05T20:42:06.364Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b2/93bf609633021e9658acaff24cfb055d8cdaf7f5855d10ebb35307900dda/rignore-0.7.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5bd8e1a91ed1a789b2cbe39eeea9204a6719d4f2cf443a9544b521a285a295f", size = 985050, upload-time = "2025-11-05T20:41:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/ec2d040469bdfd7b743df10f2201c5d285009a4263d506edbf7a06a090bb/rignore-0.7.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fc03efad5789365018e94ac4079f851a999bc154d1551c45179f7fcf45322", size = 1079164, upload-time = "2025-11-05T21:40:10.368Z" }, + { url = "https://files.pythonhosted.org/packages/df/26/4b635f4ea5baf4baa8ba8eee06163f6af6e76dfbe72deb57da34bb24b19d/rignore-0.7.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ce2617fe28c51367fd8abfd4eeea9e61664af63c17d4ea00353d8ef56dfb95fa", size = 1139028, upload-time = "2025-11-05T21:40:27.977Z" }, + { url = "https://files.pythonhosted.org/packages/6a/54/a3147ebd1e477b06eb24e2c2c56d951ae5faa9045b7b36d7892fec5080d9/rignore-0.7.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c4ad2cee85068408e7819a38243043214e2c3047e9bd4c506f8de01c302709e", size = 1119024, upload-time = "2025-11-05T21:40:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f4/27475db769a57cff18fe7e7267b36e6cdb5b1281caa185ba544171106cba/rignore-0.7.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:02cd240bfd59ecc3907766f4839cbba20530a2e470abca09eaa82225e4d946fb", size = 1128531, upload-time = "2025-11-05T21:41:02.734Z" }, + { url = "https://files.pythonhosted.org/packages/97/32/6e782d3b352e4349fa0e90bf75b13cb7f11d8908b36d9e2b262224b65d9a/rignore-0.7.6-cp310-cp310-win32.whl", hash = "sha256:fe2bd8fa1ff555259df54c376abc73855cb02628a474a40d51b358c3a1ddc55b", size = 646817, upload-time = "2025-11-05T21:41:47.51Z" }, + { url = "https://files.pythonhosted.org/packages/c0/8a/53185c69abb3bb362e8a46b8089999f820bf15655629ff8395107633c8ab/rignore-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:d80afd6071c78baf3765ec698841071b19e41c326f994cfa69b5a1df676f5d39", size = 727001, upload-time = "2025-11-05T21:41:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/25/41/b6e2be3069ef3b7f24e35d2911bd6deb83d20ed5642ad81d5a6d1c015473/rignore-0.7.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:40be8226e12d6653abbebaffaea2885f80374c1c8f76fe5ca9e0cadd120a272c", size = 885285, upload-time = "2025-11-05T20:42:39.763Z" }, + { url = "https://files.pythonhosted.org/packages/52/66/ba7f561b6062402022887706a7f2b2c2e2e2a28f1e3839202b0a2f77e36d/rignore-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182f4e5e4064d947c756819446a7d4cdede8e756b8c81cf9e509683fe38778d7", size = 823882, upload-time = "2025-11-05T20:42:23.488Z" }, + { url = "https://files.pythonhosted.org/packages/f5/81/4087453df35a90b07370647b19017029324950c1b9137d54bf1f33843f17/rignore-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16b63047648a916a87be1e51bb5c009063f1b8b6f5afe4f04f875525507e63dc", size = 899362, upload-time = "2025-11-05T20:40:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c9/390a8fdfabb76d71416be773bd9f162977bd483084f68daf19da1dec88a6/rignore-0.7.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba5524f5178deca4d7695e936604ebc742acb8958f9395776e1fcb8133f8257a", size = 873633, upload-time = "2025-11-05T20:41:06.193Z" }, + { url = "https://files.pythonhosted.org/packages/df/c9/79404fcb0faa76edfbc9df0901f8ef18568d1104919ebbbad6d608c888d1/rignore-0.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62020dbb89a1dd4b84ab3d60547b3b2eb2723641d5fb198463643f71eaaed57d", size = 1167633, upload-time = "2025-11-05T20:41:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/b3466d32d445d158a0aceb80919085baaae495b1f540fb942f91d93b5e5b/rignore-0.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34acd532769d5a6f153a52a98dcb81615c949ab11697ce26b2eb776af2e174d", size = 941434, upload-time = "2025-11-05T20:41:38.151Z" }, + { url = "https://files.pythonhosted.org/packages/e8/40/9cd949761a7af5bc27022a939c91ff622d29c7a0b66d0c13a863097dde2d/rignore-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5e53b752f9de44dff7b3be3c98455ce3bf88e69d6dc0cf4f213346c5e3416c", size = 959461, upload-time = "2025-11-05T20:42:08.476Z" }, + { url = "https://files.pythonhosted.org/packages/b5/87/1e1a145731f73bdb7835e11f80da06f79a00d68b370d9a847de979575e6d/rignore-0.7.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25b3536d13a5d6409ce85f23936f044576eeebf7b6db1d078051b288410fc049", size = 985323, upload-time = "2025-11-05T20:41:52.735Z" }, + { url = "https://files.pythonhosted.org/packages/6c/31/1ecff992fc3f59c4fcdcb6c07d5f6c1e6dfb55ccda19c083aca9d86fa1c6/rignore-0.7.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e01cad2b0b92f6b1993f29fc01f23f2d78caf4bf93b11096d28e9d578eb08ce", size = 1079173, upload-time = "2025-11-05T21:40:12.007Z" }, + { url = "https://files.pythonhosted.org/packages/17/18/162eedadb4c2282fa4c521700dbf93c9b14b8842e8354f7d72b445b8d593/rignore-0.7.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5991e46ab9b4868334c9e372ab0892b0150f3f586ff2b1e314272caeb38aaedb", size = 1139012, upload-time = "2025-11-05T21:40:29.399Z" }, + { url = "https://files.pythonhosted.org/packages/78/96/a9ca398a8af74bb143ad66c2a31303c894111977e28b0d0eab03867f1b43/rignore-0.7.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c8ae562e5d1246cba5eaeb92a47b2a279e7637102828dde41dcbe291f529a3e", size = 1118827, upload-time = "2025-11-05T21:40:46.6Z" }, + { url = "https://files.pythonhosted.org/packages/9f/22/1c1a65047df864def9a047dbb40bc0b580b8289a4280e62779cd61ae21f2/rignore-0.7.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aaf938530dcc0b47c4cfa52807aa2e5bfd5ca6d57a621125fe293098692f6345", size = 1128182, upload-time = "2025-11-05T21:41:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/bd/f4/1526eb01fdc2235aca1fd9d0189bee4021d009a8dcb0161540238c24166e/rignore-0.7.6-cp311-cp311-win32.whl", hash = "sha256:166ebce373105dd485ec213a6a2695986346e60c94ff3d84eb532a237b24a4d5", size = 646547, upload-time = "2025-11-05T21:41:49.439Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c8/dda0983e1845706beb5826459781549a840fe5a7eb934abc523e8cd17814/rignore-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:44f35ee844b1a8cea50d056e6a595190ce9d42d3cccf9f19d280ae5f3058973a", size = 727139, upload-time = "2025-11-05T21:41:34.367Z" }, + { url = "https://files.pythonhosted.org/packages/e3/47/eb1206b7bf65970d41190b879e1723fc6bbdb2d45e53565f28991a8d9d96/rignore-0.7.6-cp311-cp311-win_arm64.whl", hash = "sha256:14b58f3da4fa3d5c3fa865cab49821675371f5e979281c683e131ae29159a581", size = 657598, upload-time = "2025-11-05T21:41:23.758Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0e/012556ef3047a2628842b44e753bb15f4dc46806780ff090f1e8fe4bf1eb/rignore-0.7.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:03e82348cb7234f8d9b2834f854400ddbbd04c0f8f35495119e66adbd37827a8", size = 883488, upload-time = "2025-11-05T20:42:41.359Z" }, + { url = "https://files.pythonhosted.org/packages/93/b0/d4f1f3fe9eb3f8e382d45ce5b0547ea01c4b7e0b4b4eb87bcd66a1d2b888/rignore-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9e624f6be6116ea682e76c5feb71ea91255c67c86cb75befe774365b2931961", size = 820411, upload-time = "2025-11-05T20:42:24.782Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c8/dea564b36dedac8de21c18e1851789545bc52a0c22ece9843444d5608a6a/rignore-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda49950d405aa8d0ebe26af807c4e662dd281d926530f03f29690a2e07d649a", size = 897821, upload-time = "2025-11-05T20:40:52.613Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/ee96db17ac1835e024c5d0742eefb7e46de60020385ac883dd3d1cde2c1f/rignore-0.7.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5fd5ab3840b8c16851d327ed06e9b8be6459702a53e5ab1fc4073b684b3789e", size = 873963, upload-time = "2025-11-05T20:41:07.49Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8c/ad5a57bbb9d14d5c7e5960f712a8a0b902472ea3f4a2138cbf70d1777b75/rignore-0.7.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ced2a248352636a5c77504cb755dc02c2eef9a820a44d3f33061ce1bb8a7f2d2", size = 1169216, upload-time = "2025-11-05T20:41:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/5b00bc2a6bc1701e6878fca798cf5d9125eb3113193e33078b6fc0d99123/rignore-0.7.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04a3b73b75ddc12c9c9b21efcdaab33ca3832941d6f1d67bffd860941cd448a", size = 942942, upload-time = "2025-11-05T20:41:39.393Z" }, + { url = "https://files.pythonhosted.org/packages/85/e5/7f99bd0cc9818a91d0e8b9acc65b792e35750e3bdccd15a7ee75e64efca4/rignore-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24321efac92140b7ec910ac7c53ab0f0c86a41133d2bb4b0e6a7c94967f44dd", size = 959787, upload-time = "2025-11-05T20:42:09.765Z" }, + { url = "https://files.pythonhosted.org/packages/55/54/2ffea79a7c1eabcede1926347ebc2a81bc6b81f447d05b52af9af14948b9/rignore-0.7.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c7aa109d41e593785c55fdaa89ad80b10330affa9f9d3e3a51fa695f739b20", size = 984245, upload-time = "2025-11-05T20:41:54.062Z" }, + { url = "https://files.pythonhosted.org/packages/41/f7/e80f55dfe0f35787fa482aa18689b9c8251e045076c35477deb0007b3277/rignore-0.7.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1734dc49d1e9501b07852ef44421f84d9f378da9fbeda729e77db71f49cac28b", size = 1078647, upload-time = "2025-11-05T21:40:13.463Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cf/2c64f0b6725149f7c6e7e5a909d14354889b4beaadddaa5fff023ec71084/rignore-0.7.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5719ea14ea2b652c0c0894be5dfde954e1853a80dea27dd2fbaa749618d837f5", size = 1139186, upload-time = "2025-11-05T21:40:31.27Z" }, + { url = "https://files.pythonhosted.org/packages/75/95/a86c84909ccc24af0d094b50d54697951e576c252a4d9f21b47b52af9598/rignore-0.7.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e23424fc7ce35726854f639cb7968151a792c0c3d9d082f7f67e0c362cfecca", size = 1117604, upload-time = "2025-11-05T21:40:48.07Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5e/13b249613fd5d18d58662490ab910a9f0be758981d1797789913adb4e918/rignore-0.7.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3efdcf1dd84d45f3e2bd2f93303d9be103888f56dfa7c3349b5bf4f0657ec696", size = 1127725, upload-time = "2025-11-05T21:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/c7/28/fa5dcd1e2e16982c359128664e3785f202d3eca9b22dd0b2f91c4b3d242f/rignore-0.7.6-cp312-cp312-win32.whl", hash = "sha256:ccca9d1a8b5234c76b71546fc3c134533b013f40495f394a65614a81f7387046", size = 646145, upload-time = "2025-11-05T21:41:51.096Z" }, + { url = "https://files.pythonhosted.org/packages/26/87/69387fb5dd81a0f771936381431780b8cf66fcd2cfe9495e1aaf41548931/rignore-0.7.6-cp312-cp312-win_amd64.whl", hash = "sha256:c96a285e4a8bfec0652e0bfcf42b1aabcdda1e7625f5006d188e3b1c87fdb543", size = 726090, upload-time = "2025-11-05T21:41:36.485Z" }, + { url = "https://files.pythonhosted.org/packages/24/5f/e8418108dcda8087fb198a6f81caadbcda9fd115d61154bf0df4d6d3619b/rignore-0.7.6-cp312-cp312-win_arm64.whl", hash = "sha256:a64a750e7a8277a323f01ca50b7784a764845f6cce2fe38831cb93f0508d0051", size = 656317, upload-time = "2025-11-05T21:41:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8a/a4078f6e14932ac7edb171149c481de29969d96ddee3ece5dc4c26f9e0c3/rignore-0.7.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2bdab1d31ec9b4fb1331980ee49ea051c0d7f7bb6baa28b3125ef03cdc48fdaf", size = 883057, upload-time = "2025-11-05T20:42:42.741Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8f/f8daacd177db4bf7c2223bab41e630c52711f8af9ed279be2058d2fe4982/rignore-0.7.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90f0a00ce0c866c275bf888271f1dc0d2140f29b82fcf33cdbda1e1a6af01010", size = 820150, upload-time = "2025-11-05T20:42:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/b65b837e39c3f7064c426754714ac633b66b8c2290978af9d7f513e14aa9/rignore-0.7.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ad295537041dc2ed4b540fb1a3906bd9ede6ccdad3fe79770cd89e04e3c73c", size = 897406, upload-time = "2025-11-05T20:40:53.854Z" }, + { url = "https://files.pythonhosted.org/packages/ca/58/1970ce006c427e202ac7c081435719a076c478f07b3a23f469227788dc23/rignore-0.7.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f782dbd3a65a5ac85adfff69e5c6b101285ef3f845c3a3cae56a54bebf9fe116", size = 874050, upload-time = "2025-11-05T20:41:08.922Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/eb45db9f90137329072a732273be0d383cb7d7f50ddc8e0bceea34c1dfdf/rignore-0.7.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65cece3b36e5b0826d946494734c0e6aaf5a0337e18ff55b071438efe13d559e", size = 1167835, upload-time = "2025-11-05T20:41:24.997Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f1/6f1d72ddca41a64eed569680587a1236633587cc9f78136477ae69e2c88a/rignore-0.7.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e4bb66c13cd7602dc8931822c02dfbbd5252015c750ac5d6152b186f0a8be0", size = 941945, upload-time = "2025-11-05T20:41:40.628Z" }, + { url = "https://files.pythonhosted.org/packages/48/6f/2f178af1c1a276a065f563ec1e11e7a9e23d4996fd0465516afce4b5c636/rignore-0.7.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297e500c15766e196f68aaaa70e8b6db85fa23fdc075b880d8231fdfba738cd7", size = 959067, upload-time = "2025-11-05T20:42:11.09Z" }, + { url = "https://files.pythonhosted.org/packages/5b/db/423a81c4c1e173877c7f9b5767dcaf1ab50484a94f60a0b2ed78be3fa765/rignore-0.7.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a07084211a8d35e1a5b1d32b9661a5ed20669970b369df0cf77da3adea3405de", size = 984438, upload-time = "2025-11-05T20:41:55.443Z" }, + { url = "https://files.pythonhosted.org/packages/31/eb/c4f92cc3f2825d501d3c46a244a671eb737fc1bcf7b05a3ecd34abb3e0d7/rignore-0.7.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:181eb2a975a22256a1441a9d2f15eb1292839ea3f05606620bd9e1938302cf79", size = 1078365, upload-time = "2025-11-05T21:40:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/26/09/99442f02794bd7441bfc8ed1c7319e890449b816a7493b2db0e30af39095/rignore-0.7.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7bbcdc52b5bf9f054b34ce4af5269df5d863d9c2456243338bc193c28022bd7b", size = 1139066, upload-time = "2025-11-05T21:40:32.771Z" }, + { url = "https://files.pythonhosted.org/packages/2c/88/bcfc21e520bba975410e9419450f4b90a2ac8236b9a80fd8130e87d098af/rignore-0.7.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f2e027a6da21a7c8c0d87553c24ca5cc4364def18d146057862c23a96546238e", size = 1118036, upload-time = "2025-11-05T21:40:49.646Z" }, + { url = "https://files.pythonhosted.org/packages/e2/25/d37215e4562cda5c13312636393aea0bafe38d54d4e0517520a4cc0753ec/rignore-0.7.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee4a18b82cbbc648e4aac1510066682fe62beb5dc88e2c67c53a83954e541360", size = 1127550, upload-time = "2025-11-05T21:41:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/dc/76/a264ab38bfa1620ec12a8ff1c07778da89e16d8c0f3450b0333020d3d6dc/rignore-0.7.6-cp313-cp313-win32.whl", hash = "sha256:a7d7148b6e5e95035d4390396895adc384d37ff4e06781a36fe573bba7c283e5", size = 646097, upload-time = "2025-11-05T21:41:53.201Z" }, + { url = "https://files.pythonhosted.org/packages/62/44/3c31b8983c29ea8832b6082ddb1d07b90379c2d993bd20fce4487b71b4f4/rignore-0.7.6-cp313-cp313-win_amd64.whl", hash = "sha256:b037c4b15a64dced08fc12310ee844ec2284c4c5c1ca77bc37d0a04f7bff386e", size = 726170, upload-time = "2025-11-05T21:41:38.131Z" }, + { url = "https://files.pythonhosted.org/packages/aa/41/e26a075cab83debe41a42661262f606166157df84e0e02e2d904d134c0d8/rignore-0.7.6-cp313-cp313-win_arm64.whl", hash = "sha256:e47443de9b12fe569889bdbe020abe0e0b667516ee2ab435443f6d0869bd2804", size = 656184, upload-time = "2025-11-05T21:41:27.396Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b9/1f5bd82b87e5550cd843ceb3768b4a8ef274eb63f29333cf2f29644b3d75/rignore-0.7.6-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:8e41be9fa8f2f47239ded8920cc283699a052ac4c371f77f5ac017ebeed75732", size = 882632, upload-time = "2025-11-05T20:42:44.063Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6b/07714a3efe4a8048864e8a5b7db311ba51b921e15268b17defaebf56d3db/rignore-0.7.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6dc1e171e52cefa6c20e60c05394a71165663b48bca6c7666dee4f778f2a7d90", size = 820760, upload-time = "2025-11-05T20:42:27.885Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0f/348c829ea2d8d596e856371b14b9092f8a5dfbb62674ec9b3f67e4939a9d/rignore-0.7.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ce2268837c3600f82ab8db58f5834009dc638ee17103582960da668963bebc5", size = 899044, upload-time = "2025-11-05T20:40:55.336Z" }, + { url = "https://files.pythonhosted.org/packages/f0/30/2e1841a19b4dd23878d73edd5d82e998a83d5ed9570a89675f140ca8b2ad/rignore-0.7.6-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:690a3e1b54bfe77e89c4bacb13f046e642f8baadafc61d68f5a726f324a76ab6", size = 874144, upload-time = "2025-11-05T20:41:10.195Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bf/0ce9beb2e5f64c30e3580bef09f5829236889f01511a125f98b83169b993/rignore-0.7.6-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09d12ac7a0b6210c07bcd145007117ebd8abe99c8eeb383e9e4673910c2754b2", size = 1168062, upload-time = "2025-11-05T20:41:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8b/571c178414eb4014969865317da8a02ce4cf5241a41676ef91a59aab24de/rignore-0.7.6-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a2b2b74a8c60203b08452479b90e5ce3dbe96a916214bc9eb2e5af0b6a9beb0", size = 942542, upload-time = "2025-11-05T20:41:41.838Z" }, + { url = "https://files.pythonhosted.org/packages/19/62/7a3cf601d5a45137a7e2b89d10c05b5b86499190c4b7ca5c3c47d79ee519/rignore-0.7.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc5a531ef02131e44359419a366bfac57f773ea58f5278c2cdd915f7d10ea94", size = 958739, upload-time = "2025-11-05T20:42:12.463Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1f/4261f6a0d7caf2058a5cde2f5045f565ab91aa7badc972b57d19ce58b14e/rignore-0.7.6-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7a1f77d9c4cd7e76229e252614d963442686bfe12c787a49f4fe481df49e7a9", size = 984138, upload-time = "2025-11-05T20:41:56.775Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bf/628dfe19c75e8ce1f45f7c248f5148b17dfa89a817f8e3552ab74c3ae812/rignore-0.7.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ead81f728682ba72b5b1c3d5846b011d3e0174da978de87c61645f2ed36659a7", size = 1079299, upload-time = "2025-11-05T21:40:16.639Z" }, + { url = "https://files.pythonhosted.org/packages/af/a5/be29c50f5c0c25c637ed32db8758fdf5b901a99e08b608971cda8afb293b/rignore-0.7.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:12ffd50f520c22ffdabed8cd8bfb567d9ac165b2b854d3e679f4bcaef11a9441", size = 1139618, upload-time = "2025-11-05T21:40:34.507Z" }, + { url = "https://files.pythonhosted.org/packages/2a/40/3c46cd7ce4fa05c20b525fd60f599165e820af66e66f2c371cd50644558f/rignore-0.7.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e5a16890fbe3c894f8ca34b0fcacc2c200398d4d46ae654e03bc9b3dbf2a0a72", size = 1117626, upload-time = "2025-11-05T21:40:51.494Z" }, + { url = "https://files.pythonhosted.org/packages/8c/b9/aea926f263b8a29a23c75c2e0d8447965eb1879d3feb53cfcf84db67ed58/rignore-0.7.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3abab3bf99e8a77488ef6c7c9a799fac22224c28fe9f25cc21aa7cc2b72bfc0b", size = 1128144, upload-time = "2025-11-05T21:41:09.169Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f6/0d6242f8d0df7f2ecbe91679fefc1f75e7cd2072cb4f497abaab3f0f8523/rignore-0.7.6-cp314-cp314-win32.whl", hash = "sha256:eeef421c1782953c4375aa32f06ecae470c1285c6381eee2a30d2e02a5633001", size = 646385, upload-time = "2025-11-05T21:41:55.105Z" }, + { url = "https://files.pythonhosted.org/packages/d5/38/c0dcd7b10064f084343d6af26fe9414e46e9619c5f3224b5272e8e5d9956/rignore-0.7.6-cp314-cp314-win_amd64.whl", hash = "sha256:6aeed503b3b3d5af939b21d72a82521701a4bd3b89cd761da1e7dc78621af304", size = 725738, upload-time = "2025-11-05T21:41:39.736Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7a/290f868296c1ece914d565757ab363b04730a728b544beb567ceb3b2d96f/rignore-0.7.6-cp314-cp314-win_arm64.whl", hash = "sha256:104f215b60b3c984c386c3e747d6ab4376d5656478694e22c7bd2f788ddd8304", size = 656008, upload-time = "2025-11-05T21:41:29.028Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d2/3c74e3cd81fe8ea08a8dcd2d755c09ac2e8ad8fe409508904557b58383d3/rignore-0.7.6-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bb24a5b947656dd94cb9e41c4bc8b23cec0c435b58be0d74a874f63c259549e8", size = 882835, upload-time = "2025-11-05T20:42:45.443Z" }, + { url = "https://files.pythonhosted.org/packages/77/61/a772a34b6b63154877433ac2d048364815b24c2dd308f76b212c408101a2/rignore-0.7.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b1e33c9501cefe24b70a1eafd9821acfd0ebf0b35c3a379430a14df089993e3", size = 820301, upload-time = "2025-11-05T20:42:29.226Z" }, + { url = "https://files.pythonhosted.org/packages/71/30/054880b09c0b1b61d17eeb15279d8bf729c0ba52b36c3ada52fb827cbb3c/rignore-0.7.6-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bec3994665a44454df86deb762061e05cd4b61e3772f5b07d1882a8a0d2748d5", size = 897611, upload-time = "2025-11-05T20:40:56.475Z" }, + { url = "https://files.pythonhosted.org/packages/1e/40/b2d1c169f833d69931bf232600eaa3c7998ba4f9a402e43a822dad2ea9f2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26cba2edfe3cff1dfa72bddf65d316ddebf182f011f2f61538705d6dbaf54986", size = 873875, upload-time = "2025-11-05T20:41:11.561Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/ca5ae93d83a1a60e44b21d87deb48b177a8db1b85e82fc8a9abb24a8986d/rignore-0.7.6-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffa86694fec604c613696cb91e43892aa22e1fec5f9870e48f111c603e5ec4e9", size = 1167245, upload-time = "2025-11-05T20:41:28.29Z" }, + { url = "https://files.pythonhosted.org/packages/a5/52/cf3dce392ba2af806cba265aad6bcd9c48bb2a6cb5eee448d3319f6e505b/rignore-0.7.6-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48efe2ed95aa8104145004afb15cdfa02bea5cdde8b0344afeb0434f0d989aa2", size = 941750, upload-time = "2025-11-05T20:41:43.111Z" }, + { url = "https://files.pythonhosted.org/packages/ec/be/3f344c6218d779395e785091d05396dfd8b625f6aafbe502746fcd880af2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dcae43eb44b7f2457fef7cc87f103f9a0013017a6f4e62182c565e924948f21", size = 958896, upload-time = "2025-11-05T20:42:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/c9/34/d3fa71938aed7d00dcad87f0f9bcb02ad66c85d6ffc83ba31078ce53646a/rignore-0.7.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2cd649a7091c0dad2f11ef65630d30c698d505cbe8660dd395268e7c099cc99f", size = 983992, upload-time = "2025-11-05T20:41:58.022Z" }, + { url = "https://files.pythonhosted.org/packages/24/a4/52a697158e9920705bdbd0748d59fa63e0f3233fb92e9df9a71afbead6ca/rignore-0.7.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42de84b0289d478d30ceb7ae59023f7b0527786a9a5b490830e080f0e4ea5aeb", size = 1078181, upload-time = "2025-11-05T21:40:18.151Z" }, + { url = "https://files.pythonhosted.org/packages/ac/65/aa76dbcdabf3787a6f0fd61b5cc8ed1e88580590556d6c0207960d2384bb/rignore-0.7.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:875a617e57b53b4acbc5a91de418233849711c02e29cc1f4f9febb2f928af013", size = 1139232, upload-time = "2025-11-05T21:40:35.966Z" }, + { url = "https://files.pythonhosted.org/packages/08/44/31b31a49b3233c6842acc1c0731aa1e7fb322a7170612acf30327f700b44/rignore-0.7.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8703998902771e96e49968105207719f22926e4431b108450f3f430b4e268b7c", size = 1117349, upload-time = "2025-11-05T21:40:53.013Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ae/1b199a2302c19c658cf74e5ee1427605234e8c91787cfba0015f2ace145b/rignore-0.7.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:602ef33f3e1b04c1e9a10a3c03f8bc3cef2d2383dcc250d309be42b49923cabc", size = 1127702, upload-time = "2025-11-05T21:41:10.881Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/09/ba/e5ea89fbde8e37a90ce456e31c5e9d85512cef5ae38e0f4d2426eb776a19/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1a6671b2082c13bfd9a5cf4ce64670f832a6d41470556112c4ab0b6519b2fc4", size = 876987, upload-time = "2025-11-05T20:41:16.219Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fb/93d14193f0ec0c3d35b763f0a000e9780f63b2031f3d3756442c2152622d/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2468729b4c5295c199d084ab88a40afcb7c8b974276805105239c07855bbacee", size = 1171110, upload-time = "2025-11-05T20:41:32.631Z" }, + { url = "https://files.pythonhosted.org/packages/9e/46/08436312ff96ffa29cfa4e1a987efc37e094531db46ba5e9fda9bb792afd/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:775710777fd71e5fdf54df69cdc249996a1d6f447a2b5bfb86dbf033fddd9cf9", size = 943339, upload-time = "2025-11-05T20:41:47.128Z" }, + { url = "https://files.pythonhosted.org/packages/34/28/3b3c51328f505cfaf7e53f408f78a1e955d561135d02f9cb0341ea99f69a/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4565407f4a77f72cf9d91469e75d15d375f755f0a01236bb8aaa176278cc7085", size = 961680, upload-time = "2025-11-05T20:42:18.061Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9e/cbff75c8676d4f4a90bd58a1581249d255c7305141b0868f0abc0324836b/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc44c33f8fb2d5c9da748de7a6e6653a78aa740655e7409895e94a247ffa97c8", size = 987045, upload-time = "2025-11-05T20:42:02.315Z" }, + { url = "https://files.pythonhosted.org/packages/8c/25/d802d1d369502a7ddb8816059e7c79d2d913e17df975b863418e0aca4d8a/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8f32478f05540513c11923e8838afab9efef0131d66dca7f67f0e1bbd118af6a", size = 1080310, upload-time = "2025-11-05T21:40:23.184Z" }, + { url = "https://files.pythonhosted.org/packages/43/f0/250b785c2e473b1ab763eaf2be820934c2a5409a722e94b279dddac21c7d/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:1b63a3dd76225ea35b01dd6596aa90b275b5d0f71d6dc28fce6dd295d98614aa", size = 1140998, upload-time = "2025-11-05T21:40:40.603Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d6/bb42fd2a8bba6aea327962656e20621fd495523259db40cfb4c5f760f05c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:fe6c41175c36554a4ef0994cd1b4dbd6d73156fca779066456b781707402048e", size = 1121178, upload-time = "2025-11-05T21:40:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/97/f4/aeb548374129dce3dc191a4bb598c944d9ed663f467b9af830315d86059c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a0c6792406ae36f4e7664dc772da909451d46432ff8485774526232d4885063", size = 1130190, upload-time = "2025-11-05T21:41:16.403Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/a6250ff0c49a3cdb943910ada4116e708118e9b901c878cfae616c80a904/rignore-0.7.6-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a20b6fb61bcced9a83dfcca6599ad45182b06ba720cff7c8d891e5b78db5b65f", size = 886470, upload-time = "2025-11-05T20:42:52.314Z" }, + { url = "https://files.pythonhosted.org/packages/35/af/c69c0c51b8f9f7914d95c4ea91c29a2ac067572048cae95dd6d2efdbe05d/rignore-0.7.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:392dcabfecbe176c9ebbcb40d85a5e86a5989559c4f988c2741da7daf1b5be25", size = 825976, upload-time = "2025-11-05T20:42:35.118Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d2/1b264f56132264ea609d3213ab603d6a27016b19559a1a1ede1a66a03dcd/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22baa462abdc36fdd5a5e2dae423107723351b85ff093762f9261148b9d0a04a", size = 899739, upload-time = "2025-11-05T20:41:01.518Z" }, + { url = "https://files.pythonhosted.org/packages/55/e4/b3c5dfdd8d8a10741dfe7199ef45d19a0e42d0c13aa377c83bd6caf65d90/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53fb28882d2538cb2d231972146c4927a9d9455e62b209f85d634408c4103538", size = 874843, upload-time = "2025-11-05T20:41:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/10/d6f3750233881a2a154cefc9a6a0a9b19da526b19f7f08221b552c6f827d/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87409f7eeb1103d6b77f3472a3a0d9a5953e3ae804a55080bdcb0120ee43995b", size = 1170348, upload-time = "2025-11-05T20:41:34.21Z" }, + { url = "https://files.pythonhosted.org/packages/6e/10/ad98ca05c9771c15af734cee18114a3c280914b6e34fde9ffea2e61e88aa/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684014e42e4341ab3ea23a203551857fcc03a7f8ae96ca3aefb824663f55db32", size = 942315, upload-time = "2025-11-05T20:41:48.508Z" }, + { url = "https://files.pythonhosted.org/packages/de/00/ab5c0f872acb60d534e687e629c17e0896c62da9b389c66d3aa16b817aa8/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77356ebb01ba13f8a425c3d30fcad40e57719c0e37670d022d560884a30e4767", size = 961047, upload-time = "2025-11-05T20:42:19.403Z" }, + { url = "https://files.pythonhosted.org/packages/b8/86/3030fdc363a8f0d1cd155b4c453d6db9bab47a24fcc64d03f61d9d78fe6a/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cbd8a48abbd3747a6c830393cd578782fab5d43f4deea48c5f5e344b8fed2b0", size = 986090, upload-time = "2025-11-05T20:42:03.581Z" }, + { url = "https://files.pythonhosted.org/packages/33/b8/133aa4002cee0ebbb39362f94e4898eec7fbd09cec9fcbce1cd65b355b7f/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2673225dcec7f90497e79438c35e34638d0d0391ccea3cbb79bfb9adc0dc5bd7", size = 1079656, upload-time = "2025-11-05T21:40:24.89Z" }, + { 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]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/62/50b7727004dfe361104dfbf898c45a9a2fdfad8c72c04ae62900224d6ecf/ruff-0.14.3.tar.gz", hash = "sha256:4ff876d2ab2b161b6de0aa1f5bd714e8e9b4033dc122ee006925fbacc4f62153", size = 5558687, upload-time = "2025-10-31T00:26:26.878Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8e/0c10ff1ea5d4360ab8bfca4cb2c9d979101a391f3e79d2616c9bf348cd26/ruff-0.14.3-py3-none-linux_armv6l.whl", hash = "sha256:876b21e6c824f519446715c1342b8e60f97f93264012de9d8d10314f8a79c371", size = 12535613, upload-time = "2025-10-31T00:25:44.302Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c8/6724f4634c1daf52409fbf13fefda64aa9c8f81e44727a378b7b73dc590b/ruff-0.14.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6fd8c79b457bedd2abf2702b9b472147cd860ed7855c73a5247fa55c9117654", size = 12855812, upload-time = "2025-10-31T00:25:47.793Z" }, + { url = "https://files.pythonhosted.org/packages/de/03/db1bce591d55fd5f8a08bb02517fa0b5097b2ccabd4ea1ee29aa72b67d96/ruff-0.14.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:71ff6edca490c308f083156938c0c1a66907151263c4abdcb588602c6e696a14", size = 11944026, upload-time = "2025-10-31T00:25:49.657Z" }, + { url = "https://files.pythonhosted.org/packages/0b/75/4f8dbd48e03272715d12c87dc4fcaaf21b913f0affa5f12a4e9c6f8a0582/ruff-0.14.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:786ee3ce6139772ff9272aaf43296d975c0217ee1b97538a98171bf0d21f87ed", size = 12356818, upload-time = "2025-10-31T00:25:51.949Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9b/506ec5b140c11d44a9a4f284ea7c14ebf6f8b01e6e8917734a3325bff787/ruff-0.14.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cd6291d0061811c52b8e392f946889916757610d45d004e41140d81fb6cd5ddc", size = 12336745, upload-time = "2025-10-31T00:25:54.248Z" }, + { url = "https://files.pythonhosted.org/packages/c7/e1/c560d254048c147f35e7f8131d30bc1f63a008ac61595cf3078a3e93533d/ruff-0.14.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a497ec0c3d2c88561b6d90f9c29f5ae68221ac00d471f306fa21fa4264ce5fcd", size = 13101684, upload-time = "2025-10-31T00:25:56.253Z" }, + { url = "https://files.pythonhosted.org/packages/a5/32/e310133f8af5cd11f8cc30f52522a3ebccc5ea5bff4b492f94faceaca7a8/ruff-0.14.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e231e1be58fc568950a04fbe6887c8e4b85310e7889727e2b81db205c45059eb", size = 14535000, upload-time = "2025-10-31T00:25:58.397Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a1/7b0470a22158c6d8501eabc5e9b6043c99bede40fa1994cadf6b5c2a61c7/ruff-0.14.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:469e35872a09c0e45fecf48dd960bfbce056b5db2d5e6b50eca329b4f853ae20", size = 14156450, upload-time = "2025-10-31T00:26:00.889Z" }, + { url = "https://files.pythonhosted.org/packages/0a/96/24bfd9d1a7f532b560dcee1a87096332e461354d3882124219bcaff65c09/ruff-0.14.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d6bc90307c469cb9d28b7cfad90aaa600b10d67c6e22026869f585e1e8a2db0", size = 13568414, upload-time = "2025-10-31T00:26:03.291Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e7/138b883f0dfe4ad5b76b58bf4ae675f4d2176ac2b24bdd81b4d966b28c61/ruff-0.14.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2f8a0bbcffcfd895df39c9a4ecd59bb80dca03dc43f7fb63e647ed176b741e", size = 13315293, upload-time = "2025-10-31T00:26:05.708Z" }, + { url = "https://files.pythonhosted.org/packages/33/f4/c09bb898be97b2eb18476b7c950df8815ef14cf956074177e9fbd40b7719/ruff-0.14.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:678fdd7c7d2d94851597c23ee6336d25f9930b460b55f8598e011b57c74fd8c5", size = 13539444, upload-time = "2025-10-31T00:26:08.09Z" }, + { url = "https://files.pythonhosted.org/packages/9c/aa/b30a1db25fc6128b1dd6ff0741fa4abf969ded161599d07ca7edd0739cc0/ruff-0.14.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1ec1ac071e7e37e0221d2f2dbaf90897a988c531a8592a6a5959f0603a1ecf5e", size = 12252581, upload-time = "2025-10-31T00:26:10.297Z" }, + { url = "https://files.pythonhosted.org/packages/da/13/21096308f384d796ffe3f2960b17054110a9c3828d223ca540c2b7cc670b/ruff-0.14.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afcdc4b5335ef440d19e7df9e8ae2ad9f749352190e96d481dc501b753f0733e", size = 12307503, upload-time = "2025-10-31T00:26:12.646Z" }, + { url = "https://files.pythonhosted.org/packages/cb/cc/a350bac23f03b7dbcde3c81b154706e80c6f16b06ff1ce28ed07dc7b07b0/ruff-0.14.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7bfc42f81862749a7136267a343990f865e71fe2f99cf8d2958f684d23ce3dfa", size = 12675457, upload-time = "2025-10-31T00:26:15.044Z" }, + { url = "https://files.pythonhosted.org/packages/cb/76/46346029fa2f2078826bc88ef7167e8c198e58fe3126636e52f77488cbba/ruff-0.14.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a65e448cfd7e9c59fae8cf37f9221585d3354febaad9a07f29158af1528e165f", size = 13403980, upload-time = "2025-10-31T00:26:17.81Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a4/35f1ef68c4e7b236d4a5204e3669efdeefaef21f0ff6a456792b3d8be438/ruff-0.14.3-py3-none-win32.whl", hash = "sha256:f3d91857d023ba93e14ed2d462ab62c3428f9bbf2b4fbac50a03ca66d31991f7", size = 12500045, upload-time = "2025-10-31T00:26:20.503Z" }, + { url = "https://files.pythonhosted.org/packages/03/15/51960ae340823c9859fb60c63301d977308735403e2134e17d1d2858c7fb/ruff-0.14.3-py3-none-win_amd64.whl", hash = "sha256:d7b7006ac0756306db212fd37116cce2bd307e1e109375e1c6c106002df0ae5f", size = 13594005, upload-time = "2025-10-31T00:26:22.533Z" }, + { url = "https://files.pythonhosted.org/packages/b7/73/4de6579bac8e979fca0a77e54dec1f1e011a0d268165eb8a9bc0982a6564/ruff-0.14.3-py3-none-win_arm64.whl", hash = "sha256:26eb477ede6d399d898791d01961e16b86f02bc2486d0d1a7a9bb2379d055dc1", size = 12590017, upload-time = "2025-10-31T00:26:24.52Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } +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 = "sentry-sdk" +version = "2.49.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" } +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" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "smokeshow" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/94/c99b76517c268ef8d5c2ff88faba5a019664bd69e4754944afa294b4f24c/smokeshow-0.5.0.tar.gz", hash = "sha256:91dcabc29ac3116bff59b4d8a7bda4ae3ccc4c70742a38cec7127b8162e4a0f6", size = 101349, upload-time = "2025-01-07T19:41:51.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/10/0d23e4953eb7c1e1ad848084b3115f19234f34f907658ed11bed0d826aee/smokeshow-0.5.0-py3-none-any.whl", hash = "sha256:da12a960fc7cb525efc4035a0c3c9363b6217ea7e66bc39b9ed3cd8bed6eeedc", size = 8389, upload-time = "2025-01-07T19:41:49.194Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.45" +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 = "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" } +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" }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.27" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/5a/693d90866233e837d182da76082a6d4c2303f54d3aaaa5c78e1238c5d863/sqlmodel-0.0.27.tar.gz", hash = "sha256:ad1227f2014a03905aef32e21428640848ac09ff793047744a73dfdd077ff620", size = 118053, upload-time = "2025-10-08T16:39:11.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/92/c35e036151fe53822893979f8a13e6f235ae8191f4164a79ae60a95d66aa/sqlmodel-0.0.27-py3-none-any.whl", hash = "sha256:667fe10aa8ff5438134668228dc7d7a08306f4c5c4c7e6ad3ad68defa0e7aa49", size = 29131, upload-time = "2025-10-08T16:39:10.917Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.1.2" +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" } +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" }, +] + +[[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.50.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 = "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" } +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" }, +] + +[[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.288.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'" }, +] +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" } +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" }, +] + +[[package]] +name = "super-collections" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hjson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/de/a0c3d1244912c260638f0f925e190e493ccea37ecaea9bbad7c14413b803/super_collections-0.6.2.tar.gz", hash = "sha256:0c8d8abacd9fad2c7c1c715f036c29f5db213f8cac65f24d45ecba12b4da187a", size = 31315, upload-time = "2025-09-30T00:37:08.067Z" } +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 = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +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 = "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" }, +] + +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +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 = "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'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" }, + { 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]] +name = "tomli" +version = "2.3.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" } +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" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +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" } +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" }, +] + +[[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'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/41/bf/945d527ff706233636c73880b22c7c953f3faeb9d6c7e2e85bfbfd0134a0/trio-0.32.0-py3-none-any.whl", hash = "sha256:4ab65984ef8370b79a76659ec87aa3a30c5c7c83ff250b4de88c29a8ab6123c5", size = 512030, upload-time = "2025-10-31T07:18:15.885Z" }, +] + +[[package]] +name = "typer" +version = "0.16.0" +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 = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, +] + +[[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" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/97/3f78cfdf663e5668e8b490d8c84d6de089d2d8dbad935f0dc43555d52a90/types-orjson-3.6.2.tar.gz", hash = "sha256:cf9afcc79a86325c7aff251790338109ed6f6b1bab09d2d4262dd18c85a3c638", size = 1999, upload-time = "2022-01-07T11:31:10.518Z" } +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-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'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, +] + +[[package]] +name = "types-ujson" +version = "5.10.0.20240515" +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" } +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" }, +] + +[[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" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "ujson" +version = "5.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/d9/3f17e3c5773fb4941c68d9a37a47b1a79c9649d6c56aefbed87cc409d18a/ujson-5.11.0.tar.gz", hash = "sha256:e204ae6f909f099ba6b6b942131cee359ddda2b6e4ea39c12eb8b991fe2010e0", size = 7156583, upload-time = "2025-08-20T11:57:02.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/8bf7a4fabfd01c7eed92d9b290930ce6d14910dec708e73538baa38885d1/ujson-5.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:446e8c11c06048611c9d29ef1237065de0af07cabdd97e6b5b527b957692ec25", size = 55248, upload-time = "2025-08-20T11:55:02.368Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2e/eeab0b8b641817031ede4f790db4c4942df44a12f44d72b3954f39c6a115/ujson-5.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16ccb973b7ada0455201808ff11d48fe9c3f034a6ab5bd93b944443c88299f89", size = 53157, upload-time = "2025-08-20T11:55:04.012Z" }, + { url = "https://files.pythonhosted.org/packages/21/1b/a4e7a41870797633423ea79618526747353fd7be9191f3acfbdee0bf264b/ujson-5.11.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3134b783ab314d2298d58cda7e47e7a0f7f71fc6ade6ac86d5dbeaf4b9770fa6", size = 57657, upload-time = "2025-08-20T11:55:05.169Z" }, + { url = "https://files.pythonhosted.org/packages/94/ae/4e0d91b8f6db7c9b76423b3649612189506d5a06ddd3b6334b6d37f77a01/ujson-5.11.0-cp310-cp310-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:185f93ebccffebc8baf8302c869fac70dd5dd78694f3b875d03a31b03b062cdb", size = 59780, upload-time = "2025-08-20T11:55:06.325Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cc/46b124c2697ca2da7c65c4931ed3cb670646978157aa57a7a60f741c530f/ujson-5.11.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d06e87eded62ff0e5f5178c916337d2262fdbc03b31688142a3433eabb6511db", size = 57307, upload-time = "2025-08-20T11:55:07.493Z" }, + { url = "https://files.pythonhosted.org/packages/39/eb/20dd1282bc85dede2f1c62c45b4040bc4c389c80a05983515ab99771bca7/ujson-5.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:181fb5b15703a8b9370b25345d2a1fd1359f0f18776b3643d24e13ed9c036d4c", size = 1036369, upload-time = "2025-08-20T11:55:09.192Z" }, + { url = "https://files.pythonhosted.org/packages/64/a2/80072439065d493e3a4b1fbeec991724419a1b4c232e2d1147d257cac193/ujson-5.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4df61a6df0a4a8eb5b9b1ffd673429811f50b235539dac586bb7e9e91994138", size = 1195738, upload-time = "2025-08-20T11:55:11.402Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7e/d77f9e9c039d58299c350c978e086a804d1fceae4fd4a1cc6e8d0133f838/ujson-5.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6eff24e1abd79e0ec6d7eae651dd675ddbc41f9e43e29ef81e16b421da896915", size = 1088718, upload-time = "2025-08-20T11:55:13.297Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f1/697559d45acc849cada6b3571d53522951b1a64027400507aabc6a710178/ujson-5.11.0-cp310-cp310-win32.whl", hash = "sha256:30f607c70091483550fbd669a0b37471e5165b317d6c16e75dba2aa967608723", size = 39653, upload-time = "2025-08-20T11:55:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/86/a2/70b73a0f55abe0e6b8046d365d74230c20c5691373e6902a599b2dc79ba1/ujson-5.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3d2720e9785f84312b8e2cb0c2b87f1a0b1c53aaab3b2af3ab817d54409012e0", size = 43720, upload-time = "2025-08-20T11:55:15.897Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5f/b19104afa455630b43efcad3a24495b9c635d92aa8f2da4f30e375deb1a2/ujson-5.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:85e6796631165f719084a9af00c79195d3ebf108151452fefdcb1c8bb50f0105", size = 38410, upload-time = "2025-08-20T11:55:17.556Z" }, + { url = "https://files.pythonhosted.org/packages/da/ea/80346b826349d60ca4d612a47cdf3533694e49b45e9d1c07071bb867a184/ujson-5.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d7c46cb0fe5e7056b9acb748a4c35aa1b428025853032540bb7e41f46767321f", size = 55248, upload-time = "2025-08-20T11:55:19.033Z" }, + { url = "https://files.pythonhosted.org/packages/57/df/b53e747562c89515e18156513cc7c8ced2e5e3fd6c654acaa8752ffd7cd9/ujson-5.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8951bb7a505ab2a700e26f691bdfacf395bc7e3111e3416d325b513eea03a58", size = 53156, upload-time = "2025-08-20T11:55:20.174Z" }, + { url = "https://files.pythonhosted.org/packages/41/b8/ab67ec8c01b8a3721fd13e5cb9d85ab2a6066a3a5e9148d661a6870d6293/ujson-5.11.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952c0be400229940248c0f5356514123d428cba1946af6fa2bbd7503395fef26", size = 57657, upload-time = "2025-08-20T11:55:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c7/fb84f27cd80a2c7e2d3c6012367aecade0da936790429801803fa8d4bffc/ujson-5.11.0-cp311-cp311-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:94fcae844f1e302f6f8095c5d1c45a2f0bfb928cccf9f1b99e3ace634b980a2a", size = 59779, upload-time = "2025-08-20T11:55:22.772Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/48706f7c1e917ecb97ddcfb7b1d756040b86ed38290e28579d63bd3fcc48/ujson-5.11.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e0ec1646db172beb8d3df4c32a9d78015e671d2000af548252769e33079d9a6", size = 57284, upload-time = "2025-08-20T11:55:24.01Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ce/48877c6eb4afddfd6bd1db6be34456538c07ca2d6ed233d3f6c6efc2efe8/ujson-5.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:da473b23e3a54448b008d33f742bcd6d5fb2a897e42d1fc6e7bf306ea5d18b1b", size = 1036395, upload-time = "2025-08-20T11:55:25.725Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7a/2c20dc97ad70cd7c31ad0596ba8e2cf8794d77191ba4d1e0bded69865477/ujson-5.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:aa6b3d4f1c0d3f82930f4cbd7fe46d905a4a9205a7c13279789c1263faf06dba", size = 1195731, upload-time = "2025-08-20T11:55:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/15/f5/ca454f2f6a2c840394b6f162fff2801450803f4ff56c7af8ce37640b8a2a/ujson-5.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4843f3ab4fe1cc596bb7e02228ef4c25d35b4bb0809d6a260852a4bfcab37ba3", size = 1088710, upload-time = "2025-08-20T11:55:29.426Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d3/9ba310e07969bc9906eb7548731e33a0f448b122ad9705fed699c9b29345/ujson-5.11.0-cp311-cp311-win32.whl", hash = "sha256:e979fbc469a7f77f04ec2f4e853ba00c441bf2b06720aa259f0f720561335e34", size = 39648, upload-time = "2025-08-20T11:55:31.194Z" }, + { url = "https://files.pythonhosted.org/packages/57/f7/da05b4a8819f1360be9e71fb20182f0bb3ec611a36c3f213f4d20709e099/ujson-5.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:683f57f0dd3acdd7d9aff1de0528d603aafcb0e6d126e3dc7ce8b020a28f5d01", size = 43717, upload-time = "2025-08-20T11:55:32.241Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cc/f3f9ac0f24f00a623a48d97dc3814df5c2dc368cfb00031aa4141527a24b/ujson-5.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:7855ccea3f8dad5e66d8445d754fc1cf80265a4272b5f8059ebc7ec29b8d0835", size = 38402, upload-time = "2025-08-20T11:55:33.641Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ef/a9cb1fce38f699123ff012161599fb9f2ff3f8d482b4b18c43a2dc35073f/ujson-5.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7895f0d2d53bd6aea11743bd56e3cb82d729980636cd0ed9b89418bf66591702", size = 55434, upload-time = "2025-08-20T11:55:34.987Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/dba51a00eb30bd947791b173766cbed3492269c150a7771d2750000c965f/ujson-5.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12b5e7e22a1fe01058000d1b317d3b65cc3daf61bd2ea7a2b76721fe160fa74d", size = 53190, upload-time = "2025-08-20T11:55:36.384Z" }, + { url = "https://files.pythonhosted.org/packages/03/3c/fd11a224f73fbffa299fb9644e425f38b38b30231f7923a088dd513aabb4/ujson-5.11.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0180a480a7d099082501cad1fe85252e4d4bf926b40960fb3d9e87a3a6fbbc80", size = 57600, upload-time = "2025-08-20T11:55:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/55/b9/405103cae24899df688a3431c776e00528bd4799e7d68820e7ebcf824f92/ujson-5.11.0-cp312-cp312-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:fa79fdb47701942c2132a9dd2297a1a85941d966d8c87bfd9e29b0cf423f26cc", size = 59791, upload-time = "2025-08-20T11:55:38.877Z" }, + { url = "https://files.pythonhosted.org/packages/17/7b/2dcbc2bbfdbf68f2368fb21ab0f6735e872290bb604c75f6e06b81edcb3f/ujson-5.11.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8254e858437c00f17cb72e7a644fc42dad0ebb21ea981b71df6e84b1072aaa7c", size = 57356, upload-time = "2025-08-20T11:55:40.036Z" }, + { url = "https://files.pythonhosted.org/packages/d1/71/fea2ca18986a366c750767b694430d5ded6b20b6985fddca72f74af38a4c/ujson-5.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1aa8a2ab482f09f6c10fba37112af5f957689a79ea598399c85009f2f29898b5", size = 1036313, upload-time = "2025-08-20T11:55:41.408Z" }, + { url = "https://files.pythonhosted.org/packages/a3/bb/d4220bd7532eac6288d8115db51710fa2d7d271250797b0bfba9f1e755af/ujson-5.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a638425d3c6eed0318df663df44480f4a40dc87cc7c6da44d221418312f6413b", size = 1195782, upload-time = "2025-08-20T11:55:43.357Z" }, + { url = "https://files.pythonhosted.org/packages/80/47/226e540aa38878ce1194454385701d82df538ccb5ff8db2cf1641dde849a/ujson-5.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e3cff632c1d78023b15f7e3a81c3745cd3f94c044d1e8fa8efbd6b161997bbc", size = 1088817, upload-time = "2025-08-20T11:55:45.262Z" }, + { url = "https://files.pythonhosted.org/packages/7e/81/546042f0b23c9040d61d46ea5ca76f0cc5e0d399180ddfb2ae976ebff5b5/ujson-5.11.0-cp312-cp312-win32.whl", hash = "sha256:be6b0eaf92cae8cdee4d4c9e074bde43ef1c590ed5ba037ea26c9632fb479c88", size = 39757, upload-time = "2025-08-20T11:55:46.522Z" }, + { url = "https://files.pythonhosted.org/packages/44/1b/27c05dc8c9728f44875d74b5bfa948ce91f6c33349232619279f35c6e817/ujson-5.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:b7b136cc6abc7619124fd897ef75f8e63105298b5ca9bdf43ebd0e1fa0ee105f", size = 43859, upload-time = "2025-08-20T11:55:47.987Z" }, + { url = "https://files.pythonhosted.org/packages/22/2d/37b6557c97c3409c202c838aa9c960ca3896843b4295c4b7bb2bbd260664/ujson-5.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:6cd2df62f24c506a0ba322d5e4fe4466d47a9467b57e881ee15a31f7ecf68ff6", size = 38361, upload-time = "2025-08-20T11:55:49.122Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ec/2de9dd371d52c377abc05d2b725645326c4562fc87296a8907c7bcdf2db7/ujson-5.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:109f59885041b14ee9569bf0bb3f98579c3fa0652317b355669939e5fc5ede53", size = 55435, upload-time = "2025-08-20T11:55:50.243Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a4/f611f816eac3a581d8a4372f6967c3ed41eddbae4008d1d77f223f1a4e0a/ujson-5.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a31c6b8004438e8c20fc55ac1c0e07dad42941db24176fe9acf2815971f8e752", size = 53193, upload-time = "2025-08-20T11:55:51.373Z" }, + { url = "https://files.pythonhosted.org/packages/e9/c5/c161940967184de96f5cbbbcce45b562a4bf851d60f4c677704b1770136d/ujson-5.11.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78c684fb21255b9b90320ba7e199780f653e03f6c2528663768965f4126a5b50", size = 57603, upload-time = "2025-08-20T11:55:52.583Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d6/c7b2444238f5b2e2d0e3dab300b9ddc3606e4b1f0e4bed5a48157cebc792/ujson-5.11.0-cp313-cp313-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:4c9f5d6a27d035dd90a146f7761c2272cf7103de5127c9ab9c4cd39ea61e878a", size = 59794, upload-time = "2025-08-20T11:55:53.69Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a3/292551f936d3d02d9af148f53e1bc04306b00a7cf1fcbb86fa0d1c887242/ujson-5.11.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:837da4d27fed5fdc1b630bd18f519744b23a0b5ada1bbde1a36ba463f2900c03", size = 57363, upload-time = "2025-08-20T11:55:54.843Z" }, + { url = "https://files.pythonhosted.org/packages/90/a6/82cfa70448831b1a9e73f882225980b5c689bf539ec6400b31656a60ea46/ujson-5.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:787aff4a84da301b7f3bac09bc696e2e5670df829c6f8ecf39916b4e7e24e701", size = 1036311, upload-time = "2025-08-20T11:55:56.197Z" }, + { url = "https://files.pythonhosted.org/packages/84/5c/96e2266be50f21e9b27acaee8ca8f23ea0b85cb998c33d4f53147687839b/ujson-5.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6dd703c3e86dc6f7044c5ac0b3ae079ed96bf297974598116aa5fb7f655c3a60", size = 1195783, upload-time = "2025-08-20T11:55:58.081Z" }, + { url = "https://files.pythonhosted.org/packages/8d/20/78abe3d808cf3bb3e76f71fca46cd208317bf461c905d79f0d26b9df20f1/ujson-5.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3772e4fe6b0c1e025ba3c50841a0ca4786825a4894c8411bf8d3afe3a8061328", size = 1088822, upload-time = "2025-08-20T11:55:59.469Z" }, + { url = "https://files.pythonhosted.org/packages/d8/50/8856e24bec5e2fc7f775d867aeb7a3f137359356200ac44658f1f2c834b2/ujson-5.11.0-cp313-cp313-win32.whl", hash = "sha256:8fa2af7c1459204b7a42e98263b069bd535ea0cd978b4d6982f35af5a04a4241", size = 39753, upload-time = "2025-08-20T11:56:01.345Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/1baee0f4179a4d0f5ce086832147b6cc9b7731c24ca08e14a3fdb8d39c32/ujson-5.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:34032aeca4510a7c7102bd5933f59a37f63891f30a0706fb46487ab6f0edf8f0", size = 43866, upload-time = "2025-08-20T11:56:02.552Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8c/6d85ef5be82c6d66adced3ec5ef23353ed710a11f70b0b6a836878396334/ujson-5.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:ce076f2df2e1aa62b685086fbad67f2b1d3048369664b4cdccc50707325401f9", size = 38363, upload-time = "2025-08-20T11:56:03.688Z" }, + { url = "https://files.pythonhosted.org/packages/28/08/4518146f4984d112764b1dfa6fb7bad691c44a401adadaa5e23ccd930053/ujson-5.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65724738c73645db88f70ba1f2e6fb678f913281804d5da2fd02c8c5839af302", size = 55462, upload-time = "2025-08-20T11:56:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/29/37/2107b9a62168867a692654d8766b81bd2fd1e1ba13e2ec90555861e02b0c/ujson-5.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29113c003ca33ab71b1b480bde952fbab2a0b6b03a4ee4c3d71687cdcbd1a29d", size = 53246, upload-time = "2025-08-20T11:56:06.054Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f8/25583c70f83788edbe3ca62ce6c1b79eff465d78dec5eb2b2b56b3e98b33/ujson-5.11.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c44c703842024d796b4c78542a6fcd5c3cb948b9fc2a73ee65b9c86a22ee3638", size = 57631, upload-time = "2025-08-20T11:56:07.374Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ca/19b3a632933a09d696f10dc1b0dfa1d692e65ad507d12340116ce4f67967/ujson-5.11.0-cp314-cp314-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:e750c436fb90edf85585f5c62a35b35082502383840962c6983403d1bd96a02c", size = 59877, upload-time = "2025-08-20T11:56:08.534Z" }, + { url = "https://files.pythonhosted.org/packages/55/7a/4572af5324ad4b2bfdd2321e898a527050290147b4ea337a79a0e4e87ec7/ujson-5.11.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f278b31a7c52eb0947b2db55a5133fbc46b6f0ef49972cd1a80843b72e135aba", size = 57363, upload-time = "2025-08-20T11:56:09.758Z" }, + { url = "https://files.pythonhosted.org/packages/7b/71/a2b8c19cf4e1efe53cf439cdf7198ac60ae15471d2f1040b490c1f0f831f/ujson-5.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ab2cb8351d976e788669c8281465d44d4e94413718af497b4e7342d7b2f78018", size = 1036394, upload-time = "2025-08-20T11:56:11.168Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3e/7b98668cba3bb3735929c31b999b374ebc02c19dfa98dfebaeeb5c8597ca/ujson-5.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:090b4d11b380ae25453100b722d0609d5051ffe98f80ec52853ccf8249dfd840", size = 1195837, upload-time = "2025-08-20T11:56:12.6Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/8870f208c20b43571a5c409ebb2fe9b9dba5f494e9e60f9314ac01ea8f78/ujson-5.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:80017e870d882d5517d28995b62e4e518a894f932f1e242cbc802a2fd64d365c", size = 1088837, upload-time = "2025-08-20T11:56:14.15Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/c0e6607e37fa47929920a685a968c6b990a802dec65e9c5181e97845985d/ujson-5.11.0-cp314-cp314-win32.whl", hash = "sha256:1d663b96eb34c93392e9caae19c099ec4133ba21654b081956613327f0e973ac", size = 41022, upload-time = "2025-08-20T11:56:15.509Z" }, + { url = "https://files.pythonhosted.org/packages/4e/56/f4fe86b4c9000affd63e9219e59b222dc48b01c534533093e798bf617a7e/ujson-5.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:849e65b696f0d242833f1df4182096cedc50d414215d1371fca85c541fbff629", size = 45111, upload-time = "2025-08-20T11:56:16.597Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f3/669437f0280308db4783b12a6d88c00730b394327d8334cc7a32ef218e64/ujson-5.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:e73df8648c9470af2b6a6bf5250d4744ad2cf3d774dcf8c6e31f018bdd04d764", size = 39682, upload-time = "2025-08-20T11:56:17.763Z" }, + { url = "https://files.pythonhosted.org/packages/6e/cd/e9809b064a89fe5c4184649adeb13c1b98652db3f8518980b04227358574/ujson-5.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:de6e88f62796372fba1de973c11138f197d3e0e1d80bcb2b8aae1e826096d433", size = 55759, upload-time = "2025-08-20T11:56:18.882Z" }, + { url = "https://files.pythonhosted.org/packages/1b/be/ae26a6321179ebbb3a2e2685b9007c71bcda41ad7a77bbbe164005e956fc/ujson-5.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e56ef8066f11b80d620985ae36869a3ff7e4b74c3b6129182ec5d1df0255f3", size = 53634, upload-time = "2025-08-20T11:56:20.012Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e9/fb4a220ee6939db099f4cfeeae796ecb91e7584ad4d445d4ca7f994a9135/ujson-5.11.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a325fd2c3a056cf6c8e023f74a0c478dd282a93141356ae7f16d5309f5ff823", size = 58547, upload-time = "2025-08-20T11:56:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/bd/f8/fc4b952b8f5fea09ea3397a0bd0ad019e474b204cabcb947cead5d4d1ffc/ujson-5.11.0-cp314-cp314t-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:a0af6574fc1d9d53f4ff371f58c96673e6d988ed2b5bf666a6143c782fa007e9", size = 60489, upload-time = "2025-08-20T11:56:22.342Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e5/af5491dfda4f8b77e24cf3da68ee0d1552f99a13e5c622f4cef1380925c3/ujson-5.11.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10f29e71ecf4ecd93a6610bd8efa8e7b6467454a363c3d6416db65de883eb076", size = 58035, upload-time = "2025-08-20T11:56:23.92Z" }, + { url = "https://files.pythonhosted.org/packages/c4/09/0945349dd41f25cc8c38d78ace49f14c5052c5bbb7257d2f466fa7bdb533/ujson-5.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a0a9b76a89827a592656fe12e000cf4f12da9692f51a841a4a07aa4c7ecc41c", size = 1037212, upload-time = "2025-08-20T11:56:25.274Z" }, + { url = "https://files.pythonhosted.org/packages/49/44/8e04496acb3d5a1cbee3a54828d9652f67a37523efa3d3b18a347339680a/ujson-5.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b16930f6a0753cdc7d637b33b4e8f10d5e351e1fb83872ba6375f1e87be39746", size = 1196500, upload-time = "2025-08-20T11:56:27.517Z" }, + { url = "https://files.pythonhosted.org/packages/64/ae/4bc825860d679a0f208a19af2f39206dfd804ace2403330fdc3170334a2f/ujson-5.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:04c41afc195fd477a59db3a84d5b83a871bd648ef371cf8c6f43072d89144eef", size = 1089487, upload-time = "2025-08-20T11:56:29.07Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/74/cf/209d90506b7d6c5873f82c5a226d7aad1a1da153364e9ebf61eff0740c33/ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:86baf341d90b566d61a394869ce77188cc8668f76d7bb2c311d77a00f4bdf844", size = 56584, upload-time = "2025-08-20T11:56:52.89Z" }, + { url = "https://files.pythonhosted.org/packages/e9/97/bd939bb76943cb0e1d2b692d7e68629f51c711ef60425fa5bb6968037ecd/ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4598bf3965fc1a936bd84034312bcbe00ba87880ef1ee33e33c1e88f2c398b49", size = 51588, upload-time = "2025-08-20T11:56:54.054Z" }, + { 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.*'" }, +] +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 = [ + { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" }, +] + +[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 = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { 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]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { 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]] +name = "wcwidth" +version = "0.2.14" +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" } +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" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { 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" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/70/1469ef1d3542ae7c2c7b72bd5e3a4e6ee69d7978fa8a3af05a38eca5becf/werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67", size = 864754, upload-time = "2026-01-08T17:49:23.247Z" } +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 = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] From 961b2e844a23f3ffedc318a0c7c82dc9f5cb6fcd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:03:19 +0000 Subject: [PATCH 050/110] =?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 c1d24561c8..f7d8cb58fa 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -19,6 +19,7 @@ hide: ### Internal +* โฌ†๏ธ Migrate to uv. PR [#14676](https://github.com/fastapi/fastapi/pull/14676) by [@DoctorJohn](https://github.com/DoctorJohn). * ๐Ÿ”จ Add LLM translations tool fixer. PR [#14652](https://github.com/fastapi/fastapi/pull/14652) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐Ÿ‘ฅ Update FastAPI People - Sponsors. PR [#14626](https://github.com/fastapi/fastapi/pull/14626) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ฅ Update FastAPI GitHub topic repositories. PR [#14630](https://github.com/fastapi/fastapi/pull/14630) by [@tiangolo](https://github.com/tiangolo). From c75ae058e448063c2ace68c5c8ad0af99bee3e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sat, 10 Jan 2026 14:17:46 -0800 Subject: [PATCH 051/110] =?UTF-8?q?=F0=9F=94=A7=20Add=20pre-commit=20local?= =?UTF-8?q?=20script=20to=20fix=20language=20translations=20(#14683)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5da3cccf1e..d88b70b7b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -58,3 +58,9 @@ repos: entry: uv run ./scripts/docs.py ensure-non-translated files: ^docs/(?!en/).*|^scripts/docs\.py$ pass_filenames: false + + - id: fix-translations + language: unsupported + name: fix translations + entry: uv run ./scripts/translation_fixer.py fix-pages + files: ^docs/(?!en/).*/docs/.*\.md$ From 21d2c5cea089c9477a1d5dad26702068fa043e49 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:18:13 +0000 Subject: [PATCH 052/110] =?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 f7d8cb58fa..818a553621 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -19,6 +19,7 @@ hide: ### Internal +* ๐Ÿ”ง Add pre-commit local script to fix language translations. PR [#14683](https://github.com/fastapi/fastapi/pull/14683) by [@tiangolo](https://github.com/tiangolo). * โฌ†๏ธ Migrate to uv. PR [#14676](https://github.com/fastapi/fastapi/pull/14676) by [@DoctorJohn](https://github.com/DoctorJohn). * ๐Ÿ”จ Add LLM translations tool fixer. PR [#14652](https://github.com/fastapi/fastapi/pull/14652) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐Ÿ‘ฅ Update FastAPI People - Sponsors. PR [#14626](https://github.com/fastapi/fastapi/pull/14626) by [@tiangolo](https://github.com/tiangolo). From 7eac6e31695bd02d3607aa4416df50a02bc5445a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sat, 10 Jan 2026 14:43:44 -0800 Subject: [PATCH 053/110] =?UTF-8?q?=E2=9C=85=20Enable=20tests=20in=20CI=20?= =?UTF-8?q?for=20scripts=20(#14684)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/doc_parsing_utils.py | 20 +++++++++++-------- scripts/docs.py | 2 +- scripts/notify_translations.py | 2 +- scripts/test.sh | 2 +- .../tests/test_translation_fixer/conftest.py | 10 ++++++++++ .../test_code_blocks_lines_number_mismatch.py | 16 +++++++-------- .../test_code_blocks_mermaid.py | 8 ++++---- .../test_code_blocks_number_mismatch.py | 12 +++++++---- .../test_code_blocks_wrong_lang_code.py | 12 +++++------ .../test_number_mismatch.py | 12 +++++++---- .../test_complex_doc/test_compex_doc.py | 4 ++-- .../test_header_level_mismatch.py | 8 ++++---- .../test_header_number_mismatch.py | 12 +++++++---- .../test_html_links_number_mismatch.py | 12 +++++++---- .../test_mkd_links_number_mismatch.py | 12 +++++++---- 15 files changed, 89 insertions(+), 55 deletions(-) diff --git a/scripts/doc_parsing_utils.py b/scripts/doc_parsing_utils.py index 857d808aaa..79f2e9ec0a 100644 --- a/scripts/doc_parsing_utils.py +++ b/scripts/doc_parsing_utils.py @@ -1,5 +1,5 @@ import re -from typing import TypedDict +from typing import TypedDict, Union CODE_INCLUDE_RE = re.compile(r"^\{\*\s*(\S+)\s*(.*)\*\}$") CODE_INCLUDE_PLACEHOLDER = "<CODE_INCLUDE>" @@ -50,8 +50,8 @@ class MarkdownLinkInfo(TypedDict): line_no: int url: str text: str - title: str | None - attributes: str | None + title: Union[str, None] + attributes: Union[str, None] full_match: str @@ -285,7 +285,11 @@ def _add_lang_code_to_url(url: str, lang_code: str) -> str: def _construct_markdown_link( - url: str, text: str, title: str | None, attributes: str | None, lang_code: str + url: str, + text: str, + title: Union[str, None], + attributes: Union[str, None], + lang_code: str, ) -> str: """ Construct a markdown link, adjusting the URL for the given language code if needed. @@ -545,7 +549,7 @@ def extract_multiline_code_blocks(text: list[str]) -> list[MultilineCodeBlockInf return blocks -def _split_hash_comment(line: str) -> tuple[str, str | None]: +def _split_hash_comment(line: str) -> tuple[str, Union[str, None]]: match = HASH_COMMENT_RE.match(line) if match: code = match.group("code").rstrip() @@ -554,7 +558,7 @@ def _split_hash_comment(line: str) -> tuple[str, str | None]: return line.rstrip(), None -def _split_slashes_comment(line: str) -> tuple[str, str | None]: +def _split_slashes_comment(line: str) -> tuple[str, Union[str, None]]: match = SLASHES_COMMENT_RE.match(line) if match: code = match.group("code").rstrip() @@ -600,8 +604,8 @@ def replace_multiline_code_block( code_block: list[str] = [] for line_a, line_b in zip(block_a["content"], block_b["content"]): - line_a_comment: str | None = None - line_b_comment: str | None = None + line_a_comment: Union[str, None] = None + line_b_comment: Union[str, None] = None # Handle comments based on language if block_language in { diff --git a/scripts/docs.py b/scripts/docs.py index fbde1eca4f..84cf01c724 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -239,7 +239,7 @@ def generate_readme() -> None: Generate README.md content from main index.md """ readme_path = Path("README.md") - old_content = readme_path.read_text() + old_content = readme_path.read_text("utf-8") new_content = generate_readme_content() if new_content != old_content: print("README.md outdated from the latest index.md") diff --git a/scripts/notify_translations.py b/scripts/notify_translations.py index 2ca740a607..74cdf0dffe 100644 --- a/scripts/notify_translations.py +++ b/scripts/notify_translations.py @@ -316,7 +316,7 @@ def main() -> None: raise RuntimeError( f"No github event file available at: {settings.github_event_path}" ) - contents = settings.github_event_path.read_text() + contents = settings.github_event_path.read_text("utf-8") github_event = PartialGitHubEvent.model_validate_json(contents) logging.info(f"Using GitHub event: {github_event}") number = ( diff --git a/scripts/test.sh b/scripts/test.sh index 7d17add8fa..0bffcd1cdd 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -4,4 +4,4 @@ set -e set -x export PYTHONPATH=./docs_src -coverage run -m pytest tests ${@} +coverage run -m pytest tests scripts/tests/ ${@} diff --git a/scripts/tests/test_translation_fixer/conftest.py b/scripts/tests/test_translation_fixer/conftest.py index b2c745de11..006f519f46 100644 --- a/scripts/tests/test_translation_fixer/conftest.py +++ b/scripts/tests/test_translation_fixer/conftest.py @@ -1,9 +1,19 @@ import shutil +import sys from pathlib import Path import pytest from typer.testing import CliRunner +skip_on_windows = pytest.mark.skipif( + sys.platform == "win32", reason="Skipping on Windows" +) + + +def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: + for item in items: + item.add_marker(skip_on_windows) + @pytest.fixture(name="runner") def get_runner(): diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py index 906c8a560a..9cdbe8323a 100644 --- a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py +++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py @@ -22,10 +22,10 @@ def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path( - f"{data_path}/translated_doc_lines_number_gt.md" - ).read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_lines_number_gt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output @@ -46,10 +46,10 @@ def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): ) # assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path( - f"{data_path}/translated_doc_lines_number_lt.md" - ).read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_lines_number_lt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py index 75c589fb50..8b80c70f34 100644 --- a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py +++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py @@ -22,10 +22,10 @@ def test_translated(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 0, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") expected_content = Path( f"{data_path}/translated_doc_mermaid_translated.md" - ).read_text() + ).read_text("utf-8") assert fixed_content == expected_content # Translated doc remains unchanged assert ( @@ -50,10 +50,10 @@ def test_not_translated(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 0, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") expected_content = Path( f"{data_path}/translated_doc_mermaid_not_translated.md" - ).read_text() + ).read_text("utf-8") assert fixed_content == expected_content # Translated doc remains unchanged assert ("Skipping mermaid code block replacement") not in result.output diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py index b05dac900e..ad5767c1cb 100644 --- a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py +++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py @@ -22,8 +22,10 @@ def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output @@ -45,8 +47,10 @@ def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): ) # assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py index 6c2b18c89a..85d73e3b4a 100644 --- a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py +++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py @@ -22,10 +22,10 @@ def test_wrong_lang_code_1(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path( - f"{data_path}/translated_doc_wrong_lang_code.md" - ).read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_wrong_lang_code.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output @@ -46,10 +46,10 @@ def test_wrong_lang_code_2(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") expected_content = Path( f"{data_path}/translated_doc_wrong_lang_code_2.md" - ).read_text() + ).read_text("utf-8") assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output diff --git a/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py b/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py index 5e3eee51af..1020b890cf 100644 --- a/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py +++ b/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py @@ -22,8 +22,10 @@ def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1 - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output @@ -45,8 +47,10 @@ def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1 - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output diff --git a/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py b/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py index 86cc4d5eb4..cc7bcadda8 100644 --- a/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py +++ b/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py @@ -22,8 +22,8 @@ def test_fix(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 0, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = (data_path / "translated_doc_expected.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = (data_path / "translated_doc_expected.md").read_text("utf-8") assert fixed_content == expected_content assert "Fixing multiline code blocks in" in result.output diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py index 9fe2f7ba70..99d27db953 100644 --- a/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py +++ b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py @@ -22,10 +22,10 @@ def test_level_mismatch_1(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1 - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") expected_content = Path( f"{data_path}/translated_doc_level_mismatch_1.md" - ).read_text() + ).read_text("utf-8") assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output @@ -47,10 +47,10 @@ def test_level_mismatch_2(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1 - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") expected_content = Path( f"{data_path}/translated_doc_level_mismatch_2.md" - ).read_text() + ).read_text("utf-8") assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py index c0e78d0302..c4d0346171 100644 --- a/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py +++ b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py @@ -22,8 +22,10 @@ def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1 - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output @@ -45,8 +47,10 @@ def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1 - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output diff --git a/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py b/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py index 271e5d2058..11dcea1541 100644 --- a/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py +++ b/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py @@ -20,8 +20,10 @@ def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output @@ -43,8 +45,10 @@ def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): ) # assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py b/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py index 0f4952f35d..c72e012543 100644 --- a/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py +++ b/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py @@ -22,8 +22,10 @@ def test_gt(runner: CliRunner, root_dir: Path, copy_test_files): ) assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output @@ -45,8 +47,10 @@ def test_lt(runner: CliRunner, root_dir: Path, copy_test_files): ) # assert result.exit_code == 1, result.output - fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text() - expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text() + fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8") + expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text( + "utf-8" + ) assert fixed_content == expected_content # Translated doc remains unchanged assert "Error processing docs/lang/docs/doc.md" in result.output From 3d1f9268fc53fe4ca4dc55b9e2c1fe98e0650dc4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:44:05 +0000 Subject: [PATCH 054/110] =?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 818a553621..5090a9ea23 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -19,6 +19,7 @@ hide: ### Internal +* โœ… Enable tests in CI for scripts. PR [#14684](https://github.com/fastapi/fastapi/pull/14684) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add pre-commit local script to fix language translations. PR [#14683](https://github.com/fastapi/fastapi/pull/14683) by [@tiangolo](https://github.com/tiangolo). * โฌ†๏ธ Migrate to uv. PR [#14676](https://github.com/fastapi/fastapi/pull/14676) by [@DoctorJohn](https://github.com/DoctorJohn). * ๐Ÿ”จ Add LLM translations tool fixer. PR [#14652](https://github.com/fastapi/fastapi/pull/14652) by [@YuriiMotov](https://github.com/YuriiMotov). From cefd50702a5269351fadbf8110628356bd505449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sat, 10 Jan 2026 15:06:37 -0800 Subject: [PATCH 055/110] =?UTF-8?q?=F0=9F=90=9B=20Fix=20translation=20scri?= =?UTF-8?q?pt=20path=20(#14685)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/translate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/translate.py b/scripts/translate.py index ffecfde07b..c7e6c7ea19 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -25,7 +25,7 @@ non_translated_sections = ( "contributing.md", ) -general_prompt_path = Path(__file__).absolute().parent / "llm-general-prompt.md" +general_prompt_path = Path(__file__).absolute().parent / "general-llm-prompt.md" general_prompt = general_prompt_path.read_text(encoding="utf-8") app = typer.Typer() From 8183e748eef56b0c00c212b81fdd9034b186cd8e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 23:06:59 +0000 Subject: [PATCH 056/110] =?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 5090a9ea23..08bc2927cc 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -19,6 +19,7 @@ hide: ### Internal +* ๐Ÿ› Fix translation script path. PR [#14685](https://github.com/fastapi/fastapi/pull/14685) by [@tiangolo](https://github.com/tiangolo). * โœ… Enable tests in CI for scripts. PR [#14684](https://github.com/fastapi/fastapi/pull/14684) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add pre-commit local script to fix language translations. PR [#14683](https://github.com/fastapi/fastapi/pull/14683) by [@tiangolo](https://github.com/tiangolo). * โฌ†๏ธ Migrate to uv. PR [#14676](https://github.com/fastapi/fastapi/pull/14676) by [@DoctorJohn](https://github.com/DoctorJohn). From a2912ffa26ea49db08d9a02a4888ebe60b44e702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sat, 10 Jan 2026 15:41:20 -0800 Subject: [PATCH 057/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20es=20(update-outdated)=20(#14686)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- docs/es/docs/_llm-test.md | 46 +++++++-------- .../path-operation-advanced-configuration.md | 40 ++----------- docs/es/docs/advanced/settings.md | 56 ++----------------- ...migrate-from-pydantic-v1-to-pydantic-v2.md | 26 +++++---- .../docs/how-to/separate-openapi-schemas.md | 6 +- docs/es/docs/index.md | 26 +++++---- docs/es/docs/tutorial/bigger-applications.md | 28 +++++----- docs/es/docs/tutorial/body-updates.md | 22 +------- docs/es/docs/tutorial/body.md | 14 ++--- docs/es/docs/tutorial/extra-models.md | 39 +++++-------- .../tutorial/query-params-str-validations.md | 26 ++------- docs/es/docs/tutorial/response-model.md | 20 +------ docs/es/docs/tutorial/schema-extra-example.md | 38 +++---------- 13 files changed, 117 insertions(+), 270 deletions(-) diff --git a/docs/es/docs/_llm-test.md b/docs/es/docs/_llm-test.md index 2ab1d9314b..591b1008bd 100644 --- a/docs/es/docs/_llm-test.md +++ b/docs/es/docs/_llm-test.md @@ -1,17 +1,17 @@ # Archivo de prueba de LLM { #llm-test-file } -Este documento prueba si el <abbr title="Large Language Model โ€“ Modelo de lenguaje grande">LLM</abbr>, que traduce la documentaciรณn, entiende el `general_prompt` en `scripts/translate.py` y el prompt especรญfico del idioma en `docs/{language code}/llm-prompt.md`. El prompt especรญfico del idioma se agrega al final de `general_prompt`. +Este documento prueba si el <abbr title="Large Language Model - Modelo de lenguaje grande">LLM</abbr>, que traduce la documentaciรณn, entiende el `general_prompt` en `scripts/translate.py` y el prompt especรญfico del idioma en `docs/{language code}/llm-prompt.md`. El prompt especรญfico del idioma se agrega al final de `general_prompt`. Las pruebas aรฑadidas aquรญ serรกn vistas por todas las personas que diseรฑan prompts especรญficos del idioma. รšsalo de la siguiente manera: -* Ten un prompt especรญfico del idioma โ€“ `docs/{language code}/llm-prompt.md`. +* Ten un prompt especรญfico del idioma - `docs/{language code}/llm-prompt.md`. * Haz una traducciรณn fresca de este documento a tu idioma destino (mira, por ejemplo, el comando `translate-page` de `translate.py`). Esto crearรก la traducciรณn en `docs/{language code}/docs/_llm-test.md`. -* Comprueba si todo estรก bien en la traducciรณn. +* Revisa si las cosas estรกn bien en la traducciรณn. * Si es necesario, mejora tu prompt especรญfico del idioma, el prompt general, o el documento en inglรฉs. * Luego corrige manualmente los problemas restantes en la traducciรณn para que sea una buena traducciรณn. -* Vuelve a traducir, teniendo la buena traducciรณn en su lugar. El resultado ideal serรญa que el LLM ya no hiciera cambios a la traducciรณn. Eso significa que el prompt general y tu prompt especรญfico del idioma estรกn tan bien como pueden estar (a veces harรก algunos cambios aparentemente aleatorios; la razรณn es que <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">los LLMs no son algoritmos deterministas</a>). +* Vuelve a traducir, teniendo la buena traducciรณn en su lugar. El resultado ideal serรญa que el LLM ya no hiciera cambios a la traducciรณn. Eso significa que el prompt general y tu prompt especรญfico del idioma estรกn tan bien como pueden estar (A veces harรก algunos cambios aparentemente aleatorios; la razรณn es que <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">los LLMs no son algoritmos deterministas</a>). Las pruebas: @@ -23,7 +23,7 @@ Este es un fragmento de cรณdigo: `foo`. Y este es otro fragmento de cรณdigo: `ba //// -//// tab | Informaciรณn +//// tab | Info El contenido de los fragmentos de cรณdigo debe dejarse tal cual. @@ -45,7 +45,7 @@ El LLM probablemente traducirรก esto mal. Lo interesante es si mantiene la tradu //// -//// tab | Informaciรณn +//// tab | Info La persona que diseรฑa el prompt puede elegir si quiere convertir comillas neutras a comillas tipogrรกficas. Tambiรฉn estรก bien dejarlas como estรกn. @@ -67,7 +67,7 @@ Hardcore: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you //// -//// tab | Informaciรณn +//// tab | Info ... Sin embargo, las comillas dentro de fragmentos de cรณdigo deben quedarse tal cual. @@ -112,7 +112,7 @@ works(foo="bar") # Esto funciona ๐ŸŽ‰ //// -//// tab | Informaciรณn +//// tab | Info El cรณdigo en bloques de cรณdigo no debe modificarse, con la excepciรณn de los comentarios. @@ -154,7 +154,7 @@ Algo de texto //// -//// tab | Informaciรณn +//// tab | Info Las pestaรฑas y los bloques `Info`/`Note`/`Warning`/etc. deben tener la traducciรณn de su tรญtulo aรฑadida despuรฉs de una barra vertical (`|`). @@ -181,7 +181,7 @@ El texto del enlace debe traducirse, la direcciรณn del enlace debe apuntar a la //// -//// tab | Informaciรณn +//// tab | Info Los enlaces deben traducirse, pero su direcciรณn debe permanecer sin cambios. Una excepciรณn son los enlaces absolutos a pรกginas de la documentaciรณn de FastAPI. En ese caso deben enlazar a la traducciรณn. @@ -197,10 +197,10 @@ Aquรญ algunas cosas envueltas en elementos HTML "abbr" (algunas son inventadas): ### El abbr da una frase completa { #the-abbr-gives-a-full-phrase } -* <abbr title="Getting Things Done โ€“ Hacer las cosas">GTD</abbr> -* <abbr title="less than โ€“ menor que"><code>lt</code></abbr> -* <abbr title="XML Web Token โ€“ Token web XML">XWT</abbr> -* <abbr title="Parallel Server Gateway Interface โ€“ Interfaz de pasarela de servidor paralela">PSGI</abbr> +* <abbr title="Getting Things Done - Hacer las cosas">GTD</abbr> +* <abbr title="less than - menor que"><code>lt</code></abbr> +* <abbr title="XML Web Token - Token web XML">XWT</abbr> +* <abbr title="Parallel Server Gateway Interface - Interfaz de pasarela de servidor paralela">PSGI</abbr> ### El abbr da una explicaciรณn { #the-abbr-gives-an-explanation } @@ -209,12 +209,12 @@ Aquรญ algunas cosas envueltas en elementos HTML "abbr" (algunas son inventadas): ### El abbr da una frase completa y una explicaciรณn { #the-abbr-gives-a-full-phrase-and-an-explanation } -* <abbr title="Mozilla Developer Network โ€“ Red de Desarrolladores de Mozilla: documentaciรณn para desarrolladores, escrita por la gente de Firefox">MDN</abbr> -* <abbr title="Input/Output โ€“ Entrada/Salida: lectura o escritura de disco, comunicaciones de red.">I/O</abbr>. +* <abbr title="Mozilla Developer Network - Red de Desarrolladores de Mozilla: documentaciรณn para desarrolladores, escrita por la gente de Firefox">MDN</abbr> +* <abbr title="Input/Output: lectura o escritura de disco, comunicaciones de red.">I/O</abbr>. //// -//// tab | Informaciรณn +//// tab | Info Los atributos "title" de los elementos "abbr" se traducen siguiendo instrucciones especรญficas. @@ -242,7 +242,7 @@ Hola de nuevo. //// -//// tab | Informaciรณn +//// tab | Info La รบnica regla estricta para los encabezados es que el LLM deje la parte del hash dentro de llaves sin cambios, lo que asegura que los enlaces no se rompan. @@ -355,7 +355,7 @@ Para instrucciones especรญficas del idioma, mira p. ej. la secciรณn `### Heading * los headers * el header de autorizaciรณn * el header `Authorization` -* el header Forwarded +* el header forwarded * el sistema de inyecciรณn de dependencias * la dependencia @@ -368,7 +368,7 @@ Para instrucciones especรญficas del idioma, mira p. ej. la secciรณn `### Heading * paralelismo * multiprocesamiento -* la variable de entorno +* la env var * la variable de entorno * el `PATH` * la variable `PATH` @@ -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 -* la anotaciรณn de tipos +* las anotaciones de tipos * el worker del servidor * el worker de Uvicorn @@ -468,7 +468,7 @@ Para instrucciones especรญficas del idioma, mira p. ej. la secciรณn `### Heading * el รญtem * el paquete * el lifespan -* el bloqueo +* el lock * el middleware * la aplicaciรณn mรณvil * el mรณdulo @@ -494,7 +494,7 @@ Para instrucciones especรญficas del idioma, mira p. ej. la secciรณn `### Heading //// -//// tab | Informaciรณn +//// tab | Info Esta es una lista no completa y no normativa de tรฉrminos (mayormente) tรฉcnicos vistos en la documentaciรณn. Puede ayudar a la persona que diseรฑa el prompt a identificar para quรฉ tรฉrminos el LLM necesita una mano. Por ejemplo cuando sigue revirtiendo una buena traducciรณn a una traducciรณn subรณptima. O cuando tiene problemas conjugando/declinando un tรฉrmino en tu idioma. diff --git a/docs/es/docs/advanced/path-operation-advanced-configuration.md b/docs/es/docs/advanced/path-operation-advanced-configuration.md index 396159868b..ea58a300ad 100644 --- a/docs/es/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/es/docs/advanced/path-operation-advanced-configuration.md @@ -10,7 +10,7 @@ Si no eres un "experto" en OpenAPI, probablemente no necesites esto. Puedes establecer el `operationId` de OpenAPI para ser usado en tu *path operation* con el parรกmetro `operation_id`. -Tienes que asegurarte de que sea รบnico para cada operaciรณn. +Tendrรญas que asegurarte de que sea รบnico para cada operaciรณn. {* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} @@ -46,7 +46,7 @@ Para excluir una *path operation* del esquema OpenAPI generado (y por lo tanto, Puedes limitar las lรญneas usadas del docstring de una *path operation function* para OpenAPI. -Aรฑadir un `\f` (un carรกcter de separaciรณn de pรกgina escapado) hace que **FastAPI** trunque la salida usada para OpenAPI en este punto. +Aรฑadir un `\f` (un carรกcter "form feed" escapado) hace que **FastAPI** trunque la salida usada para OpenAPI en este punto. No aparecerรก en la documentaciรณn, pero otras herramientas (como Sphinx) podrรกn usar el resto. @@ -141,9 +141,9 @@ Podrรญas hacer eso con `openapi_extra`: {* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} -En este ejemplo, no declaramos ningรบn modelo Pydantic. De hecho, el cuerpo del request ni siquiera se <abbr title="convertido de algรบn formato plano, como bytes, a objetos de Python">parse</abbr> 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 se <abbr title="converted from some plain format, like bytes, into Python objects - convertido de algรบn formato plano, como bytes, a objetos de Python">parse</abbr> 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 cuerpo del request. +Sin embargo, podemos declarar el esquema esperado para el request body. ### Tipo de contenido personalizado de OpenAPI { #custom-openapi-content-type } @@ -153,48 +153,16 @@ 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: -//// tab | Pydantic v2 - {* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} - -//// - -/// info | Informaciรณn - -En la versiรณn 1 de Pydantic el mรฉtodo para obtener el JSON Schema para un modelo se llamaba `Item.schema()`, en la versiรณn 2 de Pydantic, el mรฉtodo se llama `Item.model_json_schema()`. - -/// - 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. Luego usamos el request directamente, y extraemos el cuerpo como `bytes`. Esto significa que FastAPI ni siquiera intentarรก parsear la carga รบtil del request como JSON. Y luego en nuestro cรณdigo, parseamos ese contenido YAML directamente, y nuevamente estamos usando el mismo modelo Pydantic para validar el contenido YAML: -//// tab | Pydantic v2 - {* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} - -//// - -/// info | Informaciรณn - -En la versiรณn 1 de Pydantic el mรฉtodo para parsear y validar un objeto era `Item.parse_obj()`, en la versiรณn 2 de Pydantic, el mรฉtodo se llama `Item.model_validate()`. - -/// - /// tip | Consejo Aquรญ reutilizamos el mismo modelo Pydantic. diff --git a/docs/es/docs/advanced/settings.md b/docs/es/docs/advanced/settings.md index 610bda5e24..a2d749103e 100644 --- a/docs/es/docs/advanced/settings.md +++ b/docs/es/docs/advanced/settings.md @@ -46,12 +46,6 @@ $ pip install "fastapi[all]" </div> -/// info | Informaciรณn - -En Pydantic v1 venรญa incluido con el paquete principal. Ahora se distribuye como este paquete independiente para que puedas elegir si instalarlo o no si no necesitas esa funcionalidad. - -/// - ### Crear el objeto `Settings` { #create-the-settings-object } Importa `BaseSettings` de Pydantic y crea una sub-clase, muy similar a un modelo de Pydantic. @@ -60,31 +54,15 @@ De la misma forma que con los modelos de Pydantic, declaras atributos de clase c Puedes usar todas las mismas funcionalidades de validaciรณn y herramientas que usas para los modelos de Pydantic, como diferentes tipos de datos y validaciones adicionales con `Field()`. -//// tab | Pydantic v2 - {* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} -//// - -//// tab | Pydantic v1 - -/// info | Informaciรณn - -En Pydantic v1 importarรญas `BaseSettings` directamente desde `pydantic` en lugar de desde `pydantic_settings`. - -/// - -{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *} - -//// - /// tip | Consejo Si quieres algo rรกpido para copiar y pegar, no uses este ejemplo, usa el รบltimo mรกs abajo. /// -Luego, cuando creas una instance de esa clase `Settings` (en este caso, en el objeto `settings`), Pydantic leerรก las variables de entorno de una manera indiferente a mayรบsculas y minรบsculas, por lo que una variable en mayรบsculas `APP_NAME` aรบn serรก leรญda para el atributo `app_name`. +Luego, cuando creas un instance de esa clase `Settings` (en este caso, en el objeto `settings`), Pydantic leerรก las variables de entorno de una manera indiferente a mayรบsculas y minรบsculas, por lo que una variable en mayรบsculas `APP_NAME` aรบn serรก leรญda para el atributo `app_name`. Luego convertirรก y validarรก los datos. Asรญ que, cuando uses ese objeto `settings`, tendrรกs datos de los tipos que declaraste (por ejemplo, `items_per_user` serรก un `int`). @@ -110,7 +88,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p /// tip | Consejo -Para establecer mรบltiples variables de entorno para un solo comando, simplemente sepรกralas con un espacio y ponlas todas antes del comando. +Para establecer mรบltiples env vars para un solo comando, simplemente sepรกralas con un espacio y ponlas todas antes del comando. /// @@ -150,7 +128,7 @@ Proveniente del ejemplo anterior, tu archivo `config.py` podrรญa verse como: {* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} -Nota que ahora no creamos una instance por defecto `settings = Settings()`. +Nota que ahora no creamos un instance por defecto `settings = Settings()`. ### El archivo principal de la app { #the-main-app-file } @@ -172,11 +150,11 @@ Y luego podemos requerirlo desde la *path operation function* como una dependenc ### Configuraciones y pruebas { #settings-and-testing } -Luego serรญa muy fรกcil proporcionar un objeto de configuraciones diferente durante las pruebas al sobrescribir una dependencia para `get_settings`: +Luego serรญa muy fรกcil proporcionar un objeto de configuraciones diferente durante las pruebas al crear una sobrescritura de dependencia para `get_settings`: {* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} -En la dependencia sobreescrita establecemos un nuevo valor para el `admin_email` al crear el nuevo objeto `Settings`, y luego devolvemos ese nuevo objeto. +En la sobrescritura de dependencia establecemos un nuevo valor para el `admin_email` al crear el nuevo objeto `Settings`, y luego devolvemos ese nuevo objeto. Luego podemos probar que se estรก usando. @@ -215,8 +193,6 @@ APP_NAME="ChimichangApp" Y luego actualizar tu `config.py` con: -//// tab | Pydantic v2 - {* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip | Consejo @@ -225,26 +201,6 @@ El atributo `model_config` se usa solo para configuraciรณn de Pydantic. Puedes l /// -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} - -/// tip | Consejo - -La clase `Config` se usa solo para configuraciรณn de Pydantic. Puedes leer mรกs en <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>. - -/// - -//// - -/// info | Informaciรณn - -En la versiรณn 1 de Pydantic la configuraciรณn se hacรญa en una clase interna `Config`, en la versiรณn 2 de Pydantic se hace en un atributo `model_config`. Este atributo toma un `dict`, y para obtener autocompletado y errores en lรญnea, puedes importar y usar `SettingsConfigDict` para definir ese `dict`. - -/// - Aquรญ definimos la configuraciรณn `env_file` dentro de tu clase Pydantic `Settings`, y establecemos el valor en el nombre del archivo con el archivo dotenv que queremos usar. ### Creando el `Settings` solo una vez con `lru_cache` { #creating-the-settings-only-once-with-lru-cache } @@ -331,7 +287,7 @@ participant execute as Ejecutar funciรณn end ``` -En el caso de nuestra dependencia `get_settings()`, la funciรณn ni siquiera toma argumentos, por lo que siempre devolverรก el mismo valor. +En el caso de nuestra dependencia `get_settings()`, la funciรณn ni siquiera toma argumentos, por lo que siempre devuelve el mismo valor. De esa manera, se comporta casi como si fuera solo una variable global. Pero como usa una funciรณn de dependencia, entonces podemos sobrescribirla fรกcilmente para las pruebas. diff --git a/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md index deda9f2e59..c862ace902 100644 --- a/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md +++ b/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -2,21 +2,23 @@ Si tienes una app de FastAPI antigua, podrรญas estar usando Pydantic versiรณn 1. -FastAPI ha tenido compatibilidad con Pydantic v1 o v2 desde la versiรณn 0.100.0. +FastAPI versiรณn 0.100.0 tenรญa compatibilidad con Pydantic v1 o v2. Usaba la que tuvieras instalada. -Si tenรญas instalado Pydantic v2, lo usaba. Si en cambio tenรญas Pydantic v1, usaba ese. +FastAPI versiรณn 0.119.0 introdujo compatibilidad parcial con Pydantic v1 desde dentro de Pydantic v2 (como `pydantic.v1`), para facilitar la migraciรณn a v2. -Pydantic v1 estรก deprecado y su soporte se eliminarรก en las prรณximas versiones de FastAPI, deberรญas migrar a Pydantic v2. Asรญ obtendrรกs las funcionalidades, mejoras y correcciones mรกs recientes. +FastAPI 0.126.0 eliminรณ la compatibilidad con Pydantic v1, aunque siguiรณ soportando `pydantic.v1` por un poquito mรกs de tiempo. /// warning | Advertencia -Ademรกs, el equipo de Pydantic dejรณ de dar soporte a Pydantic v1 para las versiones mรกs recientes de Python, comenzando con Python 3.14. +El equipo de Pydantic dejรณ de dar soporte a Pydantic v1 para las versiones mรกs recientes de Python, comenzando con **Python 3.14**. + +Esto incluye `pydantic.v1`, que ya no estรก soportado en Python 3.14 y superiores. Si quieres usar las funcionalidades mรกs recientes de Python, tendrรกs que asegurarte de usar Pydantic v2. /// -Si tienes una app de FastAPI antigua con Pydantic v1, aquรญ te muestro cรณmo migrarla a Pydantic v2 y las nuevas funcionalidades en FastAPI 0.119.0 para ayudarte con una migraciรณn gradual. +Si tienes una app de FastAPI antigua con Pydantic v1, aquรญ te muestro cรณmo migrarla a Pydantic v2, y las **funcionalidades en FastAPI 0.119.0** para ayudarte con una migraciรณn gradual. ## Guรญa oficial { #official-guide } @@ -44,9 +46,9 @@ Despuรฉs de esto, puedes ejecutar los tests y revisa si todo funciona. Si es as ## Pydantic v1 en v2 { #pydantic-v1-in-v2 } -Pydantic v2 incluye todo lo de Pydantic v1 como un submรณdulo `pydantic.v1`. +Pydantic v2 incluye todo lo de Pydantic v1 como un submรณdulo `pydantic.v1`. Pero esto ya no estรก soportado en versiones por encima de Python 3.13. -Esto significa que puedes instalar la versiรณn mรกs reciente de Pydantic v2 e importar y usar los componentes viejos de Pydantic v1 desde ese submรณdulo, como si tuvieras instalado el Pydantic v1 antiguo. +Esto significa que puedes instalar la versiรณn mรกs reciente de Pydantic v2 e importar y usar los componentes viejos de Pydantic v1 desde este submรณdulo, como si tuvieras instalado el Pydantic v1 antiguo. {* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} @@ -66,7 +68,7 @@ Ten en cuenta que, como el equipo de Pydantic ya no da soporte a Pydantic v1 en ### Pydantic v1 y v2 en la misma app { #pydantic-v1-and-v2-on-the-same-app } -No estรก soportado por Pydantic tener un modelo de Pydantic v2 con sus propios campos definidos como modelos de Pydantic v1 o viceversa. +**No estรก soportado** por Pydantic tener un modelo de Pydantic v2 con sus propios campos definidos como modelos de Pydantic v1 o viceversa. ```mermaid graph TB @@ -106,7 +108,7 @@ graph TB style V2Field fill:#f9fff3 ``` -En algunos casos, incluso es posible tener modelos de Pydantic v1 y v2 en la misma path operation de tu app de FastAPI: +En algunos casos, incluso es posible tener modelos de Pydantic v1 y v2 en la misma **path operation** de tu app de FastAPI: {* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} @@ -122,12 +124,12 @@ Si necesitas usar algunas de las herramientas especรญficas de FastAPI para parรก /// tip | Consejo -Primero prueba con `bump-pydantic`; si tus tests pasan y eso funciona, entonces terminaste con un solo comando. โœจ +Primero prueba con `bump-pydantic`, si tus tests pasan y eso funciona, entonces terminaste con un solo comando. โœจ /// -Si `bump-pydantic` no funciona para tu caso, puedes usar la compatibilidad de modelos Pydantic v1 y v2 en la misma app para hacer la migraciรณn a Pydantic v2 de forma gradual. +Si `bump-pydantic` no funciona para tu caso de uso, puedes usar la compatibilidad de modelos Pydantic v1 y v2 en la misma app para hacer la migraciรณn a Pydantic v2 de forma gradual. -Podrรญas primero actualizar Pydantic para usar la รบltima versiรณn 2 y cambiar los imports para usar `pydantic.v1` para todos tus modelos. +Podrรญas primero actualizar Pydantic para usar la รบltima versiรณn 2, y cambiar los imports para usar `pydantic.v1` para todos tus modelos. Luego puedes empezar a migrar tus modelos de Pydantic v1 a v2 por grupos, en pasos graduales. ๐Ÿšถ diff --git a/docs/es/docs/how-to/separate-openapi-schemas.md b/docs/es/docs/how-to/separate-openapi-schemas.md index 72396f8c95..903313599d 100644 --- a/docs/es/docs/how-to/separate-openapi-schemas.md +++ b/docs/es/docs/how-to/separate-openapi-schemas.md @@ -1,6 +1,6 @@ # Separaciรณn de Esquemas OpenAPI para Entrada y Salida o No { #separate-openapi-schemas-for-input-and-output-or-not } -Al usar **Pydantic v2**, el OpenAPI generado es un poco mรกs exacto y **correcto** que antes. ๐Ÿ˜Ž +Desde que se lanzรณ **Pydantic v2**, el OpenAPI generado es un poco mรกs exacto y **correcto** que antes. ๐Ÿ˜Ž De hecho, en algunos casos, incluso tendrรก **dos JSON Schemas** en OpenAPI para el mismo modelo Pydantic, para entrada y salida, dependiendo de si tienen **valores por defecto**. @@ -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 | Informaciรณn +/// info El soporte para `separate_input_output_schemas` fue agregado en FastAPI `0.102.0`. ๐Ÿค“ @@ -100,5 +100,3 @@ Y ahora habrรก un รบnico esquema para entrada y salida para el modelo, solo `Ite <div class="screenshot"> <img src="/img/tutorial/separate-openapi-schemas/image05.png"> </div> - -Este es el mismo comportamiento que en Pydantic v1. ๐Ÿค“ diff --git a/docs/es/docs/index.md b/docs/es/docs/index.md index c14369a115..14fa07e413 100644 --- a/docs/es/docs/index.md +++ b/docs/es/docs/index.md @@ -93,13 +93,13 @@ Las funcionalidades clave son: "_Estoy sรบper emocionado con **FastAPI**. ยกEs tan divertido!_" -<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">host del podcast Python Bytes</a></strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div> +<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> host del podcast</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div> --- "_Honestamente, lo que has construido parece sรบper sรณlido y pulido. En muchos aspectos, es lo que querรญa que **Hug** fuera; es realmente inspirador ver a alguien construir eso._" -<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">creador de Hug</a></strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div> +<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creador</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div> --- @@ -117,6 +117,12 @@ Las funcionalidades clave son: --- +## Mini documental de FastAPI { #fastapi-mini-documentary } + +Hay un <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">mini documental de FastAPI</a> lanzado a finales de 2025, puedes verlo online: + +<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a> + ## **Typer**, el FastAPI de las CLIs { #typer-the-fastapi-of-clis } <a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a> @@ -268,7 +274,7 @@ Verรกs la documentaciรณn interactiva automรกtica de la API (proporcionada por <a ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### Documentaciรณn de API Alternativa { #alternative-api-docs } +### Documentaciรณn alternativa de la API { #alternative-api-docs } Y ahora, ve a <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. @@ -276,7 +282,7 @@ Verรกs la documentaciรณn alternativa automรกtica (proporcionada por <a href="htt ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Actualizaciรณn del Ejemplo { #example-upgrade } +## Actualizaciรณn del ejemplo { #example-upgrade } Ahora modifica el archivo `main.py` para recibir un body desde un request `PUT`. @@ -314,7 +320,7 @@ def update_item(item_id: int, item: Item): El servidor `fastapi dev` deberรญa recargarse automรกticamente. -### Actualizaciรณn de la Documentaciรณn Interactiva de la API { #interactive-api-docs-upgrade } +### Actualizaciรณn de la documentaciรณn interactiva de la API { #interactive-api-docs-upgrade } Ahora ve a <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. @@ -330,7 +336,7 @@ Ahora ve a <a href="http://127.0.0.1:8000/docs" class="external-link" target="_b ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### Actualizaciรณn de la Documentaciรณn Alternativa de la API { #alternative-api-docs-upgrade } +### Actualizaciรณn de la documentaciรณn alternativa de la API { #alternative-api-docs-upgrade } Y ahora, ve a <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. @@ -393,13 +399,13 @@ Volviendo al ejemplo de cรณdigo anterior, **FastAPI**: * Validarรก que haya un `item_id` en el path para requests `GET` y `PUT`. * Validarรก que el `item_id` sea del tipo `int` para requests `GET` y `PUT`. * Si no lo es, el cliente verรก un error รบtil y claro. -* Comprobarรก si hay un parรกmetro de query opcional llamado `q` (como en `http://127.0.0.1:8000/items/foo?q=somequery`) para requests `GET`. +* Revisa si hay un parรกmetro de query opcional llamado `q` (como en `http://127.0.0.1:8000/items/foo?q=somequery`) para requests `GET`. * Como el parรกmetro `q` estรก declarado con `= None`, es opcional. * Sin el `None` serรญa requerido (como lo es el body en el caso con `PUT`). * Para requests `PUT` a `/items/{item_id}`, leerรก el body como JSON: - * Comprobarรก que tiene un atributo requerido `name` que debe ser un `str`. - * Comprobarรก que tiene un atributo requerido `price` que debe ser un `float`. - * Comprobarรก que tiene un atributo opcional `is_offer`, que debe ser un `bool`, si estรก presente. + * Revisa que tiene un atributo requerido `name` que debe ser un `str`. + * Revisa que tiene un atributo requerido `price` que debe ser un `float`. + * Revisa que tiene un atributo opcional `is_offer`, que debe ser un `bool`, si estรก presente. * Todo esto tambiรฉn funcionarรญa para objetos JSON profundamente anidados. * Convertirรก de y a JSON automรกticamente. * Documentarรก todo con OpenAPI, que puede ser usado por: diff --git a/docs/es/docs/tutorial/bigger-applications.md b/docs/es/docs/tutorial/bigger-applications.md index eacdb13c74..7938a12158 100644 --- a/docs/es/docs/tutorial/bigger-applications.md +++ b/docs/es/docs/tutorial/bigger-applications.md @@ -56,7 +56,7 @@ from app.routers import items La misma estructura de archivos con comentarios: -``` +```bash . โ”œโ”€โ”€ app # "app" es un paquete de Python โ”‚ย ย  โ”œโ”€โ”€ __init__.py # este archivo hace que "app" sea un "paquete de Python" @@ -185,7 +185,7 @@ El resultado final es que los paths de item son ahora: * Todos incluirรกn las `responses` predefinidas. * Todas estas *path operations* tendrรกn la lista de `dependencies` evaluadas/ejecutadas antes de ellas. * Si tambiรฉn declaras dependencias en una *path operation* especรญfica, **tambiรฉn se ejecutarรกn**. - * Las dependencias del router se ejecutan primero, luego las [dependencias en el decorador](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, y luego las dependencias de parรกmetros normales. + * Las dependencias del router se ejecutan primero, luego las [`dependencies` en el decorador](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, y luego las dependencias de parรกmetros normales. * Tambiรฉn puedes agregar [dependencias de `Security` con `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. /// tip | Consejo @@ -214,7 +214,7 @@ Asรญ que usamos un import relativo con `..` para las dependencias: /// tip | Consejo -Si sabes perfectamente cรณmo funcionan los imports, continรบa a la siguiente secciรณn. +Si sabes perfectamente cรณmo funcionan los imports, continรบa a la siguiente secciรณn abajo. /// @@ -271,7 +271,7 @@ eso significarรญa: Eso se referirรญa a algรบn paquete arriba de `app/`, con su propio archivo `__init__.py`, etc. Pero no tenemos eso. Asรญ que, eso lanzarรญa un error en nuestro ejemplo. ๐Ÿšจ -Pero ahora sabes cรณmo funciona, para que puedas usar imports relativos en tus propias aplicaciones sin importar cuรกn complejas sean. ๐Ÿค“ +Pero ahora sabes cรณmo funciona, para que puedas usar imports relativos en tus propias apps sin importar cuรกn complejas sean. ๐Ÿค“ ### Agregar algunos `tags`, `responses`, y `dependencies` personalizados { #add-some-custom-tags-responses-and-dependencies } @@ -283,7 +283,7 @@ Pero aรบn podemos agregar _mรกs_ `tags` que se aplicarรกn a una *path operation* /// tip | Consejo -Esta รบltima *path operation* tendrรก la combinaciรณn de tags: `["items", "custom"]`. +Esta รบltima path operation tendrรก la combinaciรณn de tags: `["items", "custom"]`. Y tambiรฉn tendrรก ambas responses en la documentaciรณn, una para `404` y otra para `403`. @@ -301,7 +301,7 @@ Y como la mayor parte de tu lรณgica ahora vivirรก en su propio mรณdulo especรญfi ### Importar `FastAPI` { #import-fastapi } -Importas y creas una clase `FastAPI` como de costumbre. +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`: @@ -398,7 +398,7 @@ Incluirรก todas las rutas de ese router como parte de ella. En realidad crearรก internamente una *path operation* para cada *path operation* que fue declarada en el `APIRouter`. -Asรญ, detrรกs de escena, funcionarรก como si todo fuera la misma รบnica aplicaciรณn. +Asรญ, detrรกs de escena, funcionarรก como si todo fuera la misma รบnica app. /// @@ -430,20 +430,20 @@ Podemos declarar todo eso sin tener que modificar el `APIRouter` original pasand 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. -El resultado es que, en nuestra aplicaciรณn, cada una de las *path operations* del mรณdulo `admin` tendrรก: +El resultado es que, en nuestra app, cada una de las *path operations* del mรณdulo `admin` tendrรก: * El prefix `/admin`. * El tag `admin`. * La dependencia `get_token_header`. * La response `418`. ๐Ÿต -Pero eso solo afectarรก a ese `APIRouter` en nuestra aplicaciรณn, no en ningรบn otro cรณdigo que lo utilice. +Pero eso solo afectarรก a ese `APIRouter` en nuestra app, no en ningรบn otro cรณdigo que lo utilice. Asรญ, por ejemplo, otros proyectos podrรญan usar el mismo `APIRouter` con un mรฉtodo de autenticaciรณn diferente. ### Incluir una *path operation* { #include-a-path-operation } -Tambiรฉn podemos agregar *path operations* directamente a la aplicaciรณn de `FastAPI`. +Tambiรฉn podemos agregar *path operations* directamente a la app de `FastAPI`. Aquรญ lo hacemos... solo para mostrar que podemos ๐Ÿคท: @@ -461,13 +461,13 @@ Los `APIRouter`s no estรกn "montados", no estรกn aislados del resto de la aplica Esto se debe a que queremos incluir sus *path operations* en el esquema de OpenAPI y las interfaces de usuario. -Como no podemos simplemente aislarlos y "montarlos" independientemente del resto, se "clonan" las *path operations* (se vuelven a crear), no se incluyen directamente. +Como no podemos simplemente aislarlos y "montarlos" independientemente del resto, las *path operations* se "clonan" (se vuelven a crear), no se incluyen directamente. /// ## Revisa la documentaciรณn automรกtica de la API { #check-the-automatic-api-docs } -Ahora, ejecuta tu aplicaciรณn: +Ahora, ejecuta tu app: <div class="termy"> @@ -481,7 +481,7 @@ $ fastapi dev app/main.py Y abre la documentaciรณn en <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. -Verรกs la documentaciรณn automรกtica de la API, incluyendo los paths de todos los submรณdulos, usando los paths correctos (y prefijos) y las tags correctas: +Verรกs la documentaciรณn automรกtica de la API, incluyendo los paths de todos los submรณdulos, usando los paths correctos (y prefijos) y los tags correctos: <img src="/img/tutorial/bigger-applications/image01.png"> @@ -501,4 +501,4 @@ De la misma manera que puedes incluir un `APIRouter` en una aplicaciรณn `FastAPI router.include_router(other_router) ``` -Asegรบrate de hacerlo antes de incluir `router` en la aplicaciรณn de `FastAPI`, para que las *path operations* de `other_router` tambiรฉn se incluyan. +Asegรบrate de hacerlo antes de incluir `router` en la app de `FastAPI`, para que las *path operations* de `other_router` tambiรฉn se incluyan. diff --git a/docs/es/docs/tutorial/body-updates.md b/docs/es/docs/tutorial/body-updates.md index 1f4713f350..e75e29b54b 100644 --- a/docs/es/docs/tutorial/body-updates.md +++ b/docs/es/docs/tutorial/body-updates.md @@ -1,4 +1,4 @@ -# Cuerpo - Actualizaciones { #body-updates } +# Body - Actualizaciones { #body-updates } ## Actualizaciรณn reemplazando con `PUT` { #update-replacing-with-put } @@ -50,14 +50,6 @@ Si quieres recibir actualizaciones parciales, es muy รบtil usar el parรกmetro `e Como `item.model_dump(exclude_unset=True)`. -/// info | Informaciรณn - -En Pydantic v1 el mรฉtodo se llamaba `.dict()`, fue deprecado (pero aรบn soportado) en Pydantic v2, y renombrado a `.model_dump()`. - -Los ejemplos aquรญ usan `.dict()` para compatibilidad con Pydantic v1, pero deberรญas usar `.model_dump()` si puedes usar Pydantic v2. - -/// - Eso generarรญa un `dict` solo con los datos que se establecieron al crear el modelo `item`, excluyendo los valores por defecto. Luego puedes usar esto para generar un `dict` solo con los datos que se establecieron (enviados en el request), omitiendo los valores por defecto: @@ -68,14 +60,6 @@ Luego puedes usar esto para generar un `dict` solo con los datos que se establec Ahora, puedes crear una copia del modelo existente usando `.model_copy()`, y pasar el parรกmetro `update` con un `dict` que contenga los datos a actualizar. -/// info | Informaciรณn - -En Pydantic v1 el mรฉtodo se llamaba `.copy()`, fue deprecado (pero aรบn soportado) en Pydantic v2, y renombrado a `.model_copy()`. - -Los ejemplos aquรญ usan `.copy()` para compatibilidad con Pydantic v1, pero deberรญas usar `.model_copy()` si puedes usar Pydantic v2. - -/// - Como `stored_item_model.model_copy(update=update_data)`: {* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} @@ -90,9 +74,9 @@ En resumen, para aplicar actualizaciones parciales deberรญas: * Generar un `dict` sin valores por defecto del modelo de entrada (usando `exclude_unset`). * De esta manera puedes actualizar solo los valores realmente establecidos por el usuario, en lugar de sobrescribir valores ya almacenados con valores por defecto en tu modelo. * Crear una copia del modelo almacenado, actualizando sus atributos con las actualizaciones parciales recibidas (usando el parรกmetro `update`). -* Convertir el modelo copiado en algo que pueda almacenarse en tu base de datos (por ejemplo, usando el `jsonable_encoder`). +* Convertir el modelo copiado en algo que pueda almacenarse en tu DB (por ejemplo, usando el `jsonable_encoder`). * Esto es comparable a usar el mรฉtodo `.model_dump()` del modelo de nuevo, pero asegura (y convierte) los valores a tipos de datos que pueden convertirse a JSON, por ejemplo, `datetime` a `str`. -* Guardar los datos en tu base de datos. +* Guardar los datos en tu DB. * Devolver el modelo actualizado. {* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} diff --git a/docs/es/docs/tutorial/body.md b/docs/es/docs/tutorial/body.md index 06a70dbc79..dde39f78c1 100644 --- a/docs/es/docs/tutorial/body.md +++ b/docs/es/docs/tutorial/body.md @@ -32,7 +32,8 @@ Usa tipos estรกndar de Python para todos los atributos: {* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} -Al igual que al declarar parรกmetros de query, cuando un atributo del modelo tiene un valor por defecto, no es obligatorio. De lo contrario, es obligatorio. Usa `None` para hacerlo opcional. + +Al igual que al declarar parรกmetros de query, cuando un atributo del modelo tiene un valor por defecto, no es obligatorio. De lo contrario, es obligatorio. Usa `None` para hacerlo solo opcional. Por ejemplo, el modelo anterior declara un โ€œ`object`โ€ JSON (o `dict` en Python) como: @@ -127,14 +128,6 @@ Dentro de la funciรณn, puedes acceder a todos los atributos del objeto modelo di {* ../../docs_src/body/tutorial002_py310.py *} -/// info | Informaciรณn - -En Pydantic v1 el mรฉtodo se llamaba `.dict()`, se marcรณ como obsoleto (pero sigue soportado) en Pydantic v2, y se renombrรณ a `.model_dump()`. - -Los ejemplos aquรญ usan `.dict()` por compatibilidad con Pydantic v1, pero deberรญas usar `.model_dump()` si puedes usar Pydantic v2. - -/// - ## Request body + parรกmetros de path { #request-body-path-parameters } Puedes declarar parรกmetros de path y request body al mismo tiempo. @@ -143,6 +136,7 @@ Puedes declarar parรกmetros de path y request body al mismo tiempo. {* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} + ## Request body + path + parรกmetros de query { #request-body-path-query-parameters } Tambiรฉn puedes declarar parรกmetros de **body**, **path** y **query**, todos al mismo tiempo. @@ -155,7 +149,7 @@ Los parรกmetros de la funciรณn se reconocerรกn de la siguiente manera: * Si el parรกmetro tambiรฉn se declara en el **path**, se utilizarรก como un parรกmetro de path. * Si el parรกmetro es de un **tipo singular** (como `int`, `float`, `str`, `bool`, etc.), se interpretarรก como un parรกmetro de **query**. -* Si el parรกmetro se declara como del tipo de un **modelo de Pydantic**, se interpretarรก como un **request body**. +* Si el parรกmetro se declara como del tipo de un **modelo de Pydantic**, se interpretarรก como un **body** de request. /// note | Nota diff --git a/docs/es/docs/tutorial/extra-models.md b/docs/es/docs/tutorial/extra-models.md index ed5bc80d93..d72c73e245 100644 --- a/docs/es/docs/tutorial/extra-models.md +++ b/docs/es/docs/tutorial/extra-models.md @@ -22,21 +22,13 @@ Aquรญ tienes una idea general de cรณmo podrรญan ser los modelos con sus campos d {* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} -/// info | Informaciรณn +### Acerca de `**user_in.model_dump()` { #about-user-in-model-dump } -En Pydantic v1 el mรฉtodo se llamaba `.dict()`, fue deprecado (pero aรบn soportado) en Pydantic v2, y renombrado a `.model_dump()`. - -Los ejemplos aquรญ usan `.dict()` para compatibilidad con Pydantic v1, pero deberรญas usar `.model_dump()` en su lugar si puedes usar Pydantic v2. - -/// - -### Acerca de `**user_in.dict()` { #about-user-in-dict } - -#### `.dict()` de Pydantic { #pydantics-dict } +#### `.model_dump()` de Pydantic { #pydantics-model-dump } `user_in` es un modelo Pydantic de la clase `UserIn`. -Los modelos Pydantic tienen un mรฉtodo `.dict()` que devuelve un `dict` con los datos del modelo. +Los modelos Pydantic tienen un mรฉtodo `.model_dump()` que devuelve un `dict` con los datos del modelo. Asรญ que, si creamos un objeto Pydantic `user_in` como: @@ -47,7 +39,7 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com y luego llamamos a: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() ``` ahora tenemos un `dict` con los datos en la variable `user_dict` (es un `dict` en lugar de un objeto modelo Pydantic). @@ -58,7 +50,7 @@ Y si llamamos a: print(user_dict) ``` -obtendremos un `dict` de Python con: +obtendrรญamos un `dict` de Python con: ```Python { @@ -103,20 +95,20 @@ UserInDB( #### Un modelo Pydantic a partir del contenido de otro { #a-pydantic-model-from-the-contents-of-another } -Como en el ejemplo anterior obtuvimos `user_dict` de `user_in.dict()`, este cรณdigo: +Como en el ejemplo anterior obtuvimos `user_dict` de `user_in.model_dump()`, este cรณdigo: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() UserInDB(**user_dict) ``` serรญa equivalente a: ```Python -UserInDB(**user_in.dict()) +UserInDB(**user_in.model_dump()) ``` -...porque `user_in.dict()` es un `dict`, y luego hacemos que Python lo "desempaquete" al pasarlo a `UserInDB` con el prefijo `**`. +...porque `user_in.model_dump()` es un `dict`, y luego hacemos que Python lo "desempaquete" al pasarlo a `UserInDB` con el prefijo `**`. Asรญ, obtenemos un modelo Pydantic a partir de los datos en otro modelo Pydantic. @@ -125,7 +117,7 @@ Asรญ, obtenemos un modelo Pydantic a partir de los datos en otro modelo Pydantic Y luego agregando el argumento de palabra clave adicional `hashed_password=hashed_password`, como en: ```Python -UserInDB(**user_in.dict(), hashed_password=hashed_password) +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) ``` ...termina siendo como: @@ -156,7 +148,7 @@ Y estos modelos estรกn compartiendo muchos de los datos y duplicando nombres y t Podrรญamos hacerlo mejor. -Podemos declarar un modelo `UserBase` que sirva como base para nuestros otros modelos. Y luego podemos hacer subclases de ese modelo que heredan sus atributos (anotaciones de tipos, validaciรณn, etc). +Podemos declarar un modelo `UserBase` que sirva como base para nuestros otros modelos. Y luego podemos hacer subclases de ese modelo que heredan sus atributos (declaraciones de tipos, validaciรณn, etc). Toda la conversiรณn de datos, validaciรณn, documentaciรณn, etc. seguirรก funcionando normalmente. @@ -180,20 +172,19 @@ Al definir una <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" {* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} - ### `Union` en Python 3.10 { #union-in-python-3-10 } En este ejemplo pasamos `Union[PlaneItem, CarItem]` como el valor del argumento `response_model`. -Porque lo estamos pasando como un **valor a un argumento** en lugar de ponerlo en **anotaciones de tipos**, tenemos que usar `Union` incluso en Python 3.10. +Porque lo estamos pasando como un **valor a un argumento** en lugar de ponerlo en una **anotaciรณn de tipos**, tenemos que usar `Union` incluso en Python 3.10. -Si estuviera en anotaciones de tipos podrรญamos haber usado la barra vertical, como: +Si estuviera en una anotaciรณn de tipos podrรญamos haber usado la barra vertical, como: ```Python some_variable: PlaneItem | CarItem ``` -Pero si ponemos eso en la asignaciรณn `response_model=PlaneItem | CarItem` obtendrรญamos un error, porque Python intentarรญa realizar una **operaciรณn invรกlida** entre `PlaneItem` y `CarItem` en lugar de interpretar eso como anotaciones de tipos. +Pero si ponemos eso en la asignaciรณn `response_model=PlaneItem | CarItem` obtendrรญamos un error, porque Python intentarรญa realizar una **operaciรณn invรกlida** entre `PlaneItem` y `CarItem` en lugar de interpretar eso como una anotaciรณn de tipos. ## Lista de modelos { #list-of-models } @@ -203,7 +194,6 @@ Para eso, usa el `typing.List` estรกndar de Python (o simplemente `list` en Pyth {* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} - ## Response con `dict` arbitrario { #response-with-arbitrary-dict } Tambiรฉn puedes declarar un response usando un `dict` arbitrario plano, declarando solo el tipo de las claves y valores, sin usar un modelo Pydantic. @@ -214,7 +204,6 @@ En este caso, puedes usar `typing.Dict` (o solo `dict` en Python 3.9 y posterior {* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} - ## Recapitulaciรณn { #recap } Usa mรบltiples modelos Pydantic y hereda libremente para cada caso. diff --git a/docs/es/docs/tutorial/query-params-str-validations.md b/docs/es/docs/tutorial/query-params-str-validations.md index e3f8cb5944..4af4782f83 100644 --- a/docs/es/docs/tutorial/query-params-str-validations.md +++ b/docs/es/docs/tutorial/query-params-str-validations.md @@ -109,7 +109,7 @@ FastAPI ahora: ## Alternativa (antigua): `Query` como valor por defecto { #alternative-old-query-as-the-default-value } -Versiones anteriores de FastAPI (antes de <abbr title="antes de 2023-03">0.95.0</abbr>) 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 <abbr title="before 2023-03 - antes de 2023-03">0.95.0</abbr>) 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 @@ -192,7 +192,7 @@ Tambiรฉn puedes agregar un parรกmetro `min_length`: ## Agregar expresiones regulares { #add-regular-expressions } -Puedes definir un <abbr title="Una expresiรณn regular, regex o regexp es una secuencia de caracteres que define un patrรณn de bรบsqueda para strings.">expresiรณn regular</abbr> `pattern` que el parรกmetro debe coincidir: +Puedes definir una <abbr title="Una expresiรณn regular, regex o regexp es una secuencia de caracteres que define un patrรณn de bรบsqueda para strings.">expresiรณn regular</abbr> `pattern` que el parรกmetro debe coincidir: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -206,20 +206,6 @@ Si te sientes perdido con todas estas ideas de **"expresiรณn regular"**, no te p Ahora sabes que cuando las necesites puedes usarlas en **FastAPI**. -### Pydantic v1 `regex` en lugar de `pattern` { #pydantic-v1-regex-instead-of-pattern } - -Antes de la versiรณn 2 de Pydantic y antes de FastAPI 0.100.0, el parรกmetro se llamaba `regex` en lugar de `pattern`, pero ahora estรก en desuso. - -Todavรญa podrรญas ver algo de cรณdigo que lo usa: - -//// tab | Pydantic v1 - -{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *} - -//// - -Pero que sepas que esto estรก deprecado y deberรญa actualizarse para usar el nuevo parรกmetro `pattern`. ๐Ÿค“ - ## Valores por defecto { #default-values } Puedes, por supuesto, usar valores por defecto diferentes de `None`. @@ -280,7 +266,7 @@ Entonces, con una URL como: http://localhost:8000/items/?q=foo&q=bar ``` -recibirรญas los mรบltiples valores del *query parameter* `q` (`foo` y `bar`) en una `list` de Python dentro de tu *path operation function*, en el *parรกmetro de funciรณn* `q`. +recibirรญas los mรบltiples valores de los *query parameters* `q` (`foo` y `bar`) en una `list` de Python dentro de tu *path operation function*, en el *parรกmetro de funciรณn* `q`. Entonces, el response a esa URL serรญa: @@ -386,7 +372,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 <abbr title="obsoleto, se recomienda no usarlo">deprecated</abbr>. +Tienes que dejarlo allรญ por un tiempo porque hay clientes usรกndolo, pero quieres que la documentaciรณn lo muestre claramente como <abbr title="obsolete, recommended not to use it - obsoleto, se recomienda no usarlo">deprecated</abbr>. Luego pasa el parรกmetro `deprecated=True` a `Query`: @@ -416,7 +402,7 @@ Pydantic tambiรฉn tiene <a href="https://docs.pydantic.dev/latest/concepts/valid /// -Por ejemplo, este validador personalizado comprueba que el ID del รญtem empiece con `isbn-` para un nรบmero de libro <abbr title="International Standard Book Number โ€“ Nรบmero Estรกndar Internacional de Libro">ISBN</abbr> o con `imdb-` para un ID de URL de pelรญcula de <abbr title="IMDB (Internet Movie Database) es un sitio web con informaciรณn sobre pelรญculas">IMDB</abbr>: +Por ejemplo, este validador personalizado comprueba que el ID del รญtem empiece con `isbn-` para un nรบmero de libro <abbr title="ISBN means International Standard Book Number - ISBN significa International Standard Book Number">ISBN</abbr> o con `imdb-` para un ID de URL de pelรญcula de <abbr title="IMDB (Internet Movie Database) is a website with information about movies - IMDB (Internet Movie Database) es un sitio web con informaciรณn sobre pelรญculas">IMDB</abbr>: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -450,7 +436,7 @@ 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 <abbr title="Algo que podemos iterar con un for, como una list, set, etc.">objeto iterable</abbr> con tuplas que contienen la clave y el valor para cada elemento del diccionario. +Con `data.items()` obtenemos un <abbr title="Algo que podemos iterar con un for loop, como una list, set, etc.">objeto iterable</abbr> 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())`. diff --git a/docs/es/docs/tutorial/response-model.md b/docs/es/docs/tutorial/response-model.md index 8f0ad56527..8cfe69e782 100644 --- a/docs/es/docs/tutorial/response-model.md +++ b/docs/es/docs/tutorial/response-model.md @@ -2,7 +2,7 @@ Puedes declarar el tipo utilizado para el response anotando el **tipo de retorno** de la *path operation function*. -Puedes utilizar **anotaciones de tipos** de la misma manera que lo harรญas para datos de entrada en **parรกmetros** de funciรณn, puedes utilizar modelos de Pydantic, list, diccionarios, valores escalares como enteros, booleanos, etc. +Puedes utilizar **anotaciones de tipos** de la misma manera que lo harรญas para datos de entrada en **parรกmetros** de funciรณn, puedes utilizar modelos de Pydantic, lists, diccionarios, valores escalares como enteros, booleanos, etc. {* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} @@ -27,7 +27,7 @@ Por ejemplo, podrรญas querer **devolver un diccionario** u objeto de base de dat Si aรฑadiste la anotaciรณn del tipo de retorno, las herramientas y editores se quejarรญan con un error (correcto) diciรฉndote que tu funciรณn estรก devolviendo un tipo (por ejemplo, un dict) que es diferente de lo que declaraste (por ejemplo, un modelo de Pydantic). -En esos casos, puedes usar el parรกmetro del decorador de path operation `response_model` en lugar del tipo de retorno. +En esos casos, puedes usar el parรกmetro del *decorador de path operation* `response_model` en lugar del tipo de retorno. Puedes usar el parรกmetro `response_model` en cualquiera de las *path operations*: @@ -153,7 +153,7 @@ Primero vamos a ver cรณmo los editores, mypy y otras herramientas verรญan esto. `BaseUser` tiene los campos base. Luego `UserIn` hereda de `BaseUser` y aรฑade el campo `password`, por lo que incluirรก todos los campos de ambos modelos. -Anotamos el tipo de retorno de la funciรณn como `BaseUser`, pero en realidad estamos devolviendo un instance de `UserIn`. +Anotamos el tipo de retorno de la funciรณn como `BaseUser`, pero en realidad estamos devolviendo un `UserIn` instance. El editor, mypy y otras herramientas no se quejarรกn de esto porque, en tรฉrminos de tipificaciรณn, `UserIn` es una subclase de `BaseUser`, lo que significa que es un tipo *vรกlido* cuando se espera algo que es un `BaseUser`. @@ -252,20 +252,6 @@ Entonces, si envรญas un request a esa *path operation* para el รญtem con ID `foo /// info | Informaciรณn -En Pydantic v1 el mรฉtodo se llamaba `.dict()`, fue deprecado (pero aรบn soportado) en Pydantic v2, y renombrado a `.model_dump()`. - -Los ejemplos aquรญ usan `.dict()` para compatibilidad con Pydantic v1, pero deberรญas usar `.model_dump()` en su lugar si puedes usar Pydantic v2. - -/// - -/// info | Informaciรณn - -FastAPI usa el mรฉtodo `.dict()` del modelo de Pydantic con <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">su parรกmetro `exclude_unset`</a> para lograr esto. - -/// - -/// info | Informaciรณn - Tambiรฉn puedes usar: * `response_model_exclude_defaults=True` diff --git a/docs/es/docs/tutorial/schema-extra-example.md b/docs/es/docs/tutorial/schema-extra-example.md index 686ea1a234..396a2a6bf5 100644 --- a/docs/es/docs/tutorial/schema-extra-example.md +++ b/docs/es/docs/tutorial/schema-extra-example.md @@ -8,35 +8,13 @@ Aquรญ tienes varias formas de hacerlo. Puedes declarar `examples` para un modelo de Pydantic que se aรฑadirรก al JSON Schema generado. -//// tab | Pydantic v2 - {* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} -//// +Esa informaciรณn extra se aรฑadirรก tal cual al **JSON Schema** resultante para ese modelo, y se usarรก en la documentaciรณn de la API. -//// tab | Pydantic v1 +Puedes usar el atributo `model_config` que toma un `dict` como se describe en <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">la documentaciรณn de Pydantic: Configuraciรณn</a>. -{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *} - -//// - -Esa informaciรณn extra se aรฑadirรก tal cual al **JSON Schema** generado para ese modelo, y se usarรก en la documentaciรณn de la API. - -//// tab | Pydantic v2 - -En Pydantic versiรณn 2, usarรญas el atributo `model_config`, que toma un `dict` como se describe en <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">la documentaciรณn de Pydantic: Configuraciรณn</a>. - -Puedes establecer `"json_schema_extra"` con un `dict` que contenga cualquier dato adicional que desees que aparezca en el JSON Schema generado, incluyendo `examples`. - -//// - -//// tab | Pydantic v1 - -En Pydantic versiรณn 1, usarรญas una clase interna `Config` y `schema_extra`, como se describe en <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">la documentaciรณn de Pydantic: Personalizaciรณn de Esquema</a>. - -Puedes establecer `schema_extra` con un `dict` que contenga cualquier dato adicional que desees que aparezca en el JSON Schema generado, incluyendo `examples`. - -//// +Puedes establecer `"json_schema_extra"` con un `dict` que contenga cualquier dato adicional que te gustarรญa que aparezca en el JSON Schema generado, incluyendo `examples`. /// tip | Consejo @@ -50,7 +28,7 @@ Por ejemplo, podrรญas usarlo para aรฑadir metadatos para una interfaz de usuario OpenAPI 3.1.0 (usado desde FastAPI 0.99.0) aรฑadiรณ soporte para `examples`, que es parte del estรกndar de **JSON Schema**. -Antes de eso, solo soportaba la palabra clave `example` con un solo ejemplo. Eso aรบn es soportado por OpenAPI 3.1.0, pero estรก obsoleto y no es parte del estรกndar de JSON Schema. Asรญ que se recomienda migrar de `example` a `examples`. ๐Ÿค“ +Antes de eso, solo soportaba la palabra clave `example` con un solo ejemplo. Eso aรบn es soportado por OpenAPI 3.1.0, pero estรก obsoleto y no es parte del estรกndar de JSON Schema. Asรญ que se te anima a migrar `example` a `examples`. ๐Ÿค“ Puedes leer mรกs al final de esta pรกgina. @@ -94,7 +72,7 @@ Por supuesto, tambiรฉn puedes pasar mรบltiples `examples`: {* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} -Cuando haces esto, los ejemplos serรกn parte del **JSON Schema** interno para esos datos de body. +Cuando haces esto, los ejemplos serรกn parte del **JSON Schema** interno para esos datos del body. Sin embargo, al <abbr title="2023-08-26">momento de escribir esto</abbr>, 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. @@ -203,17 +181,17 @@ Debido a eso, las versiones de FastAPI anteriores a 0.99.0 todavรญa usaban versi ### `examples` de Pydantic y FastAPI { #pydantic-and-fastapi-examples } -Cuando aรฑades `examples` dentro de un modelo de Pydantic, usando `schema_extra` o `Field(examples=["algo"])`, ese ejemplo se aรฑade al **JSON Schema** para ese modelo de Pydantic. +Cuando aรฑades `examples` dentro de un modelo de Pydantic, usando `schema_extra` o `Field(examples=["something"])`, ese ejemplo se aรฑade al **JSON Schema** para ese modelo de Pydantic. Y ese **JSON Schema** del modelo de Pydantic se incluye en el **OpenAPI** de tu API, y luego se usa en la interfaz de documentaciรณn. -En las versiones de FastAPI antes de 0.99.0 (0.99.0 y superior usan el nuevo OpenAPI 3.1.0) cuando usabas `example` o `examples` con cualquiera de las otras utilidades (`Query()`, `Body()`, etc.) esos ejemplos no se aรฑadรญan al JSON Schema que describe esos datos (ni siquiera a la propia versiรณn de JSON Schema de OpenAPI), se aรฑadรญan directamente a la declaraciรณn de la *path operation* en OpenAPI (fuera de las partes de OpenAPI que usan JSON Schema). +En las versiones de FastAPI antes de 0.99.0 (0.99.0 y superiores usan el nuevo OpenAPI 3.1.0) cuando usabas `example` o `examples` con cualquiera de las otras utilidades (`Query()`, `Body()`, etc.) esos ejemplos no se aรฑadรญan al JSON Schema que describe esos datos (ni siquiera a la propia versiรณn de JSON Schema de OpenAPI), se aรฑadรญan directamente a la declaraciรณn de la *path operation* en OpenAPI (fuera de las partes de OpenAPI que usan JSON Schema). Pero ahora que FastAPI 0.99.0 y superiores usa OpenAPI 3.1.0, que usa JSON Schema 2020-12, y Swagger UI 5.0.0 y superiores, todo es mรกs consistente y los ejemplos se incluyen en JSON Schema. ### Swagger UI y `examples` especรญficos de OpenAPI { #swagger-ui-and-openapi-specific-examples } -Ahora, como Swagger UI no soportaba mรบltiples ejemplos de JSON Schema (a fecha de 2023-08-26), los usuarios no tenรญan una forma de mostrar mรบltiples ejemplos en los documentos. +Ahora, como Swagger UI no soportaba mรบltiples ejemplos de JSON Schema (a fecha de 2023-08-26), los usuarios no tenรญan una forma de mostrar mรบltiples ejemplos en la documentaciรณn. Para resolver eso, FastAPI `0.103.0` **aรฑadiรณ soporte** para declarar el mismo viejo campo **especรญfico de OpenAPI** `examples` con el nuevo parรกmetro `openapi_examples`. ๐Ÿค“ From f03a1502a0a72d466cf4b08c900b886bdda146c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 23:41:44 +0000 Subject: [PATCH 058/110] =?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 08bc2927cc..5498039f9f 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-outdated). PR [#14686](https://github.com/fastapi/fastapi/pull/14686) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add LLM prompt file for Turkish, generated from the existing translations. PR [#14547](https://github.com/fastapi/fastapi/pull/14547) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add LLM prompt file for Traditional Chinese, generated from the existing translations. PR [#14550](https://github.com/fastapi/fastapi/pull/14550) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add LLM prompt file for Simplified Chinese, generated from the existing translations. PR [#14549](https://github.com/fastapi/fastapi/pull/14549) by [@tiangolo](https://github.com/tiangolo). From 49653aa295eedd1f0deeae6d61f4324c70d74e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sat, 10 Jan 2026 16:03:50 -0800 Subject: [PATCH 059/110] =?UTF-8?q?=F0=9F=94=A8=20Refactor=20translation?= =?UTF-8?q?=20script=20to=20allow=20committing=20in=20place=20(#14687)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/translate.yml | 6 ++++ scripts/translate.py | 52 +++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index e6edc5aaba..734a3782fc 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -30,6 +30,11 @@ on: type: string required: false default: "" + commit_in_place: + description: Whether to commit changes directly instead of making a PR + type: boolean + required: false + default: false jobs: langs: @@ -109,3 +114,4 @@ jobs: LANGUAGE: ${{ matrix.lang }} EN_PATH: ${{ github.event.inputs.en_path }} COMMAND: ${{ matrix.command }} + COMMIT_IN_PLACE: ${{ github.event.inputs.commit_in_place }} diff --git a/scripts/translate.py b/scripts/translate.py index c7e6c7ea19..eba4ad6a2f 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -360,6 +360,9 @@ def make_pr( command: Annotated[str | None, typer.Option(envvar="COMMAND")] = None, github_token: Annotated[str, typer.Option(envvar="GITHUB_TOKEN")], github_repository: Annotated[str, typer.Option(envvar="GITHUB_REPOSITORY")], + commit_in_place: Annotated[ + bool, typer.Option(envvar="COMMIT_IN_PLACE", show_default=True) + ] = False, ) -> None: print("Setting up GitHub Actions git user") repo = git.Repo(Path(__file__).absolute().parent.parent) @@ -371,14 +374,22 @@ def make_pr( ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], check=True, ) - branch_name = "translate" - if language: - branch_name += f"-{language}" - if command: - branch_name += f"-{command}" - branch_name += f"-{secrets.token_hex(4)}" - print(f"Creating a new branch {branch_name}") - subprocess.run(["git", "checkout", "-b", branch_name], check=True) + current_branch = repo.active_branch.name + if current_branch == "master" and commit_in_place: + print("Can't commit directly to master") + raise typer.Exit(code=1) + + if not commit_in_place: + branch_name = "translate" + if language: + branch_name += f"-{language}" + if command: + branch_name += f"-{command}" + branch_name += f"-{secrets.token_hex(4)}" + print(f"Creating a new branch {branch_name}") + subprocess.run(["git", "checkout", "-b", branch_name], check=True) + else: + print(f"Committing in place on branch {current_branch}") print("Adding updated files") git_path = Path("docs") subprocess.run(["git", "add", str(git_path)], check=True) @@ -391,17 +402,20 @@ def make_pr( subprocess.run(["git", "commit", "-m", message], check=True) print("Pushing branch") subprocess.run(["git", "push", "origin", branch_name], check=True) - print("Creating PR") - g = Github(github_token) - gh_repo = g.get_repo(github_repository) - body = ( - message - + "\n\nThis PR was created automatically using LLMs." - + f"\n\nIt uses the prompt file https://github.com/fastapi/fastapi/blob/master/docs/{language}/llm-prompt.md." - + "\n\nIn most cases, it's better to make PRs updating that file so that the LLM can do a better job generating the translations than suggesting changes in this PR." - ) - pr = gh_repo.create_pull(title=message, body=body, base="master", head=branch_name) - print(f"Created PR: {pr.number}") + if not commit_in_place: + print("Creating PR") + g = Github(github_token) + gh_repo = g.get_repo(github_repository) + body = ( + message + + "\n\nThis PR was created automatically using LLMs." + + f"\n\nIt uses the prompt file https://github.com/fastapi/fastapi/blob/master/docs/{language}/llm-prompt.md." + + "\n\nIn most cases, it's better to make PRs updating that file so that the LLM can do a better job generating the translations than suggesting changes in this PR." + ) + pr = gh_repo.create_pull( + title=message, body=body, base="master", head=branch_name + ) + print(f"Created PR: {pr.number}") print("Finished") From 154ce03ff0560fe43fa4f5c9715fcd6b82dbb8cd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:04:10 +0000 Subject: [PATCH 060/110] =?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 5498039f9f..a25c94b7fa 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -20,6 +20,7 @@ hide: ### Internal +* ๐Ÿ”จ Refactor translation script to allow committing in place. PR [#14687](https://github.com/fastapi/fastapi/pull/14687) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ› Fix translation script path. PR [#14685](https://github.com/fastapi/fastapi/pull/14685) by [@tiangolo](https://github.com/tiangolo). * โœ… Enable tests in CI for scripts. PR [#14684](https://github.com/fastapi/fastapi/pull/14684) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add pre-commit local script to fix language translations. PR [#14683](https://github.com/fastapi/fastapi/pull/14683) by [@tiangolo](https://github.com/tiangolo). From 6f977366a4154b762f257460b028f44e9686e5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sat, 10 Jan 2026 16:15:06 -0800 Subject: [PATCH 061/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20uk=20(update-outdated)=20(#14587)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com> --- docs/en/mkdocs.yml | 2 + docs/uk/docs/index.md | 378 +++++++++------ docs/uk/docs/learn/index.md | 4 +- docs/uk/docs/python-types.md | 433 +++++++++--------- docs/uk/docs/tutorial/background-tasks.md | 65 ++- docs/uk/docs/tutorial/body-fields.md | 45 +- docs/uk/docs/tutorial/body-multiple-params.md | 75 +-- docs/uk/docs/tutorial/body-nested-models.md | 94 ++-- docs/uk/docs/tutorial/body-updates.md | 50 +- docs/uk/docs/tutorial/body.md | 58 +-- docs/uk/docs/tutorial/cookie-param-models.md | 46 +- docs/uk/docs/tutorial/cookie-params.md | 27 +- docs/uk/docs/tutorial/cors.md | 37 +- docs/uk/docs/tutorial/debugging.md | 57 +-- docs/uk/docs/tutorial/encoder.md | 20 +- docs/uk/docs/tutorial/extra-data-types.md | 28 +- docs/uk/docs/tutorial/first-steps.md | 302 +++++++----- docs/uk/docs/tutorial/handling-errors.md | 153 +++---- docs/uk/docs/tutorial/header-param-models.md | 34 +- docs/uk/docs/tutorial/header-params.md | 48 +- docs/uk/docs/tutorial/index.md | 78 ++-- docs/uk/docs/tutorial/metadata.md | 64 +-- docs/uk/docs/tutorial/middleware.md | 84 ++-- .../path-params-numeric-validations.md | 97 ++-- docs/uk/docs/tutorial/path-params.md | 179 ++++---- docs/uk/docs/tutorial/query-param-models.md | 14 +- .../tutorial/query-params-str-validations.md | 219 ++++----- docs/uk/docs/tutorial/query-params.md | 85 ++-- docs/uk/docs/tutorial/request-files.md | 109 ++--- docs/uk/docs/tutorial/request-form-models.md | 34 +- .../docs/tutorial/request-forms-and-files.md | 18 +- docs/uk/docs/tutorial/request-forms.md | 24 +- docs/uk/docs/tutorial/response-model.md | 211 ++++----- docs/uk/docs/tutorial/response-status-code.md | 77 ++-- docs/uk/docs/tutorial/schema-extra-example.md | 130 +++--- docs/uk/docs/tutorial/security/index.md | 96 ++-- docs/uk/docs/tutorial/static-files.md | 24 +- docs/uk/docs/tutorial/testing.md | 123 ++--- scripts/docs.py | 1 + 39 files changed, 1833 insertions(+), 1790 deletions(-) diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 66094c81e4..84255f0f80 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -321,6 +321,8 @@ extra: name: pt - portuguรชs - link: /ru/ name: ru - ั€ัƒััะบะธะน ัะทั‹ะบ + - link: /uk/ + name: uk - ัƒะบั€ะฐั—ะฝััŒะบะฐ ะผะพะฒะฐ extra_css: - css/termynal.css - css/custom.css diff --git a/docs/uk/docs/index.md b/docs/uk/docs/index.md index 0811a4c7bb..526409c5cf 100644 --- a/docs/uk/docs/index.md +++ b/docs/uk/docs/index.md @@ -1,8 +1,14 @@ +# FastAPI { #fastapi } + +<style> +.md-content .md-typeset h1 { display: none; } +</style> + <p align="center"> - <a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a> + <a href="https://fastapi.tiangolo.com/uk"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a> </p> <p align="center"> - <em>ะ“ะพั‚ะพะฒะธะน ะดะพ ะฟั€ะพะดะฐะบัˆะธะฝัƒ, ะฒะธัะพะบะพะฟั€ะพะดัƒะบั‚ะธะฒะฝะธะน, ะฟั€ะพัั‚ะธะน ัƒ ะฒะธะฒั‡ะตะฝะฝั– ั‚ะฐ ัˆะฒะธะดะบะธะน ะดะปั ะฝะฐะฟะธัะฐะฝะฝั ะบะพะดัƒ ั„ั€ะตะนะผะฒะพั€ะบ</em> + <em>ะคั€ะตะนะผะฒะพั€ะบ FastAPI: ะฒะธัะพะบะฐ ะฟั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ, ะปะตะณะบะพ ะฒะธะฒั‡ะฐั‚ะธ, ัˆะฒะธะดะบะพ ะฟะธัะฐั‚ะธ ะบะพะด, ะณะพั‚ะพะฒะธะน ะดะพ ะฟั€ะพะดะฐะบัˆะธะฝัƒ</em> </p> <p align="center"> <a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank"> @@ -21,46 +27,51 @@ --- -**ะ”ะพะบัƒะผะตะฝั‚ะฐั†ั–ั**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a> +**ะ”ะพะบัƒะผะตะฝั‚ะฐั†ั–ั**: <a href="https://fastapi.tiangolo.com/uk" target="_blank">https://fastapi.tiangolo.com</a> -**ะŸั€ะพะณั€ะฐะผะฝะธะน ะบะพะด**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a> +**ะ’ะธั…ั–ะดะฝะธะน ะบะพะด**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a> --- -FastAPI - ั†ะต ััƒั‡ะฐัะฝะธะน, ัˆะฒะธะดะบะธะน (ะฒะธัะพะบะพะฟั€ะพะดัƒะบั‚ะธะฒะฝะธะน), ะฒะตะฑั„ั€ะตะนะผะฒะพั€ะบ ะดะปั ัั‚ะฒะพั€ะตะฝะฝั API ะทะฐ ะดะพะฟะพะผะพะณะพัŽ Python,ะฒ ะพัะฝะพะฒั– ัะบะพะณะพ ะปะตะถะธั‚ัŒ ัั‚ะฐะฝะดะฐั€ั‚ะฝะฐ ะฐะฝะพั‚ะฐั†ั–ั ั‚ะธะฟั–ะฒ Python. +FastAPI โ€” ั†ะต ััƒั‡ะฐัะฝะธะน, ัˆะฒะธะดะบะธะน (ะฒะธัะพะบะพะฟั€ะพะดัƒะบั‚ะธะฒะฝะธะน) ะฒะตะฑั„ั€ะตะนะผะฒะพั€ะบ ะดะปั ัั‚ะฒะพั€ะตะฝะฝั API ะทะฐ ะดะพะฟะพะผะพะณะพัŽ Python, ั‰ะพ ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั… ะฟั–ะดะบะฐะทะบะฐั… ั‚ะธะฟั–ะฒ Python. ะšะปัŽั‡ะพะฒั– ะพัะพะฑะปะธะฒะพัั‚ั–: -* **ะจะฒะธะดะบะธะน**: ะ”ัƒะถะต ะฒะธัะพะบะฐ ะฟั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ, ะฝะฐ ั€ั–ะฒะฝั– ะท **NodeJS** ั‚ะฐ **Go** (ะทะฐะฒะดัะบะธ Starlette ั‚ะฐ Pydantic). [ะžะดะธะฝ ั–ะท ะฝะฐะนัˆะฒะธะดัˆะธั… ั„ั€ะตะนะผะฒะพั€ะบั–ะฒ](#performance). +* **ะจะฒะธะดะบะธะน**: ะดัƒะถะต ะฒะธัะพะบะฐ ะฟั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ, ะฝะฐ ั€ั–ะฒะฝั– ะท **NodeJS** ั‚ะฐ **Go** (ะทะฐะฒะดัะบะธ Starlette ั‚ะฐ Pydantic). [ะžะดะธะฝ ั–ะท ะฝะฐะนัˆะฒะธะดัˆะธั… Python-ั„ั€ะตะนะผะฒะพั€ะบั–ะฒ](#performance). +* **ะจะฒะธะดะบะต ะฝะฐะฟะธัะฐะฝะฝั ะบะพะดัƒ**: ะฟั€ะธัˆะฒะธะดัˆัƒั” ั€ะพะทั€ะพะฑะบัƒ ั„ัƒะฝะบั†ั–ะพะฝะฐะปัƒ ะฟั€ะธะฑะปะธะทะฝะพ ะฝะฐ 200%โ€“300%. * +* **ะœะตะฝัˆะต ะฟะพะผะธะปะพะบ**: ะทะผะตะฝัˆัƒั” ะฟั€ะธะฑะปะธะทะฝะพ ะฝะฐ 40% ะบั–ะปัŒะบั–ัั‚ัŒ ะฟะพะผะธะปะพะบ, ัะฟั€ะธั‡ะธะฝะตะฝะธั… ะปัŽะดะธะฝะพัŽ (ั€ะพะทั€ะพะฑะฝะธะบะพะผ). * +* **ะ†ะฝั‚ัƒั—ั‚ะธะฒะฝะธะน**: ั‡ัƒะดะพะฒะฐ ะฟั–ะดั‚ั€ะธะผะบะฐ ั€ะตะดะฐะบั‚ะพั€ะฐะผะธ ะบะพะดัƒ. <abbr title="also known as auto-complete, autocompletion, IntelliSense">ะะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั</abbr> ะฒััŽะดะธ. ะœะตะฝัˆะต ั‡ะฐััƒ ะฝะฐ ะฝะฐะปะฐะณะพะดะถะตะฝะฝั. +* **ะŸั€ะพัั‚ะธะน**: ัะฟั€ะพั”ะบั‚ะพะฒะฐะฝะธะน ั‚ะฐะบ, ั‰ะพะฑ ะฑัƒั‚ะธ ะฟั€ะพัั‚ะธะผ ัƒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั– ั‚ะฐ ะฒะธะฒั‡ะตะฝะฝั–. ะœะตะฝัˆะต ั‡ะฐััƒ ะฝะฐ ั‡ะธั‚ะฐะฝะฝั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. +* **ะšะพั€ะพั‚ะบะธะน**: ะผั–ะฝั–ะผั–ะทัƒั” ะดัƒะฑะปัŽะฒะฐะฝะฝั ะบะพะดัƒ. ะšั–ะปัŒะบะฐ ะผะพะถะปะธะฒะพัั‚ะตะน ะท ะบะพะถะฝะพะณะพ ะพะณะพะปะพัˆะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ะฐ. ะœะตะฝัˆะต ะฟะพะผะธะปะพะบ. +* **ะะฐะดั–ะนะฝะธะน**: ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะบะพะด, ะณะพั‚ะพะฒะธะน ะดะพ ะฟั€ะพะดะฐะบัˆะธะฝัƒ. ะ— ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพัŽ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพัŽ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั”ัŽ. +* **ะ—ะฐัะฝะพะฒะฐะฝะธะน ะฝะฐ ัั‚ะฐะฝะดะฐั€ั‚ะฐั…**: ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ (ั– ะฟะพะฒะฝั–ัั‚ัŽ ััƒะผั–ัะฝะธะน ะท) ะฒั–ะดะบั€ะธั‚ะธะผะธ ัั‚ะฐะฝะดะฐั€ั‚ะฐะผะธ ะดะปั API: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (ั€ะฐะฝั–ัˆะต ะฒั–ะดะพะผะธะน ัะบ Swagger) ั‚ะฐ <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>. -* **ะจะฒะธะดะบะต ะฝะฐะฟะธัะฐะฝะฝั ะบะพะดัƒ**: ะŸั€ะธัˆะฒะธะดัˆัƒั” ั€ะพะทั€ะพะฑะบัƒ ั„ัƒะฝะบั†ั–ะพะฝะฐะปัƒ ะฟั€ะธะฑะปะธะทะฝะพ ะฝะฐ 200%-300%. * -* **ะœะตะฝัˆะต ะฟะพะผะธะปะพะบ**: ะ—ะผะตะฝัˆะธั‚ัŒ ะบั–ะปัŒะบั–ัั‚ัŒ ะฟะพะผะธะปะพะบ ัะฟั€ะธั‡ะธะฝะตะฝะธั… ะปัŽะดะธะฝะพัŽ (ั€ะพะทั€ะพะฑะฝะธะบะพะผ) ะฝะฐ 40%. * -* **ะ†ะฝั‚ัƒั—ั‚ะธะฒะฝะธะน**: ะงัƒะดะพะฒะฐ ะฟั–ะดั‚ั€ะธะผะบะฐ ั€ะตะดะฐะบั‚ะพั€ะฐะผะธ ะบะพะดัƒ. <abbr title="ะขะฐะบะพะถ ะฒั–ะดะพะผะต ัะบ auto-complete, autocompletion, IntelliSense.">ะ”ะพะฟะพะฒะฝะตะฝะฝั</abbr> ะฒััŽะดะธ. ะ—ะผะตะฝัˆั‚ะต ั‡ะฐั ะฝะฐ ะฝะฐะปะฐะณะพะดะถะตะฝะฝั. -* **ะŸั€ะพัั‚ะธะน**: ะกะฟั€ะพะตะบั‚ะพะฒะฐะฝะธะน, ะดะปั ะปะตะณะบะพะณะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ั‚ะฐ ะฝะฐะฒั‡ะฐะฝะฝั. ะ—ะฝะฐะดะพะฑะธั‚ัŒัั ะผะตะฝัˆะต ั‡ะฐััƒ ะฝะฐ ั‡ะธั‚ะฐะฝะฝั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. -* **ะšะพั€ะพั‚ะบะธะน**: ะ—ะฒะตะดะต ะดะพ ะผั–ะฝั–ะผัƒะผัƒ ะดัƒะฑะปัŽะฒะฐะฝะฝั ะบะพะดัƒ. ะšะพะถะตะฝ ะพะณะพะปะพัˆะตะฝะธะน ะฟะฐั€ะฐะผะตั‚ั€ ะผะพะถะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ะบั–ะปัŒะบะฐ ั„ัƒะฝะบั†ั–ะน. -* **ะะฐะดั–ะนะฝะธะน**: ะ’ะธ ะผะฐั‚ะธะผะตั‚ะต ัั‚ะฐะฑั–ะปัŒะฝะธะน ะบะพะด ะณะพั‚ะพะฒะธะน ะดะพ ะฟั€ะพะดะฐะบัˆะธะฝัƒ ะท ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพัŽ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพัŽ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั”ัŽ. -* **ะกั‚ะฐะฝะดะฐั€ั‚ะธะทะพะฒะฐะฝะธะน**: ะžัะฝะพะฒะฐะฝะธะน ั‚ะฐ ะฟะพะฒะฝั–ัั‚ัŽ ััƒะผั–ัะฝะธะน ะท ะฒั–ะดะบั€ะธั‚ะธะผะธ ัั‚ะฐะฝะดะฐั€ั‚ะฐะผะธ ะดะปั API: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (ะฟะพะฟะตั€ะตะดะฝัŒะพ ะฒั–ะดะพะผะธะน ัะบ Swagger) ั‚ะฐ <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>. +<small>* ะพั†ั–ะฝะบะฐ ะฝะฐ ะพัะฝะพะฒั– ั‚ะตัั‚ั–ะฒ, ะฟั€ะพะฒะตะดะตะฝะธั… ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพัŽ ะบะพะผะฐะฝะดะพัŽ ั€ะพะทั€ะพะฑะฝะธะบั–ะฒ, ั‰ะพ ัั‚ะฒะพั€ัŽั” ะฟั€ะพะดะฐะบัˆะฝ-ะทะฐัั‚ะพััƒะฝะบะธ.</small> -<small>* ะพั†ั–ะฝะบะฐ ะฝะฐ ะพัะฝะพะฒั– ั‚ะตัั‚ั–ะฒ ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพั— ะบะพะผะฐะฝะดะธ ั€ะพะทั€ะพะฑะฝะธะบั–ะฒ, ัั‚ะฒะพั€ะตะฝะฝั ะฟั€ะพะดัƒะบั‚ะพะฒะธั… ะทะฐัั‚ะพััƒะฝะบั–ะฒ.</small> - -## ะกะฟะพะฝัะพั€ะธ +## ะกะฟะพะฝัะพั€ะธ { #sponsors } <!-- sponsors --> -{% if sponsors %} +### ะšะปัŽั‡ะพะฒะธะน ัะฟะพะฝัะพั€ { #keystone-sponsor } + +{% for sponsor in sponsors.keystone -%} +<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> +{% endfor -%} + +### ะ—ะพะปะพั‚ั– ั‚ะฐ ัั€ั–ะฑะฝั– ัะฟะพะฝัะพั€ะธ { #gold-and-silver-sponsors } + {% for sponsor in sponsors.gold -%} <a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> {% endfor -%} {%- for sponsor in sponsors.silver -%} <a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> {% endfor %} -{% endif %} <!-- /sponsors --> -<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Other sponsors</a> +<a href="https://fastapi.tiangolo.com/uk/fastapi-people/#sponsors" class="external-link" target="_blank">ะ†ะฝัˆั– ัะฟะพะฝัะพั€ะธ</a> -## ะ’ั€ะฐะถะตะฝะฝั +## ะ’ั€ะฐะถะตะฝะฝั { #opinions } "_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" @@ -88,7 +99,7 @@ FastAPI - ั†ะต ััƒั‡ะฐัะฝะธะน, ัˆะฒะธะดะบะธะน (ะฒะธัะพะบะพะฟั€ะพะดัƒะบั‚ะธะฒ "_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" -<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div> +<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div> --- @@ -100,50 +111,54 @@ FastAPI - ั†ะต ััƒั‡ะฐัะฝะธะน, ัˆะฒะธะดะบะธะน (ะฒะธัะพะบะพะฟั€ะพะดัƒะบั‚ะธะฒ --- -## **Typer**, FastAPI CLI +"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._" + +<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div> + +--- + +## ะœั–ะฝั–-ะดะพะบัƒะผะตะฝั‚ะฐะปัŒะฝะธะน ั„ั–ะปัŒะผ ะฟั€ะพ FastAPI { #fastapi-mini-documentary } + +ะะฐะฟั€ะธะบั–ะฝั†ั– 2025 ั€ะพะบัƒ ะฒะธะนัˆะพะฒ <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">ะผั–ะฝั–-ะดะพะบัƒะผะตะฝั‚ะฐะปัŒะฝะธะน ั„ั–ะปัŒะผ ะฟั€ะพ FastAPI</a>, ะฒะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะณะปัะฝัƒั‚ะธ ะนะพะณะพ ะพะฝะปะฐะนะฝ: + +<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a> + +## **Typer**, FastAPI ะดะปั CLI { #typer-the-fastapi-of-clis } <a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a> -ะกั‚ะฒะพั€ัŽัŽั‡ะธ <abbr title="Command Line Interface">CLI</abbr> ะทะฐัั‚ะพััƒะฝะพะบ ะดะปั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะฒ ั‚ะตั€ะผั–ะฝะฐะปั–, ะทะฐะผั–ัั‚ัŒ ะฒะตะฑ-API ะทะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ ะฝะฐ <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>. +ะฏะบั‰ะพ ะฒะธ ัั‚ะฒะพั€ัŽั”ั‚ะต ะทะฐัั‚ะพััƒะฝะพะบ <abbr title="Command Line Interface">CLI</abbr> ะดะปั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะฒ ั‚ะตั€ะผั–ะฝะฐะปั– ะทะฐะผั–ัั‚ัŒ ะฒะตะฑ-API, ะทะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ ะฝะฐ <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>. -**Typer** ั” ะผะพะปะพะดัˆะธะผ ะฑั€ะฐั‚ะพะผ FastAPI. ะ† ั†ะต **FastAPI ะดะปั CLI**. โŒจ๏ธ ๐Ÿš€ +**Typer** โ€” ะผะพะปะพะดัˆะธะน ะฑั€ะฐั‚ FastAPI. ะ† ะนะพะณะพ ะทะฐะดัƒะผะฐะฝะพ ัะบ **FastAPI ะดะปั CLI**. โŒจ๏ธ ๐Ÿš€ -## ะ’ะธะผะพะณะธ +## ะ’ะธะผะพะณะธ { #requirements } FastAPI ัั‚ะพั—ั‚ัŒ ะฝะฐ ะฟะปะตั‡ะฐั… ะณั–ะณะฐะฝั‚ั–ะฒ: -* <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> ะดะปั web ั‡ะฐัั‚ะธะฝะธ. +* <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> ะดะปั ะฒะตะฑั‡ะฐัั‚ะธะฝะธ. * <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> ะดะปั ั‡ะฐัั‚ะธะฝะธ ะดะฐะฝะธั…. -## ะ’ัั‚ะฐะฒะฝะพะฒะปะตะฝะฝั +## ะ’ัั‚ะฐะฝะพะฒะปะตะฝะฝั { #installation } + +ะกั‚ะฒะพั€ั–ั‚ัŒ ั– ะฐะบั‚ะธะฒัƒะนั‚ะต <a href="https://fastapi.tiangolo.com/uk/virtual-environments/" class="external-link" target="_blank">ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต</a>, ะฐ ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ FastAPI: <div class="termy"> ```console -$ pip install fastapi +$ pip install "fastapi[standard]" ---> 100% ``` </div> -ะ’ะฐะผ ั‚ะฐะบะพะถ ะทะฝะฐะดะพะฑะธั‚ัŒัั ัะตั€ะฒะตั€ ASGI ะดะปั ะฟั€ะพะดะฐะบัˆะธะฝัƒ, ะฝะฐะฟั€ะธะบะปะฐะด <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a> ะฐะฑะพ <a href="https://github.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>. +**ะŸั€ะธะผั–ั‚ะบะฐ**: ะฟะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ะฒะทัะปะธ `"fastapi[standard]"` ัƒ ะปะฐะฟะบะธ, ั‰ะพะฑ ั†ะต ะฟั€ะฐั†ัŽะฒะฐะปะพ ะฒ ัƒัั–ั… ั‚ะตั€ะผั–ะฝะฐะปะฐั…. -<div class="termy"> +## ะŸั€ะธะบะปะฐะด { #example } -```console -$ pip install uvicorn[standard] +### ะกั‚ะฒะพั€ั–ั‚ัŒ { #create-it } ----> 100% -``` - -</div> - -## ะŸั€ะธะบะปะฐะด - -### ะกั‚ะฒะพั€ั–ั‚ัŒ - -* ะกั‚ะฒะพั€ั–ั‚ัŒ ั„ะฐะนะป `main.py` ะท: +ะกั‚ะฒะพั€ั–ั‚ัŒ ั„ะฐะนะป `main.py` ะท: ```Python from typing import Union @@ -188,22 +203,35 @@ async def read_item(item_id: int, q: Union[str, None] = None): **ะŸั€ะธะผั–ั‚ะบะฐ**: -ะกั‚ะธะบะฝัƒะฒัˆะธััŒ ะท ะฟั€ะพะฑะปะตะผะฐะผะธ, ะฝะต ะทะฐะนะฒะธะผ ะฑัƒะดะต ะพะทะฝะฐะนะพะผะธั‚ะธัั ะท ั€ะพะทะดั–ะปะพะผ _"In a hurry?"_ ะฟั€ะพ <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` ั‚ะฐ `await` ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—</a>. +ะฏะบั‰ะพ ะฒะธ ะฝะต ะทะฝะฐั”ั‚ะต, ะฟะตั€ะตะณะปัะฝัŒั‚ะต ั€ะพะทะดั–ะป _"In a hurry?"_ ะฟั€ะพ <a href="https://fastapi.tiangolo.com/uk/async/#in-a-hurry" target="_blank">`async` ั‚ะฐ `await` ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—</a>. </details> -### ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ +### ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ { #run-it } -ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ server ะท: +ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ ัะตั€ะฒะตั€ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ: <div class="termy"> ```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. ``` @@ -211,21 +239,21 @@ INFO: Application startup complete. </div> <details markdown="1"> -<summary>ะŸั€ะพ ะบะพะผะฐะฝะดะธ <code>uvicorn main:app --reload</code>...</summary> +<summary>ะŸั€ะพ ะบะพะผะฐะฝะดัƒ <code>fastapi dev main.py</code>...</summary> -ะšะพะผะฐะฝะดะฐ `uvicorn main:app` ะฟะพัะธะปะฐั”ั‚ัŒัั ะฝะฐ: +ะšะพะผะฐะฝะดะฐ `fastapi dev` ั‡ะธั‚ะฐั” ะฒะฐัˆ ั„ะฐะนะป `main.py`, ะทะฝะฐั…ะพะดะธั‚ัŒ ัƒ ะฝัŒะพะผัƒ ะทะฐัั‚ะพััƒะฝะพะบ **FastAPI** ั– ะทะฐะฟัƒัะบะฐั” ัะตั€ะฒะตั€ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>. -* `main`: ั„ะฐะนะป `main.py` ("ะœะพะดัƒะปัŒ" Python). -* `app`: ะพะฑโ€™ั”ะบั‚ ัั‚ะฒะพั€ะตะฝะธะน ัƒัะตั€ะตะดะธะฝั– `main.py` ั€ัะดะบะพะผ `app = FastAPI()`. -* `--reload`: ะฟะตั€ะตะทะฐะฟัƒัะบะฐั” ัะตั€ะฒะตั€ ะฟั–ัะปั ะทะผั–ะฝะธ ะบะพะดัƒ. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะฒะธะบะปัŽั‡ะฝะพ ะดะปั ั€ะพะทั€ะพะฑะบะธ. +ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `fastapi dev` ะทะฐะฟัƒัะบะฐั”ั‚ัŒัั ะท ะฐะฒั‚ะพ-ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝัะผ ะดะปั ะปะพะบะฐะปัŒะฝะพั— ั€ะพะทั€ะพะฑะบะธ. + +ะ”ะพะบะปะฐะดะฝั–ัˆะต ั‡ะธั‚ะฐะนั‚ะต ะฒ <a href="https://fastapi.tiangolo.com/uk/fastapi-cli/" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— FastAPI CLI</a>. </details> -### ะŸะตั€ะตะฒั–ั€ั‚ะต +### ะŸะตั€ะตะฒั–ั€ั‚ะต { #check-it } -ะ’ั–ะดะบั€ะธะนั‚ะต ะฑั€ะฐัƒะทะตั€ ั‚ะฐ ะฒะฒะตะดั–ั‚ัŒ ะฐะดั€ะตััƒ <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>. +ะ’ั–ะดะบั€ะธะนั‚ะต ะฑั€ะฐัƒะทะตั€ ั– ะฟะตั€ะตะนะดั–ั‚ัŒ ะฝะฐ <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>. -ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ ะฟะพะดั–ะฑะฝะธะน JSON: +ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต JSON-ะฒั–ะดะฟะพะฒั–ะดัŒ: ```JSON {"item_id": 5, "q": "somequery"} @@ -233,32 +261,32 @@ INFO: Application startup complete. ะ’ะธ ะฒะถะต ัั‚ะฒะพั€ะธะปะธ API, ัะบะธะน: -* ะžั‚ั€ะธะผัƒั” HTTP ะทะฐะฟะธั‚ะธ ะทะฐ _ัˆะปัั…ะฐะผะธ_ `/` ั‚ะฐ `/items/{item_id}`. +* ะžั‚ั€ะธะผัƒั” HTTP-ะทะฐะฟะธั‚ะธ ะทะฐ _ัˆะปัั…ะฐะผะธ_ `/` ั‚ะฐ `/items/{item_id}`. * ะžะฑะธะดะฒะฐ _ัˆะปัั…ะธ_ ะฟั€ะธะนะผะฐัŽั‚ัŒ `GET` <em>ะพะฟะตั€ะฐั†ั–ั—</em> (ั‚ะฐะบะพะถ ะฒั–ะดะพะผั– ัะบ HTTP _ะผะตั‚ะพะดะธ_). -* _ะจะปัั…_ `/items/{item_id}` ะผั–ัั‚ะธั‚ัŒ _ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ_ `item_id` ัะบะธะน ะผะฐั” ะฑัƒั‚ะธ ั‚ะธะฟัƒ `int`. +* _ะจะปัั…_ `/items/{item_id}` ะผั–ัั‚ะธั‚ัŒ _ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ_ `item_id`, ัะบะธะน ะผะฐั” ะฑัƒั‚ะธ ั‚ะธะฟัƒ `int`. * _ะจะปัั…_ `/items/{item_id}` ะผั–ัั‚ะธั‚ัŒ ะฝะตะพะฑะพะฒสผัะทะบะพะฒะธะน `str` _ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ_ `q`. -### ะ†ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝั– ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— API +### ะ†ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั API { #interactive-api-docs } -ะŸะตั€ะตะนะดะตะผะพ ััŽะดะธ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. +ะขะตะฟะตั€ ะฟะตั€ะตะนะดั–ั‚ัŒ ะฝะฐ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. -ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ัั‚ะฒะพั€ะตะฝัƒ ะทะฐะฒะดัะบะธ <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>): +ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ API (ะฝะฐะดะฐะฝัƒ <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝั– ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— API +### ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั API { #alternative-api-docs } -ะขะตะฟะตั€ ะฟะตั€ะตะนะดะตะผะพ ััŽะดะธ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. +ะ ั‚ะตะฟะตั€ ะฟะตั€ะตะนะดั–ั‚ัŒ ะฝะฐ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. -ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝัƒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ัั‚ะฒะพั€ะตะฝัƒ ะทะฐะฒะดัะบะธ <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>): +ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝัƒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ะฝะฐะดะฐะฝัƒ <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## ะŸั€ะธะบะปะฐะด ะพะฝะพะฒะปะตะฝะฝั +## ะŸั€ะธะบะปะฐะด ะพะฝะพะฒะปะตะฝะฝั { #example-upgrade } -ะขะตะฟะตั€ ะผะพะดะธั„ั–ะบัƒะนั‚ะต ั„ะฐะนะป `main.py`, ั‰ะพะฑ ะพั‚ั€ะธะผะฐั‚ะธ ะฒะผั–ัั‚ ะทะฐะฟะธั‚ัƒ `PUT`. +ะขะตะฟะตั€ ะทะผั–ะฝั–ั‚ัŒ ั„ะฐะนะป `main.py`, ั‰ะพะฑ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ั‚ั–ะปะพ `PUT`-ะทะฐะฟะธั‚ัƒ. -ะžะณะพะปะพัˆัƒะนั‚ะต ะฒะผั–ัั‚ ะทะฐะฟะธั‚ัƒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั… ั‚ะธะฟั–ะฒ Python ะทะฐะฒะดัะบะธ Pydantic. +ะžะณะพะปะพัั–ั‚ัŒ ั‚ั–ะปะพ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ั‚ะธะฟะธ Python, ะทะฐะฒะดัะบะธ Pydantic. ```Python hl_lines="4 9-12 25-27" from typing import Union @@ -290,41 +318,41 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -ะกะตั€ะฒะตั€ ะฟะพะฒะธะฝะตะฝ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถัƒะฒะฐั‚ะธัั (ั‚ะพะผัƒ ั‰ะพ ะ’ะธ ะดะพะดะฐะปะธ `--reload` ะดะพ `uvicorn` ะบะพะผะฐะฝะดะธ ะฒะธั‰ะต). +ะกะตั€ะฒะตั€ `fastapi dev` ะผะฐั” ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธัั. -### ะžะฝะพะฒะปะตะฝะฝั ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพั— API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +### ะžะฝะพะฒะปะตะฝะฝั ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— API { #interactive-api-docs-upgrade } -ะขะตะฟะตั€ ะฟะตั€ะตะนะดะตะผะพ ััŽะดะธ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. +ะขะตะฟะตั€ ะฟะตั€ะตะนะดั–ั‚ัŒ ะฝะฐ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. -* ะ†ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั 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) -### ะžะฝะพะฒะปะตะฝะฝั ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะพั— API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +### ะžะฝะพะฒะปะตะฝะฝั ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— API { #alternative-api-docs-upgrade } -ะ—ะฐั€ะฐะท ะฟะตั€ะตะนะดะตะผะพ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. +ะ ั‚ะตะฟะตั€ ะฟะตั€ะตะนะดั–ั‚ัŒ ะฝะฐ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. -* ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั ั‚ะฐะบะพะถ ะฟะพะบะฐะทัƒะฒะฐั‚ะธะผะต ะฝะพะฒะธะน ะฟะฐั€ะฐะผะตั‚ั€ ั– ะฒะผั–ัั‚ ะทะฐะฟะธั‚ัƒ: +* ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั ั‚ะฐะบะพะถ ะฒั–ะดะพะฑั€ะฐะทะธั‚ัŒ ะฝะพะฒะธะน ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ ั‚ะฐ ั‚ั–ะปะพ: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### ะŸั–ะดััƒะผะบะธ +### ะŸั–ะดััƒะผะบะธ { #recap } -ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ะ’ะธ **ะพะดะธะฝ ั€ะฐะท** ะพะณะพะปะพัˆัƒั”ั‚ะต ั‚ะธะฟะธ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ั‚ั–ะปะฐ ั‚ะพั‰ะพ, ัะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ัƒะฝะบั†ั–ั—. +ะžั‚ะถะต, ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต **ะพะดะธะฝ ั€ะฐะท** ั‚ะธะฟะธ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ั‚ั–ะปะฐ ั‚ะพั‰ะพ ัะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ัƒะฝะบั†ั–ั—. ะ’ะธ ั€ะพะฑะธั‚ะต ั†ะต ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั… ััƒั‡ะฐัะฝะธั… ั‚ะธะฟั–ะฒ Python. ะ’ะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะฒั‡ะฐั‚ะธ ะฝะพะฒะธะน ัะธะฝั‚ะฐะบัะธั, ะผะตั‚ะพะดะธ ั‡ะธ ะบะปะฐัะธ ะบะพะฝะบั€ะตั‚ะฝะพั— ะฑั–ะฑะปั–ะพั‚ะตะบะธ ั‚ะพั‰ะพ. -ะ’ะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน **Python**. +ะ›ะธัˆะต ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน **Python**. ะะฐะฟั€ะธะบะปะฐะด, ะดะปั `int`: @@ -332,35 +360,35 @@ def update_item(item_id: int, item: Item): item_id: int ``` -ะฐะฑะพ ะดะปั ะฑั–ะปัŒัˆ ัะบะปะฐะดะฝะพั— ะผะพะดะตะปั– `Item`: +ะฐะฑะพ ะดะปั ัะบะปะฐะดะฝั–ัˆะพั— ะผะพะดะตะปั– `Item`: ```Python item: Item ``` -...ั– ะท ั†ะธะผ ั”ะดะธะฝะธะผ ะพะณะพะปะพัˆะตะฝะฝัะผ ะ’ะธ ะพั‚ั€ะธะผัƒั”ั‚ะต: +...ั– ะท ั†ะธะผ ั”ะดะธะฝะธะผ ะพะณะพะปะพัˆะตะฝะฝัะผ ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต: -* ะŸั–ะดั‚ั€ะธะผะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ, ะฒะบะปัŽั‡ะฐัŽั‡ะธ: - * ะ’ะฐั€ั–ะฐะฝั‚ะธ ะทะฐะฟะพะฒะฝะตะฝะฝั. - * ะŸะตั€ะตะฒั–ั€ะบัƒ ั‚ะธะฟั–ะฒ. -* ะŸะตั€ะตะฒั–ั€ะบัƒ ะดะฐะฝะธั…: - * ะะฒั‚ะพะผะฐั‚ะธั‡ะฝั– ั‚ะฐ ะทั€ะพะทัƒะผั–ะปั– ะฟะพะผะธะปะบะธ, ัƒ ั€ะฐะทั– ะฝะตะบะพั€ะตะบั‚ะฝะธั… ะดะฐะฝะธั…. - * ะŸะตั€ะตะฒั–ั€ะบะฐ ะฝะฐะฒั–ั‚ัŒ ะดะปั JSON ะท ะฒะธัะพะบะธะผ ั€ั–ะฒะฝะตะผ ะฒะบะปะฐะดะตะฝะพัั‚ั–. -* <abbr title="ั‚ะฐะบะพะถ ะฒั–ะดะพะผะธะน ัะบ: serialization, parsing, marshalling">ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั</abbr> ะฒั…ั–ะดะฝะธั… ะดะฐะฝะธั…: ะท ะผะตั€ะตะถั– ะดะพ ะดะฐะฝะธั… ั– ั‚ะธะฟั–ะฒ Python. ะงะธั‚ะฐะฝะฝั ะท: +* ะŸั–ะดั‚ั€ะธะผะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ, ะฒะบะปัŽั‡ะฝะพ ะท: + * ะะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝัะผ. + * ะŸะตั€ะตะฒั–ั€ะบะพัŽ ั‚ะธะฟั–ะฒ. +* ะ’ะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั…: + * ะะฒั‚ะพะผะฐั‚ะธั‡ะฝั– ั‚ะฐ ะทั€ะพะทัƒะผั–ะปั– ะฟะพะผะธะปะบะธ, ะบะพะปะธ ะดะฐะฝั– ะฝะตะบะพั€ะตะบั‚ะฝั–. + * ะ’ะฐะปั–ะดะฐั†ั–ัŽ ะฝะฐะฒั–ั‚ัŒ ะดะปั ะณะปะธะฑะพะบะพ ะฒะบะปะฐะดะตะฝะธั… JSON-ะพะฑสผั”ะบั‚ั–ะฒ. +* <abbr title="also known as: serialization, parsing, marshalling">ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั</abbr> ะฒั…ั–ะดะฝะธั… ะดะฐะฝะธั…: ะท ะผะตั€ะตะถั– ะดะพ ะดะฐะฝะธั… ั– ั‚ะธะฟั–ะฒ Python. ะงะธั‚ะฐะฝะฝั ะท: * JSON. * ะŸะฐั€ะฐะผะตั‚ั€ั–ะฒ ัˆะปัั…ัƒ. * ะŸะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะฟะธั‚ัƒ. * Cookies. * Headers. * Forms. - * ะคะฐะนะปั–ะฒ. -* <abbr title="ั‚ะฐะบะพะถ ะฒั–ะดะพะผะธะน ัะบ: serialization, parsing, marshalling">ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั</abbr> ะฒะธั…ั–ะดะฝะธั… ะดะฐะฝะธั…: ะท ั‚ะธะฟั–ะฒ ั– ะดะฐะฝะธั… Python ะดะพ ะผะตั€ะตะถะตะฒะธั… ะดะฐะฝะธั… (ัะบ JSON): - * ะšะพะฝะฒะตั€ั‚ะฐั†ั–ั Python ั‚ะธะฟั–ะฒ (`str`, `int`, `float`, `bool`, `list`, ั‚ะพั‰ะพ). - * `datetime` ะพะฑ'ั”ะบั‚ะธ. - * `UUID` ะพะฑ'ั”ะบั‚ะธ. - * ะœะพะดะตะปั– ะฑะฐะทะธ ะดะฐะฝะธั…. + * Files. +* <abbr title="also known as: serialization, parsing, marshalling">ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั</abbr> ะฒะธั…ั–ะดะฝะธั… ะดะฐะฝะธั…: ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ะท ะดะฐะฝะธั… ั– ั‚ะธะฟั–ะฒ Python ัƒ ะผะตั€ะตะถะตะฒั– ะดะฐะฝั– (ัะบ JSON): + * ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั‚ะธะฟั–ะฒ Python (`str`, `int`, `float`, `bool`, `list`, ั‚ะพั‰ะพ). + * ะžะฑสผั”ะบั‚ั–ะฒ `datetime`. + * ะžะฑสผั”ะบั‚ั–ะฒ `UUID`. + * ะœะพะดะตะปะตะน ะฑะฐะทะธ ะดะฐะฝะธั…. * ...ั‚ะฐ ะฑะฐะณะฐั‚ะพ ั–ะฝัˆะพะณะพ. -* ะะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ API, ะฒะบะปัŽั‡ะฐัŽั‡ะธ 2 ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝั– ั–ะฝั‚ะตั€ั„ะตะนัะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ: +* ะะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ API, ะฒะบะปัŽั‡ะฝะพ ะท 2 ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะธะผะธ ั–ะฝั‚ะตั€ั„ะตะนัะฐะผะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ: * Swagger UI. * ReDoc. @@ -368,26 +396,26 @@ item: Item ะŸะพะฒะตั€ั‚ะฐัŽั‡ะธััŒ ะดะพ ะฟะพะฟะตั€ะตะดะฝัŒะพะณะพ ะฟั€ะธะบะปะฐะดัƒ ะบะพะดัƒ, **FastAPI**: -* ะŸั–ะดั‚ะฒะตั€ะดะธั‚ัŒ ะฝะฐัะฒะฝั–ัั‚ัŒ `item_id` ัƒ ัˆะปัั…ัƒ ะดะปั ะทะฐะฟะธั‚ั–ะฒ `GET` ั‚ะฐ `PUT`. -* ะŸั–ะดั‚ะฒะตั€ะดะธั‚ัŒ, ั‰ะพ `item_id` ะผะฐั” ั‚ะธะฟ `int` ะดะปั ะทะฐะฟะธั‚ั–ะฒ `GET` and `PUT`. +* ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‰ะพ `item_id` ั” ัƒ ัˆะปัั…ัƒ ะดะปั `GET` ั‚ะฐ `PUT`-ะทะฐะฟะธั‚ั–ะฒ. +* ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‰ะพ `item_id` ะผะฐั” ั‚ะธะฟ `int` ะดะปั `GET` ั‚ะฐ `PUT`-ะทะฐะฟะธั‚ั–ะฒ. * ะฏะบั‰ะพ ั†ะต ะฝะต ั‚ะฐะบ, ะบะปั–ั”ะฝั‚ ะฟะพะฑะฐั‡ะธั‚ัŒ ะบะพั€ะธัะฝัƒ, ะทั€ะพะทัƒะผั–ะปัƒ ะฟะพะผะธะปะบัƒ. -* ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‡ะธ ั” ะฝะตะพะฑะพะฒ'ัะทะบะพะฒะธะน ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ ะท ะฝะฐะทะฒะพัŽ `q` (ะฐ ัะฐะผะต `http://127.0.0.1:8000/items/foo?q=somequery`) ะดะปั ะทะฐะฟะธั‚ั–ะฒ `GET`. +* ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‡ะธ ั” ะฝะตะพะฑะพะฒ'ัะทะบะพะฒะธะน ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ ะท ะฝะฐะทะฒะพัŽ `q` (ัะบ ัƒ `http://127.0.0.1:8000/items/foo?q=somequery`) ะดะปั `GET`-ะทะฐะฟะธั‚ั–ะฒ. * ะžัะบั–ะปัŒะบะธ ะฟะฐั€ะฐะผะตั‚ั€ `q` ะพะณะพะปะพัˆะตะฝะพ ัะบ `= None`, ะฒั–ะฝ ะฝะตะพะฑะพะฒ'ัะทะบะพะฒะธะน. - * ะ—ะฐ ะฒั–ะดััƒั‚ะฝะพัั‚ั– `None` ะฒั–ะฝ ะฑัƒะฒ ะฑะธ ะพะฑะพะฒ'ัะทะบะพะฒะธะผ (ัะบ ั– ะฒะผั–ัั‚ ัƒ ะฒะธะฟะฐะดะบัƒ ะท `PUT`). -* ะ”ะปั ะทะฐะฟะธั‚ั–ะฒ `PUT` ั–ะท `/items/{item_id}`, ั‡ะธั‚ะฐั” ะฒะผั–ัั‚ ัะบ JSON: - * ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‡ะธ ะผะฐั” ะพะฑะพะฒ'ัะทะบะพะฒะธะน ะฐั‚ั€ะธะฑัƒั‚ `name` ั‚ะธะฟ `str`. - * ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‡ะธ ะผะฐั” ะพะฑะพะฒ'ัะทะบะพะฒะธะน ะฐั‚ั€ะธะฑัƒั‚ `price` ั‚ะธะฟ `float`. - * ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‡ะธ ั–ัะฝัƒั” ะฝะตะพะฑะพะฒ'ัะทะบะพะฒะธะน ะฐั‚ั€ะธะฑัƒั‚ `is_offer` ั‚ะฐ ั‡ะธ ะผะฐั” ะฒั–ะฝ ั‚ะธะฟ `bool`. - * ะฃัะต ั†ะต ั‚ะฐะบะพะถ ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ะดะปั ะณะปะธะฑะพะบะพ ะฒะบะปะฐะดะตะฝะธั… ะพะฑ'ั”ะบั‚ั–ะฒ JSON. -* ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะพะฝะฒะตั€ั‚ัƒั” ั–ะท ั‚ะฐ ะฒ JSON. -* ะ”ะพะบัƒะผะตะฝั‚ัƒั” ะฒัะต ะทะฐ ะดะพะฟะพะผะพะณะพัŽ OpenAPI, ัะบะธะน ะผะพะถะต ะฑัƒั‚ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะพ ะฒ: + * ะ‘ะตะท `None` ะฒั–ะฝ ะฑัƒะฒ ะฑะธ ะพะฑะพะฒ'ัะทะบะพะฒะธะผ (ัะบ ั– ั‚ั–ะปะพ ัƒ ะฒะธะฟะฐะดะบัƒ ะท `PUT`). +* ะ”ะปั `PUT`-ะทะฐะฟะธั‚ั–ะฒ ะดะพ `/items/{item_id}` ะฟั€ะพั‡ะธั‚ะฐั” ั‚ั–ะปะพ ัะบ JSON: + * ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‰ะพ ั” ะพะฑะพะฒสผัะทะบะพะฒะธะน ะฐั‚ั€ะธะฑัƒั‚ `name`, ัะบะธะน ะผะฐั” ะฑัƒั‚ะธ ั‚ะธะฟัƒ `str`. + * ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‰ะพ ั” ะพะฑะพะฒสผัะทะบะพะฒะธะน ะฐั‚ั€ะธะฑัƒั‚ `price`, ัะบะธะน ะผะฐั” ะฑัƒั‚ะธ ั‚ะธะฟัƒ `float`. + * ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ, ั‰ะพ ั” ะฝะตะพะฑะพะฒสผัะทะบะพะฒะธะน ะฐั‚ั€ะธะฑัƒั‚ `is_offer`, ัะบะธะน ะผะฐั” ะฑัƒั‚ะธ ั‚ะธะฟัƒ `bool`, ัะบั‰ะพ ะฒั–ะฝ ะฟั€ะธััƒั‚ะฝั–ะน. + * ะฃัะต ั†ะต ั‚ะฐะบะพะถ ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ะดะปั ะณะปะธะฑะพะบะพ ะฒะบะปะฐะดะตะฝะธั… JSON-ะพะฑสผั”ะบั‚ั–ะฒ. +* ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะตั€ะตั‚ะฒะพั€ัŽะฒะฐั‚ะธะผะต ะท ั‚ะฐ ะฒ JSON. +* ะ”ะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธะผะต ะฒัะต ะทะฐ ะดะพะฟะพะผะพะณะพัŽ OpenAPI, ัะบะธะน ะผะพะถะต ะฑัƒั‚ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะพ ะฒ: * ะ†ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะธั… ัะธัั‚ะตะผะฐั… ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. * ะกะธัั‚ะตะผะฐั… ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพั— ะณะตะฝะตั€ะฐั†ั–ั— ะบะปั–ั”ะฝั‚ััŒะบะพะณะพ ะบะพะดัƒ ะดะปั ะฑะฐะณะฐั‚ัŒะพั… ะผะพะฒ. -* ะะฐะดะฐั” ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ 2 ะฒะตะฑั–ะฝั‚ะตั€ั„ะตะนัะธ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. +* ะะฐะดะฐะฒะฐั‚ะธะผะต ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ 2 ะฒะตะฑั–ะฝั‚ะตั€ั„ะตะนัะธ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. --- -ะœะธ ะปะธัˆะต ั‚ั€ั–ัˆะบะธ ะดะพั‚ะพั€ะบะฝัƒะปะธัั ะดะพ ะบะพะดัƒ, ะฐะปะต ะ’ะธ ะฒะถะต ะผะฐั”ั‚ะต ัƒัะฒะปะตะฝะฝั ะฟั€ะพ ั‚ะต, ัะบ ะฒัะต ะฟั€ะฐั†ัŽั”. +ะœะธ ะปะธัˆะต ั‚ั€ั–ัˆะบะธ ะดะพั‚ะพั€ะบะฝัƒะปะธัั ะดะพ ะฟะพะฒะตั€ั…ะฝั–, ะฐะปะต ะฒะธ ะฒะถะต ะผะฐั”ั‚ะต ัƒัะฒะปะตะฝะฝั ะฟั€ะพ ั‚ะต, ัะบ ัƒัะต ะฟั€ะฐั†ัŽั”. ะกะฟั€ะพะฑัƒะนั‚ะต ะทะผั–ะฝะธั‚ะธ ั€ัะดะพะบ: @@ -407,57 +435,131 @@ item: Item ... "item_price": item.price ... ``` -...ั– ะฟะพะฑะฐั‡ะธั‚ะต, ัะบ ะฒะฐัˆ ั€ะตะดะฐะบั‚ะพั€ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะฐะฟะพะฒะฝัŽะฒะฐั‚ะธะผะต ะฐั‚ั€ะธะฑัƒั‚ะธ ั‚ะฐ ะทะฝะฐั‚ะธะผะต ั—ั…ะฝั– ั‚ะธะฟะธ: +...ั– ะฟะพะฑะฐั‡ะธั‚ะต, ัะบ ะฒะฐัˆ ั€ะตะดะฐะบั‚ะพั€ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะดะพะฟะพะฒะฝัŽะฒะฐั‚ะธะผะต ะฐั‚ั€ะธะฑัƒั‚ะธ ั‚ะฐ ะทะฝะฐั‚ะธะผะต ั—ั…ะฝั– ั‚ะธะฟะธ: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -ะ”ะปั ะฑั–ะปัŒัˆ ะฟะพะฒะฝะพะณะพ ะพะทะฝะฐะนะพะผะปะตะฝะฝั ะท ะดะพะดะฐั‚ะบะพะฒะธะผะธ ั„ัƒะฝะบั†ั–ัะผะธ, ะฟะตั€ะตะณะปัะฝัŒั‚ะต <a href="https://fastapi.tiangolo.com/tutorial/">ะขัƒั‚ะพั€ั–ะฐะป - ะŸะพัั–ะฑะฝะธะบ ะšะพั€ะธัั‚ัƒะฒะฐั‡ะฐ</a>. +ะ”ะปั ะฑั–ะปัŒัˆ ะฟะพะฒะฝะพะณะพ ะฟั€ะธะบะปะฐะดัƒ, ั‰ะพ ะฒะบะปัŽั‡ะฐั” ะฑั–ะปัŒัˆะต ะผะพะถะปะธะฒะพัั‚ะตะน, ะฟะตั€ะตะณะปัะฝัŒั‚ะต <a href="https://fastapi.tiangolo.com/uk/tutorial/">ะขัƒั‚ะพั€ั–ะฐะป โ€” ะŸะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ</a>. -**Spoiler alert**: ั‚ัƒั‚ะพั€ั–ะฐะป - ะฟะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะผั–ัั‚ะธั‚ัŒ: +**Spoiler alert**: ั‚ัƒั‚ะพั€ั–ะฐะป โ€” ะฟะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะผั–ัั‚ะธั‚ัŒ: -* ะžะณะพะปะพัˆะตะฝะฝั **ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ** ะท ั–ะฝัˆะธั… ะผั–ัั†ัŒ ัะบ: **headers**, **cookies**, **form fields** ั‚ะฐ **files**. -* ะฏะบ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ **ะฟะตั€ะตะฒั–ั€ะบัƒ ะพะฑะผะตะถะตะฝัŒ** ัะบ `maximum_length` ะฐะฑะพ `regex`. -* ะ”ัƒะถะต ะฟะพั‚ัƒะถะฝะฐ ั– ะฟั€ะพัั‚ะฐ ัƒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั– ัะธัั‚ะตะผะฐ **<abbr title="ั‚ะฐะบะพะถ ะฒั–ะดะพะผะฐ ัะบ: components, resources, providers, services, injectables">ะ†ะฝ'ั”ะบั†ั–ั ะ—ะฐะปะตะถะฝะพัั‚ะตะน</abbr>**. -* ะ‘ะตะทะฟะตะบะฐ ั‚ะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั, ะฒะบะปัŽั‡ะฐัŽั‡ะธ ะฟั–ะดั‚ั€ะธะผะบัƒ **OAuth2** ะท **JWT tokens** ั‚ะฐ **HTTP Basic** ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ัŽ. +* ะžะณะพะปะพัˆะตะฝะฝั **ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ** ะท ั–ะฝัˆะธั… ั€ั–ะทะฝะธั… ะผั–ัั†ัŒ, ัะบ-ะพั‚: **headers**, **cookies**, **form fields** ั‚ะฐ **files**. +* ะฏะบ ะฒัั‚ะฐะฝะพะฒะปัŽะฒะฐั‚ะธ **ะพะฑะผะตะถะตะฝะฝั ะฒะฐะปั–ะดะฐั†ั–ั—** ัะบ `maximum_length` ะฐะฑะพ `regex`. +* ะ”ัƒะถะต ะฟะพั‚ัƒะถะฝัƒ ั– ะฟั€ะพัั‚ัƒ ัƒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั– ัะธัั‚ะตะผัƒ **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>**. +* ะ‘ะตะทะฟะตะบัƒ ั‚ะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ัŽ, ะฒะบะปัŽั‡ะฝะพ ะท ะฟั–ะดั‚ั€ะธะผะบะพัŽ **OAuth2** ะท **JWT tokens** ั‚ะฐ **HTTP Basic** auth. * ะ”ะพัะบะพะฝะฐะปั–ัˆั– (ะฐะปะต ะพะดะฝะฐะบะพะฒะพ ะฟั€ะพัั‚ั–) ั‚ะตั…ะฝั–ะบะธ ะดะปั ะพะณะพะปะพัˆะตะฝะฝั **ะณะปะธะฑะพะบะพ ะฒะบะปะฐะดะตะฝะธั… ะผะพะดะตะปะตะน JSON** (ะทะฐะฒะดัะบะธ Pydantic). -* ะ‘ะฐะณะฐั‚ะพ ะดะพะดะฐั‚ะบะพะฒะธั… ั„ัƒะฝะบั†ั–ะน (ะทะฐะฒะดัะบะธ Starlette) ัะบ-ะพั‚: +* ะ†ะฝั‚ะตะณั€ะฐั†ั–ัŽ **GraphQL** ะท <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> ั‚ะฐ ั–ะฝัˆะธะผะธ ะฑั–ะฑะปั–ะพั‚ะตะบะฐะผะธ. +* ะ‘ะฐะณะฐั‚ะพ ะดะพะดะฐั‚ะบะพะฒะธั… ะผะพะถะปะธะฒะพัั‚ะตะน (ะทะฐะฒะดัะบะธ Starlette) ัะบ-ะพั‚: * **WebSockets** * ะฝะฐะดะทะฒะธั‡ะฐะนะฝะพ ะฟั€ะพัั‚ั– ั‚ะตัั‚ะธ ะฝะฐ ะพัะฝะพะฒั– HTTPX ั‚ะฐ `pytest` * **CORS** * **Cookie Sessions** * ...ั‚ะฐ ะฑั–ะปัŒัˆะต. -## ะŸั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ +### ะ ะพะทะณะพั€ั‚ะฐะฝะฝั ะทะฐัั‚ะพััƒะฝะบัƒ (ะฝะตะพะฑะพะฒสผัะทะบะพะฒะพ) { #deploy-your-app-optional } -ะะตะทะฐะปะตะถะฝั– ั‚ะตัั‚ะธ TechEmpower ะฟะพะบะฐะทัƒัŽั‚ัŒ ั‰ะพ ะทะฐัั‚ะพััƒะฝะบะธ **FastAPI**, ัะบั– ะฟั€ะฐั†ัŽัŽั‚ัŒ ะฟั–ะด ะบะตั€ัƒะฒะฐะฝะฝัะผ Uvicorn <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">ั” ะพะดะฝะธะผะธ ะท ะฝะฐะนัˆะฒะธะดัˆะธั… ัะตั€ะตะด ะดะพัั‚ัƒะฟะฝะธั… ั„ั€ะตะนะผะฒะพั€ะบั–ะฒ ะฒ Python</a>, ะฟะพัั‚ัƒะฟะฐัŽั‡ะธััŒ ะปะธัˆะต Starlette ั‚ะฐ Uvicorn (ัะบั– ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะฒ FastAPI). (*) +ะ—ะฐ ะฑะฐะถะฐะฝะฝั ะฒะธ ะผะพะถะตั‚ะต ั€ะพะทะณะพั€ะฝัƒั‚ะธ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ FastAPI ัƒ <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, ะฟะตั€ะตะนะดั–ั‚ัŒ ั– ะฟั€ะธั”ะดะฝะฐะนั‚ะตัั ะดะพ ัะฟะธัะบัƒ ะพั‡ั–ะบัƒะฒะฐะฝะฝั, ัะบั‰ะพ ะฒะธ ั‰ะต ั†ัŒะพะณะพ ะฝะต ะทั€ะพะฑะธะปะธ. ๐Ÿš€ -ะฉะพะฑ ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ั†ะต, ะฟะตั€ะตะณะปัะฝัŒั‚ะต ั€ะพะทะดั–ะป <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Benchmarks</a>. +ะฏะบั‰ะพ ัƒ ะฒะฐั ะฒะถะต ั” ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั **FastAPI Cloud** (ะผะธ ะทะฐะฟั€ะพัะธะปะธ ะฒะฐั ะทั– ัะฟะธัะบัƒ ะพั‡ั–ะบัƒะฒะฐะฝะฝั ๐Ÿ˜‰), ะฒะธ ะผะพะถะตั‚ะต ั€ะพะทะณะพั€ะฝัƒั‚ะธ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ ะพะดะฝั–ั”ัŽ ะบะพะผะฐะฝะดะพัŽ. -## ะะตะพะฑะพะฒ'ัะทะบะพะฒั– ะทะฐะปะตะถะฝะพัั‚ั– +ะŸะตั€ะตะด ั€ะพะทะณะพั€ั‚ะฐะฝะฝัะผ ะฟะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ะฒะฒั–ะนัˆะปะธ ะฒ ัะธัั‚ะตะผัƒ: -Pydantic ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”: +<div class="termy"> + +```console +$ fastapi login + +You are logged in to FastAPI Cloud ๐Ÿš€ +``` + +</div> + +ะŸะพั‚ั–ะผ ั€ะพะทะณะพั€ะฝั–ั‚ัŒ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ: + +<div class="termy"> + +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +โœ… Deployment successful! + +๐Ÿ” Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +</div> + +ะžััŒ ั– ะฒัะต! ะขะตะฟะตั€ ะฒะธ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ะดะพัั‚ัƒะฟ ะดะพ ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ ะทะฐ ั†ั–ั”ัŽ URL-ะฐะดั€ะตัะพัŽ. โœจ + +#### ะŸั€ะพ FastAPI Cloud { #about-fastapi-cloud } + +**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** ัั‚ะฒะพั€ะตะฝะพ ั‚ะธะผ ัะฐะผะธะผ ะฐะฒั‚ะพั€ะพะผ ั– ะบะพะผะฐะฝะดะพัŽ, ั‰ะพ ัั‚ะพัั‚ัŒ ะทะฐ **FastAPI**. + +ะ’ั–ะฝ ัะฟั€ะพั‰ัƒั” ะฟั€ะพั†ะตั **ัั‚ะฒะพั€ะตะฝะฝั**, **ั€ะพะทะณะพั€ั‚ะฐะฝะฝั** ั‚ะฐ **ะดะพัั‚ัƒะฟัƒ** ะดะพ API ะท ะผั–ะฝั–ะผะฐะปัŒะฝะธะผะธ ะทัƒัะธะปะปัะผะธ. + +ะ’ั–ะฝ ะทะฐะฑะตะทะฟะตั‡ัƒั” ั‚ะพะน ัะฐะผะธะน **developer experience** ัั‚ะฒะพั€ะตะฝะฝั ะทะฐัั‚ะพััƒะฝะบั–ะฒ ะฝะฐ FastAPI ะฟั–ะด ั‡ะฐั ั—ั… **ั€ะพะทะณะพั€ั‚ะฐะฝะฝั** ัƒ ั…ะผะฐั€ั–. ๐ŸŽ‰ + +FastAPI Cloud โ€” ะพัะฝะพะฒะฝะธะน ัะฟะพะฝัะพั€ ั– ะดะถะตั€ะตะปะพ ั„ั–ะฝะฐะฝััƒะฒะฐะฝะฝั open source ะฟั€ะพั”ะบั‚ั–ะฒ *FastAPI and friends*. โœจ + +#### ะ ะพะทะณะพั€ั‚ะฐะฝะฝั ะฒ ั–ะฝัˆะธั… ั…ะผะฐั€ะฝะธั… ะฟั€ะพะฒะฐะนะดะตั€ั–ะฒ { #deploy-to-other-cloud-providers } + +FastAPI โ€” open source ั– ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ ัั‚ะฐะฝะดะฐั€ั‚ะฐั…. ะ’ะธ ะผะพะถะตั‚ะต ั€ะพะทะณะพั€ั‚ะฐั‚ะธ ะทะฐัั‚ะพััƒะฝะบะธ FastAPI ะฒ ะฑัƒะดัŒ-ัะบะพะผัƒ ั…ะผะฐั€ะฝะพะผัƒ ะฟั€ะพะฒะฐะนะดะตั€ั–, ัะบะธะน ะฒะธ ะพะฑะตั€ะตั‚ะต. + +ะ”ะพั‚ั€ะธะผัƒะนั‚ะตัั ั–ะฝัั‚ั€ัƒะบั†ั–ะน ะฒะฐัˆะพะณะพ ั…ะผะฐั€ะฝะพะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ, ั‰ะพะฑ ั€ะพะทะณะพั€ะฝัƒั‚ะธ ะทะฐัั‚ะพััƒะฝะบะธ FastAPI ัƒ ะฝัŒะพะณะพ. ๐Ÿค“ + +## ะŸั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ { #performance } + +ะะตะทะฐะปะตะถะฝั– ั‚ะตัั‚ะธ TechEmpower ะฟะพะบะฐะทัƒัŽั‚ัŒ ะทะฐัั‚ะพััƒะฝะบะธ **FastAPI**, ัะบั– ะฟั€ะฐั†ัŽัŽั‚ัŒ ะฟั–ะด ะบะตั€ัƒะฒะฐะฝะฝัะผ Uvicorn, ัะบ <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">ะพะดะฝั– ะท ะฝะฐะนัˆะฒะธะดัˆะธั… ะดะพัั‚ัƒะฟะฝะธั… Python-ั„ั€ะตะนะผะฒะพั€ะบั–ะฒ</a>, ะฟะพัั‚ัƒะฟะฐัŽั‡ะธััŒ ะปะธัˆะต Starlette ั‚ะฐ Uvicorn (ัะบั– ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะฒ FastAPI). (*) + +ะฉะพะฑ ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต, ะฟะตั€ะตะณะปัะฝัŒั‚ะต ั€ะพะทะดั–ะป <a href="https://fastapi.tiangolo.com/uk/benchmarks/" class="internal-link" target="_blank">Benchmarks</a>. + +## ะ—ะฐะปะตะถะฝะพัั‚ั– { #dependencies } + +FastAPI ะทะฐะปะตะถะธั‚ัŒ ะฒั–ะด Pydantic ั– Starlette. + +### ะ—ะฐะปะตะถะฝะพัั‚ั– `standard` { #standard-dependencies } + +ะšะพะปะธ ะฒะธ ะฒัั‚ะฐะฝะพะฒะปัŽั”ั‚ะต FastAPI ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `pip install "fastapi[standard]"`, ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะณั€ัƒะฟัƒ ะฝะตะพะฑะพะฒสผัะทะบะพะฒะธั… ะทะฐะปะตะถะฝะพัั‚ะตะน `standard`: + +ะ’ะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั Pydantic: * <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - ะดะปั ะฒะฐะปั–ะดะฐั†ั–ั— ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ. -* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - ะดะปั ัƒะฟั€ะฐะฒะปั–ะฝะฝั ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝัะผะธ. + +ะ’ะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั Starlette: + +* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - ะฟะพั‚ั€ั–ะฑะฝะพ, ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `TestClient`. +* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - ะฟะพั‚ั€ั–ะฑะฝะพ, ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ัŽ ัˆะฐะฑะปะพะฝั–ะฒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. +* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - ะฟะพั‚ั€ั–ะฑะฝะพ, ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐั‚ะธ <abbr title="converting the string that comes from an HTTP request into Python data">ยซparsingยป</abbr> ั„ะพั€ะผ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `request.form()`. + +ะ’ะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั FastAPI: + +* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - ะดะปั ัะตั€ะฒะตั€ะฐ, ัะบะธะน ะทะฐะฒะฐะฝั‚ะฐะถัƒั” ั‚ะฐ ะพะฑัะปัƒะณะพะฒัƒั” ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ. ะฆะต ะฒะบะปัŽั‡ะฐั” `uvicorn[standard]`, ะดะพ ัะบะพะณะพ ะฒั…ะพะดัั‚ัŒ ะดะตัะบั– ะทะฐะปะตะถะฝะพัั‚ั– (ะฝะฐะฟั€ะธะบะปะฐะด, `uvloop`), ะฟะพั‚ั€ั–ะฑะฝั– ะดะปั ะฒะธัะพะบะพะฟั€ะพะดัƒะบั‚ะธะฒะฝะพั— ั€ะพะฑะพั‚ะธ ัะตั€ะฒะตั€ะฐ. +* `fastapi-cli[standard]` - ั‰ะพะฑ ะฝะฐะดะฐั‚ะธ ะบะพะผะฐะฝะดัƒ `fastapi`. + * ะฆะต ะฒะบะปัŽั‡ะฐั” `fastapi-cloud-cli`, ัะบะธะน ะดะพะทะฒะพะปัั” ั€ะพะทะณะพั€ั‚ะฐั‚ะธ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ FastAPI ัƒ <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>. + +### ะ‘ะตะท ะทะฐะปะตะถะฝะพัั‚ะตะน `standard` { #without-standard-dependencies } + +ะฏะบั‰ะพ ะฒะธ ะฝะต ั…ะพั‡ะตั‚ะต ะฒะบะปัŽั‡ะฐั‚ะธ ะฝะตะพะฑะพะฒสผัะทะบะพะฒั– ะทะฐะปะตะถะฝะพัั‚ั– `standard`, ะฒะธ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ั‡ะตั€ะตะท `pip install fastapi` ะทะฐะผั–ัั‚ัŒ `pip install "fastapi[standard]"`. + +### ะ‘ะตะท `fastapi-cloud-cli` { #without-fastapi-cloud-cli } + +ะฏะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ FastAPI ะทั– ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะผะธ ะทะฐะปะตะถะฝะพัั‚ัะผะธ, ะฐะปะต ะฑะตะท `fastapi-cloud-cli`, ะฒะธ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ั‡ะตั€ะตะท `pip install "fastapi[standard-no-fastapi-cloud-cli]"`. + +### ะ”ะพะดะฐั‚ะบะพะฒั– ะฝะตะพะฑะพะฒสผัะทะบะพะฒั– ะทะฐะปะตะถะฝะพัั‚ั– { #additional-optional-dependencies } + +ะ„ ั‰ะต ะดะตัะบั– ะดะพะดะฐั‚ะบะพะฒั– ะทะฐะปะตะถะฝะพัั‚ั–, ัะบั– ะฒะธ ะผะพะถะตั‚ะต ะทะฐั…ะพั‚ั–ั‚ะธ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ. + +ะ”ะพะดะฐั‚ะบะพะฒั– ะฝะตะพะฑะพะฒสผัะทะบะพะฒั– ะทะฐะปะตะถะฝะพัั‚ั– Pydantic: + +* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - ะดะปั ะบะตั€ัƒะฒะฐะฝะฝั ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝัะผะธ. * <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - ะดะปั ะดะพะดะฐั‚ะบะพะฒะธั… ั‚ะธะฟั–ะฒ, ั‰ะพ ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝั– ะท Pydantic. +ะ”ะพะดะฐั‚ะบะพะฒั– ะฝะตะพะฑะพะฒสผัะทะบะพะฒั– ะทะฐะปะตะถะฝะพัั‚ั– FastAPI: -Starlette ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”: +* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - ะฟะพั‚ั€ั–ะฑะฝะพ, ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `ORJSONResponse`. +* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - ะฟะพั‚ั€ั–ะฑะฝะพ, ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `UJSONResponse`. -* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - ะะตะพะฑั…ั–ะดะฝะพ, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `TestClient`. -* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - ะะตะพะฑั…ั–ะดะฝะพ, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัˆะฐะฑะปะพะฝะธ ัะบ ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ัŽ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. -* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - ะะตะพะฑั…ั–ะดะฝะพ, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐั‚ะธ <abbr title="ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั€ัะดะบะฐ, ัะบะธะน ะฝะฐะดั…ะพะดะธั‚ัŒ ั–ะท ะทะฐะฟะธั‚ัƒ HTTP, ะฝะฐ ะดะฐะฝั– Python">"ั€ะพะทะฑั–ั€"</abbr> ั„ะพั€ะผะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `request.form()`. -* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - ะะตะพะฑั…ั–ะดะฝะพ ะดะปั ะฟั–ะดั‚ั€ะธะผะบะธ `SessionMiddleware`. -* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - ะะตะพะฑั…ั–ะดะฝะพ ะดะปั ะฟั–ะดั‚ั€ะธะผะบะธ Starlette `SchemaGenerator` (ะนะผะพะฒั–ั€ะฝะพ, ะฒะฐะผ ั†ะต ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะท FastAPI). - -FastAPI / Starlette ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ: - -* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - ะดะปั ัะตั€ะฒะตั€ะฐ, ัะบะธะน ะทะฐะฒะฐะฝั‚ะฐะถัƒั” ั‚ะฐ ะพะฑัะปัƒะณะพะฒัƒั” ะฒะฐัˆัƒ ะฟั€ะพะณั€ะฐะผัƒ. -* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - ะะตะพะฑั…ั–ะดะฝะพ, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `ORJSONResponse`. -* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - ะะตะพะฑั…ั–ะดะฝะพ, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `UJSONResponse`. - -ะ’ะธ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะฒัะต ั†ะต ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `pip install fastapi[all]`. - -## ะ›ั–ั†ะตะฝะทั–ั +## ะ›ั–ั†ะตะฝะทั–ั { #license } ะฆะตะน ะฟั€ะพั”ะบั‚ ะปั–ั†ะตะฝะทะพะฒะฐะฝะพ ะทะณั–ะดะฝะพ ะท ัƒะผะพะฒะฐะผะธ ะปั–ั†ะตะฝะทั–ั— MIT. diff --git a/docs/uk/docs/learn/index.md b/docs/uk/docs/learn/index.md index 7f9f21e572..6e28d414ad 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 676bafb15c..a82d13a285 100644 --- a/docs/uk/docs/python-types.md +++ b/docs/uk/docs/python-types.md @@ -1,29 +1,28 @@ -# ะ’ัั‚ัƒะฟ ะดะพ ั‚ะธะฟั–ะฒ Python +# ะ’ัั‚ัƒะฟ ะดะพ ั‚ะธะฟั–ะฒ Python { #python-types-intro } -Python ะฟั–ะดั‚ั€ะธะผัƒั” ะดะพะดะฐั‚ะบะพะฒั– "ะฟั–ะดะบะฐะทะบะธ ั‚ะธะฟัƒ" ("type hints") (ั‚ะฐะบะพะถ ะทะฒะฐะฝั– "ะฐะฝะพั‚ะฐั†ั–ัะผะธ ั‚ะธะฟัƒ" ("type annotations")). +Python ะฟั–ะดั‚ั€ะธะผัƒั” ะดะพะดะฐั‚ะบะพะฒั– ยซะฟั–ะดะบะฐะทะบะธ ั‚ะธะฟั–ะฒยป (ั‚ะฐะบะพะถ ะทะฒะฐะฝั– ยซะฐะฝะพั‚ะฐั†ั–ัะผะธ ั‚ะธะฟั–ะฒยป). -ะฆั– **"type hints"** ั” ัะฟะตั†ั–ะฐะปัŒะฝะธะผ ัะธะฝั‚ะฐะบัะธัะพะผ, ั‰ะพ ะดะพะทะฒะพะปัั” ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ <abbr title="ะฝะฐะฟั€ะธะบะปะฐะด: str, int, float, bool">ั‚ะธะฟ</abbr> ะทะผั–ะฝะฝะพั—. +ะฆั– **ยซะฟั–ะดะบะฐะทะบะธ ั‚ะธะฟั–ะฒยป** ะฐะฑะพ ะฐะฝะพั‚ะฐั†ั–ั— โ€” ั†ะต ัะฟะตั†ั–ะฐะปัŒะฝะธะน ัะธะฝั‚ะฐะบัะธั, ั‰ะพ ะดะพะทะฒะพะปัั” ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ <abbr title="ะฝะฐะฟั€ะธะบะปะฐะด: str, int, float, bool">ั‚ะธะฟ</abbr> ะทะผั–ะฝะฝะพั—. -ะ—ะฐ ะดะพะฟะพะผะพะณะพัŽ ะพะณะพะปะพัˆะตะฝะฝั ั‚ะธะฟั–ะฒ ะดะปั ะฒะฐัˆะธั… ะทะผั–ะฝะฝะธั…, ั€ะตะดะฐะบั‚ะพั€ะธ ั‚ะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะผะพะถัƒั‚ัŒ ะฝะฐะดะฐั‚ะธ ะฒะฐะผ ะบั€ะฐั‰ัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ. +ะ—ะฐ ะดะพะฟะพะผะพะณะพัŽ ะพะณะพะปะพัˆะตะฝะฝั ั‚ะธะฟั–ะฒ ะดะปั ะฒะฐัˆะธั… ะทะผั–ะฝะฝะธั… ั€ะตะดะฐะบั‚ะพั€ะธ ั‚ะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะผะพะถัƒั‚ัŒ ะฝะฐะดะฐั‚ะธ ะฒะฐะผ ะบั€ะฐั‰ัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ. -ะฆะต ะฟั€ะพัั‚ะพ **ัˆะฒะธะดะบะธะน ะฟะพัั–ะฑะฝะธะบ / ะฝะฐะณะฐะดัƒะฒะฐะฝะฝั** ะฟั€ะพ ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ัƒ Python. ะ’ั–ะฝ ะฟะพะบั€ะธะฒะฐั” ะปะธัˆะต ะผั–ะฝั–ะผัƒะผ, ะฝะตะพะฑั…ั–ะดะฝะธะน ั‰ะพะฑ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั—ั… ะท **FastAPI**... ั‰ะพ ะฝะฐัะฟั€ะฐะฒะดั– ะดัƒะถะต ะผะฐะปะพ. +ะฆะต ะปะธัˆะต **ัˆะฒะธะดะบะธะน ั‚ัƒั‚ะพั€ั–ะฐะป / ะฝะฐะณะฐะดัƒะฒะฐะฝะฝั** ะฟั€ะพ ะฟั–ะดะบะฐะทะบะธ ั‚ะธะฟั–ะฒ ัƒ Python. ะ’ั–ะฝ ะฟะพะบั€ะธะฒะฐั” ะปะธัˆะต ะผั–ะฝั–ะผัƒะผ, ะฝะตะพะฑั…ั–ะดะฝะธะน ั‰ะพะฑ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั—ั… ะท **FastAPI**... ั‰ะพ ะฝะฐัะฟั€ะฐะฒะดั– ะดัƒะถะต ะผะฐะปะพ. -**FastAPI** ะฟะพะฒะฝั–ัั‚ัŽ ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ ั†ะธั… ะฐะฝะพั‚ะฐั†ั–ัั… ั‚ะธะฟั–ะฒ, ะฒะพะฝะธ ะดะฐัŽั‚ัŒ ะนะพะผัƒ ะฑะฐะณะฐั‚ะพ ะฟะตั€ะตะฒะฐะณ. +**FastAPI** ะฟะพะฒะฝั–ัั‚ัŽ ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ ั†ะธั… ะฟั–ะดะบะฐะทะบะฐั… ั‚ะธะฟั–ะฒ, ะฒะพะฝะธ ะดะฐัŽั‚ัŒ ะนะพะผัƒ ะฑะฐะณะฐั‚ะพ ะฟะตั€ะตะฒะฐะณ ั– ะบะพั€ะธัั‚ั–. ะะปะต ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะธ ะฝั–ะบะพะปะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะฐั”ั‚ะต **FastAPI**, ะฒะฐะผ ะฑัƒะดะต ะบะพั€ะธัะฝะพ ะดั–ะทะฝะฐั‚ะธััŒ ั‚ั€ะพั…ะธ ะฟั€ะพ ะฝะธั…. -/// note +/// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะฏะบั‰ะพ ะฒะธ ะตะบัะฟะตั€ั‚ ัƒ Python ั– ะฒะธ ะฒะถะต ะทะฝะฐั”ั‚ะต ัƒัะต ะฟั€ะพ ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ - ะฟะตั€ะตะนะดั–ั‚ัŒ ะดะพ ะฝะฐัั‚ัƒะฟะฝะพะณะพ ั€ะพะทะดั–ะปัƒ. +ะฏะบั‰ะพ ะฒะธ ะตะบัะฟะตั€ั‚ ัƒ Python ั– ะฒะธ ะฒะถะต ะทะฝะฐั”ั‚ะต ะฒัะต ะฟั€ะพ ะฟั–ะดะบะฐะทะบะธ ั‚ะธะฟั–ะฒ, ะฟะตั€ะตะนะดั–ั‚ัŒ ะดะพ ะฝะฐัั‚ัƒะฟะฝะพะณะพ ั€ะพะทะดั–ะปัƒ. /// -## ะœะพั‚ะธะฒะฐั†ั–ั +## ะœะพั‚ะธะฒะฐั†ั–ั { #motivation } ะ”ะฐะฒะฐะนั‚ะต ะฟะพั‡ะฝะตะผะพ ะท ะฟั€ะพัั‚ะพะณะพ ะฟั€ะธะบะปะฐะดัƒ: -{* ../../docs_src/python_types/tutorial001.py *} - +{* ../../docs_src/python_types/tutorial001_py39.py *} ะ’ะธะบะปะธะบ ั†ั–ั”ั— ะฟั€ะพะณั€ะฐะผะธ ะฒะธะฒะพะดะธั‚ัŒ: @@ -34,13 +33,12 @@ John Doe ะคัƒะฝะบั†ั–ั ะฒะธะบะพะฝัƒั” ะฝะฐัั‚ัƒะฟะฝะต: * ะ‘ะตั€ะต `first_name` ั‚ะฐ `last_name`. -* ะšะพะฝะฒะตั€ั‚ัƒั” ะบะพะถะฝัƒ ะปั–ั‚ะตั€ัƒ ะบะพะถะฝะพะณะพ ัะปะพะฒะฐ ัƒ ะฒะตั€ั…ะฝั–ะน ั€ะตะณั–ัั‚ั€ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `title()`. -* <abbr title="ะ—โ€™ั”ะดะฝัƒั” ั—ั…, ัะบ ะพะดะฝะต ั†ั–ะปะต. ะ— ะฒะผั–ัั‚ะพะผ ะพะดะธะฝ ะทะฐ ะพะดะฝะธะผ.">ะšะพะฝะบะฐั‚ะตะฝัƒั”</abbr> ั—ั… ั€ะฐะทะพะผ ั–ะท ะฟั€ะพะฑั–ะปะพะผ ะฟะพ ัะตั€ะตะดะธะฝั–. +* ะŸะตั€ะตั‚ะฒะพั€ัŽั” ะฟะตั€ัˆัƒ ะปั–ั‚ะตั€ัƒ ะบะพะถะฝะพะณะพ ะท ะฝะธั… ัƒ ะฒะตั€ั…ะฝั–ะน ั€ะตะณั–ัั‚ั€ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `title()`. +* <abbr title="ะžะฑโ€™ั”ะดะฝัƒั” ั—ั… ั€ะฐะทะพะผ, ัะบ ะพะดะฝะต ั†ั–ะปะต. ะ— ะฒะผั–ัั‚ะพะผ ะพะดะธะฝ ะทะฐ ะพะดะฝะธะผ.">ะšะพะฝะบะฐั‚ะตะฝัƒั”</abbr> ั—ั… ั€ะฐะทะพะผ ั–ะท ะฟั€ะพะฑั–ะปะพะผ ะฟะพ ัะตั€ะตะดะธะฝั–. -{* ../../docs_src/python_types/tutorial001.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} - -### ะ ะตะดะฐะณัƒะนั‚ะต ั†ะต +### ะ ะตะดะฐะณัƒะนั‚ะต ั†ะต { #edit-it } ะฆะต ะดัƒะถะต ะฟั€ะพัั‚ะฐ ะฟั€ะพะณั€ะฐะผะฐ. @@ -48,11 +46,11 @@ John Doe ะฃ ะฟะตะฒะฝะธะน ะผะพะผะตะฝั‚ ะฒะธ ั€ะพะทะฟะพั‡ะฐะปะธ ะฑ ะฒะธะทะฝะฐั‡ะตะฝะฝั ั„ัƒะฝะบั†ั–ั—, ัƒ ะฒะฐั ะฑัƒะปะธ ะฑ ะณะพั‚ะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ... -ะะปะต ั‚ะพะดั– ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะปะธะบะฐั‚ะธ "ั‚ะพะน ะผะตั‚ะพะด, ัะบะธะน ะฟะตั€ะตะฒะพะดะธั‚ัŒ ะฟะตั€ัˆัƒ ะปั–ั‚ะตั€ัƒ ัƒ ะฒะตั€ั…ะฝั–ะน ั€ะตะณั–ัั‚ั€". +ะะปะต ั‚ะพะดั– ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะปะธะบะฐั‚ะธ ยซั‚ะพะน ะผะตั‚ะพะด, ัะบะธะน ะฟะตั€ะตั‚ะฒะพั€ัŽั” ะฟะตั€ัˆัƒ ะปั–ั‚ะตั€ัƒ ัƒ ะฒะตั€ั…ะฝั–ะน ั€ะตะณั–ัั‚ั€ยป. ะฆะต ะฑัƒะดะต `upper`? ะงะธ `uppercase`? `first_uppercase`? `capitalize`? -ะขะพะดั– ะฒะธ ัะฟั€ะพะฑัƒั”ั‚ะต ะดะฐะฒะฝัŒะพะณะพ ะดั€ัƒะณะฐ ะฟั€ะพะณั€ะฐะผั–ัั‚ะฐ - ะฐะฒั‚ะพะทะฐะฟะพะฒะฝะตะฝะฝั ั€ะตะดะฐะบั‚ะพั€ะฐ ะบะพะดัƒ. +ะขะพะดั– ะฒะธ ัะฟั€ะพะฑัƒั”ั‚ะต ะดะฐะฒะฝัŒะพะณะพ ะดั€ัƒะณะฐ ะฟั€ะพะณั€ะฐะผั–ัั‚ะฐ โ€” ะฐะฒั‚ะพะทะฐะฟะพะฒะฝะตะฝะฝั ั€ะตะดะฐะบั‚ะพั€ะฐ ะบะพะดัƒ. ะ’ะธ ะฝะฐะดั€ัƒะบัƒั”ั‚ะต ะฟะตั€ัˆะธะน ะฟะฐั€ะฐะผะตั‚ั€ ั„ัƒะฝะบั†ั–ั—, `first_name`, ั‚ะพะดั– ะบั€ะฐะฟะบัƒ (`.`), ะฐ ั‚ะพะดั– ะฝะฐั‚ะธัะฝะตั‚ะต `Ctrl+Space`, ั‰ะพะฑ ะทะฐะฟัƒัั‚ะธั‚ะธ ะฐะฒั‚ะพะทะฐะฟะพะฒะฝะตะฝะฝั. @@ -60,7 +58,7 @@ John Doe <img src="/img/python-types/image01.png"> -### ะ”ะพะดะฐะนั‚ะต ั‚ะธะฟะธ +### ะ”ะพะดะฐะนั‚ะต ั‚ะธะฟะธ { #add-types } ะ”ะฐะฒะฐะนั‚ะต ะทะผั–ะฝะธะผะพ ะพะดะธะฝ ั€ัะดะพะบ ะท ะฟะพะฟะตั€ะตะดะฝัŒะพั— ะฒะตั€ัั–ั—. @@ -78,10 +76,9 @@ John Doe ะžััŒ ั– ะฒัะต. -ะฆะต "type hints": - -{* ../../docs_src/python_types/tutorial002.py hl[1] *} +ะฆะต ยซะฟั–ะดะบะฐะทะบะธ ั‚ะธะฟั–ะฒยป: +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} ะฆะต ะฝะต ั‚ะต ัะฐะผะต, ั‰ะพ ะพะณะพะปะพัˆะตะฝะฝั ะทะฝะฐั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ัะบ ั†ะต ะฑัƒะปะพ ะฑ ะท: @@ -91,43 +88,41 @@ John Doe ะฆะต ะทะพะฒัั–ะผ ั–ะฝัˆะต. -ะœะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ะผะพ ะดะฒะพะบั€ะฐะฟะบัƒ (`:`), ะฝะต ะดะพั€ั–ะฒะฝัŽั” (`=`). +ะœะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ะผะพ ะดะฒะพะบั€ะฐะฟะบัƒ (`:`), ะฝะต ะทะฝะฐะบ ะดะพั€ั–ะฒะฝัŽั” (`=`). -ะ† ะดะพะดะฐะฒะฐะฝะฝั ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟัƒ ะทะฐะทะฒะธั‡ะฐะน ะฝะต ะทะผั–ะฝัŽั” ั‚ะพะณะพ, ั‰ะพ ัั‚ะฐะปะพััŒ ะฑะธ ะฑะตะท ะฝะธั…. +ะ† ะดะพะดะฐะฒะฐะฝะฝั ะฟั–ะดะบะฐะทะพะบ ั‚ะธะฟั–ะฒ ะทะฐะทะฒะธั‡ะฐะน ะฝะต ะทะผั–ะฝัŽั” ั‚ะพะณะพ, ั‰ะพ ะฒั–ะดะฑัƒะฒะฐั”ั‚ัŒัั, ะฟะพั€ั–ะฒะฝัะฝะพ ะท ั‚ะธะผ, ั‰ะพ ะฒั–ะดะฑัƒะฒะฐะปะพัั ะฑ ะฑะตะท ะฝะธั…. -ะะปะต ั‚ะตะฟะตั€, ัƒัะฒั–ั‚ัŒ ั‰ะพ ะฒะธ ะฟะพัะตั€ะตะด ะฟั€ะพั†ะตััƒ ัั‚ะฒะพั€ะตะฝะฝั ั„ัƒะฝะบั†ั–ั—, ะฐะปะต ะท ะฐะฝะพั‚ะฐั†ั–ัะผะธ ั‚ะธะฟั–ะฒ. +ะะปะต ั‚ะตะฟะตั€ ัƒัะฒั–ั‚ัŒ, ั‰ะพ ะฒะธ ะทะฝะพะฒัƒ ะฟะพัะตั€ะตะด ะฟั€ะพั†ะตััƒ ัั‚ะฒะพั€ะตะฝะฝั ั„ัƒะฝะบั†ั–ั—, ะฐะปะต ะท ะฟั–ะดะบะฐะทะบะฐะผะธ ั‚ะธะฟั–ะฒ. -ะ’ ั†ะตะน ะถะต ะผะพะผะตะฝั‚, ะฒะธ ัะฟั€ะพะฑัƒั”ั‚ะต ะฒะธะบะปะธะบะฐั‚ะธ ะฐะฒั‚ะพะทะฐะฟะพะฒะฝะตะฝะฝั ะท ะดะพะฟะพะผะพะณะพัŽ `Ctrl+Space` ั– ะฟะพะฑะฐั‡ะธั‚ะต: +ะฃ ั‚ะพะน ัะฐะผะธะน ะผะพะผะตะฝั‚ ะฒะธ ัะฟั€ะพะฑัƒั”ั‚ะต ะฒะธะบะปะธะบะฐั‚ะธ ะฐะฒั‚ะพะทะฐะฟะพะฒะฝะตะฝะฝั ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Ctrl+Space` ั– ะฟะพะฑะฐั‡ะธั‚ะต: <img src="/img/python-types/image02.png"> -ะ ะฐะทะพะผ ะท ั†ะธะผ, ะฒะธ ะผะพะถะตั‚ะต ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐั‚ะธ, ะฟะตั€ะตะณะปัะดะฐั‚ะธ ะพะฟั†ั–ั—, ะดะพะฟะพะบะธ ะฒะธ ะฝะต ะทะฝะฐะนะดะตั‚ะต ะพะดะฝัƒ, ั‰ะพ ะทะฒัƒั‡ะธั‚ัŒ ัั…ะพะถะต: +ะ ะฐะทะพะผ ะท ั†ะธะผ ะฒะธ ะผะพะถะตั‚ะต ะฟั€ะพะบั€ัƒั‡ัƒะฒะฐั‚ะธ, ะฟะตั€ะตะณะปัะดะฐัŽั‡ะธ ะพะฟั†ั–ั—, ะดะพะฟะพะบะธ ะฝะต ะทะฝะฐะนะดะตั‚ะต ั‚ัƒ, ั‰ะพ ยซั‰ะพััŒ ะฒะฐะผ ะฟั–ะดะบะฐะทัƒั”ยป: <img src="/img/python-types/image03.png"> -## ะ‘ั–ะปัŒัˆะต ะผะพั‚ะธะฒะฐั†ั–ั— +## ะ‘ั–ะปัŒัˆะต ะผะพั‚ะธะฒะฐั†ั–ั— { #more-motivation } -ะŸะตั€ะตะฒั–ั€ั‚ะต ั†ัŽ ั„ัƒะฝะบั†ั–ัŽ, ะฒะพะฝะฐ ะฒะถะต ะผะฐั” ะฐะฝะพั‚ะฐั†ั–ัŽ ั‚ะธะฟัƒ: - -{* ../../docs_src/python_types/tutorial003.py hl[1] *} +ะŸะตั€ะตะฒั–ั€ั‚ะต ั†ัŽ ั„ัƒะฝะบั†ั–ัŽ, ะฒะพะฝะฐ ะฒะถะต ะผะฐั” ะฟั–ะดะบะฐะทะบะธ ั‚ะธะฟั–ะฒ: +{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} ะžัะบั–ะปัŒะบะธ ั€ะตะดะฐะบั‚ะพั€ ะทะฝะฐั” ั‚ะธะฟะธ ะทะผั–ะฝะฝะธั…, ะฒะธ ะฝะต ั‚ั–ะปัŒะบะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะฐะฒั‚ะพะทะฐะฟะพะฒะฝะตะฝะฝั, ะฒะธ ั‚ะฐะบะพะถ ะพั‚ั€ะธะผะฐั”ั‚ะต ะฟะตั€ะตะฒั–ั€ะบัƒ ะฟะพะผะธะปะพะบ: <img src="/img/python-types/image04.png"> -ะขะตะฟะตั€ ะฒะธ ะทะฝะฐั”ั‚ะต, ั‰ะพะฑ ะฒะธะฟั€ะฐะฒะธั‚ะธ ั†ะต, ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตั‚ะฒะพั€ะธั‚ะธ `age` ัƒ ัั‚ั€ะพะบัƒ ะท ะดะพะฟะพะผะพะณะพัŽ `str(age)`: +ะขะตะฟะตั€ ะฒะธ ะทะฝะฐั”ั‚ะต, ั‰ะพะฑ ะฒะธะฟั€ะฐะฒะธั‚ะธ ั†ะต, ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตั‚ะฒะพั€ะธั‚ะธ `age` ัƒ ั€ัะดะพะบ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `str(age)`: -{* ../../docs_src/python_types/tutorial004.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +## ะžะณะพะปะพัˆะตะฝะฝั ั‚ะธะฟั–ะฒ { #declaring-types } -## ะžะณะพะปะพัˆะตะฝะฝั ั‚ะธะฟั–ะฒ - -ะฉะพะนะฝะพ ะฒะธ ะฟะพะฑะฐั‡ะธะปะธ ะพัะฝะพะฒะฝะต ะผั–ัั†ะต ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ะฐะฝะพั‚ะฐั†ั–ะน ั‚ะธะฟัƒ. ะฏะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ัƒะฝะบั†ั–ั—. +ะฉะพะนะฝะพ ะฒะธ ะฟะพะฑะฐั‡ะธะปะธ ะพัะฝะพะฒะฝะต ะผั–ัั†ะต ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ะฟั–ะดะบะฐะทะพะบ ั‚ะธะฟั–ะฒ. ะฏะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ัƒะฝะบั†ั–ั—. ะฆะต ั‚ะฐะบะพะถ ะพัะฝะพะฒะฝะต ะผั–ัั†ะต, ะดะต ะฒะธ ะฑ ั—ั… ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะปะธ ัƒ **FastAPI**. -### ะŸั€ะพัั‚ั– ั‚ะธะฟะธ +### ะŸั€ะพัั‚ั– ั‚ะธะฟะธ { #simple-types } ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ัƒัั– ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ั‚ะธะฟะธ ัƒ Python, ะฝะต ั‚ั–ะปัŒะบะธ `str`. @@ -138,78 +133,51 @@ John Doe * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} - -### Generic-ั‚ะธะฟะธ ะท ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ั‚ะธะฟั–ะฒ +### Generic-ั‚ะธะฟะธ ะท ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ั‚ะธะฟั–ะฒ { #generic-types-with-type-parameters } ะ†ัะฝัƒัŽั‚ัŒ ะดะตัะบั– ัั‚ั€ัƒะบั‚ัƒั€ะธ ะดะฐะฝะธั…, ัะบั– ะผะพะถัƒั‚ัŒ ะผั–ัั‚ะธั‚ะธ ั–ะฝัˆั– ะทะฝะฐั‡ะตะฝะฝั, ะฝะฐะฟั€ะธะบะปะฐะด `dict`, `list`, `set` ั‚ะฐ `tuple`. ะ† ะฒะฝัƒั‚ั€ั–ัˆะฝั– ะทะฝะฐั‡ะตะฝะฝั ั‚ะฐะบะพะถ ะผะพะถัƒั‚ัŒ ะผะฐั‚ะธ ัะฒั–ะน ั‚ะธะฟ. -ะฆั– ั‚ะธะฟะธ, ัะบั– ะผะฐัŽั‚ัŒ ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟะธ, ะฝะฐะทะธะฒะฐัŽั‚ัŒัั "**generic**" ั‚ะธะฟะฐะผะธ. ะ† ะพะณะพะปะพัะธั‚ะธ ั—ั… ะผะพะถะฝะฐ ะฝะฐะฒั–ั‚ัŒ ั–ะท ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะผะธ ั‚ะธะฟะฐะผะธ. +ะฆั– ั‚ะธะฟะธ, ัะบั– ะผะฐัŽั‚ัŒ ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟะธ, ะฝะฐะทะธะฒะฐัŽั‚ัŒัั ยซ**generic**ยป ั‚ะธะฟะฐะผะธ. ะ† ะพะณะพะปะพัะธั‚ะธ ั—ั… ะผะพะถะฝะฐ ะฝะฐะฒั–ั‚ัŒ ั–ะท ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะผะธ ั‚ะธะฟะฐะผะธ. -ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ ั†ั– ั‚ะธะฟะธ ั‚ะฐ ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟะธ, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ะผะพะดัƒะปัŒ Python `typing`. ะ’ั–ะฝ ั–ัะฝัƒั” ัะฟะตั†ั–ะฐะปัŒะฝะพ ะดะปั ะฟั–ะดั‚ั€ะธะผะบะธ ะฐะฝะพั‚ะฐั†ั–ะน ั‚ะธะฟั–ะฒ. +ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ ั†ั– ั‚ะธะฟะธ ั‚ะฐ ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟะธ, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ะผะพะดัƒะปัŒ Python `typing`. ะ’ั–ะฝ ั–ัะฝัƒั” ัะฟะตั†ั–ะฐะปัŒะฝะพ ะดะปั ะฟั–ะดั‚ั€ะธะผะบะธ ั†ะธั… ะฟั–ะดะบะฐะทะพะบ ั‚ะธะฟั–ะฒ. -#### ะะพะฒั–ัˆั– ะฒะตั€ัั–ั— Python +#### ะะพะฒั–ัˆั– ะฒะตั€ัั–ั— Python { #newer-versions-of-python } ะกะธะฝั‚ะฐะบัะธั ั–ะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ `typing` **ััƒะผั–ัะฝะธะน** ะท ัƒัั–ะผะฐ ะฒะตั€ัั–ัะผะธ, ะฒั–ะด Python 3.6 ะดะพ ะพัั‚ะฐะฝะฝั–ั…, ะฒะบะปัŽั‡ะฐัŽั‡ะธ Python 3.9, Python 3.10 ั‚ะพั‰ะพ. -ะฃ ะผั–ั€ัƒ ั€ะพะทะฒะธั‚ะบัƒ Python **ะฝะพะฒั–ัˆั– ะฒะตั€ัั–ั—** ะผะฐัŽั‚ัŒ ะฟะพะบั€ะฐั‰ะตะฝัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ ะฐะฝะพั‚ะฐั†ั–ะน ั‚ะธะฟั–ะฒ ั– ะฒ ะฑะฐะณะฐั‚ัŒะพั… ะฒะธะฟะฐะดะบะฐั… ะฒะฐะผ ะฝะฐะฒั–ั‚ัŒ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฑัƒะดะต ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดัƒะปัŒ `typing` ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ะฐะฝะพั‚ะฐั†ั–ะน ั‚ะธะฟัƒ. +ะฃ ะผั–ั€ัƒ ั€ะพะทะฒะธั‚ะบัƒ Python **ะฝะพะฒั–ัˆั– ะฒะตั€ัั–ั—** ะผะฐัŽั‚ัŒ ะฟะพะบั€ะฐั‰ะตะฝัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ ั†ะธั… ะฐะฝะพั‚ะฐั†ั–ะน ั‚ะธะฟั–ะฒ ั– ะฒ ะฑะฐะณะฐั‚ัŒะพั… ะฒะธะฟะฐะดะบะฐั… ะฒะฐะผ ะฝะฐะฒั–ั‚ัŒ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฑัƒะดะต ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดัƒะปัŒ `typing` ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ะฐะฝะพั‚ะฐั†ั–ะน ั‚ะธะฟั–ะฒ. -ะฏะบั‰ะพ ะฒะธ ะผะพะถะตั‚ะต ะฒะธะฑั€ะฐั‚ะธ ะฝะพะฒั–ัˆัƒ ะฒะตั€ัั–ัŽ Python ะดะปั ัะฒะพะณะพ ะฟั€ะพะตะบั‚ัƒ, ะฒะธ ะทะผะพะถะตั‚ะต ัะบะพั€ะธัั‚ะฐั‚ะธัั ั†ั–ั”ัŽ ะดะพะดะฐั‚ะบะพะฒะพัŽ ะฟั€ะพัั‚ะพั‚ะพัŽ. ะ”ะธะฒั–ั‚ัŒัั ะบั–ะปัŒะบะฐ ะฟั€ะธะบะปะฐะดั–ะฒ ะฝะธะถั‡ะต. +ะฏะบั‰ะพ ะฒะธ ะผะพะถะตั‚ะต ะฒะธะฑั€ะฐั‚ะธ ะฝะพะฒั–ัˆัƒ ะฒะตั€ัั–ัŽ Python ะดะปั ัะฒะพะณะพ ะฟั€ะพะตะบั‚ัƒ, ะฒะธ ะทะผะพะถะตั‚ะต ัะบะพั€ะธัั‚ะฐั‚ะธัั ั†ั–ั”ัŽ ะดะพะดะฐั‚ะบะพะฒะพัŽ ะฟั€ะพัั‚ะพั‚ะพัŽ. -#### List (ัะฟะธัะพะบ) +ะฃ ะฒัั–ะน ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ั” ะฟั€ะธะบะปะฐะดะธ, ััƒะผั–ัะฝั– ะท ะบะพะถะฝะพัŽ ะฒะตั€ัั–ั”ัŽ 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 } ะะฐะฟั€ะธะบะปะฐะด, ะดะฐะฒะฐะนั‚ะต ะฒะธะทะฝะฐั‡ะธะผะพ ะทะผั–ะฝะฝัƒ, ัะบะฐ ะฑัƒะดะต `list` ั–ะท `str`. -//// tab | Python 3.8 ั– ะฒะธั‰ะต +ะžะณะพะปะพัั–ั‚ัŒ ะทะผั–ะฝะฝัƒ ะท ั‚ะธะผ ัะฐะผะธะผ ัะธะฝั‚ะฐะบัะธัะพะผ ะดะฒะพะบั€ะฐะฟะบะธ (`:`). -ะ— ะผะพะดัƒะปั `typing`, ั–ะผะฟะพั€ั‚ัƒั”ะผะพ `List` (ะท ะฒะตะปะธะบะพั— ะปั–ั‚ะตั€ะธ `L`): - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -ะžะณะพะปะพัะธะผะพ ะทะผั–ะฝะฝัƒ ะท ั‚ะธะผ ัะฐะผะธะผ ัะธะฝั‚ะฐะบัะธัะพะผ ะดะฒะพะบั€ะฐะฟะบะธ (`:`). - -ะฏะบ ั‚ะธะฟ ะฒะบะฐะถะตะผะพ `List`, ัะบะธะน ะฒะธ ั–ะผะฟะพั€ั‚ัƒะฒะฐะปะธ ะท `typing`. +ะฏะบ ั‚ะธะฟ ะฒะบะฐะถั–ั‚ัŒ `list`. ะžัะบั–ะปัŒะบะธ ัะฟะธัะพะบ ั” ั‚ะธะฟะพะผ, ัะบะธะน ะผั–ัั‚ะธั‚ัŒ ะดะตัะบั– ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟะธ, ะฒะธ ะฟะพะผั–ั‰ะฐั”ั‚ะต ั—ั… ัƒ ะบะฒะฐะดั€ะฐั‚ะฝั– ะดัƒะถะบะธ: -```Python hl_lines="4" -{!> ../../docs_src/python_types/tutorial006.py!} -``` +{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} -//// +/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -//// tab | Python 3.9 ั– ะฒะธั‰ะต +ะฆั– ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟะธ ะฒ ะบะฒะฐะดั€ะฐั‚ะฝะธั… ะดัƒะถะบะฐั… ะฝะฐะทะธะฒะฐัŽั‚ัŒัั ยซะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ั‚ะธะฟัƒยป. -ะžะณะพะปะพัะธะผะพ ะทะผั–ะฝะฝัƒ ะท ั‚ะธะผ ัะฐะผะธะผ ัะธะฝั‚ะฐะบัะธัะพะผ ะดะฒะพะบั€ะฐะฟะบะธ (`:`). - -ะฏะบ ั‚ะธะฟ ะฒะบะฐะถะตะผะพ `list`. - -ะžัะบั–ะปัŒะบะธ ัะฟะธัะพะบ ั” ั‚ะธะฟะพะผ, ัะบะธะน ะผั–ัั‚ะธั‚ัŒ ะดะตัะบั– ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟะธ, ะฒะธ ะฟะพะผั–ั‰ะฐั”ั‚ะต ั—ั… ัƒ ะบะฒะฐะดั€ะฐั‚ะฝั– ะดัƒะถะบะธ: - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -/// info - -ะฆั– ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟะธ ะฒ ะบะฒะฐะดั€ะฐั‚ะฝะธั… ะดัƒะถะบะฐั… ะฝะฐะทะธะฒะฐัŽั‚ัŒัั "ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ั‚ะธะฟัƒ". - -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ, `str` ั†ะต ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟัƒ ะฟะตั€ะตะดะฐะฝะธะน ัƒ `List` (ะฐะฑะพ `list` ัƒ Python 3.9 ั– ะฒะธั‰ะต). +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ `str` โ€” ั†ะต ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟัƒ, ะฟะตั€ะตะดะฐะฝะธะน ัƒ `list`. /// -ะฆะต ะพะทะฝะฐั‡ะฐั”: "ะทะผั–ะฝะฝะฐ `items` ั†ะต `list`, ั– ะบะพะถะตะฝ ะท ะตะปะตะผะตะฝั‚ั–ะฒ ัƒ ั†ัŒะพะผัƒ ัะฟะธัะบัƒ - `str`". - -/// tip - -ะฏะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต Python 3.9 ั– ะฒะธั‰ะต, ะฒะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ `List` ะท `typing`, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฝะฐั‚ะพะผั–ัั‚ัŒ ั‚ะธะฟ `list`. - -/// +ะฆะต ะพะทะฝะฐั‡ะฐั”: ยซะทะผั–ะฝะฝะฐ `items` โ€” ั†ะต `list`, ั– ะบะพะถะตะฝ ะท ะตะปะตะผะตะฝั‚ั–ะฒ ัƒ ั†ัŒะพะผัƒ ัะฟะธัะบัƒ โ€” `str`ยป. ะ—ั€ะพะฑะธะฒัˆะธ ั†ะต, ะฒะฐัˆ ั€ะตะดะฐะบั‚ะพั€ ะผะพะถะต ะฝะฐะดะฐั‚ะธ ะฟั–ะดั‚ั€ะธะผะบัƒ ะฝะฐะฒั–ั‚ัŒ ะฟั–ะด ั‡ะฐั ะพะฑั€ะพะฑะบะธ ะตะปะตะผะตะฝั‚ั–ะฒ ะทั– ัะฟะธัะบัƒ: @@ -221,78 +189,42 @@ John Doe ะ† ะฒัะต ะถ ั€ะตะดะฐะบั‚ะพั€ ะทะฝะฐั”, ั‰ะพ ั†ะต `str`, ั– ะฝะฐะดะฐั” ะฟั–ะดั‚ั€ะธะผะบัƒ ะดะปั ั†ัŒะพะณะพ. -#### Tuple and Set (ะบะพั€ั‚ะตะถ ั‚ะฐ ะฝะฐะฑั–ั€) +#### Tuple and Set { #tuple-and-set } ะ’ะธ ะฟะพะฒะธะฝะฝั– ะทั€ะพะฑะธั‚ะธ ั‚ะต ะถ ัะฐะผะต, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ `tuple` ั– `set`: -//// tab | Python 3.8 ั– ะฒะธั‰ะต - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial007.py!} -``` - -//// - -//// tab | Python 3.9 ั– ะฒะธั‰ะต - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} ะฆะต ะพะทะฝะฐั‡ะฐั”: -* ะ—ะผั–ะฝะฝะฐ `items_t` ั†ะต `tuple` ะท 3 ะตะปะตะผะตะฝั‚ะฐะผะธ, `int`, ั‰ะต `int`, ั‚ะฐ `str`. -* ะ—ะผั–ะฝะฝะฐ `items_s` ั†ะต `set`, ั– ะบะพะถะตะฝ ะนะพะณะพ ะตะปะตะผะตะฝั‚ ั‚ะธะฟัƒ `bytes`. +* ะ—ะผั–ะฝะฝะฐ `items_t` โ€” ั†ะต `tuple` ะท 3 ะตะปะตะผะตะฝั‚ะฐะผะธ: `int`, ั‰ะต `int`, ั‚ะฐ `str`. +* ะ—ะผั–ะฝะฝะฐ `items_s` โ€” ั†ะต `set`, ั– ะบะพะถะตะฝ ะนะพะณะพ ะตะปะตะผะตะฝั‚ ะผะฐั” ั‚ะธะฟ `bytes`. -#### Dict (ัะปะพะฒะฝะธะบ) +#### Dict { #dict } ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ `dict`, ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตะดะฐั‚ะธ 2 ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะธะฟัƒ, ั€ะพะทะดั–ะปะตะฝั– ะบะพะผะฐะผะธ. -ะŸะตั€ัˆะธะน ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟัƒ ะดะปั ะบะปัŽั‡ะฐ ัƒ `dict`. +ะŸะตั€ัˆะธะน ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟัƒ ะดะปั ะบะปัŽั‡ั–ะฒ ัƒ `dict`. -ะ”ั€ัƒะณะธะน ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟัƒ ะดะปั ะทะฝะฐั‡ะตะฝะฝั ัƒ `dict`: +ะ”ั€ัƒะณะธะน ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟัƒ ะดะปั ะทะฝะฐั‡ะตะฝัŒ ัƒ `dict`: -//// tab | Python 3.8 ั– ะฒะธั‰ะต - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008.py!} -``` - -//// - -//// tab | Python 3.9 ั– ะฒะธั‰ะต - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} ะฆะต ะพะทะฝะฐั‡ะฐั”: -* ะ—ะผั–ะฝะฝะฐ `prices` ั†ะต `dict`: - * ะšะปัŽั‡ั– ั†ัŒะพะณะพ `dict` ั‚ะธะฟัƒ `str` (ะฝะฐะฟั€ะธะบะปะฐะด, ะฝะฐะทะฒะฐ ะบะพะถะฝะพะณะพ ะตะปะตะผะตะฝั‚ัƒ). - * ะ—ะฝะฐั‡ะตะฝะฝั ั†ัŒะพะณะพ `dict` ั‚ะธะฟัƒ `float` (ะฝะฐะฟั€ะธะบะปะฐะด, ั†ั–ะฝะฐ ะบะพะถะฝะพะณะพ ะตะปะตะผะตะฝั‚ัƒ). +* ะ—ะผั–ะฝะฝะฐ `prices` โ€” ั†ะต `dict`: + * ะšะปัŽั‡ั– ั†ัŒะพะณะพ `dict` ะผะฐัŽั‚ัŒ ั‚ะธะฟ `str` (ัะบะฐะถั–ะผะพ, ะฝะฐะทะฒะฐ ะบะพะถะฝะพะณะพ ะตะปะตะผะตะฝั‚ัƒ). + * ะ—ะฝะฐั‡ะตะฝะฝั ั†ัŒะพะณะพ `dict` ะผะฐัŽั‚ัŒ ั‚ะธะฟ `float` (ัะบะฐะถั–ะผะพ, ั†ั–ะฝะฐ ะบะพะถะฝะพะณะพ ะตะปะตะผะตะฝั‚ัƒ). -#### Union (ะพะฑ'ั”ะดะฝะฐะฝะฝั) +#### Union { #union } -ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ, ั‰ะพ ะทะผั–ะฝะฝะฐ ะผะพะถะต ะฑัƒั‚ะธ ะฑัƒะดัŒ-ัะบะธะผ ั–ะท **ะบั–ะปัŒะบะพั… ั‚ะธะฟั–ะฒ**, ะฝะฐะฟั€ะธะบะปะฐะด, `int` ะฐะฑะพ `str`. +ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ, ั‰ะพ ะทะผั–ะฝะฝะฐ ะผะพะถะต ะฑัƒั‚ะธ ะฑัƒะดัŒ-ัะบะธะผ ั–ะท **ะบั–ะปัŒะบะพั… ั‚ะธะฟั–ะฒ**, ะฝะฐะฟั€ะธะบะปะฐะด `int` ะฐะฑะพ `str`. ะฃ Python 3.6 ั– ะฒะธั‰ะต (ะฒะบะปัŽั‡ะฐัŽั‡ะธ Python 3.10) ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ะธะฟ `Union` ะท `typing` ั– ะฒัั‚ะฐะฒะปัั‚ะธ ะฒ ะบะฒะฐะดั€ะฐั‚ะฝั– ะดัƒะถะบะธ ะผะพะถะปะธะฒั– ั‚ะธะฟะธ, ัะบั– ะผะพะถะฝะฐ ะฟั€ะธะนะฝัั‚ะธ. -ะฃ Python 3.10 ั‚ะฐะบะพะถ ั” **ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะธะน ัะธะฝั‚ะฐะบัะธั**, ัƒ ัะบะพะผัƒ ะฒะธ ะผะพะถะตั‚ะต ั€ะพะทะดั–ะปะธั‚ะธ ะผะพะถะปะธะฒั– ั‚ะธะฟะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ <abbr title='ั‚ะฐะบะพะถ ะฝะฐะทะธะฒะฐัŽั‚ัŒ ยซะฟะพะฑั–ั‚ะพะฒะธะผ "ะฐะฑะพ" ะพะฟะตั€ะฐั‚ะพั€ะพะผยป, ะฐะปะต ั†ะต ะทะฝะฐั‡ะตะฝะฝั ั‚ัƒั‚ ะฝะต ะฐะบั‚ัƒะฐะปัŒะฝะต'>ะฒะตั€ั‚ะธะบะฐะปัŒะฝะพั— ัะผัƒะณะธ (`|`)</abbr>. +ะฃ Python 3.10 ั‚ะฐะบะพะถ ั” **ะฝะพะฒะธะน ัะธะฝั‚ะฐะบัะธั**, ัƒ ัะบะพะผัƒ ะฒะธ ะผะพะถะตั‚ะต ั€ะพะทะดั–ะปะธั‚ะธ ะผะพะถะปะธะฒั– ั‚ะธะฟะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ <abbr title='ั‚ะฐะบะพะถ ะฝะฐะทะธะฒะฐัŽั‚ัŒ ยซะฟะพะฑั–ั‚ะพะฒะธะผ "ะฐะฑะพ" ะพะฟะตั€ะฐั‚ะพั€ะพะผยป, ะฐะปะต ั†ะต ะทะฝะฐั‡ะตะฝะฝั ั‚ัƒั‚ ะฝะต ะฐะบั‚ัƒะฐะปัŒะฝะต'>ะฒะตั€ั‚ะธะบะฐะปัŒะฝะพั— ัะผัƒะณะธ (`|`)</abbr>. -//// tab | Python 3.8 ั– ะฒะธั‰ะต - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b.py!} -``` - -//// - -//// tab | Python 3.10 ั– ะฒะธั‰ะต +//// tab | Python 3.10+ ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} @@ -300,16 +232,24 @@ John Doe //// +//// tab | Python 3.9+ + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial008b_py39.py!} +``` + +//// + ะ’ ะพะฑะพั… ะฒะธะฟะฐะดะบะฐั… ั†ะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ `item` ะผะพะถะต ะฑัƒั‚ะธ `int` ะฐะฑะพ `str`. -#### Possibly `None` (Optional) +#### ะœะพะถะปะธะฒะพ `None` { #possibly-none } ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั ะผะพะถะต ะผะฐั‚ะธ ั‚ะธะฟ, ะฝะฐะฟั€ะธะบะปะฐะด `str`, ะฐะปะต ั‚ะฐะบะพะถ ะผะพะถะต ะฑัƒั‚ะธ `None`. ะฃ Python 3.6 ั– ะฒะธั‰ะต (ะฒะบะปัŽั‡ะฐัŽั‡ะธ Python 3.10) ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะนะพะณะพ, ั–ะผะฟะพั€ั‚ัƒะฒะฐะฒัˆะธ ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `Optional` ะท ะผะพะดัƒะปั `typing`. ```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009.py!} +{!../../docs_src/python_types/tutorial009_py39.py!} ``` ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `Optional[str]` ะทะฐะผั–ัั‚ัŒ ะฟั€ะพัั‚ะพ `str` ะดะพะทะฒะพะปะธั‚ัŒ ั€ะตะดะฐะบั‚ะพั€ัƒ ะดะพะฟะพะผะพะณั‚ะธ ะฒะฐะผ ะฒะธัะฒะธั‚ะธ ะฟะพะผะธะปะบะธ, ะบะพะปะธ ะฒะธ ะผะพะณะปะธ ะฑ ะฒะฒะฐะถะฐั‚ะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐะฒะถะดะธ ั” `str`, ั…ะพั‡ะฐ ะฝะฐัะฟั€ะฐะฒะดั– ะฒะพะฝะพ ั‚ะฐะบะพะถ ะผะพะถะต ะฑัƒั‚ะธ `None`. @@ -318,23 +258,7 @@ John Doe ะฆะต ั‚ะฐะบะพะถ ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒ Python 3.10 ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Something | None`: -//// tab | Python 3.8 ั– ะฒะธั‰ะต - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009.py!} -``` - -//// - -//// tab | Python 3.8 ั– ะฒะธั‰ะต - ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฐ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b.py!} -``` - -//// - -//// tab | Python 3.10 ั– ะฒะธั‰ะต +//// tab | Python 3.10+ ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial009_py310.py!} @@ -342,32 +266,90 @@ John Doe //// -#### Generic ั‚ะธะฟะธ +//// 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.8 ั– ะฒะธั‰ะต +//// tab | Python 3.10+ -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Union` -* `Optional` -* ...ั‚ะฐ ั–ะฝัˆั–. - -//// - -//// tab | Python 3.9 ั– ะฒะธั‰ะต - -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ั– ัะฐะผั– ะฒะฑัƒะดะพะฒะฐะฝั– ั‚ะธะฟะธ, ัะบ generic (ะท ะบะฒะฐะดั€ะฐั‚ะฝะธะผะธ ะดัƒะถะบะฐะผะธ ั‚ะฐ ั‚ะธะฟะฐะผะธ ะฒัะตั€ะตะดะธะฝั–): +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ั– ัะฐะผั– ะฒะฑัƒะดะพะฒะฐะฝั– ั‚ะธะฟะธ ัะบ generic (ะท ะบะฒะฐะดั€ะฐั‚ะฝะธะผะธ ะดัƒะถะบะฐะผะธ ั‚ะฐ ั‚ะธะฟะฐะผะธ ะฒัะตั€ะตะดะธะฝั–): * `list` * `tuple` * `set` * `dict` -ะ† ั‚ะต ัะฐะผะต, ั‰ะพ ะน ัƒ Python 3.8, ั–ะท ะผะพะดัƒะปั `typing`: +ะ† ั‚ะฐะบ ัะฐะผะพ, ัะบ ั– ะฒ ะฟะพะฟะตั€ะตะดะฝั–ั… ะฒะตั€ัั–ัั… Python, ะท ะผะพะดัƒะปั `typing`: + +* `Union` +* `Optional` +* ...ั‚ะฐ ั–ะฝัˆั–. + +ะฃ Python 3.10 ัะบ ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒัƒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัŽ generic `Union` ั‚ะฐ `Optional` ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ <abbr title='ั‚ะฐะบะพะถ ะฝะฐะทะธะฒะฐัŽั‚ัŒ ยซะฟะพะฑั–ั‚ะพะฒะธะผ "ะฐะฑะพ" ะพะฟะตั€ะฐั‚ะพั€ะพะผยป, ะฐะปะต ั†ะต ะทะฝะฐั‡ะตะฝะฝั ั‚ัƒั‚ ะฝะต ะฐะบั‚ัƒะฐะปัŒะฝะต'>ะฒะตั€ั‚ะธะบะฐะปัŒะฝัƒ ัะผัƒะณัƒ (`|`)</abbr> ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ะพะฑโ€™ั”ะดะฝะฐะฝัŒ ั‚ะธะฟั–ะฒ โ€” ั†ะต ะทะฝะฐั‡ะฝะพ ะบั€ะฐั‰ะต ะน ะฟั€ะพัั‚ั–ัˆะต. + +//// + +//// tab | Python 3.9+ + +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ั– ัะฐะผั– ะฒะฑัƒะดะพะฒะฐะฝั– ั‚ะธะฟะธ ัะบ generic (ะท ะบะฒะฐะดั€ะฐั‚ะฝะธะผะธ ะดัƒะถะบะฐะผะธ ั‚ะฐ ั‚ะธะฟะฐะผะธ ะฒัะตั€ะตะดะธะฝั–): + +* `list` +* `tuple` +* `set` +* `dict` + +ะ† generic ะท ะผะพะดัƒะปั `typing`: * `Union` * `Optional` @@ -375,46 +357,29 @@ John Doe //// -//// tab | Python 3.10 ั– ะฒะธั‰ะต - -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ั– ัะฐะผั– ะฒะฑัƒะดะพะฒะฐะฝั– ั‚ะธะฟะธ, ัะบ generic (ะท ะบะฒะฐะดั€ะฐั‚ะฝะธะผะธ ะดัƒะถะบะฐะผะธ ั‚ะฐ ั‚ะธะฟะฐะผะธ ะฒัะตั€ะตะดะธะฝั–): - -* `list` -* `tuple` -* `set` -* `dict` - -ะ† ั‚ะต ัะฐะผะต, ั‰ะพ ะน ัƒ Python 3.8, ั–ะท ะผะพะดัƒะปั `typing`: - -* `Union` -* `Optional` (ั‚ะฐะบ ัะฐะผะพ ัะบ ัƒ Python 3.8) -* ...ั‚ะฐ ั–ะฝัˆั–. - -ะฃ Python 3.10, ัะบ ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฐ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัŽ `Union` ั‚ะฐ `Optional`, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ <abbr title='ั‚ะฐะบะพะถ ะฝะฐะทะธะฒะฐัŽั‚ัŒ ยซะฟะพะฑั–ั‚ะพะฒะธะผ "ะฐะฑะพ" ะพะฟะตั€ะฐั‚ะพั€ะพะผยป, ะฐะปะต ั†ะต ะทะฝะฐั‡ะตะฝะฝั ั‚ัƒั‚ ะฝะต ะฐะบั‚ัƒะฐะปัŒะฝะต'>ะฒะตั€ั‚ะธะบะฐะปัŒะฝัƒ ัะผัƒะณัƒ (`|`)</abbr> ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ะพะฑ'ั”ะดะฝะฐะฝะฝั ั‚ะธะฟั–ะฒ. - -//// - -### ะšะปะฐัะธ ัะบ ั‚ะธะฟะธ +### ะšะปะฐัะธ ัะบ ั‚ะธะฟะธ { #classes-as-types } ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะบะปะฐั ัะบ ั‚ะธะฟ ะทะผั–ะฝะฝะพั—. ะกะบะฐะถั–ะผะพ, ัƒ ะฒะฐั ั” ะบะปะฐั `Person` ะท ั–ะผสผัะผ: -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} - +{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} ะŸะพั‚ั–ะผ ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะทะผั–ะฝะฝัƒ ั‚ะธะฟัƒ `Person`: -{* ../../docs_src/python_types/tutorial010.py hl[6] *} - +{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} ะ† ะทะฝะพะฒัƒ ะถ ั‚ะฐะบะธ, ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะฒััŽ ะฟั–ะดั‚ั€ะธะผะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ: <img src="/img/python-types/image06.png"> -## Pydantic ะผะพะดะตะปั– +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ั†ะต ะพะทะฝะฐั‡ะฐั”: ยซ`one_person` โ€” ั†ะต **ะตะบะทะตะผะฟะปัั€** ะบะปะฐััƒ `Person`ยป. -<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> ั†ะต ะฑั–ะฑะปั–ะพั‚ะตะบะฐ Python ะดะปั ะฒะฐะปั–ะดะฐั†ั–ั— ะดะฐะฝะธั…. +ะฆะต ะฝะต ะพะทะฝะฐั‡ะฐั”: ยซ`one_person` โ€” ั†ะต **ะบะปะฐั** ะท ะฝะฐะทะฒะพัŽ `Person`ยป. + +## Pydantic ะผะพะดะตะปั– { #pydantic-models } + +<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> โ€” ั†ะต ะฑั–ะฑะปั–ะพั‚ะตะบะฐ Python ะดะปั ะฒะฐะปั–ะดะฐั†ั–ั— ะดะฐะฝะธั…. ะ’ะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ยซั„ะพั€ะผัƒยป ะดะฐะฝะธั… ัะบ ะบะปะฐัะธ ะท ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ. @@ -424,33 +389,11 @@ John Doe ะ† ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะฒััŽ ะฟั–ะดั‚ั€ะธะผะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ ะท ั†ะธะผ ะพั‚ั€ะธะผะฐะฝะธะผ ะพะฑโ€™ั”ะบั‚ะพะผ. -ะŸั€ะธะบะปะฐะด ะท ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Pydantic: +ะŸั€ะธะบะปะฐะด ะท ะพั„ั–ั†ั–ะนะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Pydantic: -//// tab | Python 3.8 ั– ะฒะธั‰ะต +{* ../../docs_src/python_types/tutorial011_py310.py *} -```Python -{!> ../../docs_src/python_types/tutorial011.py!} -``` - -//// - -//// tab | Python 3.9 ั– ะฒะธั‰ะต - -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.10 ั– ะฒะธั‰ะต - -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -/// info +/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฉะพะฑ ะดั–ะทะฝะฐั‚ะธััŒ ะฑั–ะปัŒัˆะต ะฟั€ะพ <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic, ะฟะตั€ะตะณะปัะฝัŒั‚ะต ะนะพะณะพ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ</a>. @@ -460,11 +403,43 @@ John Doe ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฝะฐะฑะฐะณะฐั‚ะพ ะฑั–ะปัŒัˆะต ั†ัŒะพะณะพ ะฒััŒะพะณะพ ะฝะฐ ะฟั€ะฐะบั‚ะธั†ั– ะฒ [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}. -## ะะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ัƒ **FastAPI** +/// tip | ะŸะพั€ะฐะดะฐ -**FastAPI** ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ั†ั– ะฟั–ะดะบะฐะทะบะธ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะบั–ะปัŒะบะพั… ั€ะตั‡ะตะน. +Pydantic ะผะฐั” ัะฟะตั†ั–ะฐะปัŒะฝัƒ ะฟะพะฒะตะดั–ะฝะบัƒ, ะบะพะปะธ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Optional` ะฐะฑะพ `Union[Something, None]` ะฑะตะท ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ; ะดะตั‚ะฐะปัŒะฝั–ัˆะต ะฟั€ะพ ั†ะต ะผะพะถะฝะฐ ะฟั€ะพั‡ะธั‚ะฐั‚ะธ ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Pydantic ะฟั€ะพ <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>. -ะ— **FastAPI** ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ ะท ะฟั–ะดะบะฐะทะบะฐะผะธ ั‚ะธะฟัƒ, ั– ะพั‚ั€ะธะผัƒั”ั‚ะต: +/// + +## ะŸั–ะดะบะฐะทะบะธ ั‚ะธะฟั–ะฒ ะท ะฐะฝะพั‚ะฐั†ั–ัะผะธ ะผะตั‚ะฐะดะฐะฝะธั… { #type-hints-with-metadata-annotations } + +ะฃ Python ั‚ะฐะบะพะถ ั” ะผะพะถะปะธะฒั–ัั‚ัŒ ะดะพะดะฐะฒะฐั‚ะธ **ะดะพะดะฐั‚ะบะพะฒั– <abbr title="ะ”ะฐะฝั– ะฟั€ะพ ะดะฐะฝั–, ัƒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ โ€” ั–ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ั‚ะธะฟ, ะฝะฐะฟั€ะธะบะปะฐะด ะพะฟะธั.">ะผะตั‚ะฐะดะฐะฝั–</abbr>** ะดะพ ั†ะธั… ะฟั–ะดะบะฐะทะพะบ ั‚ะธะฟั–ะฒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `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** ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ ะท ะฟั–ะดะบะฐะทะบะฐะผะธ ั‚ะธะฟั–ะฒ, ั– ะพั‚ั€ะธะผัƒั”ั‚ะต: * **ะŸั–ะดั‚ั€ะธะผะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ**. * **ะŸะตั€ะตะฒั–ั€ะบัƒ ั‚ะธะฟั–ะฒ**. @@ -473,17 +448,17 @@ John Doe * **ะ’ะธะทะฝะฐั‡ะตะฝะฝั ะฒะธะผะพะณ**: ะท ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัˆะปัั…ัƒ ะทะฐะฟะธั‚ัƒ, ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะฟะธั‚ัƒ, ะทะฐะณะพะปะพะฒะบั–ะฒ, ั‚ั–ะป, ะทะฐะปะตะถะฝะพัั‚ะตะน ั‚ะพั‰ะพ. * **ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั ะดะฐะฝะธั…**: ั–ะท ะทะฐะฟะธั‚ัƒ ะฒ ะฝะตะพะฑั…ั–ะดะฝะธะน ั‚ะธะฟ. -* **ะŸะตั€ะตะฒั–ั€ะบะฐ ะดะฐะฝะธั…**: ั‰ะพ ะฝะฐะดั…ะพะดัั‚ัŒ ะฒั–ะด ะบะพะถะฝะพะณะพ ะทะฐะฟะธั‚ัƒ: +* **ะŸะตั€ะตะฒั–ั€ะบะธ ะดะฐะฝะธั…**: ั‰ะพ ะฝะฐะดั…ะพะดัั‚ัŒ ะฒั–ะด ะบะพะถะฝะพะณะพ ะทะฐะฟะธั‚ัƒ: * ะ“ะตะฝะตั€ัƒะฒะฐะฝะฝั **ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะธั… ะฟะพะผะธะปะพะบ**, ั‰ะพ ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒัั ะบะปั–ั”ะฝั‚ัƒ, ะบะพะปะธ ะดะฐะฝั– ะฝะตะดั–ะนัะฝั–. * **ะ”ะพะบัƒะผะตะฝั‚ัƒะฒะฐะฝะฝั** API ะทะฐ ะดะพะฟะพะผะพะณะพัŽ OpenAPI: * ัะบะธะน ะฟะพั‚ั–ะผ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะดะปั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพั— ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะบะพั€ะธัั‚ัƒะฒะฐะปัŒะฝะธั†ัŒะบะธั… ั–ะฝั‚ะตั€ั„ะตะนัั–ะฒ. -ะ’ัะต ั†ะต ะผะพะถะต ะทะดะฐั‚ะธัั ะฐะฑัั‚ั€ะฐะบั‚ะฝะธะผ. ะะต ั…ะฒะธะปัŽะนั‚ะตัั. ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฒัะต ั†ะต ะฒ ะดั–ั— ะฒ [ะขัƒั‚ะพั€ั–ะฐะป - ะŸะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ](tutorial/index.md){.internal-link target=_blank}. +ะ’ัะต ั†ะต ะผะพะถะต ะทะดะฐั‚ะธัั ะฐะฑัั‚ั€ะฐะบั‚ะฝะธะผ. ะะต ั…ะฒะธะปัŽะนั‚ะตัั. ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฒัะต ั†ะต ะฒ ะดั–ั— ะฒ [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}. ะ’ะฐะถะปะธะฒะพ ั‚ะต, ั‰ะพ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั… ั‚ะธะฟั–ะฒ Python ะฒ ะพะดะฝะพะผัƒ ะผั–ัั†ั– (ะทะฐะผั–ัั‚ัŒ ั‚ะพะณะพ, ั‰ะพะฑ ะดะพะดะฐะฒะฐั‚ะธ ะฑั–ะปัŒัˆะต ะบะปะฐัั–ะฒ, ะดะตะบะพั€ะฐั‚ะพั€ั–ะฒ ั‚ะพั‰ะพ), **FastAPI** ะทั€ะพะฑะธั‚ัŒ ะฑะฐะณะฐั‚ะพ ั€ะพะฑะพั‚ะธ ะทะฐ ะฒะฐั. -/// info +/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะฏะบั‰ะพ ะฒะธ ะฒะถะต ะฟั€ะพะนัˆะปะธ ะฒะตััŒ ะฝะฐะฒั‡ะฐะปัŒะฝะธะน ะฟะพัั–ะฑะฝะธะบ ั– ะฟะพะฒะตั€ะฝัƒะปะธัั, ั‰ะพะฑ ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ั‚ะธะฟะธ, ะพััŒ ั…ะพั€ะพัˆะธะน ั€ะตััƒั€ั <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">"ัˆะฟะฐั€ะณะฐะปะบะฐ" ะฒั–ะด `mypy`</a>. +ะฏะบั‰ะพ ะฒะธ ะฒะถะต ะฟั€ะพะนัˆะปะธ ะฒะตััŒ ั‚ัƒั‚ะพั€ั–ะฐะป ั– ะฟะพะฒะตั€ะฝัƒะปะธัั, ั‰ะพะฑ ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ั‚ะธะฟะธ, ะพััŒ ั…ะพั€ะพัˆะธะน ั€ะตััƒั€ั: <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">ยซัˆะฟะฐั€ะณะฐะปะบะฐยป ะฒั–ะด `mypy`</a>. /// diff --git a/docs/uk/docs/tutorial/background-tasks.md b/docs/uk/docs/tutorial/background-tasks.md index 0a9349650d..6d7804195e 100644 --- a/docs/uk/docs/tutorial/background-tasks.md +++ b/docs/uk/docs/tutorial/background-tasks.md @@ -1,28 +1,27 @@ -# ะคะพะฝะพะฒั– ะทะฐะดะฐั‡ั– +# ะคะพะฝะพะฒั– ะทะฐะดะฐั‡ั– { #background-tasks } ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ัŽะฒะฐั‚ะธ ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั–, ัะบั– ะฑัƒะดัƒั‚ัŒ ะฒะธะบะพะฝัƒะฒะฐั‚ะธัั *ะฟั–ัะปั* ะฟะพะฒะตั€ะฝะตะฝะฝั ะฒั–ะดะฟะพะฒั–ะดั–. ะฆะต ะบะพั€ะธัะฝะพ ะดะปั ะพะฟะตั€ะฐั†ั–ะน, ัะบั– ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพะฝะฐั‚ะธ ะฟั–ัะปั ะพะฑั€ะพะฑะบะธ ะทะฐะฟะธั‚ัƒ, ะฐะปะต ะบะปั–ั”ะฝั‚ัƒ ะฝะต ะพะฑะพะฒโ€™ัะทะบะพะฒะพ ั‡ะตะบะฐั‚ะธ ะทะฐะฒะตั€ัˆะตะฝะฝั ั†ั–ั”ั— ะพะฟะตั€ะฐั†ั–ั— ะฟะตั€ะตะด ะพั‚ั€ะธะผะฐะฝะฝัะผ ะฒั–ะดะฟะพะฒั–ะดั–. -ะŸั€ะธะบะปะฐะดะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั: +ะฆะต ะฒะบะปัŽั‡ะฐั”, ะฝะฐะฟั€ะธะบะปะฐะด: * ะะฐะดัะธะปะฐะฝะฝั email-ัะฟะพะฒั–ั‰ะตะฝัŒ ะฟั–ัะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟะตะฒะฝะพั— ะดั–ั—: - * ะŸั–ะดะบะปัŽั‡ะตะฝะฝั ะดะพ ะฟะพัˆั‚ะพะฒะพะณะพ ัะตั€ะฒะตั€ะฐ ั‚ะฐ ะฝะฐะดัะธะปะฐะฝะฝั ะปะธัั‚ะฐ ะผะพะถะต ะทะฐะนะผะฐั‚ะธ ะบั–ะปัŒะบะฐ ัะตะบัƒะฝะด. ะ’ะธ ะผะพะถะตั‚ะต ะฒั–ะดั€ะฐะทัƒ ะฟะพะฒะตั€ะฝัƒั‚ะธ ะฒั–ะดะฟะพะฒั–ะดัŒ, ะฐ email ะฒั–ะดะฟั€ะฐะฒะธั‚ะธ ัƒ ั„ะพะฝั–. + * ะŸั–ะดะบะปัŽั‡ะตะฝะฝั ะดะพ ะฟะพัˆั‚ะพะฒะพะณะพ ัะตั€ะฒะตั€ะฐ ั‚ะฐ ะฝะฐะดัะธะปะฐะฝะฝั ะปะธัั‚ะฐ ะผะพะถะต ะทะฐะนะผะฐั‚ะธ ะบั–ะปัŒะบะฐ ัะตะบัƒะฝะด. ะ’ะธ ะผะพะถะตั‚ะต ะฒั–ะดั€ะฐะทัƒ ะฟะพะฒะตั€ะฝัƒั‚ะธ ะฒั–ะดะฟะพะฒั–ะดัŒ, ะฐ email-ัะฟะพะฒั–ั‰ะตะฝะฝั ะฝะฐะดั–ัะปะฐั‚ะธ ัƒ ั„ะพะฝั–. * ะžะฑั€ะพะฑะบะฐ ะดะฐะฝะธั…: - * ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะพั‚ั€ะธะผะฐะฝะพ ั„ะฐะนะป, ัะบะธะน ะฟะพั‚ั€ั–ะฑะฝะพ ะพะฑั€ะพะฑะธั‚ะธ ะดะพะฒะณะพั‚ั€ะธะฒะฐะปะธะผ ะฟั€ะพั†ะตัะพะผ, ะผะพะถะฝะฐ ะฟะพะฒะตั€ะฝัƒั‚ะธ ะฒั–ะดะฟะพะฒั–ะดัŒ "Accepted" ("ะŸั€ะธะนะฝัั‚ะพ", HTTP 202) ั– ะฒะธะบะพะฝะฐั‚ะธ ะพะฑั€ะพะฑะบัƒ ั„ะฐะนะปัƒ ัƒ ั„ะพะฝั–. + * ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะฒะธ ะพั‚ั€ะธะผะฐะปะธ ั„ะฐะนะป, ัะบะธะน ะฟะพั‚ั€ั–ะฑะฝะพ ะพะฑั€ะพะฑะธั‚ะธ ะดะพะฒะณะพั‚ั€ะธะฒะฐะปะธะผ ะฟั€ะพั†ะตัะพะผ, ะผะพะถะฝะฐ ะฟะพะฒะตั€ะฝัƒั‚ะธ ะฒั–ะดะฟะพะฒั–ะดัŒ ยซAcceptedยป (HTTP 202) ั– ะฒะธะบะพะฝะฐั‚ะธ ะพะฑั€ะพะฑะบัƒ ั„ะฐะนะปัƒ ัƒ ั„ะพะฝั–. -## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `BackgroundTasks` +## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `BackgroundTasks` { #using-backgroundtasks } -ะกะฟะพั‡ะฐั‚ะบัƒ ั–ะผะฟะพั€ั‚ัƒะนั‚ะต `BackgroundTasks` ั– ะดะพะดะฐะนั‚ะต ะนะพะณะพ ัะบ ะฟะฐั€ะฐะผะตั‚ั€ ัƒ ะ’ะฐัˆัƒ *ั„ัƒะฝะบั†ั–ัŽ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* (path operation function) ะดะพ `BackgroundTasks`: +ะกะฟะพั‡ะฐั‚ะบัƒ ั–ะผะฟะพั€ั‚ัƒะนั‚ะต `BackgroundTasks` ั– ะพะณะพะปะพัั–ั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ ัƒ ะฒะฐัˆั–ะน *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะท ะฐะฝะพั‚ะฐั†ั–ั”ัŽ ั‚ะธะฟัƒ `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} -**FastAPI** ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัั‚ะฒะพั€ะธั‚ัŒ ะพะฑ'ั”ะบั‚ `BackgroundTasks` ั– ะฟะตั€ะตะดะฐัั‚ัŒ ะนะพะณะพ ัƒ ั†ะตะน ะฟะฐั€ะฐะผะตั‚ั€. +**FastAPI** ัั‚ะฒะพั€ะธั‚ัŒ ะดะปั ะฒะฐั ะพะฑโ€™ั”ะบั‚ ั‚ะธะฟัƒ `BackgroundTasks` ั– ะฟะตั€ะตะดะฐัั‚ัŒ ะนะพะณะพ ัะบ ั†ะตะน ะฟะฐั€ะฐะผะตั‚ั€. +## ะกั‚ะฒะพั€ะตะฝะฝั ั„ัƒะฝะบั†ั–ั— ะทะฐะดะฐั‡ั– { #create-a-task-function } -## ะกั‚ะฒะพั€ะตะฝะฝั ั„ัƒะฝะบั†ั–ั— ะทะฐะดะฐั‡ั– - -ะกั‚ะฒะพั€ั–ั‚ัŒ ั„ัƒะฝะบั†ั–ัŽ, ัะบะฐ ะฑัƒะดะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ั„ะพะฝะพะฒัƒ ะทะฐะดะฐั‡ัƒ. +ะกั‚ะฒะพั€ั–ั‚ัŒ ั„ัƒะฝะบั†ั–ัŽ, ัะบะฐ ะฑัƒะดะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธัั ัะบ ั„ะพะฝะพะฒะฐ ะทะฐะดะฐั‡ะฐ. ะฆะต ะทะฒะธั‡ะฐะนะฝะฐ ั„ัƒะฝะบั†ั–ั, ัะบะฐ ะผะพะถะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ. @@ -32,54 +31,54 @@ ะ† ะพัะบั–ะปัŒะบะธ ะพะฟะตั€ะฐั†ั–ั ะทะฐะฟะธััƒ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” `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 } -ะฃัะตั€ะตะดะธะฝั– ะ’ะฐัˆะพั— *ั„ัƒะฝะบั†ั–ั— ะพะฑั€ะพะฑะบะธ ัˆะปัั…ัƒ*, ะฟะตั€ะตะดะฐะนั‚ะต ั„ัƒะฝะบั†ั–ัŽ ะทะฐะดะฐั‡ั– ะฒ ะพะฑ'ั”ะบั‚ *background tasks*, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะผะตั‚ะพะด `.add_task()`: +ะฃัะตั€ะตะดะธะฝั– ะฒะฐัˆะพั— *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะฟะตั€ะตะดะฐะนั‚ะต ั„ัƒะฝะบั†ั–ัŽ ะทะฐะดะฐั‡ั– ะฒ ะพะฑ'ั”ะบั‚ *background tasks*, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะผะตั‚ะพะด `.add_task()`: -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} `.add_task()` ะฟั€ะธะนะผะฐั” ะฐั€ะณัƒะผะตะฝั‚ะธ: -* ะคัƒะฝะบั†ั–ั ะทะฐะดะฐั‡ะฐ, ัะบะฐ ะฑัƒะดะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธัั ัƒ ั„ะพะฝะพะฒะพะผัƒ ั€ะตะถะธะผั– (`write_notification`). ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะฟะตั€ะตะดะฐั”ั‚ัŒัั ะพะฑสผั”ะบั‚ ะฑะตะท ะดัƒะถะพะบ. -* ะ‘ัƒะดัŒ-ัะบะฐ ะฟะพัะปั–ะดะพะฒะฝั–ัั‚ัŒ ะฐั€ะณัƒะผะตะฝั‚ั–ะฒ, ัะบั– ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตะดะฐั‚ะธ ัƒ ั„ัƒะฝะบั†ั–ัŽ ะทะฐะฒะดะฐะฝะฝั ัƒ ะฒั–ะดะฟะพะฒั–ะดะฝะพะผัƒ ะฟะพั€ัะดะบัƒ (`email`). -* ะ‘ัƒะดัŒ-ัะบั– ั–ะผะตะฝะพะฒะฐะฝั– ะฐั€ะณัƒะผะตะฝั‚ะธ, ัะบั– ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตะดะฐั‚ะธ ัƒ ั„ัƒะฝะบั†ั–ัŽ ะทะฐะดะฐั‡ัƒ (`message="some notification"`). +* ะคัƒะฝะบั†ั–ัŽ ะทะฐะดะฐั‡ั–, ัะบะฐ ะฑัƒะดะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธัั ัƒ ั„ะพะฝะพะฒะพะผัƒ ั€ะตะถะธะผั– (`write_notification`). +* ะ‘ัƒะดัŒ-ัะบัƒ ะฟะพัะปั–ะดะพะฒะฝั–ัั‚ัŒ ะฐั€ะณัƒะผะตะฝั‚ั–ะฒ, ัะบั– ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตะดะฐั‚ะธ ัƒ ั„ัƒะฝะบั†ั–ัŽ ะทะฐะดะฐั‡ั– ัƒ ะฒั–ะดะฟะพะฒั–ะดะฝะพะผัƒ ะฟะพั€ัะดะบัƒ (`email`). +* ะ‘ัƒะดัŒ-ัะบั– ั–ะผะตะฝะพะฒะฐะฝั– ะฐั€ะณัƒะผะตะฝั‚ะธ, ัะบั– ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตะดะฐั‚ะธ ัƒ ั„ัƒะฝะบั†ั–ัŽ ะทะฐะดะฐั‡ั– (`message="some notification"`). -## ะ’ะฟั€ะพะฒะฐะดะถะตะฝะฝั ะทะฐะปะตะถะฝะพัั‚ะตะน +## ะ’ะฟั€ะพะฒะฐะดะถะตะฝะฝั ะทะฐะปะตะถะฝะพัั‚ะตะน { #dependency-injection } -ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `BackgroundTasks` ั‚ะฐะบะพะถ ะฟั€ะฐั†ัŽั” ะท ัะธัั‚ะตะผะพัŽ ะฒะฟั€ะพะฒะฐะดะถะตะฝะฝั ะทะฐะปะตะถะฝะพัั‚ะตะน. ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟัƒ `BackgroundTasks` ะฝะฐ ั€ั–ะทะฝะธั… ั€ั–ะฒะฝัั…: ัƒ *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ัƒ ะทะฐะปะตะถะฝะพัั‚ั– (dependable), ัƒ ะฟั–ะด ะทะฐะปะตะถะฝะพัั‚ั– ั‚ะพั‰ะพ. +ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `BackgroundTasks` ั‚ะฐะบะพะถ ะฟั€ะฐั†ัŽั” ะท ัะธัั‚ะตะผะพัŽ ะฒะฟั€ะพะฒะฐะดะถะตะฝะฝั ะทะฐะปะตะถะฝะพัั‚ะตะน. ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟัƒ `BackgroundTasks` ะฝะฐ ั€ั–ะทะฝะธั… ั€ั–ะฒะฝัั…: ัƒ *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ัƒ ะทะฐะปะตะถะฝะพัั‚ั– (dependable), ัƒ ะฟั–ะดะทะฐะปะตะถะฝะพัั‚ั– ั‚ะพั‰ะพ. -**FastAPI** ะทะฝะฐั”, ัะบ ะดั–ัั‚ะธ ะฒ ะบะพะถะฝะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั– ัะบ ะฟะพะฒั‚ะพั€ะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะดะธะฝ ั– ั‚ะพะน ัะฐะผะธะน ะพะฑ'ั”ะบั‚, ั‰ะพะฑ ัƒัั– ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั– ะฑัƒะปะธ ะพะฑโ€™ั”ะดะฝะฐะฝั– ั‚ะฐ ะฒะธะบะพะฝัƒะฒะฐะปะธัั ัƒ ั„ะพะฝะพะฒะพะผัƒ ั€ะตะถะธะผั– ะฟั–ัะปั ะทะฐะฒะตั€ัˆะตะฝะฝั ะพัะฝะพะฒะฝะพะณะพ ะทะฐะฟะธั‚ัƒ. +**FastAPI** ะทะฝะฐั”, ัะบ ะดั–ัั‚ะธ ะฒ ะบะพะถะฝะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั– ัะบ ะฟะพะฒั‚ะพั€ะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะดะธะฝ ั– ั‚ะพะน ัะฐะผะธะน ะพะฑ'ั”ะบั‚, ั‰ะพะฑ ัƒัั– ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั– ะฑัƒะปะธ ะพะฑโ€™ั”ะดะฝะฐะฝั– ั‚ะฐ ะฒะธะบะพะฝัƒะฒะฐะปะธัั ัƒ ั„ะพะฝะพะฒะพะผัƒ ั€ะตะถะธะผั– ะฟั–ัะปั ะทะฐะฒะตั€ัˆะตะฝะฝั ะพัะฝะพะฒะฝะพะณะพ ะทะฐะฟะธั‚ัƒ: {* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *} ะฃ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั– ะฟะพะฒั–ะดะพะผะปะตะฝะฝั ะฑัƒะดัƒั‚ัŒ ะทะฐะฟะธัะฐะฝั– ัƒ ั„ะฐะนะป `log.txt` *ะฟั–ัะปั* ั‚ะพะณะพ, ัะบ ะฒั–ะดะฟะพะฒั–ะดัŒ ะฑัƒะดะต ะฝะฐะดั–ัะปะฐะฝะฐ. -ะฏะบั‰ะพ ัƒ ะทะฐะฟะธั‚ั– ะฑัƒะฒ ะฟะตั€ะตะดะฐะฝะธะน query-ะฟะฐั€ะฐะผะตั‚ั€, ะฒั–ะฝ ะฑัƒะดะต ะทะฐะฟะธัะฐะฝะธะน ัƒ ะปะพะณ ัƒ ั„ะพะฝะพะฒั–ะน ะทะฐะดะฐั‡ั–. +ะฏะบั‰ะพ ัƒ ะทะฐะฟะธั‚ั– ะฑัƒะฒ ะฟะตั€ะตะดะฐะฝะธะน query, ะฒั–ะฝ ะฑัƒะดะต ะทะฐะฟะธัะฐะฝะธะน ัƒ ะปะพะณ ัƒ ั„ะพะฝะพะฒั–ะน ะทะฐะดะฐั‡ั–. -ะ ะฟะพั‚ั–ะผ ั–ะฝัˆะฐ ั„ะพะฝะพะฒะฐ ะทะฐะดะฐั‡ะฐ, ัะบะฐ ัั‚ะฒะพั€ัŽั”ั‚ัŒัั ัƒ *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะทะฐะฟะธัˆะต ะฟะพะฒั–ะดะพะผะปะตะฝะฝั ะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ path ะฟะฐั€ะฐะผะตั‚ั€ะฐ `email`. +ะ ะฟะพั‚ั–ะผ ั–ะฝัˆะฐ ั„ะพะฝะพะฒะฐ ะทะฐะดะฐั‡ะฐ, ะทะณะตะฝะตั€ะพะฒะฐะฝะฐ ัƒ *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะทะฐะฟะธัˆะต ะฟะพะฒั–ะดะพะผะปะตะฝะฝั ะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ path ะฟะฐั€ะฐะผะตั‚ั€ะฐ `email`. -## ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– +## ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– { #technical-details } ะšะปะฐั `BackgroundTasks` ะฟะพั…ะพะดะธั‚ัŒ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะท <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`starlette.background`</a>. -ะ’ั–ะฝ ั–ะผะฟะพั€ั‚ัƒั”ั‚ัŒัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ัƒ FastAPI, ั‰ะพะฑ ะ’ะธ ะผะพะณะปะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะนะพะณะพ ะท `fastapi` ั– ะฒะธะฟะฐะดะบะพะฒะพ ะฝะต ั–ะผะฟะพั€ั‚ัƒะฒะฐะปะธ `BackgroundTask` (ะฑะตะท s ะฒ ะบั–ะฝั†ั–) ะท `starlette.background`. +ะ’ั–ะฝ ั–ะผะฟะพั€ั‚ัƒั”ั‚ัŒัั/ะฒะบะปัŽั‡ะฐั”ั‚ัŒัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ัƒ FastAPI, ั‰ะพะฑ ะฒะธ ะผะพะณะปะธ ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ ะนะพะณะพ ะท `fastapi` ั– ะฒะธะฟะฐะดะบะพะฒะพ ะฝะต ั–ะผะฟะพั€ั‚ัƒะฒะฐะปะธ ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะธะน `BackgroundTask` (ะฑะตะท `s` ะฒ ะบั–ะฝั†ั–) ะท `starlette.background`. ะฏะบั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะปะธัˆะต `BackgroundTasks` (ะฐ ะฝะต `BackgroundTask`), ั‚ะพ ะนะพะณะพ ะผะพะถะฝะฐ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ัะบ ะฟะฐั€ะฐะผะตั‚ั€ ัƒ *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ั– **FastAPI** ะฟะพะดะฑะฐั” ะฟั€ะพ ะฒัะต ั–ะฝัˆะต, ั‚ะฐะบ ัะฐะผะพ ัะบ ั– ะฟั€ะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะพะฑ'ั”ะบั‚ะฐ `Request`. -ะขะฐะบะพะถ ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `BackgroundTask` ะพะบั€ะตะผะพ ะฒ FastAPI, ะฐะปะต ะดะปั ั†ัŒะพะณะพ ะ’ะฐะผ ะดะพะฒะตะดะตั‚ัŒัั ัั‚ะฒะพั€ะธั‚ะธ ะพะฑ'ั”ะบั‚ ัƒ ะบะพะดั– ั‚ะฐ ะฟะพะฒะตั€ะฝัƒั‚ะธ Starlette `Response`, ะฒะบะปัŽั‡ะฐัŽั‡ะธ ะนะพะณะพ. +ะขะฐะบะพะถ ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `BackgroundTask` ะพะบั€ะตะผะพ ะฒ FastAPI, ะฐะปะต ะดะปั ั†ัŒะพะณะพ ะฒะฐะผ ะดะพะฒะตะดะตั‚ัŒัั ัั‚ะฒะพั€ะธั‚ะธ ะพะฑ'ั”ะบั‚ ัƒ ะบะพะดั– ั‚ะฐ ะฟะพะฒะตั€ะฝัƒั‚ะธ Starlette `Response`, ะฒะบะปัŽั‡ะฐัŽั‡ะธ ะนะพะณะพ. -ะ”ะตั‚ะฐะปัŒะฝั–ัˆะต ะผะพะถะฝะฐ ะฟะพั‡ะธั‚ะฐั‚ะธ ะฒ <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">ะพั„ั–ั†ั–ะนะฝั–ะน ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Starlette ะฟั€ะพ ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั– </a>. +ะ”ะตั‚ะฐะปัŒะฝั–ัˆะต ะผะพะถะฝะฐ ะฟะพั‡ะธั‚ะฐั‚ะธ ะฒ <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">ะพั„ั–ั†ั–ะนะฝั–ะน ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Starlette ะฟั€ะพ Background Tasks</a>. -## ะ—ะฐัั‚ะตั€ะตะถะตะฝะฝั +## ะ—ะฐัั‚ะตั€ะตะถะตะฝะฝั { #caveat } -ะฏะบั‰ะพ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ัะบะปะฐะดะฝั– ั„ะพะฝะพะฒั– ะพะฑั‡ะธัะปะตะฝะฝั, ั– ะฟั€ะธ ั†ัŒะพะผัƒ ะฝะตะผะฐ ะฟะพั‚ั€ะตะฑะธ ะทะฐะฟัƒัะบะฐั‚ะธ ั—ั… ัƒ ั‚ะพะผัƒ ะถ ะฟั€ะพั†ะตัั– (ะฝะฐะฟั€ะธะบะปะฐะด, ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ัะฟั–ะปัŒะฝะพะณะพ ะดะพัั‚ัƒะฟัƒ ะดะพ ะฟะฐะผโ€™ัั‚ั– ั‡ะธ ะทะผั–ะฝะฝะธั…), ะผะพะถะปะธะฒะพ, ะฒะฐั€ั‚ะพ ัะบะพั€ะธัั‚ะฐั‚ะธัั ะฑั–ะปัŒัˆ ะฟะพั‚ัƒะถะฝะธะผะธ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ, ั‚ะฐะบะธะผะธ ัะบ <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>. +ะฏะบั‰ะพ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ัะบะปะฐะดะฝั– ั„ะพะฝะพะฒั– ะพะฑั‡ะธัะปะตะฝะฝั, ั– ะฟั€ะธ ั†ัŒะพะผัƒ ะฝะตะผะฐ ะฟะพั‚ั€ะตะฑะธ ะทะฐะฟัƒัะบะฐั‚ะธ ั—ั… ัƒ ั‚ะพะผัƒ ะถ ะฟั€ะพั†ะตัั– (ะฝะฐะฟั€ะธะบะปะฐะด, ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ัะฟั–ะปัŒะฝะพะณะพ ะดะพัั‚ัƒะฟัƒ ะดะพ ะฟะฐะผโ€™ัั‚ั– ั‡ะธ ะทะผั–ะฝะฝะธั…), ะผะพะถะปะธะฒะพ, ะฒะฐั€ั‚ะพ ัะบะพั€ะธัั‚ะฐั‚ะธัั ะฑั–ะปัŒัˆ ะฟะพั‚ัƒะถะฝะธะผะธ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ, ั‚ะฐะบะธะผะธ ัะบ <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>. -ะขะฐะบั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะทะฐะทะฒะธั‡ะฐะน ะฟะพั‚ั€ะตะฑัƒัŽั‚ัŒ ัะบะปะฐะดะฝั–ัˆะพั— ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ั— ั‚ะฐ ะผะตะฝะตะดะถะตั€ะฐ ั‡ะตั€ะณะธ ะฟะพะฒั–ะดะพะผะปะตะฝัŒ/ะทะฐะฒะดะฐะฝัŒ, ะฝะฐะฟั€ะธะบะปะฐะด, RabbitMQ ะฐะฑะพ Redis. ะžะดะฝะฐะบ ะฒะพะฝะธ ะดะพะทะฒะพะปััŽั‚ัŒ ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั– ะฒ ะบั–ะปัŒะบะพั… ะฟั€ะพั†ะตัะฐั… ั– ะฝะฐะฒั–ั‚ัŒ ะฝะฐ ะบั–ะปัŒะบะพั… ัะตั€ะฒะตั€ะฐั…. +ะขะฐะบั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะทะฐะทะฒะธั‡ะฐะน ะฟะพั‚ั€ะตะฑัƒัŽั‚ัŒ ัะบะปะฐะดะฝั–ัˆะพั— ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ั— ั‚ะฐ ะผะตะฝะตะดะถะตั€ะฐ ั‡ะตั€ะณะธ ะฟะพะฒั–ะดะพะผะปะตะฝัŒ/ะทะฐะฒะดะฐะฝัŒ, ะฝะฐะฟั€ะธะบะปะฐะด, RabbitMQ ะฐะฑะพ Redis. ะžะดะฝะฐะบ ะฒะพะฝะธ ะดะพะทะฒะพะปััŽั‚ัŒ ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั– ะฒ ะบั–ะปัŒะบะพั… ะฟั€ะพั†ะตัะฐั… ั– ะพัะพะฑะปะธะฒะพ โ€” ะฝะฐ ะบั–ะปัŒะบะพั… ัะตั€ะฒะตั€ะฐั…. -ะฏะบั‰ะพ ะถ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะพั‚ั€ะธะผะฐั‚ะธ ะดะพัั‚ัƒะฟ ะดะพ ะทะผั–ะฝะฝะธั… ั– ะพะฑโ€™ั”ะบั‚ั–ะฒ ั–ะท ั‚ั–ั”ั— ะถ **FastAPI** - ะฟั€ะพะณั€ะฐะผะธ ะฐะฑะพ ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ะฝะตะฒะตะปะธะบั– ั„ะพะฝะพะฒั– ะทะฐะฒะดะฐะฝะฝั (ะฝะฐะฟั€ะธะบะปะฐะด, ะฝะฐะดัะธะปะฐั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะตะปะตะบั‚ั€ะพะฝะฝะพัŽ ะฟะพัˆั‚ะพัŽ), ะดะพัั‚ะฐั‚ะฝัŒะพ ะฟั€ะพัั‚ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `BackgroundTasks`. +ะฏะบั‰ะพ ะถ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะพั‚ั€ะธะผะฐั‚ะธ ะดะพัั‚ัƒะฟ ะดะพ ะทะผั–ะฝะฝะธั… ั– ะพะฑโ€™ั”ะบั‚ั–ะฒ ั–ะท ั‚ั–ั”ั— ะถ **FastAPI**-ะฟั€ะพะณั€ะฐะผะธ ะฐะฑะพ ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ะฝะตะฒะตะปะธะบั– ั„ะพะฝะพะฒั– ะทะฐะฒะดะฐะฝะฝั (ะฝะฐะฟั€ะธะบะปะฐะด, ะฝะฐะดัะธะปะฐั‚ะธ email-ัะฟะพะฒั–ั‰ะตะฝะฝั), ะดะพัั‚ะฐั‚ะฝัŒะพ ะฟั€ะพัั‚ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `BackgroundTasks`. -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `BackgroundTasks` ัะบ ะฟะฐั€ะฐะผะตั‚ั€ ัƒ *ั„ัƒะฝะบั†ั–ัั… ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ั‚ะฐ ะทะฐะปะตะถะฝะพัั‚ัั…, ั‰ะพะฑ ะดะพะดะฐะฒะฐั‚ะธ ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั–. +ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `BackgroundTasks` ัะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ัƒ *ั„ัƒะฝะบั†ั–ัั… ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ั‚ะฐ ะทะฐะปะตะถะฝะพัั‚ัั…, ั‰ะพะฑ ะดะพะดะฐะฒะฐั‚ะธ ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั–. diff --git a/docs/uk/docs/tutorial/body-fields.md b/docs/uk/docs/tutorial/body-fields.md index 7ddd9d104b..70d94f3d6e 100644 --- a/docs/uk/docs/tutorial/body-fields.md +++ b/docs/uk/docs/tutorial/body-fields.md @@ -1,60 +1,61 @@ -# ะขั–ะปะพ - ะŸะพะปั +# ะขั–ะปะพ โ€” ะŸะพะปั { #body-fields } -ะขะฐะบ ัะฐะผะพ ัะบ ะฒะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ัƒ ะฟะฐั€ะฐะผะตั‚ั€ะฐั… *ั„ัƒะฝะบั†ั–ั— ะพะฑั€ะพะฑะบะธ ัˆะปัั…ัƒ* ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Query`, `Path` ั‚ะฐ `Body`, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะฐั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ัŽ ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ะฒัะตั€ะตะดะธะฝั– ะผะพะดะตะปะตะน Pydantic ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Field` ะฒั–ะด Pydantic. +ะขะฐะบ ัะฐะผะพ ัะบ ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ะฒ ะฟะฐั€ะฐะผะตั‚ั€ะฐั… *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Query`, `Path` ั‚ะฐ `Body`, ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ัŽ ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ะฒัะตั€ะตะดะธะฝั– ะผะพะดะตะปะตะน Pydantic, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `Field` ะฒั–ะด Pydantic. -## ะ†ะผะฟะพั€ั‚ `Field` +## ะ†ะผะฟะพั€ั‚ `Field` { #import-field } ะกะฟะพั‡ะฐั‚ะบัƒ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ ั†ะต: {* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *} -/// warning -ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ `Field` ั–ะผะฟะพั€ั‚ัƒั”ั‚ัŒัั ะฟั€ัะผะพ ะท `pydantic`, ะฐ ะฝะต ะท `fastapi`, ัะบ ะฒัั– ั–ะฝัˆั– (`Query`, `Path`, `Body` ั‚ะพั‰ะพ). +/// warning | ะŸะพะฟะตั€ะตะดะถะตะฝะฝั + +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ `Field` ั–ะผะฟะพั€ั‚ัƒั”ั‚ัŒัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะท `pydantic`, ะฐ ะฝะต ะท `fastapi`, ัะบ ัƒัะต ั–ะฝัˆะต (`Query`, `Path`, `Body` ั‚ะพั‰ะพ). /// -## ะžะณะพะปะพัˆะตะฝะฝั ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ ะผะพะดะตะปั– +## ะžะณะพะปะพัˆะตะฝะฝั ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ ะผะพะดะตะปั– { #declare-model-attributes } -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Field` ะท ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ ะผะพะดะตะปั–: +ะŸะพั‚ั–ะผ ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Field` ะท ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ ะผะพะดะตะปั–: {* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *} -`Field` ะฟั€ะฐั†ัŽั” ั‚ะฐะบ ัะฐะผะพ, ัะบ `Query`, `Path` ั– `Body`, ัƒ ะฝัŒะพะณะพ ั‚ะฐะบั– ัะฐะผั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะพั‰ะพ. +`Field` ะฟั€ะฐั†ัŽั” ั‚ะฐะบ ัะฐะผะพ, ัะบ `Query`, `Path` ั– `Body`, ะผะฐั” ั‚ั– ัะฐะผั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะพั‰ะพ. /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– -ะะฐัะฟั€ะฐะฒะดั–, `Query`, `Path` ั‚ะฐ ั–ะฝัˆั–, ั‰ะพ ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะดะฐะปั–, ัั‚ะฒะพั€ัŽัŽั‚ัŒ ะพะฑ'ั”ะบั‚ะธ ะฟั–ะดะบะปะฐัั–ะฒ ะทะฐะณะฐะปัŒะฝะพะณะพ ะบะปะฐััƒ `Param`, ะบะพั‚ั€ะธะน ัะฐะผ ั” ะฟั–ะดะบะปะฐัะพะผ ะบะปะฐััƒ `FieldInfo` ะท Pydantic. +ะะฐัะฟั€ะฐะฒะดั– `Query`, `Path` ั‚ะฐ ั–ะฝัˆั–, ัะบั– ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะดะฐะปั–, ัั‚ะฒะพั€ัŽัŽั‚ัŒ ะพะฑ'ั”ะบั‚ะธ ะฟั–ะดะบะปะฐัั–ะฒ ัะฟั–ะปัŒะฝะพะณะพ ะบะปะฐััƒ `Param`, ัะบะธะน ัะฐะผ ั” ะฟั–ะดะบะปะฐัะพะผ ะบะปะฐััƒ `FieldInfo` ะท Pydantic. ะ† `Field` ะฒั–ะด Pydantic ั‚ะฐะบะพะถ ะฟะพะฒะตั€ั‚ะฐั” ะตะบะทะตะผะฟะปัั€ `FieldInfo`. -`Body` ั‚ะฐะบะพะถ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฟะพะฒะตั€ั‚ะฐั” ะพะฑ'ั”ะบั‚ะธ ะฟั–ะดะบะปะฐััƒ `FieldInfo`. ะ† ั” ั–ะฝัˆั– ะฟั–ะดะบะปะฐัะธ, ัะบั– ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฟั–ะทะฝั–ัˆะต, ั‰ะพ ั” ะฟั–ะดะบะปะฐัะฐะผะธ ะบะปะฐััƒ Body. +`Body` ั‚ะฐะบะพะถ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฟะพะฒะตั€ั‚ะฐั” ะพะฑ'ั”ะบั‚ะธ ะฟั–ะดะบะปะฐััƒ `FieldInfo`. ะ† ั” ั–ะฝัˆั–, ัะบั– ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฟั–ะทะฝั–ัˆะต, ั‰ะพ ั” ะฟั–ะดะบะปะฐัะฐะผะธ ะบะปะฐััƒ `Body`. -ะŸะฐะผ'ัั‚ะฐะนั‚ะต, ั‰ะพ ะบะพะปะธ ะฒะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต 'Query', 'Path' ั‚ะฐ ั–ะฝัˆะต ะท 'fastapi', ะฒะพะฝะธ ั„ะฐะบั‚ะธั‡ะฝะพ ั” ั„ัƒะฝะบั†ั–ัะผะธ, ัะบั– ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒ ัะฟะตั†ั–ะฐะปัŒะฝั– ะบะปะฐัะธ. +ะŸะฐะผ'ัั‚ะฐะนั‚ะต, ั‰ะพ ะบะพะปะธ ะฒะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต `Query`, `Path` ั‚ะฐ ั–ะฝัˆั– ะท `fastapi`, ั†ะต ั„ะฐะบั‚ะธั‡ะฝะพ ั„ัƒะฝะบั†ั–ั—, ัะบั– ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒ ัะฟะตั†ั–ะฐะปัŒะฝั– ะบะปะฐัะธ. /// -/// tip +/// tip | ะŸะพั€ะฐะดะฐ -ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะบะพะถะตะฝ ะฐั‚ั€ะธะฑัƒั‚ ะผะพะดะตะปั– ั–ะท ั‚ะธะฟะพะผ, ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ั‚ะฐ `Field` ะผะฐั” ั‚ัƒ ัะฐะผัƒ ัั‚ั€ัƒะบั‚ัƒั€ัƒ, ั‰ะพ ะน ะฟะฐั€ะฐะผะตั‚ั€ *ั„ัƒะฝะบั†ั–ั— ะพะฑั€ะพะฑะบะธ ัˆะปัั…ัƒ*, ะท `Field` ะทะฐะผั–ัั‚ัŒ `Path`, `Query` ั– `Body`. +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะบะพะถะตะฝ ะฐั‚ั€ะธะฑัƒั‚ ะผะพะดะตะปั– ะท ั‚ะธะฟะพะผ, ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ั– `Field` ะผะฐั” ั‚ัƒ ัะฐะผัƒ ัั‚ั€ัƒะบั‚ัƒั€ัƒ, ั‰ะพ ะน ะฟะฐั€ะฐะผะตั‚ั€ *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะท `Field` ะทะฐะผั–ัั‚ัŒ `Path`, `Query` ั– `Body`. /// -## ะ”ะพะดะฐะฒะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั— +## ะ”ะพะดะฐะฒะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั— { #add-extra-information } -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ัƒ `Field`, `Query`, `Body` ั‚ะพั‰ะพ. ะ† ะฒะพะฝะฐ ะฑัƒะดะต ะฒะบะปัŽั‡ะตะฝะฐ ัƒ ะทะณะตะฝะตั€ะพะฒะฐะฝัƒ JSON ัั…ะตะผัƒ. +ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะฒ `Field`, `Query`, `Body` ั‚ะพั‰ะพ. ะ† ะฒะพะฝะฐ ะฑัƒะดะต ะฒะบะปัŽั‡ะตะฝะฐ ะดะพ ะทะณะตะฝะตั€ะพะฒะฐะฝะพั— JSON Schema. -ะ’ะธ ะดั–ะทะฝะฐั”ั‚ะตัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ะดะพะดะฐะฒะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั— ะฟั–ะทะฝั–ัˆะต ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—, ะบะพะปะธ ะฒะธะฒั‡ะฐั‚ะธะผะตั‚ะต ะฒะธะทะฝะฐั‡ะตะฝะฝั ะฟั€ะธะบะปะฐะดั–ะฒ. +ะ’ะธ ะดั–ะทะฝะฐั”ั‚ะตัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ะดะพะดะฐะฒะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั— ะฟั–ะทะฝั–ัˆะต ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—, ะบะพะปะธ ะฒะธะฒั‡ะฐั‚ะธะผะตั‚ะต, ัะบ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฟั€ะธะบะปะฐะดะธ. -/// warning +/// warning | ะŸะพะฟะตั€ะตะดะถะตะฝะฝั -ะ”ะพะดะฐั‚ะบะพะฒั– ะบะปัŽั‡ั–, ะฟะตั€ะตะดะฐะฝั– ะฒ `Field`, ั‚ะฐะบะพะถ ะฑัƒะดัƒั‚ัŒ ะฟั€ะธััƒั‚ะฝั– ัƒ ะทะณะตะฝะตั€ะพะฒะฐะฝั–ะน ัั…ะตะผั– OpenAPI ะดะปั ะฒะฐัˆะพะณะพ ะดะพะดะฐั‚ะบะฐ. -ะžัะบั–ะปัŒะบะธ ั†ั– ะบะปัŽั‡ั– ะฝะต ะพะฑะพะฒ'ัะทะบะพะฒะพ ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ั‡ะฐัั‚ะธะฝะพัŽ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OpenAPI, ะดะตัะบั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ OpenAPI, ะฝะฐะฟั€ะธะบะปะฐะด, [OpenAPI ะฒะฐะปั–ะดะฐั‚ะพั€](https://validator.swagger.io/), ะผะพะถัƒั‚ัŒ ะฝะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธ ะท ะฒะฐัˆะพัŽ ะทะณะตะฝะตั€ะพะฒะฐะฝะพัŽ ัั…ะตะผะพัŽ. +ะ”ะพะดะฐั‚ะบะพะฒั– ะบะปัŽั‡ั–, ะฟะตั€ะตะดะฐะฝั– ะฒ `Field`, ั‚ะฐะบะพะถ ะฑัƒะดัƒั‚ัŒ ะฟั€ะธััƒั‚ะฝั– ะฒ ะพั‚ั€ะธะผะฐะฝั–ะน ัั…ะตะผั– OpenAPI ะดะปั ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ. +ะžัะบั–ะปัŒะบะธ ั†ั– ะบะปัŽั‡ั– ะฝะต ะพะฑะพะฒ'ัะทะบะพะฒะพ ั” ั‡ะฐัั‚ะธะฝะพัŽ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OpenAPI, ะดะตัะบั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ OpenAPI, ะฝะฐะฟั€ะธะบะปะฐะด [ะฒะฐะปั–ะดะฐั‚ะพั€ OpenAPI](https://validator.swagger.io/), ะผะพะถัƒั‚ัŒ ะฝะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธ ะท ะฒะฐัˆะพัŽ ะทะณะตะฝะตั€ะพะฒะฐะฝะพัŽ ัั…ะตะผะพัŽ. /// -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Field` ะท Pydantic ะดะปั ะฒะธะทะฝะฐั‡ะตะฝะฝั ะดะพะดะฐั‚ะบะพะฒะธั… ะฟะตั€ะตะฒั–ั€ะพะบ ั‚ะฐ ะผะตั‚ะฐะดะฐะฝะธั… ะดะปั ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ ะผะพะดะตะปั–. +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Field` ะฒั–ะด Pydantic, ั‰ะพะฑ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ะดะปั ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ ะผะพะดะตะปั–. -ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ั–ะผะตะฝะพะฒะฐะฝั– ะฐั€ะณัƒะผะตะฝั‚ะธ ะดะปั ะฟะตั€ะตะดะฐั‡ั– ะดะพะดะฐั‚ะบะพะฒะธั… ะผะตั‚ะฐะดะฐะฝะธั… JSON ัั…ะตะผะธ. +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– keyword arguments, ั‰ะพะฑ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะผะตั‚ะฐะดะฐะฝั– JSON Schema. diff --git a/docs/uk/docs/tutorial/body-multiple-params.md b/docs/uk/docs/tutorial/body-multiple-params.md index e2acf8a703..dc9a768c35 100644 --- a/docs/uk/docs/tutorial/body-multiple-params.md +++ b/docs/uk/docs/tutorial/body-multiple-params.md @@ -1,24 +1,24 @@ -# ะขั–ะปะพ ะทะฐะฟะธั‚ัƒ - ะ”ะตะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ +# ะขั–ะปะพ - ะ”ะตะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ { #body-multiple-parameters } -ะขะตะฟะตั€, ะบะพะปะธ ะผะธ ั€ะพะทะณะปัะฝัƒะปะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `Path` ั‚ะฐ `Query`, ั€ะพะทะณะปัะฝัŒะผะพ ะฑั–ะปัŒัˆ ะฟั€ะพััƒะฝัƒั‚ั– ัะฟะพัะพะฑะธ ะพะณะพะปะพัˆะตะฝะฝั ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ ะฒ **FastAPI**. +ะขะตะฟะตั€, ะบะพะปะธ ะผะธ ะฟะพะฑะฐั‡ะธะปะธ, ัะบ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Path` ั– `Query`, ั€ะพะทะณะปัะฝัŒะผะพ ะฑั–ะปัŒัˆ ะฟั€ะพััƒะฝัƒั‚ั– ะฒะฐั€ั–ะฐะฝั‚ะธ ะพะณะพะปะพัˆะตะฝะฝั ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ. -## ะ—ะผั–ัˆัƒะฒะฐะฝะฝั `Path`, `Query` ั‚ะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ +## ะ—ะผั–ัˆัƒะฒะฐะฝะฝั `Path`, `Query` ั‚ะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ { #mix-path-query-and-body-parameters } -ะŸะพ-ะฟะตั€ัˆะต, ะทะฒั–ัะฝะพ, ะ’ะธ ะผะพะถะตั‚ะต ะฒั–ะปัŒะฝะพ ะทะผั–ัˆัƒะฒะฐั‚ะธ ะพะณะพะปะพัˆะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `Path`, `Query` ั‚ะฐ ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ, ั– **FastAPI** ะฟั€ะฐะฒะธะปัŒะฝะพ ั—ั… ะพะฑั€ะพะฑะธั‚ัŒ. +ะŸะพ-ะฟะตั€ัˆะต, ะทะฒั–ัะฝะพ, ะฒะธ ะผะพะถะตั‚ะต ะฒั–ะปัŒะฝะพ ะทะผั–ัˆัƒะฒะฐั‚ะธ ะพะณะพะปะพัˆะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `Path`, `Query` ั‚ะฐ ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ, ั– **FastAPI** ะทะฝะฐั‚ะธะผะต, ั‰ะพ ั€ะพะฑะธั‚ะธ. -ะขะฐะบะพะถ ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ั–ะปะฐ ัะบ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒั–, ะฒัั‚ะฐะฝะพะฒะธะฒัˆะธ ะดะปั ะฝะธั… ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `None`: +ะขะฐะบะพะถ ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ั–ะปะฐ ัะบ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒั–, ะฒัั‚ะฐะฝะพะฒะธะฒัˆะธ ะดะปั ะฝะธั… ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `None`: {* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} /// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะฒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ะฟะฐั€ะฐะผะตั‚ั€ `item`, ัะบะธะน ะฑะตั€ะตั‚ัŒัั ะท ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ, ั” ะฝะตะพะฑะพะฒ'ัะทะบะพะฒะธะผ, ะพัะบั–ะปัŒะบะธ ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `None`. +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะฒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ะฟะฐั€ะฐะผะตั‚ั€ `item`, ัะบะธะน ะฑะตั€ะตั‚ัŒัั ะท ั‚ั–ะปะฐ, ั” ะฝะตะพะฑะพะฒ'ัะทะบะพะฒะธะผ. ะžัะบั–ะปัŒะบะธ ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `None`. /// -## ะ”ะตะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ +## ะ”ะตะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ { #multiple-body-parameters } -ะฃ ะฟะพะฟะตั€ะตะดะฝัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั– *ะพะฟะตั€ะฐั†ั–ั ัˆะปัั…ัƒ* ะพั‡ั–ะบัƒะฒะฐะปะฐ JSON ะท ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ `Item`, ะฝะฐะฟั€ะธะบะปะฐะด: +ะฃ ะฟะพะฟะตั€ะตะดะฝัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั– *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะพั‡ั–ะบัƒะฒะฐะปะธ ะฑ JSON-ั‚ั–ะปะพ ะท ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ `Item`, ะฝะฐะฟั€ะธะบะปะฐะด: ```JSON { @@ -28,13 +28,15 @@ "tax": 3.2 } ``` -ะะปะต ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะดะตะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ, ะฝะฐะฟั€ะธะบะปะฐะด `item` ั‚ะฐ `user`: + +ะะปะต ะฒะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะดะตะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ, ะฝะฐะฟั€ะธะบะปะฐะด `item` ั‚ะฐ `user`: {* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ **FastAPI** ั€ะพะทะฟั–ะทะฝะฐั”, ั‰ะพ ั” ะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ (ะดะฒะฐ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั” ะผะพะดะตะปัะผะธ Pydantic). -ะขะพะผัƒ ะฒั–ะฝ ะฒะธะบะพั€ะธัั‚ะฐั” ะฝะฐะทะฒะธ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัะบ ะบะปัŽั‡ั– (ะฝะฐะทะฒะธ ะฟะพะปั–ะฒ) ัƒ ั‚ั–ะปั– ะทะฐะฟะธั‚ัƒ, ะพั‡ั–ะบัƒัŽั‡ะธ: +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ **FastAPI** ะฟะพะผั–ั‚ะธั‚ัŒ, ั‰ะพ ัƒ ั„ัƒะฝะบั†ั–ั— ั” ะฑั–ะปัŒัˆะต ะฝั–ะถ ะพะดะธะฝ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ั–ะปะฐ (ั” ะดะฒะฐ ะฟะฐั€ะฐะผะตั‚ั€ะธ, ัะบั– ั” ะผะพะดะตะปัะผะธ Pydantic). + +ะขะพะถ ะฒั–ะฝ ะฒะธะบะพั€ะธัั‚ะฐั” ะฝะฐะทะฒะธ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัะบ ะบะปัŽั‡ั– (ะฝะฐะทะฒะธ ะฟะพะปั–ะฒ) ัƒ ั‚ั–ะปั– ั‚ะฐ ะพั‡ั–ะบัƒะฒะฐั‚ะธะผะต ั‚ั–ะปะพ ั‚ะฐะบะพะณะพ ะฒะธะณะปัะดัƒ: ```JSON { @@ -53,27 +55,28 @@ /// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ั…ะพั‡ะฐ `item` ะพะณะพะปะพัˆะตะฝะพ, ั‚ะฐะบ ัะฐะผะพ ัะบ ั– ั€ะฐะฝั–ัˆะต, ั‚ะตะฟะตั€ ะฒั–ะฝ ะพั‡ั–ะบัƒั”ั‚ัŒัั ะฒ ั‚ั–ะปั– ะฟั–ะด ะบะปัŽั‡ะตะผ `item`. +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ั…ะพั‡ะฐ `item` ะพะณะพะปะพัˆะตะฝะพ ั‚ะฐะบ ัะฐะผะพ, ัะบ ั– ั€ะฐะฝั–ัˆะต, ั‚ะตะฟะตั€ ะฒั–ะฝ ะพั‡ั–ะบัƒั”ั‚ัŒัั ะฒัะตั€ะตะดะธะฝั– ั‚ั–ะปะฐ ะท ะบะปัŽั‡ะตะผ `item`. /// -**FastAPI** ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะพะฝะฒะตั€ั‚ัƒั” ะดะฐะฝั– ั–ะท ะทะฐะฟะธั‚ัƒ ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ, ั‰ะพะฑ ะฟะฐั€ะฐะผะตั‚ั€ `item` ะพั‚ั€ะธะผะฐะฒ ัะฒั–ะน ะฒะผั–ัั‚, ั– ั‚ะต ะถ ัะฐะผะต ัั‚ะพััƒั”ั‚ัŒัั `user`. +**FastAPI** ะฒะธะบะพะฝะฐั” ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั–ะท ะทะฐะฟะธั‚ัƒ, ั‰ะพะฑ ะฟะฐั€ะฐะผะตั‚ั€ `item` ะพั‚ั€ะธะผะฐะฒ ัะฒั–ะน ะบะพะฝะบั€ะตั‚ะฝะธะน ะฒะผั–ัั‚, ั– ั‚ะต ะถ ัะฐะผะต ะดะปั `user`. -ะ’ั–ะฝ ะฒะธะบะพะฝะฐั” ะฒะฐะปั–ะดะฐั†ั–ัŽ ัะบะปะฐะดะตะฝะธั… ะดะฐะฝะธั… ั– ะทะฐะดะพะบัƒะผะตะฝั‚ัƒั” ั—ั… ะฒั–ะดะฟะพะฒั–ะดะฝะธะผ ั‡ะธะฝะพะผ ัƒ ัั…ะตะผั– OpenAPI ั‚ะฐ ะฒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝั–ะน ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. +ะ’ั–ะฝ ะฒะธะบะพะฝะฐั” ะฒะฐะปั–ะดะฐั†ั–ัŽ ัะบะปะฐะดะตะฝะธั… ะดะฐะฝะธั… ั– ะทะฐะดะพะบัƒะผะตะฝั‚ัƒั” ั†ะต ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ ัƒ ัั…ะตะผั– OpenAPI ั‚ะฐ ะฒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝั–ะน ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. -## ะžะดะธะฝะธั‡ะฝั– ะทะฝะฐั‡ะตะฝะฝั ะฒ ั‚ั–ะปั– ะทะฐะฟะธั‚ัƒ +## ะžะดะธะฝะธั‡ะฝั– ะทะฝะฐั‡ะตะฝะฝั ะฒ ั‚ั–ะปั– { #singular-values-in-body } ะขะฐะบ ัะฐะผะพ ัะบ ั” `Query` ั– `Path` ะดะปั ะฒะธะทะฝะฐั‡ะตะฝะฝั ะดะพะดะฐั‚ะบะพะฒะธั… ะดะฐะฝะธั… ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะฟะธั‚ัƒ ั‚ะฐ ัˆะปัั…ัƒ, **FastAPI** ะฝะฐะดะฐั” ะตะบะฒั–ะฒะฐะปะตะฝั‚ะฝะธะน `Body`. -ะะฐะฟั€ะธะบะปะฐะด, ั€ะพะทัˆะธั€ัŽัŽั‡ะธ ะฟะพะฟะตั€ะตะดะฝัŽ ะผะพะดะตะปัŒ, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธั€ั–ัˆะธั‚ะธ ะดะพะดะฐั‚ะธ ั‰ะต ะพะดะธะฝ ะบะปัŽั‡ `importance` ะฒ ั‚ะต ะถ ัะฐะผะต ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ ั€ะฐะทะพะผ ั–ะท `item` ั– `user`. +ะะฐะฟั€ะธะบะปะฐะด, ั€ะพะทัˆะธั€ะธะฒัˆะธ ะฟะพะฟะตั€ะตะดะฝัŽ ะผะพะดะตะปัŒ, ะฒะธ ะผะพะถะตั‚ะต ะฒะธั€ั–ัˆะธั‚ะธ ะดะพะดะฐั‚ะธ ั‰ะต ะพะดะธะฝ ะบะปัŽั‡ `importance` ัƒ ั‚ะต ัะฐะผะต ั‚ั–ะปะพ, ะพะบั€ั–ะผ `item` ั– `user`. -ะฏะบั‰ะพ ะ’ะธ ะพะณะพะปะพัะธั‚ะต ะนะพะณะพ ัะบ ั”, ั‚ะพ, ะพัะบั–ะปัŒะบะธ ั†ะต ะพะดะธะฝะธั‡ะฝะต ะทะฝะฐั‡ะตะฝะฝั, **FastAPI** ะฟั€ะธะฟัƒัะบะฐั‚ะธะผะต, ั‰ะพ ั†ะต ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ (query parameter). +ะฏะบั‰ะพ ะพะณะพะปะพัะธั‚ะธ ะนะพะณะพ ัะบ ั”, ะพัะบั–ะปัŒะบะธ ั†ะต ะพะดะธะฝะธั‡ะฝะต ะทะฝะฐั‡ะตะฝะฝั, **FastAPI** ะฟั€ะธะฟัƒัั‚ะธั‚ัŒ, ั‰ะพ ั†ะต ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ. -ะะปะต ะ’ะธ ะผะพะถะตั‚ะต ะฒะบะฐะทะฐั‚ะธ **FastAPI** ะพะฑั€ะพะฑะปัั‚ะธ ะนะพะณะพ ัะบ ั–ะฝัˆะธะน ะบะปัŽั‡ ั‚ั–ะปะฐ (body key), ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `Body`: +ะะปะต ะฒะธ ะผะพะถะตั‚ะต ะฒะบะฐะทะฐั‚ะธ **FastAPI** ะพะฑั€ะพะฑะปัั‚ะธ ะนะพะณะพ ัะบ ั–ะฝัˆะธะน ะบะปัŽั‡ ั‚ั–ะปะฐ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `Body`: {* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ **FastAPI** ะพั‡ั–ะบัƒะฒะฐั‚ะธะผะต ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ ัƒ ั‚ะฐะบะพะผัƒ ะฒะธะณะปัะดั–: + +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ **FastAPI** ะพั‡ั–ะบัƒะฒะฐั‚ะธะผะต ั‚ั–ะปะพ ั‚ะฐะบะพะณะพ ะฒะธะณะปัะดัƒ: ```JSON { @@ -90,19 +93,20 @@ "importance": 5 } ``` -ะ—ะฝะพะฒัƒ ะถ ั‚ะฐะบะธ, **FastAPI** ะบะพะฝะฒะตั€ั‚ัƒะฒะฐั‚ะธะผะต ั‚ะธะฟะธ ะดะฐะฝะธั…, ะฟะตั€ะตะฒั–ั€ัั‚ะธะผะต ั—ั…, ัั‚ะฒะพั€ัŽะฒะฐั‚ะธะผะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ั‚ะพั‰ะพ. -## ะ”ะตะบั–ะปัŒะบะฐ body ั‚ะฐ query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ +ะ—ะฝะพะฒัƒ ะถ ั‚ะฐะบะธ, ะฒั–ะฝ ะฟะตั€ะตั‚ะฒะพั€ัŽะฒะฐั‚ะธะผะต ั‚ะธะฟะธ ะดะฐะฝะธั…, ะฟะตั€ะตะฒั–ั€ัั‚ะธะผะต, ะดะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธะผะต ั‚ะพั‰ะพ. -ะ—ะฒั–ัะฝะพ, ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– query ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฟะธั‚ัƒ, ะบะพะปะธ ั†ะต ะฝะตะพะฑั…ั–ะดะฝะพ, ะฝะฐ ะดะพะดะฐั‚ะพะบ ะดะพ ะฑัƒะดัŒ-ัะบะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ. +## ะ”ะตะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ ั‚ะฐ query { #multiple-body-params-and-query } -ะžัะบั–ะปัŒะบะธ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะพะบั€ะตะผั– ะทะฝะฐั‡ะตะฝะฝั ั–ะฝั‚ะตั€ะฟั€ะตั‚ัƒัŽั‚ัŒัั ัะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฟะธั‚ัƒ, ะ’ะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ัะฒะฝะพ ะดะพะดะฐะฒะฐั‚ะธ `Query`, ะผะพะถะฝะฐ ะฟั€ะพัั‚ะพ ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ: +ะ—ะฒั–ัะฝะพ, ะฒะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– query ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‰ะพั€ะฐะทัƒ, ะบะพะปะธ ั†ะต ะฟะพั‚ั€ั–ะฑะฝะพ, ะดะพะดะฐั‚ะบะพะฒะพ ะดะพ ะฑัƒะดัŒ-ัะบะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ. + +ะžัะบั–ะปัŒะบะธ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะพะดะธะฝะธั‡ะฝั– ะทะฝะฐั‡ะตะฝะฝั ั–ะฝั‚ะตั€ะฟั€ะตั‚ัƒัŽั‚ัŒัั ัะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฟะธั‚ัƒ, ะฒะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ัะฒะฝะพ ะดะพะดะฐะฒะฐั‚ะธ `Query`, ะฒะธ ะผะพะถะตั‚ะต ะฟั€ะพัั‚ะพ ะทั€ะพะฑะธั‚ะธ: ```Python q: Union[str, None] = None ``` -ะะฑะพ ะฒ Python 3.10 ั‚ะฐ ะฒะธั‰ะต: +ะะฑะพ ะฒ Python 3.10 ั– ะฒะธั‰ะต: ```Python q: str | None = None @@ -115,17 +119,17 @@ q: str | None = None /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -`Body` ั‚ะฐะบะพะถ ะผะฐั” ั‚ั– ัะฐะผั– ะดะพะดะฐั‚ะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฒะฐะปั–ะดะฐั†ั–ั— ั‚ะฐ ะผะตั‚ะฐะดะฐะฝะธั…, ั‰ะพ ะน `Query`, `Path` ั‚ะฐ ั–ะฝัˆั–, ัะบั– ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฟั–ะทะฝั–ัˆะต. +`Body` ั‚ะฐะบะพะถ ะผะฐั” ะฒัั– ั‚ั– ัะฐะผั– ะดะพะดะฐั‚ะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฒะฐะปั–ะดะฐั†ั–ั— ั‚ะฐ ะผะตั‚ะฐะดะฐะฝะธั…, ั‰ะพ ะน `Query`, `Path` ั‚ะฐ ั–ะฝัˆั–, ัะบั– ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฟั–ะทะฝั–ัˆะต. /// -## ะ’ะบะปะฐะดะตะฝะธะน ะฟะพะพะดะธะฝะพะบะธะน ะฟะฐั€ะฐะผะตั‚ั€ ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ +## ะ’ะฑัƒะดัƒะฒะฐั‚ะธ ะพะดะธะฝ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ั–ะปะฐ { #embed-a-single-body-parameter } -ะŸั€ะธะฟัƒัั‚ะธะผะพ, ัƒ ะฒะฐั ั” ะปะธัˆะต ะพะดะธะฝ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ `item` ะท ะผะพะดะตะปั– Pydantic `Item`. +ะกะบะฐะถั–ะผะพ, ัƒ ะฒะฐั ั” ะปะธัˆะต ะพะดะธะฝ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ั–ะปะฐ `item` ะท ะผะพะดะตะปั– Pydantic `Item`. -ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ **FastAPI** ะพั‡ั–ะบัƒะฒะฐั‚ะธะผะต, ั‰ะพ ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ ะผั–ัั‚ะธั‚ะธะผะต ะฒะผั–ัั‚ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ. +ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ **FastAPI** ะพั‡ั–ะบัƒะฒะฐั‚ะธะผะต ะนะพะณะพ ั‚ั–ะปะพ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ. -ะะปะต ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะฒั–ะฝ ะพั‡ั–ะบัƒะฒะฐะฒ JSON ะท ะบะปัŽั‡ะตะผ `item`, ะฐ ะฒัะตั€ะตะดะธะฝั– โ€” ะฒะผั–ัั‚ ะผะพะดะตะปั– (ั‚ะฐะบ, ัะบ ั†ะต ะฒั–ะดะฑัƒะฒะฐั”ั‚ัŒัั ะฟั€ะธ ะพะณะพะปะพัˆะตะฝะฝั– ะดะพะดะฐั‚ะบะพะฒะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ), ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ัะฟะตั†ั–ะฐะปัŒะฝะธะน ะฟะฐั€ะฐะผะตั‚ั€ `Body` โ€” `embed`: +ะะปะต ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะฒั–ะฝ ะพั‡ั–ะบัƒะฒะฐะฒ JSON ะท ะบะปัŽั‡ะตะผ `item`, ะฐ ะฒัะตั€ะตะดะธะฝั– ะฝัŒะพะณะพ โ€” ะฒะผั–ัั‚ ะผะพะดะตะปั–, ัะบ ั†ะต ะฒั–ะดะฑัƒะฒะฐั”ั‚ัŒัั, ะบะพะปะธ ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ะดะพะดะฐั‚ะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ั–ะปะฐ, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ัะฟะตั†ั–ะฐะปัŒะฝะธะน ะฟะฐั€ะฐะผะตั‚ั€ `Body` โ€” `embed`: ```Python item: Item = Body(embed=True) @@ -135,7 +139,8 @@ item: Item = Body(embed=True) {* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *} -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ **FastAPI** ะพั‡ั–ะบัƒะฒะฐั‚ะธะผะต ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ ั‚ะฐะบะพะณะพ ะฒะธะณะปัะดัƒ: + +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ **FastAPI** ะพั‡ั–ะบัƒะฒะฐั‚ะธะผะต ั‚ั–ะปะพ ั‚ะฐะบะพะณะพ ะฒะธะณะปัะดัƒ: ```JSON hl_lines="2" { @@ -159,12 +164,12 @@ item: Item = Body(embed=True) } ``` -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐะฒะฐั‚ะธ ะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ ะดะพ ะ’ะฐัˆะพั— *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* (*path operation function*), ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะทะฐะฟะธั‚ ะผะพะถะต ะผะฐั‚ะธ ะปะธัˆะต ะพะดะฝะต ั‚ั–ะปะพ. +ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐะฒะฐั‚ะธ ะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั‚ั–ะปะฐ ะดะพ ะฒะฐัˆะพั— *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะทะฐะฟะธั‚ ะผะพะถะต ะผะฐั‚ะธ ะปะธัˆะต ะพะดะฝะต ั‚ั–ะปะพ. -ะะปะต **FastAPI** ะพะฑั€ะพะฑะธั‚ัŒ ั†ะต, ะฝะฐะดะฐัั‚ัŒ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝั– ะดะฐะฝั– ัƒ ั„ัƒะฝะบั†ั–ั—, ะฟะตั€ะตะฒั–ั€ะธั‚ัŒ ั—ั… ั‚ะฐ ะทะฐะดะพะบัƒะผะตะฝั‚ัƒั” ะบะพั€ะตะบั‚ะฝัƒ ัั…ะตะผัƒ ะฒ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*. +ะะปะต **FastAPI** ะพะฑั€ะพะฑะธั‚ัŒ ั†ะต, ะฝะฐะดะฐัั‚ัŒ ะฒะฐะผ ะฟั€ะฐะฒะธะปัŒะฝั– ะดะฐะฝั– ัƒ ั„ัƒะฝะบั†ั–ั— ั‚ะฐ ะฟะตั€ะตะฒั–ั€ะธั‚ัŒ ั– ะทะฐะดะพะบัƒะผะตะฝั‚ัƒั” ะฟั€ะฐะฒะธะปัŒะฝัƒ ัั…ะตะผัƒ ะฒ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*. -ะขะฐะบะพะถ ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะพะบั€ะตะผั– ะทะฝะฐั‡ะตะฝะฝั, ัะบั– ะฑัƒะดัƒั‚ัŒ ะพั‚ั€ะธะผะฐะฝั– ัะบ ั‡ะฐัั‚ะธะฝะฐ ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ. +ะขะฐะบะพะถ ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะพะดะธะฝะธั‡ะฝั– ะทะฝะฐั‡ะตะฝะฝั, ั‰ะพะฑ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ั—ั… ัะบ ั‡ะฐัั‚ะธะฝัƒ ั‚ั–ะปะฐ. -ะšั€ั–ะผ ั‚ะพะณะพ, ะ’ะธ ะผะพะถะตั‚ะต ะฒะบะฐะทะฐั‚ะธ **FastAPI** ะฒะฑัƒะดะพะฒัƒะฒะฐั‚ะธ ั‚ั–ะปะพ ะฒ ะบะปัŽั‡, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะพะณะพะปะพัˆะตะฝะพ ะปะธัˆะต ะพะดะธะฝ ะฟะฐั€ะฐะผะตั‚ั€. +ะ† ะฒะธ ะผะพะถะตั‚ะต ะฒะบะฐะทะฐั‚ะธ **FastAPI** ะฒะฑัƒะดะพะฒัƒะฒะฐั‚ะธ ั‚ั–ะปะพ ะฒ ะบะปัŽั‡, ะฝะฐะฒั–ั‚ัŒ ะบะพะปะธ ะพะณะพะปะพัˆะตะฝะพ ะปะธัˆะต ะพะดะธะฝ ะฟะฐั€ะฐะผะตั‚ั€. diff --git a/docs/uk/docs/tutorial/body-nested-models.md b/docs/uk/docs/tutorial/body-nested-models.md index abc33f2eb3..6d0669358a 100644 --- a/docs/uk/docs/tutorial/body-nested-models.md +++ b/docs/uk/docs/tutorial/body-nested-models.md @@ -1,8 +1,8 @@ -# ะขั–ะปะพ ะทะฐะฟะธั‚ัƒ - ะ’ะบะปะฐะดะตะฝั– ะผะพะดะตะปั– +# ะขั–ะปะพ - ะ’ะบะปะฐะดะตะฝั– ะผะพะดะตะปั– { #body-nested-models } -ะ— **FastAPI** ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะฐั‚ะธ, ะฟะตั€ะตะฒั–ั€ัั‚ะธ, ะดะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธ ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดะตะปั–, ัะบั– ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะฒะบะปะฐะดะตะฝั– ะฝะฐ ะฑัƒะดัŒ-ัะบัƒ ะณะปะธะฑะธะฝัƒ (ะทะฐะฒะดัะบะธ Pydantic). +ะ— **FastAPI** ะฒะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะฐั‚ะธ, ะฟะตั€ะตะฒั–ั€ัั‚ะธ, ะดะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธ ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดะตะปั–, ัะบั– ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะฒะบะปะฐะดะตะฝั– ะฝะฐ ะฑัƒะดัŒ-ัะบัƒ ะณะปะธะฑะธะฝัƒ (ะทะฐะฒะดัะบะธ Pydantic). -## ะŸะพะปั ัะฟะธัะบัƒ +## ะŸะพะปั ัะฟะธัะบัƒ { #list-fields } ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ะฐั‚ั€ะธะฑัƒั‚ ัะบ ะฟั–ะดั‚ะธะฟ. ะะฐะฟั€ะธะบะปะฐะด, Python-ัะฟะธัะพะบ (`list`): @@ -10,47 +10,28 @@ ะฆะต ะทั€ะพะฑะธั‚ัŒ `tags` ัะฟะธัะบะพะผ, ั…ะพั‡ะฐ ะฝะต ะฒะธะทะฝะฐั‡ะฐั”ั‚ัŒัั ั‚ะธะฟ ะตะปะตะผะตะฝั‚ั–ะฒ ัะฟะธัะบัƒ. -## ะŸะพะปั ัะฟะธัะบัƒ ะท ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ั‚ะธะฟัƒ +## ะŸะพะปั ัะฟะธัะบัƒ ะท ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ั‚ะธะฟัƒ { #list-fields-with-type-parameter } -ะะปะต Python ะผะฐั” ัะฟะตั†ะธั„ั–ั‡ะฝะธะน ัะฟะพัั–ะฑ ะพะณะพะปะพัˆะตะฝะฝั ัะฟะธัะบั–ะฒ ะท ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะผะธ ั‚ะธะฟะฐะผะธ ะฐะฑะพ "ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ั‚ะธะฟัƒ": -### ะ†ะผะฟะพั€ั‚ัƒั”ะผะพ `List` ะท ะผะพะดัƒะปั typing +ะะปะต Python ะผะฐั” ัะฟะตั†ะธั„ั–ั‡ะฝะธะน ัะฟะพัั–ะฑ ะพะณะพะปะพัˆะตะฝะฝั ัะฟะธัะบั–ะฒ ะท ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะผะธ ั‚ะธะฟะฐะผะธ ะฐะฑะพ ยซะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ั‚ะธะฟัƒยป: -ะฃ Python 3.9 ั– ะฒะธั‰ะต ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน `list` ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ั‚ะฐะบะธั… ั‚ะธะฟั–ะฒ, ัะบ ะผะธ ะฟะพะฑะฐั‡ะธะผะพ ะฝะธะถั‡ะต. ๐Ÿ’ก +### ะžะณะพะปะพัˆะตะฝะฝั `list` ะท ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ั‚ะธะฟัƒ { #declare-a-list-with-a-type-parameter } -ะะปะต ะฒ Python ะฒะตั€ัั–ั— ะดะพ 3.9 (ะฒั–ะด 3.6 ั– ะฒะธั‰ะต) ัะฟะพั‡ะฐั‚ะบัƒ ะฟะพั‚ั€ั–ะฑะฝะพ ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ `List` ะท ะผะพะดัƒะปั ัั‚ะฐะฝะดะฐั€ั‚ะฝะพั— ะฑั–ะฑะปั–ะพั‚ะตะบะธ Python `typing`: - -{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} - -### ะžะณะพะปะพัˆะตะฝะฝั `list` ะท ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ั‚ะธะฟัƒ - -ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ ั‚ะธะฟะธ ะท ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ั‚ะธะฟัƒ (ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะผะธ ั‚ะธะฟะฐะผะธ), ั‚ะฐะบะธะผะธ ัะบ `list`, `dict`, `tuple`: - -* ะฏะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ะฒะตั€ัั–ัŽ Python ะดะพ 3.9, ั–ะผะฟะพั€ั‚ัƒะนั‚ะต ั—ั… ะฒั–ะดะฟะพะฒั–ะดะฝัƒ ะฒะตั€ัั–ัŽ ะท ะผะพะดัƒะปั `typing`. -* ะŸะตั€ะตะดะฐะนั‚ะต ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟะธ ัะบ "ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะธะฟัƒ", ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะบะฒะฐะดั€ะฐั‚ะฝั– ะดัƒะถะบะธ: `[` and `]`. - -ะฃ Python 3.9 ั†ะต ะฑัƒะดะต ะฒะธะณะปัะดะฐั‚ะธ ั‚ะฐะบ: +ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ ั‚ะธะฟะธ ะท ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ั‚ะธะฟัƒ (ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะผะธ ั‚ะธะฟะฐะผะธ), ั‚ะฐะบะธะผะธ ัะบ `list`, `dict`, `tuple`, +ะฟะตั€ะตะดะฐะนั‚ะต ะฒะฝัƒั‚ั€ั–ัˆะฝั– ั‚ะธะฟ(ะธ) ัะบ ยซะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะธะฟัƒยป, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะบะฒะฐะดั€ะฐั‚ะฝั– ะดัƒะถะบะธ: `[` and `]` ```Python my_list: list[str] ``` -ะฃ ะฒะตั€ัั–ัั… Python ะดะพ 3.9 ั†ะต ะฒะธะณะปัะดะฐั” ั‚ะฐะบ: - -```Python -from typing import List - -my_list: List[str] -``` - ะฆะต ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ัะธะฝั‚ะฐะบัะธั Python ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ั‚ะธะฟั–ะฒ. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ั‚ะพะน ัะฐะผะธะน ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ัะธะฝั‚ะฐะบัะธั ะดะปั ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ ะผะพะดะตะปะตะน ะท ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะผะธ ั‚ะธะฟะฐะผะธ. -ะžั‚ะถะต, ัƒ ะฝะฐัˆะพะผัƒ ะฟั€ะธะบะปะฐะดั–, ะผะธ ะผะพะถะตะผะพ ะทั€ะพะฑะธั‚ะธ `tags` ัะฐะผะต "ัะฟะธัะบะพะผ ั€ัะดะบั–ะฒ": +ะžั‚ะถะต, ัƒ ะฝะฐัˆะพะผัƒ ะฟั€ะธะบะปะฐะดั–, ะผะธ ะผะพะถะตะผะพ ะทั€ะพะฑะธั‚ะธ `tags` ัะฐะผะต ยซัะฟะธัะบะพะผ ั€ัะดะบั–ะฒยป: {* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} -## ะขะธะฟะธ ะผะฝะพะถะธะฝ +## ะขะธะฟะธ ะผะฝะพะถะธะฝ { #set-types } ะะปะต ะฟะพั‚ั–ะผ ะผะธ ะฟะพะดัƒะผะฐะปะธ, ั‰ะพ ั‚ะตะณะธ ะฝะต ะฟะพะฒะธะฝะฝั– ะฟะพะฒั‚ะพั€ัŽะฒะฐั‚ะธัั, ะฒะพะฝะธ, ะนะผะพะฒั–ั€ะฝะพ, ะฟะพะฒะธะฝะฝั– ะฑัƒั‚ะธ ัƒะฝั–ะบะฐะปัŒะฝะธะผะธ ั€ัะดะบะฐะผะธ. @@ -60,29 +41,29 @@ my_list: List[str] {* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *} -ะะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะทะฐะฟะธั‚ ะท ะดัƒะฑะปัŒะพะฒะฐะฝะธะผะธ ะดะฐะฝะธะผะธ, ะฒั–ะฝ ะฑัƒะดะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะธะน ัƒ ะผะฝะพะถะธะฝัƒ ัƒะฝั–ะบะฐะปัŒะฝะธั… ะตะปะตะผะตะฝั‚ั–ะฒ. +ะะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะทะฐะฟะธั‚ ะท ะดัƒะฑะปัŒะพะฒะฐะฝะธะผะธ ะดะฐะฝะธะผะธ, ะฒั–ะฝ ะฑัƒะดะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะธะน ัƒ ะผะฝะพะถะธะฝัƒ ัƒะฝั–ะบะฐะปัŒะฝะธั… ะตะปะตะผะตะฝั‚ั–ะฒ. -ะ† ะบะพะปะธ ะ’ะธ ะฑัƒะดะตั‚ะต ะฒะธะฒะพะดะธั‚ะธ ั†ั– ะดะฐะฝั–, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะดะถะตั€ะตะปะพ ะผั–ัั‚ะธั‚ัŒ ะดัƒะฑะปั–ะบะฐั‚ะธ, ะฒะพะฝะธ ะฑัƒะดัƒั‚ัŒ ะฒะธะฒะตะดะตะฝั– ัะบ ะผะฝะพะถะธะฝะฐ ัƒะฝั–ะบะฐะปัŒะฝะธั… ะตะปะตะผะตะฝั‚ั–ะฒ. +ะ† ะบะพะปะธ ะฒะธ ะฑัƒะดะตั‚ะต ะฒะธะฒะพะดะธั‚ะธ ั†ั– ะดะฐะฝั–, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะดะถะตั€ะตะปะพ ะผั–ัั‚ะธั‚ัŒ ะดัƒะฑะปั–ะบะฐั‚ะธ, ะฒะพะฝะธ ะฑัƒะดัƒั‚ัŒ ะฒะธะฒะตะดะตะฝั– ัะบ ะผะฝะพะถะธะฝะฐ ัƒะฝั–ะบะฐะปัŒะฝะธั… ะตะปะตะผะตะฝั‚ั–ะฒ. ะ† ั†ะต ะฑัƒะดะต ะฐะฝะพั‚ะพะฒะฐะฝะพ/ะดะพะบัƒะผะตะฝั‚ะพะฒะฐะฝะพ ะฒั–ะดะฟะพะฒั–ะดะฝะพ. -## ะ’ะบะปะฐะดะตะฝั– ะผะพะดะตะปั– +## ะ’ะบะปะฐะดะตะฝั– ะผะพะดะตะปั– { #nested-models } ะšะพะถะตะฝ ะฐั‚ั€ะธะฑัƒั‚ ะผะพะดะตะปั– Pydantic ะผะฐั” ั‚ะธะฟ. ะะปะต ั†ะตะน ั‚ะธะฟ ัะฐะผ ะผะพะถะต ะฑัƒั‚ะธ ั–ะฝัˆะพัŽ ะผะพะดะตะปะปัŽ Pydantic. -ะžั‚ะถะต, ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะณะปะธะฑะพะบะพ ะฒะบะปะฐะดะตะฝั– JSON "ะพะฑ'ั”ะบั‚ะธ" ะท ะบะพะฝะบั€ะตั‚ะฝะธะผะธ ั–ะผะตะฝะฐะผะธ ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ, ั‚ะธะฟะฐะผะธ ั‚ะฐ ะฟะตั€ะตะฒั–ั€ะบะฐะผะธ. +ะžั‚ะถะต, ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะณะปะธะฑะพะบะพ ะฒะบะปะฐะดะตะฝั– JSON ยซะพะฑ'ั”ะบั‚ะธยป ะท ะบะพะฝะบั€ะตั‚ะฝะธะผะธ ั–ะผะตะฝะฐะผะธ ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ, ั‚ะธะฟะฐะผะธ ั‚ะฐ ะฟะตั€ะตะฒั–ั€ะบะฐะผะธ. ะฃัะต ั†ะต, ะฒะบะปะฐะดะตะฝะต ะฑะตะท ะพะฑะผะตะถะตะฝัŒ. -### ะ’ะธะทะฝะฐั‡ะตะฝะฝั ะฟั–ะดะผะพะดะตะปั– +### ะ’ะธะทะฝะฐั‡ะตะฝะฝั ะฟั–ะดะผะพะดะตะปั– { #define-a-submodel } ะะฐะฟั€ะธะบะปะฐะด, ะผะธ ะผะพะถะตะผะพ ะฒะธะทะฝะฐั‡ะธั‚ะธ ะผะพะดะตะปัŒ `Image`: {* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} -### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟั–ะดะผะพะดะตะปั– ัะบ ั‚ะธะฟัƒ +### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟั–ะดะผะพะดะตะปั– ัะบ ั‚ะธะฟัƒ { #use-the-submodel-as-a-type } ะ ะฟะพั‚ั–ะผ ะผะธ ะผะพะถะตะผะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั—ั— ัะบ ั‚ะธะฟ ะฐั‚ั€ะธะฑัƒั‚ะฐ: @@ -104,16 +85,16 @@ my_list: List[str] } ``` -ะ—ะฐะฒะดัะบะธ ั‚ะฐะบั–ะน ะดะตะบะปะฐั€ะฐั†ั–ั— ัƒ **FastAPI** ะ’ะธ ะพั‚ั€ะธะผัƒั”ั‚ะต: +ะ—ะฐะฒะดัะบะธ ั‚ะฐะบั–ะน ะดะตะบะปะฐั€ะฐั†ั–ั— ัƒ **FastAPI** ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต: * ะŸั–ะดั‚ั€ะธะผะบัƒ ะฒ ั€ะตะดะฐะบั‚ะพั€ั– (ะฐะฒั‚ะพะทะฐะฒะตั€ัˆะตะฝะฝั ั‚ะพั‰ะพ), ะฝะฐะฒั–ั‚ัŒ ะดะปั ะฒะบะปะฐะดะตะฝะธั… ะผะพะดะตะปะตะน * ะšะพะฝะฒะตั€ั‚ะฐั†ั–ัŽ ะดะฐะฝะธั… * ะ’ะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั… * ะะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ -## ะกะฟะตั†ั–ะฐะปัŒะฝั– ั‚ะธะฟะธ ั‚ะฐ ะฒะฐะปั–ะดะฐั†ั–ั +## ะกะฟะตั†ั–ะฐะปัŒะฝั– ั‚ะธะฟะธ ั‚ะฐ ะฒะฐะปั–ะดะฐั†ั–ั { #special-types-and-validation } -ะžะบั€ั–ะผ ะทะฒะธั‡ะฐะนะฝะธั… ั‚ะธะฟั–ะฒ, ั‚ะฐะบะธั… ัะบ `str`, `int`, `float`, ั‚ะฐ ั–ะฝ. ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัะบะปะฐะดะฝั–ัˆั– ั‚ะธะฟะธ, ัะบั– ะฝะฐัะปั–ะดัƒัŽั‚ัŒ `str`. +ะžะบั€ั–ะผ ะทะฒะธั‡ะฐะนะฝะธั… ั‚ะธะฟั–ะฒ, ั‚ะฐะบะธั… ัะบ `str`, `int`, `float`, ั‚ะฐ ั–ะฝ. ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัะบะปะฐะดะฝั–ัˆั– ั‚ะธะฟะธ, ัะบั– ะฝะฐัะปั–ะดัƒัŽั‚ัŒ `str`. ะฉะพะฑ ะฟะพะฑะฐั‡ะธั‚ะธ ะฒัั– ะดะพัั‚ัƒะฟะฝั– ะฒะฐั€ั–ะฐะฝั‚ะธ, ะพะทะฝะฐะนะพะผั‚ะตัั ะท ะพะณะปัะดะพะผ <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">ั‚ะธะฟั–ะฒ ัƒ Pydantic</a>. ะ”ะตัะบั– ะฟั€ะธะบะปะฐะดะธ ะฑัƒะดัƒั‚ัŒ ัƒ ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปะฐั…. @@ -123,9 +104,9 @@ my_list: List[str] ะ ัะดะพะบ ะฑัƒะดะต ะฟะตั€ะตะฒั–ั€ะตะฝะพ ัะบ ะดั–ะนัะฝัƒ URL-ะฐะดั€ะตััƒ ั– ะทะฐะดะพะบัƒะผะตะฝั‚ะพะฒะฐะฝะพ ะฒ JSON Schema / OpenAPI ัะบ URL. -## ะั‚ั€ะธะฑัƒั‚ะธ ะทั– ัะฟะธัะบะฐะผะธ ะฟั–ะดะผะพะดะตะปะตะน +## ะั‚ั€ะธะฑัƒั‚ะธ ะทั– ัะฟะธัะบะฐะผะธ ะฟั–ะดะผะพะดะตะปะตะน { #attributes-with-lists-of-submodels } -ะฃ Pydantic ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดะตะปั– ัะบ ะฟั–ะดั‚ะธะฟะธ ะดะปั `list`, `set` ั‚ะพั‰ะพ: +ะฃ Pydantic ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดะตะปั– ัะบ ะฟั–ะดั‚ะธะฟะธ ะดะปั `list`, `set` ั‚ะพั‰ะพ: {* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *} @@ -161,7 +142,7 @@ my_list: List[str] /// -## ะ“ะปะธะฑะพะบะพ ะฒะบะปะฐะดะตะฝั– ะผะพะดะตะปั– +## ะ“ะปะธะฑะพะบะพ ะฒะบะปะฐะดะตะฝั– ะผะพะดะตะปั– { #deeply-nested-models } ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะฐั‚ะธ ะฒะบะปะฐะดะตะฝั– ะผะพะดะตะปั– ะดะพะฒั–ะปัŒะฝะพั— ะณะปะธะฑะธะฝะธ: @@ -173,14 +154,9 @@ my_list: List[str] /// -## ะขั–ะปะฐ ะทะฐะฟะธั‚ั–ะฒ, ั‰ะพ ัะบะปะฐะดะฐัŽั‚ัŒัั ะทั– ัะฟะธัะบั–ะฒ +## ะขั–ะปะฐ ะทะฐะฟะธั‚ั–ะฒ, ั‰ะพ ัะบะปะฐะดะฐัŽั‚ัŒัั ะทั– ัะฟะธัะบั–ะฒ { #bodies-of-pure-lists } -ะฏะบั‰ะพ ะฒะตั€ั…ะฝั–ะน ั€ั–ะฒะตะฝัŒ JSON ั‚ั–ะปะฐ, ัะบะต ะ’ะธ ะพั‡ั–ะบัƒั”ั‚ะต, ั” JSON `ะผะฐัะธะฒะพะผ` (ัƒ Python โ€” `list`), ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ั‚ะธะฟ ัƒ ะฟะฐั€ะฐะผะตั‚ั€ั– ั„ัƒะฝะบั†ั–ั—, ัะบ ั– ะฒ ะผะพะดะตะปัั… Pydantic: - -```Python -images: List[Image] -``` -ะฐะฑะพ ะฒ Python 3.9 ั– ะฒะธั‰ะต: +ะฏะบั‰ะพ ะฒะตั€ั…ะฝั–ะน ั€ั–ะฒะตะฝัŒ JSON ั‚ั–ะปะฐ, ัะบะต ะฒะธ ะพั‡ั–ะบัƒั”ั‚ะต, ั” JSON `ะผะฐัะธะฒะพะผ` (ัƒ Python โ€” `list`), ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ั‚ะธะฟ ัƒ ะฟะฐั€ะฐะผะตั‚ั€ั– ั„ัƒะฝะบั†ั–ั—, ัะบ ั– ะฒ ะผะพะดะตะปัั… Pydantic: ```Python images: list[Image] @@ -190,7 +166,7 @@ images: list[Image] {* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} -## ะŸั–ะดั‚ั€ะธะผะบะฐ ะฒ ั€ะตะดะฐะบั‚ะพั€ั– ะฒััŽะดะธ +## ะŸั–ะดั‚ั€ะธะผะบะฐ ะฒ ั€ะตะดะฐะบั‚ะพั€ั– ะฒััŽะดะธ { #editor-support-everywhere } ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะฟั–ะดั‚ั€ะธะผะบัƒ ะฒ ั€ะตะดะฐะบั‚ะพั€ั– ะฒััŽะดะธ. @@ -200,23 +176,23 @@ images: list[Image] ะ’ะธ ะฝะต ะทะผะพะณะปะธ ะฑ ะพั‚ั€ะธะผะฐั‚ะธ ั‚ะฐะบัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ ะฒ ั€ะตะดะฐะบั‚ะพั€ั–, ัะบะฑะธ ะฟั€ะฐั†ัŽะฒะฐะปะธ ะฝะฐะฟั€ัะผัƒ ะทั– `dict`, ะฐ ะฝะต ะท ะผะพะดะตะปัะผะธ Pydantic. -ะะปะต ะ’ะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ั‚ัƒั€ะฑัƒะฒะฐั‚ะธัั ะฟั€ะพ ั†ะต: ะฒั…ั–ะดะฝั– dict'ะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะพะฝะฒะตั€ั‚ัƒัŽั‚ัŒัั, ะฐ ะฒะธั…ั–ะดะฝั– ะดะฐะฝั– ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะตั€ะตั‚ะฒะพั€ัŽัŽั‚ัŒัั ะฒ JSON. +ะะปะต ะฒะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ั‚ัƒั€ะฑัƒะฒะฐั‚ะธัั ะฟั€ะพ ั†ะต: ะฒั…ั–ะดะฝั– dict'ะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะพะฝะฒะตั€ั‚ัƒัŽั‚ัŒัั, ะฐ ะฒะธั…ั–ะดะฝั– ะดะฐะฝั– ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะตั€ะตั‚ะฒะพั€ัŽัŽั‚ัŒัั ะฒ JSON. -## ะขั–ะปะฐ ะท ะดะพะฒั–ะปัŒะฝะธะผะธ `dict` +## ะขั–ะปะฐ ะท ะดะพะฒั–ะปัŒะฝะธะผะธ `dict` { #bodies-of-arbitrary-dicts } ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ั‚ั–ะปะพ ัะบ `dict` ะท ะบะปัŽั‡ะฐะผะธ ะพะดะฝะพะณะพ ั‚ะธะฟัƒ ั‚ะฐ ะทะฝะฐั‡ะตะฝะฝัะผะธ ั–ะฝัˆะพะณะพ ั‚ะธะฟัƒ. -ะฆะต ะบะพั€ะธัะฝะพ, ัะบั‰ะพ ะ’ะธ ะฝะต ะทะฝะฐั”ั‚ะต ะฝะฐะฟะตั€ะตะด, ัะบั– ั–ะผะตะฝะฐ ะฟะพะปั–ะฒ ะฑัƒะดัƒั‚ัŒ ะดั–ะนัะฝะธะผะธ (ัะบ ัƒ ะฒะธะฟะฐะดะบัƒ ะท ะผะพะดะตะปัะผะธ Pydantic). +ะฆะต ะบะพั€ะธัะฝะพ, ัะบั‰ะพ ะฒะธ ะฝะต ะทะฝะฐั”ั‚ะต ะฝะฐะฟะตั€ะตะด, ัะบั– ั–ะผะตะฝะฐ ะฟะพะปั–ะฒ ะฑัƒะดัƒั‚ัŒ ะดั–ะนัะฝะธะผะธ (ัะบ ัƒ ะฒะธะฟะฐะดะบัƒ ะท ะผะพะดะตะปัะผะธ Pydantic). -ะฆะต ะฑัƒะดะต ะบะพั€ะธัะฝะพ, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะฟั€ะธะนะผะฐั‚ะธ ะบะปัŽั‡ั–, ัะบั– ะทะฐะทะดะฐะปะตะณั–ะดัŒ ะฝะตะฒั–ะดะพะผั–. +ะฆะต ะฑัƒะดะต ะบะพั€ะธัะฝะพ, ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฟั€ะธะนะผะฐั‚ะธ ะบะปัŽั‡ั–, ัะบั– ะทะฐะทะดะฐะปะตะณั–ะดัŒ ะฝะตะฒั–ะดะพะผั–. --- -ะฆะต ั‚ะฐะบะพะถ ะทั€ัƒั‡ะฝะพ, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะผะฐั‚ะธ ะบะปัŽั‡ั– ั–ะฝัˆะพะณะพ ั‚ะธะฟัƒ (ะฝะฐะฟั€ะธะบะปะฐะด, `int`). +ะฆะต ั‚ะฐะบะพะถ ะทั€ัƒั‡ะฝะพ, ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะผะฐั‚ะธ ะบะปัŽั‡ั– ั–ะฝัˆะพะณะพ ั‚ะธะฟัƒ (ะฝะฐะฟั€ะธะบะปะฐะด, `int`). ะžััŒ ั‰ะพ ะผะธ ั€ะพะทะณะปัะฝะตะผะพ ะดะฐะปั–. -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ะ’ะธ ะผะพะถะตั‚ะต ะฟั€ะธะนะผะฐั‚ะธ ะฑัƒะดัŒ-ัะบะธะน `dict`, ัะบั‰ะพ ะนะพะณะพ ะบะปัŽั‡ั– โ€” ั†ะต `int`, ะฐ ะทะฝะฐั‡ะตะฝะฝั โ€” `float`: +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ะฒะธ ะผะพะถะตั‚ะต ะฟั€ะธะนะผะฐั‚ะธ ะฑัƒะดัŒ-ัะบะธะน `dict`, ัะบั‰ะพ ะนะพะณะพ ะบะปัŽั‡ั– โ€” ั†ะต `int`, ะฐ ะทะฝะฐั‡ะตะฝะฝั โ€” `float`: {* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} @@ -228,18 +204,18 @@ images: list[Image] ะฆะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะบะปั–ั”ะฝั‚ะธ ะฒะฐัˆะพะณะพ API ะฝะฐะดัะธะปะฐั‚ะธะผัƒั‚ัŒ ะบะปัŽั‡ั– ัƒ ะฒะธะณะปัะดั– ั€ัะดะบั–ะฒ, ัะบั‰ะพ ะฒะพะฝะธ ะผั–ัั‚ัั‚ัŒ ั†ั–ะปั– ั‡ะธัะปะฐ, Pydantic ะบะพะฝะฒะตั€ั‚ัƒั” ั—ั… ั– ะฟั€ะพะฒะตะดะต ะฒะฐะปั–ะดะฐั†ั–ัŽ. -ะขะพะฑั‚ะพ `dict`, ัะบะธะน ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ัะบ `weights`, ะผะฐั‚ะธะผะต ะบะปัŽั‡ั– ั‚ะธะฟัƒ `int` ั‚ะฐ ะทะฝะฐั‡ะตะฝะฝั ั‚ะธะฟัƒ `float`. +ะขะพะฑั‚ะพ `dict`, ัะบะธะน ะฒะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ัะบ `weights`, ะผะฐั‚ะธะผะต ะบะปัŽั‡ั– ั‚ะธะฟัƒ `int` ั‚ะฐ ะทะฝะฐั‡ะตะฝะฝั ั‚ะธะฟัƒ `float`. /// -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ— **FastAPI** ะ’ะธ ะผะฐั”ั‚ะต ะผะฐะบัะธะผะฐะปัŒะฝัƒ ะณะฝัƒั‡ะบั–ัั‚ัŒ ะทะฐะฒะดัะบะธ ะผะพะดะตะปัะผ Pydantic, ะทะฑะตั€ั–ะณะฐัŽั‡ะธ ะฟั€ะธ ั†ัŒะพะผัƒ ะบะพะด ะฟั€ะพัั‚ะธะผ, ะบะพั€ะพั‚ะบะธะผ ั‚ะฐ ะตะปะตะณะฐะฝั‚ะฝะธะผ. +ะ— **FastAPI** ะฒะธ ะผะฐั”ั‚ะต ะผะฐะบัะธะผะฐะปัŒะฝัƒ ะณะฝัƒั‡ะบั–ัั‚ัŒ ะทะฐะฒะดัะบะธ ะผะพะดะตะปัะผ Pydantic, ะทะฑะตั€ั–ะณะฐัŽั‡ะธ ะฟั€ะธ ั†ัŒะพะผัƒ ะบะพะด ะฟั€ะพัั‚ะธะผ, ะบะพั€ะพั‚ะบะธะผ ั‚ะฐ ะตะปะตะณะฐะฝั‚ะฝะธะผ. ะ ั‚ะฐะบะพะถ ะพั‚ั€ะธะผัƒั”ั‚ะต ะฒัั– ะฟะตั€ะตะฒะฐะณะธ: * ะŸั–ะดั‚ั€ะธะผะบะฐ ะฒ ั€ะตะดะฐะบั‚ะพั€ั– (ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั ะฒััŽะดะธ!) * ะšะพะฝะฒะตั€ั‚ะฐั†ั–ั ะดะฐะฝะธั… (ะฟะฐั€ัะธะฝะณ/ัะตั€ะธะฐะปั–ะทะฐั†ั–ั) -* ะ’ะฐะปั–ะดะฐั†ั–ั ะดะฐะฝะธั… +* ะ’ะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั… * ะ”ะพะบัƒะผะตะฝั‚ะฐั†ั–ั ัั…ะตะผ * ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ัั‚ะฒะพั€ะตะฝะฝั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— diff --git a/docs/uk/docs/tutorial/body-updates.md b/docs/uk/docs/tutorial/body-updates.md index e78b5a5bf3..2ae68291ca 100644 --- a/docs/uk/docs/tutorial/body-updates.md +++ b/docs/uk/docs/tutorial/body-updates.md @@ -1,8 +1,8 @@ -# ะขั–ะปะพ โ€“ ะžะฝะพะฒะปะตะฝะฝั +# ะขั–ะปะพ โ€” ะžะฝะพะฒะปะตะฝะฝั { #body-updates } -## ะžะฝะพะฒะปะตะฝะฝั ะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ `PUT` +## ะžะฝะพะฒะปะตะฝะฝั ั–ะท ะทะฐะผั–ะฝะพัŽ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `PUT` { #update-replacing-with-put } -ะฉะพะฑ ะพะฝะพะฒะธั‚ะธ ะตะปะตะผะตะฝั‚, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a> ะพะฟะตั€ะฐั†ั–ัŽ. +ะฉะพะฑ ะพะฝะพะฒะธั‚ะธ ะตะปะตะผะตะฝั‚, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a> ะพะฟะตั€ะฐั†ั–ัŽ. ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `jsonable_encoder`, ั‰ะพะฑ ะฟะตั€ะตั‚ะฒะพั€ะธั‚ะธ ะฒั…ั–ะดะฝั– ะดะฐะฝั– ะฝะฐ ั‚ะฐะบั–, ัะบั– ะผะพะถะฝะฐ ะทะฑะตั€ั–ะณะฐั‚ะธ ัะบ JSON (ะฝะฐะฟั€ะธะบะปะฐะด, ัƒ NoSQL ะฑะฐะทั– ะดะฐะฝะธั…). ะะฐะฟั€ะธะบะปะฐะด, ะฟะตั€ะตั‚ะฒะพั€ัŽัŽั‡ะธ `datetime` ัƒ `str`. @@ -10,7 +10,7 @@ `PUT` ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะฐะฝะธั…, ัะบั– ะผะฐัŽั‚ัŒ ะทะฐะผั–ะฝะธั‚ะธ ั‡ะธะฝะฝั– ะดะฐะฝั–. -### ะŸะพะฟะตั€ะตะดะถะตะฝะฝั ะฟั€ะพ ะทะฐะผั–ะฝัƒ +### ะŸะพะฟะตั€ะตะดะถะตะฝะฝั ะฟั€ะพ ะทะฐะผั–ะฝัƒ { #warning-about-replacing } ะฆะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะพะฝะพะฒะธั‚ะธ ะตะปะตะผะตะฝั‚ `bar`, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `PUT` ะท ั‚ั–ะปะพะผ: @@ -26,7 +26,7 @@ ะ† ะดะฐะฝั– ะฑัƒะดัƒั‚ัŒ ะทะฑะตั€ะตะถะตะฝั– ะท ั†ะธะผ "ะฝะพะฒะธะผ" ะทะฝะฐั‡ะตะฝะฝัะผ `tax` = `10.5`. -## ะงะฐัั‚ะบะพะฒั– ะพะฝะพะฒะปะตะฝะฝั ะท `PATCH` +## ะงะฐัั‚ะบะพะฒั– ะพะฝะพะฒะปะตะฝะฝั ะท `PATCH` { #partial-updates-with-patch } ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะฟะตั€ะฐั†ั–ัŽ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> ะดะปั *ั‡ะฐัั‚ะบะพะฒะพะณะพ* ะพะฝะพะฒะปะตะฝะฝั ะดะฐะฝะธั…. @@ -34,53 +34,37 @@ /// note | ะŸั€ะธะผั–ั‚ะบะฐ -`PATCH` ะผะตะฝัˆ ะฒั–ะดะพะผะธะน ั– ั€ั–ะดัˆะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั, ะฝั–ะถ `PUT`. +`PATCH` ะผะตะฝัˆ ะฟะพัˆะธั€ะตะฝะธะน ั– ะผะตะฝัˆ ะฒั–ะดะพะผะธะน, ะฝั–ะถ `PUT`. ะ† ะฑะฐะณะฐั‚ะพ ะบะพะผะฐะฝะด ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ ะปะธัˆะต `PUT`, ะฝะฐะฒั–ั‚ัŒ ะดะปั ั‡ะฐัั‚ะบะพะฒะธั… ะพะฝะพะฒะปะตะฝัŒ. -ะ’ะธ **ะฒั–ะปัŒะฝั–** ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั—ั… ั‚ะฐะบ, ัะบ ั…ะพั‡ะตั‚ะต, **FastAPI** ะฝะต ะฝะฐะบะปะฐะดะฐั” ะพะฑะผะตะถะตะฝัŒ. +ะ’ะธ **ะฒั–ะปัŒะฝั–** ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั—ั… ั‚ะฐะบ, ัะบ ั…ะพั‡ะตั‚ะต, **FastAPI** ะฝะต ะฝะฐะบะปะฐะดะฐั” ะถะพะดะฝะธั… ะพะฑะผะตะถะตะฝัŒ. -ะะปะต ั†ะตะน ะฟะพัั–ะฑะฝะธะบ ะฟะพะบะฐะทัƒั” ะ’ะฐะผ ะฑั–ะปัŒัˆ-ะผะตะฝัˆ ัะบ ั—ั… ะทะฐะดัƒะผะฐะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ. +ะะปะต ั†ะตะน ะฟะพัั–ะฑะฝะธะบ ะฟะพะบะฐะทัƒั” ะฒะฐะผ, ะฑั–ะปัŒัˆ-ะผะตะฝัˆ, ัะบ ั—ั… ะทะฐะดัƒะผะฐะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ. /// -### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ะฐ `exclude_unset` ัƒ Pydantic +### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ะฐ `exclude_unset` ัƒ Pydantic { #using-pydantics-exclude-unset-parameter } -ะฏะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ั‡ะฐัั‚ะบะพะฒั– ะพะฝะพะฒะปะตะฝะฝั, ะดัƒะถะต ะทั€ัƒั‡ะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `exclude_unset` ัƒ ะผะตั‚ะพะดั– `.model_dump()` ะผะพะดะตะปั– Pydantic. +ะฏะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ั‡ะฐัั‚ะบะพะฒั– ะพะฝะพะฒะปะตะฝะฝั, ะดัƒะถะต ะบะพั€ะธัะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `exclude_unset` ัƒ `.model_dump()` ะผะพะดะตะปั– Pydantic. ะะฐะฟั€ะธะบะปะฐะด: `item.model_dump(exclude_unset=True)`. -/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั +ะฆะต ะทะณะตะฝะตั€ัƒั” `dict` ะปะธัˆะต ะท ั‚ะธะผะธ ะดะฐะฝะธะผะธ, ัะบั– ะฑัƒะปะธ ะฒัั‚ะฐะฝะพะฒะปะตะฝั– ะฟั–ะด ั‡ะฐั ัั‚ะฒะพั€ะตะฝะฝั ะผะพะดะตะปั– `item`, ะฒะธะบะปัŽั‡ะฐัŽั‡ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. -ะฃ Pydantic v1 ั†ะตะน ะผะตั‚ะพะด ะฝะฐะทะธะฒะฐะฒัั `.dict()`, ะฒั–ะฝ ะฑัƒะฒ ะทะฐัั‚ะฐั€ั–ะปะธะน (ะฐะปะต ะฒัะต ั‰ะต ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั) ัƒ Pydantic v2, ั– ะฑัƒะฒ ะฟะตั€ะตะนะผะตะฝะพะฒะฐะฝะธะน ัƒ `.model_dump()`. - -ะŸั€ะธะบะปะฐะดะธ ั‚ัƒั‚ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ `.dict()` ะดะปั ััƒะผั–ัะฝะพัั‚ั– ะท Pydantic v1, ะฐะปะต ะ’ะฐะผ ัะปั–ะด ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `.model_dump()`, ัะบั‰ะพ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Pydantic v2. - -/// - -ะฆะต ัั‚ะฒะพั€ะธั‚ัŒ `dict` ะปะธัˆะต ะท ั‚ะธะผะธ ะดะฐะฝะธะผะธ, ัะบั– ะฑัƒะปะธ ัะฒะฝะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝั– ะฟั–ะด ั‡ะฐั ัั‚ะฒะพั€ะตะฝะฝั ะผะพะดะตะปั– `item`, ะฒะธะบะปัŽั‡ะฐัŽั‡ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. - -ะขะพะดั– ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั†ะต, ั‰ะพะฑ ัั‚ะฒะพั€ะธั‚ะธ `dict` ะปะธัˆะต ะท ะดะฐะฝะธะผะธ, ัะบั– ะฑัƒะปะธ ะฒัั‚ะฐะฝะพะฒะปะตะฝั– (ะฝะฐะดั–ัะปะฐะฝั– ัƒ ะทะฐะฟะธั‚ั–), ะฟั€ะพะฟัƒัะบะฐัŽั‡ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: +ะขะพะดั– ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั†ะต, ั‰ะพะฑ ะทะณะตะฝะตั€ัƒะฒะฐั‚ะธ `dict` ะปะธัˆะต ะท ะดะฐะฝะธะผะธ, ัะบั– ะฑัƒะปะธ ะฒัั‚ะฐะฝะพะฒะปะตะฝั– (ะฝะฐะดั–ัะปะฐะฝั– ัƒ ะทะฐะฟะธั‚ั–), ะฟั€ะพะฟัƒัะบะฐัŽั‡ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: {* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *} -### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ะฐ `update` ัƒ Pydantic +### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ะฐ `update` ัƒ Pydantic { #using-pydantics-update-parameter } -ะขะตะฟะตั€ ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ ะบะพะฟั–ัŽ ะฝะฐัะฒะฝะพั— ะผะพะดะตะปั– ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `.model_copy()`, ั– ะฟะตั€ะตะดะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `update` ะท `dict` , ัะบะธะน ะผั–ัั‚ะธั‚ัŒ ะดะฐะฝั– ะดะปั ะพะฝะพะฒะปะตะฝะฝั. - -/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั - -ะฃ Pydantic v1 ะผะตั‚ะพะด ะฝะฐะทะธะฒะฐะฒัั `.copy()`, ะฒั–ะฝ ะฑัƒะฒ ะทะฐัั‚ะฐั€ั–ะปะธะน (ะฐะปะต ะฒัะต ั‰ะต ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั) ัƒ Pydantic v2, ั– ะฑัƒะฒ ะฟะตั€ะตะนะผะตะฝะพะฒะฐะฝะธะน ัƒ `.model_copy()`. - -ะŸั€ะธะบะปะฐะดะธ ั‚ัƒั‚ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ `.copy()` ะดะปั ััƒะผั–ัะฝะพัั‚ั– ะท Pydantic v1, ะฐะปะต ัะบั‰ะพ ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Pydantic v2 โ€” ะ’ะฐะผ ัะปั–ะด ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `.model_copy()` ะทะฐะผั–ัั‚ัŒ ั†ัŒะพะณะพ. - -/// +ะขะตะฟะตั€ ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ ะบะพะฟั–ัŽ ะฝะฐัะฒะฝะพั— ะผะพะดะตะปั– ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `.model_copy()`, ั– ะฟะตั€ะตะดะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `update` ะท `dict`, ัะบะธะน ะผั–ัั‚ะธั‚ัŒ ะดะฐะฝั– ะดะปั ะพะฝะพะฒะปะตะฝะฝั. ะะฐะฟั€ะธะบะปะฐะด: `stored_item_model.model_copy(update=update_data)`: {* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} -### ะŸั–ะดััƒะผะพะบ ั‡ะฐัั‚ะบะพะฒะธั… ะพะฝะพะฒะปะตะฝัŒ +### ะŸั–ะดััƒะผะพะบ ั‡ะฐัั‚ะบะพะฒะธั… ะพะฝะพะฒะปะตะฝัŒ { #partial-updates-recap } ะฃ ะฟั–ะดััƒะผะบัƒ, ั‰ะพะฑ ะทะฐัั‚ะพััƒะฒะฐั‚ะธ ั‡ะฐัั‚ะบะพะฒั– ะพะฝะพะฒะปะตะฝะฝั, ะ’ะธ: @@ -101,7 +85,7 @@ ะะฐัะฟั€ะฐะฒะดั– ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั†ัŽ ัะฐะผัƒ ั‚ะตั…ะฝั–ะบัƒ ั– ะท ะพะฟะตั€ะฐั†ั–ั”ัŽ HTTP `PUT`. -ะะปะต ะฟั€ะธะบะปะฐะด ั‚ัƒั‚ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” `PATCH`, ั‚ะพะผัƒ ั‰ะพ ะฒั–ะฝ ะฑัƒะฒ ัั‚ะฒะพั€ะตะฝะธะน ัะฐะผะต ะดะปั ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบั–ะฒ. +ะะปะต ะฟั€ะธะบะปะฐะด ั‚ัƒั‚ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” `PATCH`, ั‚ะพะผัƒ ั‰ะพ ะฒั–ะฝ ะฑัƒะฒ ัั‚ะฒะพั€ะตะฝะธะน ะดะปั ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบั–ะฒ. /// @@ -109,7 +93,7 @@ ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะผะพะดะตะปัŒ ะทะฐะฟะธั‚ัƒ ะฒัะต ั‰ะต ะฟั€ะพั…ะพะดะธั‚ัŒ ะฒะฐะปั–ะดะฐั†ั–ัŽ. -ะขะพะถ, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ั‡ะฐัั‚ะบะพะฒั– ะพะฝะพะฒะปะตะฝะฝั, ัะบั– ะผะพะถัƒั‚ัŒ ะฝะต ะผั–ัั‚ะธั‚ะธ ะถะพะดะฝะพะณะพ ะฐั‚ั€ะธะฑัƒั‚ะฐ, ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะผะฐั‚ะธ ะผะพะดะตะปัŒ, ะดะต ะฒัั– ะฐั‚ั€ะธะฑัƒั‚ะธ ะฟะพะทะฝะฐั‡ะตะฝั– ัะบ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒั– (ะทั– ะทะฝะฐั‡ะตะฝะฝัะผะธ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฐะฑะพ `None`). +ะขะพะถ, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ั‡ะฐัั‚ะบะพะฒั– ะพะฝะพะฒะปะตะฝะฝั, ัะบั– ะผะพะถัƒั‚ัŒ ะฟั€ะพะฟัƒัะบะฐั‚ะธ ะฒัั– ะฐั‚ั€ะธะฑัƒั‚ะธ, ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะผะฐั‚ะธ ะผะพะดะตะปัŒ, ะดะต ะฒัั– ะฐั‚ั€ะธะฑัƒั‚ะธ ะฟะพะทะฝะฐั‡ะตะฝั– ัะบ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒั– (ะทั– ะทะฝะฐั‡ะตะฝะฝัะผะธ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฐะฑะพ `None`). ะฉะพะฑ ั€ะพะทั€ั–ะทะฝัั‚ะธ ะผะพะดะตะปั– ะท ัƒัั–ะผะฐ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ ะดะปั **ะพะฝะพะฒะปะตะฝะฝั** ั– ะผะพะดะตะปั– ะท ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ ะดะปั **ัั‚ะฒะพั€ะตะฝะฝั**, ะ’ะธ ะผะพะถะตั‚ะต ัะบะพั€ะธัั‚ะฐั‚ะธััŒ ั–ะดะตัะผะธ, ะพะฟะธัะฐะฝะธะผะธ ัƒ [ะ”ะพะดะฐั‚ะบะพะฒั– ะผะพะดะตะปั–](extra-models.md){.internal-link target=_blank}. diff --git a/docs/uk/docs/tutorial/body.md b/docs/uk/docs/tutorial/body.md index 38fed7bb8d..ca1f308ab5 100644 --- a/docs/uk/docs/tutorial/body.md +++ b/docs/uk/docs/tutorial/body.md @@ -1,14 +1,14 @@ -# ะขั–ะปะพ ะทะฐะฟะธั‚ัƒ +# ะขั–ะปะพ ะทะฐะฟะธั‚ัƒ { #request-body } ะšะพะปะธ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฝะฐะดั–ัะปะฐั‚ะธ ะดะฐะฝั– ะท ะบะปั–ั”ะฝั‚ะฐ (ัะบะฐะถั–ะผะพ, ะฑั€ะฐัƒะทะตั€ะฐ) ะดะพ ะฒะฐัˆะพะณะพ API, ะฒะธ ะฝะฐะดัะธะปะฐั”ั‚ะต ั—ั… ัะบ **ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ**. ะขั–ะปะพ **ะทะฐะฟะธั‚ัƒ**ย โ€” ั†ะต ะดะฐะฝั–, ะฝะฐะดั–ัะปะฐะฝั– ะบะปั–ั”ะฝั‚ะพะผ ะดะพ ะฒะฐัˆะพะณะพ API. ะขั–ะปะพ **ะฒั–ะดะฟะพะฒั–ะดั–**ย โ€” ั†ะต ะดะฐะฝั–, ัะบั– ะฒะฐัˆ API ะฝะฐะดัะธะปะฐั” ะบะปั–ั”ะฝั‚ัƒ. -ะ’ะฐัˆ API ะผะฐะนะถะต ะทะฐะฒะถะดะธ ะผะฐั” ะฝะฐะดัะธะปะฐั‚ะธ ั‚ั–ะปะพ **ะฒั–ะดะฟะพะฒั–ะดั–**. ะะปะต ะบะปั–ั”ะฝั‚ะฐะผ ะฝะต ะพะฑะพะฒโ€™ัะทะบะพะฒะพ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะพัั‚ั–ะนะฝะพ ะฝะฐะดัะธะปะฐั‚ะธ ั‚ั–ะปะฐ **ะทะฐะฟะธั‚ั–ะฒ**. +ะ’ะฐัˆ API ะผะฐะนะถะต ะทะฐะฒะถะดะธ ะผะฐั” ะฝะฐะดัะธะปะฐั‚ะธ ั‚ั–ะปะพ **ะฒั–ะดะฟะพะฒั–ะดั–**. ะะปะต ะบะปั–ั”ะฝั‚ะฐะผ ะฝะต ะพะฑะพะฒโ€™ัะทะบะพะฒะพ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะพัั‚ั–ะนะฝะพ ะฝะฐะดัะธะปะฐั‚ะธ ั‚ั–ะปะฐ **ะทะฐะฟะธั‚ั–ะฒ** โ€” ั–ะฝะบะพะปะธ ะฒะพะฝะธ ะปะธัˆะต ะทะฐะฟะธั‚ัƒัŽั‚ัŒ ัˆะปัั…, ะผะพะถะปะธะฒะพ ะท ะดะตัะบะธะผะธ ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ะทะฐะฟะธั‚ัƒ, ะฐะปะต ะฝะต ะฝะฐะดัะธะปะฐัŽั‚ัŒ ั‚ั–ะปะพ. ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ ั‚ั–ะปะพ **ะทะฐะฟะธั‚ัƒ**, ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> ะผะพะดะตะปั– ะท ัƒัั–ั”ัŽ ั—ั… ะฟะพั‚ัƒะถะฝั–ัั‚ัŽ ั‚ะฐ ะฟะตั€ะตะฒะฐะณะฐะผะธ. -/// info +/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฉะพะฑ ะฝะฐะดั–ัะปะฐั‚ะธ ะดะฐะฝั–, ะฒะธ ะฟะพะฒะธะฝะฝั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะดะธะฝ ั–ะท: `POST` (ะฑั–ะปัŒัˆ ะฟะพัˆะธั€ะตะฝะธะน), `PUT`, `DELETE` ะฐะฑะพ `PATCH`. @@ -18,21 +18,22 @@ /// -## ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต `BaseModel` ะฒั–ะด Pydantic +## ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต `BaseModel` ะฒั–ะด Pydantic { #import-pydantics-basemodel } ะกะฟะพั‡ะฐั‚ะบัƒ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ `BaseModel` ะท `pydantic`: -{* ../../docs_src/body/tutorial001.py hl[4] *} +{* ../../docs_src/body/tutorial001_py310.py hl[2] *} -## ะกั‚ะฒะพั€ั–ั‚ัŒ ัะฒะพัŽ ะผะพะดะตะปัŒ ะดะฐะฝะธั… +## ะกั‚ะฒะพั€ั–ั‚ัŒ ัะฒะพัŽ ะผะพะดะตะปัŒ ะดะฐะฝะธั… { #create-your-data-model } ะŸะพั‚ั–ะผ ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ัะฒะพัŽ ะผะพะดะตะปัŒ ะดะฐะฝะธั… ัะบ ะบะปะฐั, ัะบะธะน ัƒัะฟะฐะดะบะพะฒัƒั”ั‚ัŒัั ะฒั–ะด `BaseModel`. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ั‚ะธะฟะธ Python ะดะปั ะฒัั–ั… ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ: -{* ../../docs_src/body/tutorial001.py hl[7:11] *} +{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} -ะขะฐะบ ัะฐะผะพ, ัะบ ั– ะฟั€ะธ ะพะณะพะปะพัˆะตะฝะฝั– ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะฟะธั‚ัƒ, ะบะพะปะธ ะฐั‚ั€ะธะฑัƒั‚ ะผะพะดะตะปั– ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฒั–ะฝ ะฝะต ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ. ะ’ ั–ะฝัˆะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั†ะต ะฟะพั‚ั€ั–ะฑะฝะพ. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `None`, ั‰ะพะฑ ะทั€ะพะฑะธั‚ะธ ะนะพะณะพ ะฝะตะพะฑะพะฒ'ัะทะบะพะฒะธะผ. + +ะขะฐะบ ัะฐะผะพ, ัะบ ั– ะฟั€ะธ ะพะณะพะปะพัˆะตะฝะฝั– ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะฟะธั‚ัƒ, ะบะพะปะธ ะฐั‚ั€ะธะฑัƒั‚ ะผะพะดะตะปั– ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฒั–ะฝ ะฝะต ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ. ะ’ ั–ะฝัˆะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั†ะต ะฟะพั‚ั€ั–ะฑะฝะพ. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `None`, ั‰ะพะฑ ะทั€ะพะฑะธั‚ะธ ะนะพะณะพ ะฟั€ะพัั‚ะพ ะฝะตะพะฑะพะฒ'ัะทะบะพะฒะธะผ. ะะฐะฟั€ะธะบะปะฐะด, ั†ั ะผะพะดะตะปัŒ ะฒะธั‰ะต ะพะณะพะปะพัˆัƒั” JSON "`ะพะฑ'ั”ะบั‚`" (ะฐะฑะพ Python `dict`), ัะบ: @@ -54,15 +55,15 @@ } ``` -## ะžะณะพะปะพัะธ ั—ั— ัะบ ะฟะฐั€ะฐะผะตั‚ั€ +## ะžะณะพะปะพัั–ั‚ัŒ ั—ั— ัะบ ะฟะฐั€ะฐะผะตั‚ั€ { #declare-it-as-a-parameter } ะฉะพะฑ ะดะพะดะฐั‚ะธ ะผะพะดะตะปัŒ ะดะฐะฝะธั… ะดะพ ะฒะฐัˆะพั— *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะพะณะพะปะพัั–ั‚ัŒ ั—ั— ั‚ะฐะบ ัะฐะผะพ, ัะบ ะฒะธ ะพะณะพะปะพัะธะปะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ัˆะปัั…ัƒ ั‚ะฐ ะทะฐะฟะธั‚ัƒ: -{* ../../docs_src/body/tutorial001.py hl[18] *} +{* ../../docs_src/body/tutorial001_py310.py hl[16] *} ...ั– ะฒะบะฐะถั–ั‚ัŒ ั—ั— ั‚ะธะฟ ัะบ ะผะพะดะตะปัŒ, ัะบัƒ ะฒะธ ัั‚ะฒะพั€ะธะปะธ, `Item`. -## ะ ะตะทัƒะปัŒั‚ะฐั‚ะธ +## ะ ะตะทัƒะปัŒั‚ะฐั‚ะธ { #results } ะ›ะธัˆะต ะท ั†ะธะผ ะพะณะพะปะพัˆะตะฝะฝัะผ ั‚ะธะฟัƒ Python **FastAPI** ะฑัƒะดะต: @@ -73,9 +74,9 @@ * ะะฐะดะฐะฒะฐั‚ะธ ะพั‚ั€ะธะผะฐะฝั– ะดะฐะฝั– ัƒ ะฟะฐั€ะฐะผะตั‚ั€ั– `item`. * ะžัะบั–ะปัŒะบะธ ะฒะธ ะพะณะพะปะพัะธะปะธ ะนะพะณะพ ัƒ ั„ัƒะฝะบั†ั–ั— ัะบ ั‚ะธะฟ `Item`, ะฒะธ ั‚ะฐะบะพะถ ะผะฐั‚ะธะผะตั‚ะต ะฒััŽ ะฟั–ะดั‚ั€ะธะผะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ (ะฐะฒั‚ะพะทะฐะฟะพะฒะฝะตะฝะฝั, ั‚ะพั‰ะพ) ะดะปั ะฒัั–ั… ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ ั‚ะฐ ั—ั… ั‚ะธะฟั–ะฒ. * ะ“ะตะฝะตั€ัƒะฒะฐั‚ะธ <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> ะฒะธะทะฝะฐั‡ะตะฝะฝั ะดะปั ะฒะฐัˆะพั— ะผะพะดะตะปั–, ะฒะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั—ั… ะดะต ะทะฐะฒะณะพะดะฝะพ, ัะบั‰ะพ ั†ะต ะผะฐั” ัะตะฝั ะดะปั ะฒะฐัˆะพะณะพ ะฟั€ะพะตะบั‚ัƒ. -* ะฆั– ัั…ะตะผะธ ะฑัƒะดัƒั‚ัŒ ั‡ะฐัั‚ะธะฝะพัŽ ะทะณะตะฝะตั€ะพะฒะฐะฝะพั— ัั…ะตะผะธ OpenAPI ั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผัƒั‚ัŒัั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพัŽ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั”ัŽ ั–ะฝั‚ะตั€ั„ะตะนััƒ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ. +* ะฆั– ัั…ะตะผะธ ะฑัƒะดัƒั‚ัŒ ั‡ะฐัั‚ะธะฝะพัŽ ะทะณะตะฝะตั€ะพะฒะฐะฝะพั— ัั…ะตะผะธ OpenAPI ั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผัƒั‚ัŒัั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพัŽ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั”ัŽ <abbr title="User Interfaces โ€“ ะ†ะฝั‚ะตั€ั„ะตะนัะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ">UIs</abbr>. -## ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั +## ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั { #automatic-docs } ะกั…ะตะผะธ JSON ะฒะฐัˆะธั… ะผะพะดะตะปะตะน ะฑัƒะดัƒั‚ัŒ ั‡ะฐัั‚ะธะฝะพัŽ ะฒะฐัˆะพั— ัั…ะตะผะธ, ะทะณะตะฝะตั€ะพะฒะฐะฝะพั— OpenAPI, ั– ะฑัƒะดัƒั‚ัŒ ะฟะพะบะฐะทะฐะฝั– ะฒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝั–ะน API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—: @@ -85,7 +86,7 @@ <img src="/img/tutorial/body/image02.png"> -## ะŸั–ะดั‚ั€ะธะผะบะฐ ั€ะตะดะฐะบั‚ะพั€ะฐ +## ะŸั–ะดั‚ั€ะธะผะบะฐ ั€ะตะดะฐะบั‚ะพั€ะฐ { #editor-support } ะฃ ะฒะฐัˆะพะผัƒ ั€ะตะดะฐะบั‚ะพั€ั–, ะฒัะตั€ะตะดะธะฝั– ะฒะฐัˆะพั— ั„ัƒะฝะบั†ั–ั—, ะฒะธ ะฑัƒะดะตั‚ะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะฟั–ะดะบะฐะทะบะธ ั‚ะธะฟัƒ ั‚ะฐ ะทะฐะฒะตั€ัˆะตะฝะฝั ัะบั€ั–ะทัŒ (ั†ะต ะฑ ะฝะต ัั‚ะฐะปะพัั, ัะบะฑะธ ะฒะธ ะพั‚ั€ะธะผะฐะปะธ `dict` ะทะฐะผั–ัั‚ัŒ ะผะพะดะตะปั– Pydantic): @@ -107,7 +108,7 @@ <img src="/img/tutorial/body/image05.png"> -/// tip +/// tip | ะŸะพั€ะฐะดะฐ ะฏะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> ัะบ ะฒะฐัˆ ั€ะตะดะฐะบั‚ะพั€, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>. @@ -121,42 +122,45 @@ /// -## ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะผะพะดะตะปัŒ +## ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะผะพะดะตะปัŒ { #use-the-model } ะฃัะตั€ะตะดะธะฝั– ั„ัƒะฝะบั†ั–ั— ะฒะธ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ะฟั€ัะผะธะน ะดะพัั‚ัƒะฟ ะดะพ ะฒัั–ั… ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ ะพะฑโ€™ั”ะบั‚ะฐ ะผะพะดะตะปั–: -{* ../../docs_src/body/tutorial002.py hl[21] *} +{* ../../docs_src/body/tutorial002_py310.py *} -## ะขั–ะปะพ ะทะฐะฟะธั‚ัƒ + ะฟะฐั€ะฐะผะตั‚ั€ะธ ัˆะปัั…ัƒ +## ะขั–ะปะพ ะทะฐะฟะธั‚ัƒ + ะฟะฐั€ะฐะผะตั‚ั€ะธ ัˆะปัั…ัƒ { #request-body-path-parameters } ะ’ะธ ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ัˆะปัั…ัƒ ั‚ะฐ ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ. **FastAPI** ั€ะพะทะฟั–ะทะฝะฐั”, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ัƒะฝะบั†ั–ั—, ัะบั– ะฒั–ะดะฟะพะฒั–ะดะฐัŽั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ะฐะผ ัˆะปัั…ัƒ, ะผะฐัŽั‚ัŒ ะฑัƒั‚ะธ **ะฒะทัั‚ั– ะท ัˆะปัั…ัƒ**, ะฐ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ัƒะฝะบั†ั–ั—, ัะบั– ะพะณะพะปะพัˆัƒัŽั‚ัŒัั ัะบ ะผะพะดะตะปั– Pydantic, **ะฒะทัั‚ั– ะท ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ**. -{* ../../docs_src/body/tutorial003.py hl[17:18] *} +{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} -## ะขั–ะปะพ ะทะฐะฟะธั‚ัƒ + ัˆะปัั… + ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฟะธั‚ัƒ + +## ะขั–ะปะพ ะทะฐะฟะธั‚ัƒ + ัˆะปัั… + ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฟะธั‚ัƒ { #request-body-path-query-parameters } ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ **ั‚ั–ะปะพ**, **ัˆะปัั…** ั– **ะทะฐะฟะธั‚** ะพะดะฝะพั‡ะฐัะฝะพ. **FastAPI** ั€ะพะทะฟั–ะทะฝะฐั” ะบะพะถะตะฝ ะท ะฝะธั… ั– ะฒั–ะทัŒะผะต ะดะฐะฝั– ะท ะฟะพั‚ั€ั–ะฑะฝะพะณะพ ะผั–ัั†ั. -{* ../../docs_src/body/tutorial004.py hl[18] *} +{* ../../docs_src/body/tutorial004_py310.py hl[16] *} ะŸะฐั€ะฐะผะตั‚ั€ะธ ั„ัƒะฝะบั†ั–ั— ะฑัƒะดัƒั‚ัŒ ั€ะพะทะฟั–ะทะฝะฐะฒะฐั‚ะธัั ะฝะฐัั‚ัƒะฟะฝะธะผ ั‡ะธะฝะพะผ: * ะฏะบั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะฐะบะพะถ ะพะณะพะปะพัˆะตะฝะพ ะฒ **ัˆะปัั…ัƒ**, ะฒั–ะฝ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะตั‚ัŒัั ัะบ ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ. * ะฏะบั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะผะฐั” **ัะธะฝะณัƒะปัั€ะฝะธะน ั‚ะธะฟ** (ะฝะฐะฟั€ะธะบะปะฐะด, `int`, `float`, `str`, `bool` ั‚ะพั‰ะพ), ะฒั–ะฝ ะฑัƒะดะต ั–ะฝั‚ะตั€ะฟั€ะตั‚ัƒะฒะฐั‚ะธัั ัะบ ะฟะฐั€ะฐะผะตั‚ั€ **ะทะฐะฟะธั‚ัƒ**. -* ะฏะบั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะพะณะพะปะพัˆัƒั”ั‚ัŒัั ัะบ ั‚ะธะฟ **Pydantic ะผะพะดะตะปั–**, ะฒั–ะฝ ั–ะฝั‚ะตั€ะฟั€ะตั‚ัƒั”ั‚ัŒัั ัะบ **ั‚ั–ะปะพ** ะทะฐะฟะธั‚ัƒ. +* ะฏะบั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะพะณะพะปะพัˆัƒั”ั‚ัŒัั ัะบ ั‚ะธะฟ **Pydantic ะผะพะดะตะปั–**, ะฒั–ะฝ ั–ะฝั‚ะตั€ะฟั€ะตั‚ัƒั”ั‚ัŒัั ัะบ **ั‚ั–ะปะพ** **ะทะฐะฟะธั‚ัƒ**. -/// note +/// note | ะŸั€ะธะผั–ั‚ะบะฐ -FastAPI ะฑัƒะดะต ะทะฝะฐั‚ะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั "q" ะฝะต ั” ะพะฑะพะฒ'ัะทะบะพะฒะธะผ ั‡ะตั€ะตะท ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ "= None". +FastAPI ะฑัƒะดะต ะทะฝะฐั‚ะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั `q` ะฝะต ั” ะพะฑะพะฒ'ัะทะบะพะฒะธะผ ั‡ะตั€ะตะท ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `= None`. -`Optional` ัƒ `Optional[str]` ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั FastAPI, ะฐะปะต ะดะพะทะฒะพะปะธั‚ัŒ ะฒะฐัˆะพะผัƒ ั€ะตะดะฐะบั‚ะพั€ัƒ ะฝะฐะดะฐั‚ะธ ะฒะฐะผ ะบั€ะฐั‰ัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ ั‚ะฐ ะฒะธัะฒะปัั‚ะธ ะฟะพะผะธะปะบะธ. +`str | None` (Python 3.10+) ะฐะฑะพ `Union` ัƒ `Union[str, None]` (Python 3.9+) FastAPI ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”, ั‰ะพะฑ ะฒะธะทะฝะฐั‡ะธั‚ะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั ะฝะต ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ โ€” ะฒั–ะฝ ะทะฝะฐั‚ะธะผะต, ั‰ะพ ะฒะพะฝะพ ะฝะต ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ั‚ะพะผัƒ ั‰ะพ ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `= None`. + +ะะปะต ะดะพะดะฐะฒะฐะฝะฝั ะฐะฝะพั‚ะฐั†ั–ะน ั‚ะธะฟั–ะฒ ะดะพะทะฒะพะปะธั‚ัŒ ะฒะฐัˆะพะผัƒ ั€ะตะดะฐะบั‚ะพั€ัƒ ะฝะฐะดะฐั‚ะธ ะฒะฐะผ ะบั€ะฐั‰ัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ ั‚ะฐ ะฒะธัะฒะปัั‚ะธ ะฟะพะผะธะปะบะธ. /// -## ะ‘ะตะท Pydantic +## ะ‘ะตะท Pydantic { #without-pydantic } -ะฏะบั‰ะพ ะฒะธ ะฝะต ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดะตะปั– Pydantic, ะฒะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ **Body**. ะŸะตั€ะตะณะปัะฝัŒั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะดะปั [ะขั–ะปะพ โ€“ ะšั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ: ัะธะฝะณัƒะปัั€ะฝั– ะทะฝะฐั‡ะตะฝะฝั ะฒ ั‚ั–ะปั–](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. +ะฏะบั‰ะพ ะฒะธ ะฝะต ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดะตะปั– Pydantic, ะฒะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ **Body**. ะŸะตั€ะตะณะปัะฝัŒั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะดะปั [Body - Multiple Parameters: Singular values in 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 f070b6ac83..3c6407716e 100644 --- a/docs/uk/docs/tutorial/cookie-param-models.md +++ b/docs/uk/docs/tutorial/cookie-param-models.md @@ -1,32 +1,32 @@ -# ะœะพะดะตะปั– ะดะปั Cookie-ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ +# ะœะพะดะตะปั– ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ Cookie { #cookie-parameter-models } -ะฏะบั‰ะพ ัƒ ะ’ะฐั ั” ะณั€ัƒะฟะฐ **cookies** ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ัะบั– ะฟะพะฒ'ัะทะฐะฝั– ะผั–ะถ ัะพะฑะพัŽ, ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ **Pydantic-ะผะพะดะตะปัŒ**, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ั—ั…. ๐Ÿช +ะฏะบั‰ะพ ัƒ ะ’ะฐั ั” ะณั€ัƒะฟะฐ **cookies**, ัะบั– ะฟะพะฒ'ัะทะฐะฝั– ะผั–ะถ ัะพะฑะพัŽ, ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ **Pydantic-ะผะพะดะตะปัŒ**, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ั—ั…. ๐Ÿช ะฆะต ะดะพะทะฒะพะปะธั‚ัŒ ะ’ะฐะผ ะฟะพะฒั‚ะพั€ะฝะพ **ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดะตะปัŒ** ัƒ **ั€ั–ะทะฝะธั… ะผั–ัั†ัั…**, ะฐ ั‚ะฐะบะพะถ ะพะณะพะปะพัะธั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ัŽ ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ะดะปั ะฒัั–ั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะพะดะฝะพั‡ะฐัะฝะพ. ๐Ÿ˜Ž -/// note | ะะพั‚ะฐั‚ะบะธ +/// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะฆะต ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั ะท ะฒะตั€ัั–ั— FastAPI `0.115.0`. ๐Ÿค“ +ะฆะต ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั ะท ะฒะตั€ัั–ั— FastAPI `0.115.0`. ๐Ÿค“ /// /// tip | ะŸะพั€ะฐะดะฐ -ะฆั ะถ ั‚ะตั…ะฝั–ะบะฐ ะทะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั ะดะพ `Query`, `Cookie`, ั‚ะฐ `Header`. ๐Ÿ˜Ž +ะฆั ะถ ั‚ะตั…ะฝั–ะบะฐ ะทะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั ะดะพ `Query`, `Cookie` ั‚ะฐ `Header`. ๐Ÿ˜Ž /// -## Cookie ะท Pydantic-ะผะพะดะตะปะปัŽ +## 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** ะฑัƒะดะต **ะฒะธั‚ัะณัƒะฒะฐั‚ะธ** ะดะฐะฝั– ะดะปั **ะบะพะถะฝะพะณะพ ะฟะพะปั** ะท **cookie** ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ะพั‚ั€ะธะผะฐะฝะธั… ัƒ ะทะฐะฟะธั‚ั–, ั– ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ะ’ะฐะผ Pydantic-ะผะพะดะตะปัŒ, ัะบัƒ ะ’ะธ ะฒะธะทะฝะฐั‡ะธะปะธ. +**FastAPI** ะฑัƒะดะต **ะฒะธั‚ัะณัƒะฒะฐั‚ะธ** ะดะฐะฝั– ะดะปั **ะบะพะถะฝะพะณะพ ะฟะพะปั** ะท **cookies**, ะพั‚ั€ะธะผะฐะฝะธั… ัƒ ะทะฐะฟะธั‚ั–, ั– ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ะ’ะฐะผ Pydantic-ะผะพะดะตะปัŒ, ัะบัƒ ะ’ะธ ะฒะธะทะฝะฐั‡ะธะปะธ. -## ะŸะตั€ะตะฒั–ั€ะบะฐ ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +## ะŸะตั€ะตะฒั–ั€ะบะฐ ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— { #check-the-docs } -ะ’ะธ ะผะพะถะตั‚ะต ะฟะพะฑะฐั‡ะธั‚ะธ ะฒะธะทะฝะฐั‡ะตะฝั– cookie ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั– ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะทะฐ ะฐะดั€ะตัะพัŽ `/docs`: +ะ’ะธ ะผะพะถะตั‚ะต ะฟะพะฑะฐั‡ะธั‚ะธ ะฒะธะทะฝะฐั‡ะตะฝั– cookies ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั– ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะทะฐ ะฐะดั€ะตัะพัŽ `/docs`: <div class="screenshot"> <img src="/img/tutorial/cookie-param-models/image01.png"> @@ -34,29 +34,29 @@ /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะœะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ะพัะบั–ะปัŒะบะธ **ะฑั€ะฐัƒะทะตั€ะธ ะพะฑั€ะพะฑะปััŽั‚ัŒ cookie** ะพัะพะฑะปะธะฒะธะผ ั‡ะธะฝะพะผ ั– "ะทะฐ ะปะฐัˆั‚ัƒะฝะบะฐะผะธ", ะฒะพะฝะธ **ะฝะต** ะดะพะทะฒะพะปััŽั‚ัŒ **JavaScript** ะปะตะณะบะพ ะท ะฝะธะผะธ ะฟั€ะฐั†ัŽะฒะฐั‚ะธ. +ะœะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ะพัะบั–ะปัŒะบะธ **ะฑั€ะฐัƒะทะตั€ะธ ะพะฑั€ะพะฑะปััŽั‚ัŒ cookies** ะพัะพะฑะปะธะฒะธะผ ั‡ะธะฝะพะผ ั– ยซะทะฐ ะปะฐัˆั‚ัƒะฝะบะฐะผะธยป, ะฒะพะฝะธ **ะฝะต** ะดะพะทะฒะพะปััŽั‚ัŒ **JavaScript** ะปะตะณะบะพ ะท ะฝะธะผะธ ะฟั€ะฐั†ัŽะฒะฐั‚ะธ. -ะฏะบั‰ะพ ะ’ะธ ะทะฐะนะดะตั‚ะต ะดะพ **ั–ะฝั‚ะตั€ั„ะตะนััƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— API** ะทะฐ ะฐะดั€ะตัะพัŽ `/docs`, ะ’ะธ ะทะผะพะถะตั‚ะต ะฟะพะฑะฐั‡ะธั‚ะธ **ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ** ะดะปั cookie ัƒ ะ’ะฐัˆะธั… **ะพะฟะตั€ะฐั†ั–ัั… ัˆะปัั…ัƒ**. +ะฏะบั‰ะพ ะ’ะธ ะทะฐะนะดะตั‚ะต ะดะพ **ั–ะฝั‚ะตั€ั„ะตะนััƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— API** ะทะฐ ะฐะดั€ะตัะพัŽ `/docs`, ะ’ะธ ะทะผะพะถะตั‚ะต ะฟะพะฑะฐั‡ะธั‚ะธ **ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ** ะดะปั cookies ัƒ ะ’ะฐัˆะธั… *ะพะฟะตั€ะฐั†ั–ัั… ัˆะปัั…ัƒ*. -ะะปะต ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะ’ะธ ะทะฐะฟะพะฒะฝะธั‚ะต ะดะฐะฝั– ะน ะฝะฐั‚ะธัะฝะตั‚ะต "Execute", ะพัะบั–ะปัŒะบะธ ั–ะฝั‚ะตั€ั„ะตะนั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะฟั€ะฐั†ัŽั” ะท **JavaScript**, cookie ะฝะต ะฑัƒะดัƒั‚ัŒ ะฒั–ะดะฟั€ะฐะฒะปะตะฝั–, ั– ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต **ะฟะพะผะธะปะบัƒ**, ะฝั–ะฑะธ ะ’ะธ ะฝะต ะฒะฒะตะปะธ ะถะพะดะฝะธั… ะทะฝะฐั‡ะตะฝัŒ. +ะะปะต ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะ’ะธ ะทะฐะฟะพะฒะฝะธั‚ะต ะดะฐะฝั– ะน ะฝะฐั‚ะธัะฝะตั‚ะต "Execute", ะพัะบั–ะปัŒะบะธ ั–ะฝั‚ะตั€ั„ะตะนั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะฟั€ะฐั†ัŽั” ะท **JavaScript**, cookies ะฝะต ะฑัƒะดัƒั‚ัŒ ะฒั–ะดะฟั€ะฐะฒะปะตะฝั–, ั– ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต **ะฟะพะผะธะปะบัƒ**, ะฝั–ะฑะธ ะ’ะธ ะฝะต ะฒะฒะตะปะธ ะถะพะดะฝะธั… ะทะฝะฐั‡ะตะฝัŒ. /// -## ะ—ะฐะฑะพั€ะพะฝะฐ ะดะพะดะฐั‚ะบะพะฒะธั… cookie +## ะ—ะฐะฑะพั€ะพะฝะฐ ะดะพะดะฐั‚ะบะพะฒะธั… cookie { #forbid-extra-cookies } -ะฃ ะดะตัะบะธั… ัะฟะตั†ั–ะฐะปัŒะฝะธั… ะฒะธะฟะฐะดะบะฐั… (ะนะผะพะฒั–ั€ะฝะพ, ะฝะต ะดัƒะถะต ะฟะพัˆะธั€ะตะฝะธั…) ะ’ะธ ะผะพะถะตั‚ะต ะทะฐั…ะพั‚ั–ั‚ะธ **ะพะฑะผะตะถะธั‚ะธ** ัะฟะธัะพะบ cookie, ัะบั– ั…ะพั‡ะตั‚ะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ. +ะฃ ะดะตัะบะธั… ัะฟะตั†ั–ะฐะปัŒะฝะธั… ะฒะธะฟะฐะดะบะฐั… (ะนะผะพะฒั–ั€ะฝะพ, ะฝะต ะดัƒะถะต ะฟะพัˆะธั€ะตะฝะธั…) ะ’ะธ ะผะพะถะตั‚ะต ะทะฐั…ะพั‚ั–ั‚ะธ **ะพะฑะผะตะถะธั‚ะธ** cookies, ัะบั– ั…ะพั‡ะตั‚ะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ. -ะ’ะฐัˆะฐ API ั‚ะตะฟะตั€ ะผะฐั” ะผะพะถะปะธะฒั–ัั‚ัŒ ะบะพะฝั‚ั€ะพะปัŽะฒะฐั‚ะธ ะฒะปะฐัะฝัƒ <abbr title="ะฆะต ะถะฐั€ั‚, ัะบั‰ะพ ั‰ะพ. ะฆะต ะฝะต ะผะฐั” ะฝั–ั‡ะพะณะพ ัะฟั–ะปัŒะฝะพะณะพ ะทั– ะทะณะพะดะพัŽ ะฝะฐ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั cookie, ะฐะปะต ั†ะต ะบัƒะผะตะดะฝะพ, ั‰ะพ ะฝะฐะฒั–ั‚ัŒ API ั‚ะตะฟะตั€ ะผะพะถะต ะฒั–ะดั…ะธะปัั‚ะธ ะฑั–ะดะฝั– cookie. ะ›ะพะฒั–ั‚ัŒ ะฟะตั‡ะธะฒะพ. ๐Ÿช">ะทะณะพะดัƒ ะฝะฐ cookie</abbr>. ๐Ÿคช๐Ÿช +ะ’ะฐัˆะฐ API ั‚ะตะฟะตั€ ะผะฐั” ะผะพะถะปะธะฒั–ัั‚ัŒ ะบะพะฝั‚ั€ะพะปัŽะฒะฐั‚ะธ ะฒะปะฐัะฝัƒ <abbr title="This is a joke, just in case. It has nothing to do with cookie consents, but it's funny that even the API can now reject the poor cookies. Have a cookie. ๐Ÿช">ะทะณะพะดัƒ ะฝะฐ cookie</abbr>. ๐Ÿคช๐Ÿช -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะผะพะดะตะปั– Pydantic, ั‰ะพะฑ `ะทะฐะฑะพั€ะพะฝะธั‚ะธ` ะฑัƒะดัŒ-ัะบั– `ะดะพะดะฐั‚ะบะพะฒั–` ะฟะพะปั: +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะผะพะดะตะปั– 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**, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” ะฒั–ะดะฟะพะฒั–ะดัŒ ะท **ะฟะพะผะธะปะบะพัŽ**. +ะฏะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ ัะบั–ััŒ **ะดะพะดะฐั‚ะบะพะฒั– cookies**, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” ะฒั–ะดะฟะพะฒั–ะดัŒ ะท **ะฟะพะผะธะปะบะพัŽ**. -ะ‘ั–ะดะฝั– ะฑะฐะฝะตั€ะธ cookie, ัะบั– ั‚ะฐะบ ัั‚ะฐั€ะฐะฝะฝะพ ะฝะฐะผะฐะณะฐัŽั‚ัŒัั ะพั‚ั€ะธะผะฐั‚ะธ ะ’ะฐัˆัƒ ะทะณะพะดัƒ, ั‰ะพะฑ <abbr title="ะฆะต ั‰ะต ะพะดะธะฝ ะถะฐั€ั‚. ะะต ะทะฒะตั€ั‚ะฐะนั‚ะต ัƒะฒะฐะณะธ. ะ’ั–ะทัŒะผั–ั‚ัŒ ะบะฐะฒัƒ ะดะปั ัะฒะพะณะพ ะฟะตั‡ะธะฒะฐ. โ˜•">API ั—ั— ะฒั–ะดั…ะธะปะธะปะฐ</abbr>. ๐Ÿช +ะ‘ั–ะดะฝั– ะฑะฐะฝะตั€ะธ cookie, ัะบั– ั‚ะฐะบ ัั‚ะฐั€ะฐะฝะฝะพ ะฝะฐะผะฐะณะฐัŽั‚ัŒัั ะพั‚ั€ะธะผะฐั‚ะธ ะ’ะฐัˆัƒ ะทะณะพะดัƒ, ั‰ะพะฑ <abbr title="This is another joke. Don't pay attention to me. Have some coffee for your cookie. โ˜•">API ั—ั— ะฒั–ะดั…ะธะปะธะปะฐ</abbr>. ๐Ÿช -ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ cookie `santa_tracker` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `good-list-please`, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” ะฒั–ะดะฟะพะฒั–ะดัŒ ะท ะฟะพะผะธะปะบะพัŽ, ัะบะฐ ะฟะพะฒั–ะดะพะผะธั‚ัŒ, ั‰ะพ <abbr title="ะกะฐะฝั‚ะฐ ะฝะต ัั…ะฒะฐะปัŽั” ะฒั–ะดััƒั‚ะฝั–ัั‚ัŒ cookie. ๐ŸŽ… ะ“ะฐั€ะฐะทะด, ะฑั–ะปัŒัˆะต ะถะฐั€ั‚ั–ะฒ ะฝะต ะฑัƒะดะต.">cookie `santa_tracker` ะฝะต ะดะพะทะฒะพะปะตะฝะพ</abbr>: +ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ cookie `santa_tracker` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `good-list-please`, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” ะฒั–ะดะฟะพะฒั–ะดัŒ ะท ะฟะพะผะธะปะบะพัŽ, ัะบะฐ ะฟะพะฒั–ะดะพะผะธั‚ัŒ, ั‰ะพ `santa_tracker` <abbr title="Santa disapproves the lack of cookies. ๐ŸŽ… Okay, no more cookie jokes.">cookie ะฝะต ะดะพะทะฒะพะปะตะฝะพ</abbr>: ```json { @@ -71,6 +71,6 @@ } ``` -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #summary } -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ **Pydantic-ะผะพะดะตะปั–** ะดะปั ะพะณะพะปะพัˆะตะฝะฝั <abbr title="ะžั‚ั€ะธะผะฐะนั‚ะต ะพัั‚ะฐะฝะฝั” ะฟะตั‡ะธะฒะพ ะฟะตั€ะตะด ั‚ะธะผ, ัะบ ะฟั–ั‚ะธ. ๐Ÿช">cookie</abbr> ัƒ FastAPI. ๐Ÿ˜Ž +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ **Pydantic-ะผะพะดะตะปั–** ะดะปั ะพะณะพะปะพัˆะตะฝะฝั <abbr title="Have a last cookie before you go. ๐Ÿช">**cookies**</abbr> ัƒ **FastAPI**. ๐Ÿ˜Ž diff --git a/docs/uk/docs/tutorial/cookie-params.md b/docs/uk/docs/tutorial/cookie-params.md index b320645cb3..8a5b44e8aa 100644 --- a/docs/uk/docs/tutorial/cookie-params.md +++ b/docs/uk/docs/tutorial/cookie-params.md @@ -1,24 +1,25 @@ -# ะŸะฐั€ะฐะผะตั‚ั€ะธ 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, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั‚ะฐะบัƒ ะถ ะบะพะฝัั‚ั€ัƒะบั†ั–ัŽ ัะบ ะดะปั `Path` ั– `Query`. -ะŸะตั€ัˆะต ะทะฝะฐั‡ะตะฝะฝั ั†ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฒะธ ะผะพะถะตั‚ะต ั‚ะฐะบะพะถ ะฟะตั€ะตะดะฐั‚ะธ ะฒัั– ะดะพะดะฐั‚ะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฒะฐะปั–ะดะฐั†ั–ั— ั‡ะธ ะฐะฝะพั‚ะฐั†ั–ั—: +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฐ ั‚ะฐะบะพะถ ัƒัั– ะดะพะดะฐั‚ะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฒะฐะปั–ะดะฐั†ั–ั— ั‡ะธ ะฐะฝะพั‚ะฐั†ั–ั—: {* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *} -/// note | ะขะตั…ะฝั–ั‡ะฝั– ะ”ะตั‚ะฐะปั– +/// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– + +`Cookie` ั†ะต "ัะตัั‚ั€ะฐ" ะบะปะฐัั–ะฒ `Path` ั– `Query`. ะ’ะพะฝะธ ั‚ะฐะบะพะถ ะฝะฐัะปั–ะดัƒัŽั‚ัŒัั ะฒั–ะด ะพะดะฝะพะณะพ ัะฟั–ะปัŒะฝะพะณะพ ะบะปะฐััƒ `Param`. -`Cookie` ั†ะต "ัะตัั‚ั€ะฐ" ะบะปะฐัั–ะฒ `Path` ั– `Query`. ะ’ะพะฝะธ ะฝะฐัะปั–ะดัƒัŽั‚ัŒัั ะฒั–ะด ะพะดะฝะพะณะพ ะฑะฐั‚ัŒะบั–ะฒััŒะบะพะณะพ ะบะปะฐััƒ `Param`. ะะปะต ะฟะฐะผ'ัั‚ะฐะนั‚ะต, ั‰ะพ ะบะพะปะธ ะฒะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต `Query`, `Path`, `Cookie` ั‚ะฐ ั–ะฝัˆะต ะท `fastapi`, ั†ะต ั„ะฐะบั‚ะธั‡ะฝะพ ั„ัƒะฝะบั†ั–ั—, ั‰ะพ ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒ ัะฟะตั†ั–ะฐะปัŒะฝั– ะบะปะฐัะธ. /// @@ -29,6 +30,16 @@ /// -## ะŸั–ะดััƒะผะบะธ +/// info + +ะœะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ะพัะบั–ะปัŒะบะธ **ะฑั€ะฐัƒะทะตั€ะธ ะพะฑั€ะพะฑะปััŽั‚ัŒ cookies** ัะฟะตั†ั–ะฐะปัŒะฝะธะผ ั‡ะธะฝะพะผ ั– ะทะฐ ะปะฐัˆั‚ัƒะฝะบะฐะผะธ, ะฒะพะฝะธ **ะฝะต** ะดะพะทะฒะพะปััŽั‚ัŒ **JavaScript** ะปะตะณะบะพ ะฒะทะฐั”ะผะพะดั–ัั‚ะธ ะท ะฝะธะผะธ. + +ะฏะบั‰ะพ ะฒะธ ะฟะตั€ะตะนะดะตั‚ะต ะดะพ **ั–ะฝั‚ะตั€ั„ะตะนััƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— API** ะทะฐ ะฐะดั€ะตัะพัŽ `/docs`, ะฒะธ ะทะผะพะถะตั‚ะต ะฟะพะฑะฐั‡ะธั‚ะธ **ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ** ะดะปั cookies ะดะปั ะฒะฐัˆะธั… *ะพะฟะตั€ะฐั†ั–ะน ัˆะปัั…ัƒ*. + +ะะปะต ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะธ **ะทะฐะฟะพะฒะฝะธั‚ะต ะดะฐะฝั–** ั– ะฝะฐั‚ะธัะฝะตั‚ะต "Execute", ะพัะบั–ะปัŒะบะธ ั–ะฝั‚ะตั€ั„ะตะนั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะฟั€ะฐั†ัŽั” ะท **JavaScript**, cookies ะฝะต ะฑัƒะดะต ะฝะฐะดั–ัะปะฐะฝะพ, ั– ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฟะพะฒั–ะดะพะผะปะตะฝะฝั ะฟั€ะพ **ะฟะพะผะธะปะบัƒ**, ะฝั–ะฑะธ ะฒะธ ะฝะต ะฒะฒะตะปะธ ะถะพะดะฝะธั… ะทะฝะฐั‡ะตะฝัŒ. + +/// + +## ะŸั–ะดััƒะผะบะธ { #recap } ะ’ะธะทะฝะฐั‡ะฐะนั‚ะต cookies ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Cookie`, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั‚ะพะน ะถะต ัะฟั–ะปัŒะฝะธะน ัˆะฐะฑะปะพะฝ, ั‰ะพ ั– `Query` ั‚ะฐ `Path`. diff --git a/docs/uk/docs/tutorial/cors.md b/docs/uk/docs/tutorial/cors.md index 95b204d0f7..f3ed8a7d94 100644 --- a/docs/uk/docs/tutorial/cors.md +++ b/docs/uk/docs/tutorial/cors.md @@ -1,8 +1,8 @@ -# CORS (ะžะฑะผั–ะฝ ั€ะตััƒั€ัะฐะผะธ ะผั–ะถ ั€ั–ะทะฝะธะผะธ ะดะถะตั€ะตะปะฐะผะธ) +# CORS (ะžะฑะผั–ะฝ ั€ะตััƒั€ัะฐะผะธ ะผั–ะถ ั€ั–ะทะฝะธะผะธ ะดะถะตั€ะตะปะฐะผะธ) { #cors-cross-origin-resource-sharing } -<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS ะฐะฑะพ "ะžะฑะผั–ะฝ ั€ะตััƒั€ัะฐะผะธ ะผั–ะถ ั€ั–ะทะฝะธะผะธ ะดะถะตั€ะตะปะฐะผะธ"</a> ั” ัะธั‚ัƒะฐั†ั–ั, ะบะพะปะธ ั„ั€ะพะฝั‚ะตะฝะด, ั‰ะพ ะฟั€ะฐั†ัŽั” ะฒ ะฑั€ะฐัƒะทะตั€ั–, ะผั–ัั‚ะธั‚ัŒ JavaScript-ะบะพะด, ัะบะธะน ะฒะทะฐั”ะผะพะดั–ั” ะท ะฑะตะบะตะฝะดะพะผ, ั€ะพะทั‚ะฐัˆะพะฒะฐะฝะธะผ ะฒ ั–ะฝัˆะพะผัƒ "ะดะถะตั€ะตะปั–" (origin). +<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS ะฐะฑะพ "Cross-Origin Resource Sharing"</a> ั” ัะธั‚ัƒะฐั†ั–ั, ะบะพะปะธ ั„ั€ะพะฝั‚ะตะฝะด, ั‰ะพ ะฟั€ะฐั†ัŽั” ะฒ ะฑั€ะฐัƒะทะตั€ั–, ะผั–ัั‚ะธั‚ัŒ JavaScript-ะบะพะด, ัะบะธะน ะฒะทะฐั”ะผะพะดั–ั” ะท ะฑะตะบะตะฝะดะพะผ, ั€ะพะทั‚ะฐัˆะพะฒะฐะฝะธะผ ะฒ ั–ะฝัˆะพะผัƒ "ะดะถะตั€ะตะปั–" (origin). -## ะ”ะถะตั€ะตะปะพ (Origin) +## ะ”ะถะตั€ะตะปะพ (Origin) { #origin } ะ”ะถะตั€ะตะปะพ ะฒะธะทะฝะฐั‡ะฐั”ั‚ัŒัั ะบะพะผะฑั–ะฝะฐั†ั–ั”ัŽ ะฟั€ะพั‚ะพะบะพะปัƒ (`http`, `https`), ะดะพะผะตะฝัƒ (`myapp.com`, `localhost`, `localhost.tiangolo.com`), ะฟะพั€ั‚ัƒ (`80`, `443`, `8080`). @@ -15,7 +15,7 @@ ะะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะพะฝะธ ะฒัั– ะผั–ัั‚ัั‚ัŒ `localhost`, ะฒะพะฝะธ ะผะฐัŽั‚ัŒ ั€ั–ะทะฝั– ะฟั€ะพั‚ะพะบะพะปะธ ะฐะฑะพ ะฟะพั€ั‚ะธ, ั‰ะพ ั€ะพะฑะธั‚ัŒ ั—ั… ะพะบั€ะตะผะธะผะธ "ะดะถะตั€ะตะปะฐะผะธ". -## ะšั€ะพะบะธ +## ะšั€ะพะบะธ { #steps } ะŸั€ะธะฟัƒัั‚ะธะผะพ, ั‰ะพ ะ’ะฐัˆ ั„ั€ะพะฝั‚ะตะฝะด ะฟั€ะฐั†ัŽั” ะฒ ะฑั€ะฐัƒะทะตั€ั– ะฝะฐ `http://localhost:8080`, ะฐ ะนะพะณะพ JavaScript ะฝะฐะผะฐะณะฐั”ั‚ัŒัั ะฒั–ะดะฟั€ะฐะฒะธั‚ะธ ะทะฐะฟะธั‚ ะดะพ ะฑะตะบะตะฝะดัƒ, ัะบะธะน ะฟั€ะฐั†ัŽั” ะฝะฐ `http://localhost` (ะžัะบั–ะปัŒะบะธ ะผะธ ะฝะต ะฒะบะฐะทัƒั”ะผะพ ะฟะพั€ั‚, ะฑั€ะฐัƒะทะตั€ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฟั€ะธะฟัƒัะบะฐั” ะฟะพั€ั‚ `80`). @@ -25,15 +25,15 @@ ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ัะฟะธัะพะบ ะผะฐั” ะผั–ัั‚ะธั‚ะธ `http://localhost:8080`, ั‰ะพะฑ ั„ั€ะพะฝั‚ะตะฝะด ะฝะฐ ะฟะพั€ั‚ัƒ `:8080` ะฟั€ะฐั†ัŽะฒะฐะฒ ะบะพั€ะตะบั‚ะฝะพ. -## ะกะธะผะฒะพะปัŒะฝะต ะฟั–ะดัั‚ะฐะฒะปัะฝะฝั +## ะกะธะผะฒะพะปัŒะฝะต ะฟั–ะดัั‚ะฐะฒะปัะฝะฝั { #wildcards } ะœะพะถะฝะฐ ั‚ะฐะบะพะถ ะพะณะพะปะพัะธั‚ะธ ัะฟะธัะพะบ ัะบ `"*"` ("ัะธะผะฒะพะปัŒะฝะต ะฟั–ะดัั‚ะฐะฒะปัะฝะฝั"), ั‰ะพ ะพะทะฝะฐั‡ะฐั” ะดะพะทะฒั–ะป ะดะปั ะฒัั–ั… ะดะถะตั€ะตะป. -ะžะดะฝะฐะบ ั†ะต ะดะพะทะฒะพะปะธั‚ัŒ ะปะธัˆะต ะฟะตะฒะฝั– ั‚ะธะฟะธ ะบะพะผัƒะฝั–ะบะฐั†ั–ั—, ะฒะธะบะปัŽั‡ะฐัŽั‡ะธ ะฒัะต, ั‰ะพ ะฟะพะฒ'ัะทะฐะฝะต ะท ะพะฑะปั–ะบะพะฒะธะผะธ ะดะฐะฝะธะผะธ: Cookies, ะทะฐะณะพะปะพะฒะบะธ ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั—, ั‚ะฐะบั– ัะบ ั‚ั–, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะท Bearer ั‚ะพะบะตะฝะฐะผะธ ั‚ะพั‰ะพ. +ะžะดะฝะฐะบ ั†ะต ะดะพะทะฒะพะปะธั‚ัŒ ะปะธัˆะต ะฟะตะฒะฝั– ั‚ะธะฟะธ ะบะพะผัƒะฝั–ะบะฐั†ั–ั—, ะฒะธะบะปัŽั‡ะฐัŽั‡ะธ ะฒัะต, ั‰ะพ ะฟะพะฒ'ัะทะฐะฝะต ะท ะพะฑะปั–ะบะพะฒะธะผะธ ะดะฐะฝะธะผะธ: Cookies, ะทะฐะณะพะปะพะฒะบะธ ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั—, ั‚ะฐะบั– ัะบ ั‚ั–, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะท Bearer Tokens, ั‚ะพั‰ะพ. ะขะพะผัƒ ะดะปั ะบะพั€ะตะบั‚ะฝะพั— ั€ะพะฑะพั‚ะธ ะบั€ะฐั‰ะต ัะฒะฝะพ ะฒะบะฐะทัƒะฒะฐั‚ะธ ะดะพะทะฒะพะปะตะฝั– ะดะถะตั€ะตะปะฐ. -## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `CORSMiddleware` +## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `CORSMiddleware` { #use-corsmiddleware } ะ’ะธ ะผะพะถะตั‚ะต ะฝะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ั†ะต ัƒ ะ’ะฐัˆะพะผัƒ ะดะพะดะฐั‚ะบัƒ **FastAPI** ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `CORSMiddleware`. @@ -44,41 +44,44 @@ ะขะฐะบะพะถ ะผะพะถะฝะฐ ะฒะบะฐะทะฐั‚ะธ, ั‡ะธ ะดะพะทะฒะพะปัั” ะ’ะฐัˆ ะฑะตะบะตะฝะด: -* ะžะฑะปั–ะบะพะฒั– ะดะฐะฝั– (ะทะฐะณะพะปะพะฒะบะธ ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั—, ัookies, ั‚ะพั‰ะพ). +* ะžะฑะปั–ะบะพะฒั– ะดะฐะฝั– (ะทะฐะณะพะปะพะฒะบะธ ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั—, Cookies, ั‚ะพั‰ะพ). * ะšะพะฝะบั€ะตั‚ะฝั– HTTP-ะผะตั‚ะพะดะธ (`POST`, `PUT`) ะฐะฑะพ ะฒัั– ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `"*"` * ะšะพะฝะบั€ะตั‚ะฝั– 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` ั” ะดะพัะธั‚ัŒ ะพะฑะผะตะถะตะฝะธะผะธ, ั‚ะพะผัƒ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ัะฒะฝะพ ะฒะบะฐะทะฐั‚ะธ ะบะพะฝะบั€ะตั‚ะฝั– ะดะถะตั€ะตะปะฐ, ะผะตั‚ะพะดะธ ะฐะฑะพ ะทะฐะณะพะปะพะฒะบะธ, ั‰ะพะฑ ะฑั€ะฐัƒะทะตั€ะธ ะผะพะณะปะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั—ั… ัƒ ะบะพะฝั‚ะตะบัั‚ั– ะทะฐะฟะธั‚ั–ะฒ ะผั–ะถ ั€ั–ะทะฝะธะผะธ ะดะพะผะตะฝะฐะผะธ. +ะŸะฐั€ะฐะผะตั‚ั€ะธ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ัƒ ั€ะตะฐะปั–ะทะฐั†ั–ั— `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` ะทะฐะฒะถะดะธ ะดะพะทะฒะพะปะตะฝั– ะดะปั <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">ะฟั€ะพัั‚ะธั… CORS-ะทะฐะฟะธั‚ั–ะฒ</a>. -* `allow_credentials` - ะ’ะธะทะฝะฐั‡ะฐั”, ั‡ะธ ะฟั–ะดั‚ั€ะธะผัƒัŽั‚ัŒัั ั„ะฐะนะปะธ cookie ะดะปั ะผั–ะถะดะพะผะตะฝะฝะธั… ะทะฐะฟะธั‚ั–ะฒ. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `False`. ะขะฐะบะพะถ, ัะบั‰ะพ ะฟะพั‚ั€ั–ะฑะฝะพ ะดะพะทะฒะพะปะธั‚ะธ ะพะฑะผั–ะฝ ะพะฑะปั–ะบะพะฒะธะผะธ ะดะฐะฝะธะผะธ (`allow_credentials = True`), ะฟะฐั€ะฐะผะตั‚ั€ `allow_origins` ะฝะต ะผะพะถะต ะฑัƒั‚ะธ ะฒัั‚ะฐะฝะพะฒะปะตะฝะธะน ัะบ `['*']`, ะฝะตะพะฑั…ั–ะดะฝะพ ะฒะบะฐะทะฐั‚ะธ ะบะพะฝะบั€ะตั‚ะฝั– ะดะถะตั€ะตะปะฐ. +* `allow_headers` - ะกะฟะธัะพะบ HTTP-ะทะฐะณะพะปะพะฒะบั–ะฒ ะทะฐะฟะธั‚ัƒ, ัะบั– ะฟั–ะดั‚ั€ะธะผัƒัŽั‚ัŒัั ะดะปั ะผั–ะถะดะพะผะตะฝะฝะธั… ะทะฐะฟะธั‚ั–ะฒ. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `[]`. ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `['*']`, ั‰ะพะฑ ะดะพะทะฒะพะปะธั‚ะธ ะฒัั– ะทะฐะณะพะปะพะฒะบะธ. ะ—ะฐะณะพะปะพะฒะบะธ `Accept`, `Accept-Language`, `Content-Language` ั– `Content-Type` ะทะฐะฒะถะดะธ ะดะพะทะฒะพะปะตะฝั– ะดะปั <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">ะฟั€ะพัั‚ะธั… CORS-ะทะฐะฟะธั‚ั–ะฒ</a>. +* `allow_credentials` - ะ’ะธะทะฝะฐั‡ะฐั”, ั‡ะธ ะฟะพะฒะธะฝะฝั– ะฟั–ะดั‚ั€ะธะผัƒะฒะฐั‚ะธัั cookies ะดะปั ะผั–ะถะดะพะผะตะฝะฝะธั… ะทะฐะฟะธั‚ั–ะฒ. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `False`. + + ะ–ะพะดะตะฝ ั–ะท ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `allow_origins`, `allow_methods` ั– `allow_headers` ะฝะต ะผะพะถะฝะฐ ะฒัั‚ะฐะฝะพะฒะปัŽะฒะฐั‚ะธ ัะบ `['*']`, ัะบั‰ะพ `allow_credentials` ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ ัะบ `True`. ะฃัั– ะฒะพะฝะธ ะผะฐัŽั‚ัŒ ะฑัƒั‚ะธ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#credentialed_requests_and_wildcards" class="external-link" rel="noopener" target="_blank">ัะฒะฝะพ ะฒะบะฐะทะฐะฝั–</a>. + * `expose_headers` - ะ’ะบะฐะทัƒั”, ัะบั– ะทะฐะณะพะปะพะฒะบะธ ะฒั–ะดะฟะพะฒั–ะดั– ะฟะพะฒะธะฝะฝั– ะฑัƒั‚ะธ ะดะพัั‚ัƒะฟะฝั– ะดะปั ะฑั€ะฐัƒะทะตั€ะฐ. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `[]`. * `max_age` - ะ’ัั‚ะฐะฝะพะฒะปัŽั” ะผะฐะบัะธะผะฐะปัŒะฝะธะน ั‡ะฐั (ัƒ ัะตะบัƒะฝะดะฐั…) ะดะปั ะบะตัˆัƒะฒะฐะฝะฝั CORS-ะฒั–ะดะฟะพะฒั–ะดะตะน ัƒ ะฑั€ะฐัƒะทะตั€ะฐั…. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `600`. ะฆะตะน middleware ะพะฑั€ะพะฑะปัั” ะดะฒะฐ ั‚ะธะฟะธ HTTP-ะทะฐะฟะธั‚ั–ะฒ... -### ะŸะพะฟะตั€ะตะดะฝั– CORS-ะทะฐะฟะธั‚ะธ (preflight requests) +### ะŸะพะฟะตั€ะตะดะฝั– CORS-ะทะฐะฟะธั‚ะธ { #cors-preflight-requests } ะฆะต ะฑัƒะดัŒ-ัะบั– `OPTIONS` - ะทะฐะฟะธั‚ะธ, ั‰ะพ ะผั–ัั‚ัั‚ัŒ ะทะฐะณะพะปะพะฒะบะธ `Origin` ั‚ะฐ `Access-Control-Request-Method`. ะฃ ั‚ะฐะบะพะผัƒ ะฒะธะฟะฐะดะบัƒ middleware ะฟะตั€ะตั…ะพะฟะธั‚ัŒ ะฒั…ั–ะดะฝะธะน ะทะฐะฟะธั‚ ั– ะฒั–ะดะฟะพะฒั–ัั‚ัŒ ะฒั–ะดะฟะพะฒั–ะดะฝะธะผะธ CORS-ะทะฐะณะพะปะพะฒะบะฐะผะธ, ะฟะพะฒะตั€ั‚ะฐัŽั‡ะธ ะฐะฑะพ `200`, ะฐะฑะพ `400` ะดะปั ั–ะฝั„ะพั€ะผะฐั†ั–ะนะฝะธั… ั†ั–ะปะตะน. -### ะŸั€ะพัั‚ั– ะทะฐะฟะธั‚ะธ +### ะŸั€ะพัั‚ั– ะทะฐะฟะธั‚ะธ { #simple-requests } ะ‘ัƒะดัŒ-ัะบั– ะทะฐะฟะธั‚ะธ ั–ะท ะทะฐะณะพะปะพะฒะบะพะผ `Origin`. ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ middleware ะฟั€ะพะฟัƒัั‚ะธั‚ัŒ ะทะฐะฟะธั‚ ัะบ ะทะฒะธั‡ะฐะนะฝะธะน, ะฐะปะต ะดะพะดะฐัั‚ัŒ ะฒั–ะดะฟะพะฒั–ะดะฝั– CORS-ะทะฐะณะพะปะพะฒะบะธ ัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ. -## ะ”ะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั +## ะ”ะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั { #more-info } -ะ‘ั–ะปัŒัˆะต ะฟั€ะพ <abbr title="Cross-Origin Resource Sharing">CORS</abbr> ะผะพะถะฝะฐ ะดั–ะทะฝะฐั‚ะธัั ะฒ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Mozilla</a>. +ะ‘ั–ะปัŒัˆะต ะฟั€ะพ <abbr title="Cross-Origin Resource Sharing">CORS</abbr> ะผะพะถะฝะฐ ะดั–ะทะฝะฐั‚ะธัั ะฒ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Mozilla ะฟั€ะพ CORS</a>. /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– diff --git a/docs/uk/docs/tutorial/debugging.md b/docs/uk/docs/tutorial/debugging.md index b0e5344f81..0db418dcc9 100644 --- a/docs/uk/docs/tutorial/debugging.md +++ b/docs/uk/docs/tutorial/debugging.md @@ -1,16 +1,16 @@ -# ะะฐะปะฐะณะพะดะถะตะฝะฝั (Debugging) +# ะะฐะปะฐะณะพะดะถะตะฝะฝั { #debugging } -ะ’ะธ ะผะพะถะตั‚ะต ะฟั–ะด'ั”ะดะฝะฐั‚ะธ ะดะตะฑะฐะณะตั€ ัƒ ะ’ะฐัˆะพะผัƒ ั€ะตะดะฐะบั‚ะพั€ั– ะบะพะดัƒ, ะฝะฐะฟั€ะธะบะปะฐะด, ัƒ Visual Studio Code ะฐะฑะพ PyCharm. +ะ’ะธ ะผะพะถะตั‚ะต ะฟั–ะด'ั”ะดะฝะฐั‚ะธ ะดะตะฑะฐะณะตั€ ัƒ ะฒะฐัˆะพะผัƒ ั€ะตะดะฐะบั‚ะพั€ั– ะบะพะดัƒ, ะฝะฐะฟั€ะธะบะปะฐะด, ัƒ Visual Studio Code ะฐะฑะพ PyCharm. -## ะ’ะธะบะปะธะบ `uvicorn` +## ะ’ะธะบะปะธะบ `uvicorn` { #call-uvicorn } -ะฃ ะ’ะฐัˆะพะผัƒ FastAPI-ะดะพะดะฐั‚ะบัƒ ั–ะผะฟะพั€ั‚ัƒะนั‚ะต ั‚ะฐ ะทะฐะฟัƒัั‚ั–ั‚ัŒ `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__"` โ€” ั†ะต ะทะฐะฑะตะทะฟะตั‡ะตะฝะฝั ะฒะธะบะพะฝะฐะฝะฝั ะฟะตะฒะฝะพะณะพ ะบะพะดัƒ ั‚ั–ะปัŒะบะธ ั‚ะพะดั–, ะบะพะปะธ ั„ะฐะนะป ะทะฐะฟัƒัะบะฐั”ั‚ัŒัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ: +ะ“ะพะปะพะฒะฝะฐ ะผะตั‚ะฐ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `__name__ == "__main__"` โ€” ั†ะต ะทะฐะฑะตะทะฟะตั‡ะตะฝะฝั ะฒะธะบะพะฝะฐะฝะฝั ะฟะตะฒะฝะพะณะพ ะบะพะดัƒ ะปะธัˆะต ั‚ะพะดั–, ะบะพะปะธ ะฒะฐัˆ ั„ะฐะนะป ะทะฐะฟัƒัะบะฐั”ั‚ัŒัั ั‚ะฐะบ: <div class="termy"> @@ -20,17 +20,17 @@ $ python myapp.py </div> -ะฐะปะต ะฝะต ะฒะธะบะพะฝัƒั”ั‚ัŒัั ะฟั€ะธ ะนะพะณะพ ั–ะผะฟะพั€ั‚ั– ะฒ ั–ะฝัˆะธะน ั„ะฐะนะป, ะฝะฐะฟั€ะธะบะปะฐะด: +ะฐะปะต ะฝะต ะฒะธะบะพะฝัƒั”ั‚ัŒัั, ะบะพะปะธ ั–ะฝัˆะธะน ั„ะฐะนะป ั–ะผะฟะพั€ั‚ัƒั” ะนะพะณะพ, ะฝะฐะฟั€ะธะบะปะฐะด: ```Python from myapp import app ``` -#### ะ”ะตั‚ะฐะปัŒะฝั–ัˆะต +#### ะ”ะตั‚ะฐะปัŒะฝั–ัˆะต { #more-details } -ะŸั€ะธะฟัƒัั‚ะธะผะพ, ะ’ะฐัˆ ั„ะฐะนะป ะฝะฐะทะธะฒะฐั”ั‚ัŒัั `myapp.py`. +ะŸั€ะธะฟัƒัั‚ะธะผะพ, ะฒะฐัˆ ั„ะฐะนะป ะฝะฐะทะธะฒะฐั”ั‚ัŒัั `myapp.py`. -ะฏะบั‰ะพ ะ’ะธ ะทะฐะฟัƒัั‚ะธั‚ะต ะนะพะณะพ ั‚ะฐะบ: +ะฏะบั‰ะพ ะฒะธ ะทะฐะฟัƒัั‚ะธั‚ะต ะนะพะณะพ ั‚ะฐะบ: <div class="termy"> @@ -40,7 +40,7 @@ $ python myapp.py </div> -ั‚ะพะดั– ะฒะฝัƒั‚ั€ั–ัˆะฝั ะทะผั–ะฝะฝะฐ `__name__`, ัะบะฐ ัั‚ะฒะพั€ัŽั”ั‚ัŒัั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ Python, ะผะฐั‚ะธะผะต ะทะฝะฐั‡ะตะฝะฝั `"__main__"`. +ั‚ะพะดั– ะฒะฝัƒั‚ั€ั–ัˆะฝั ะทะผั–ะฝะฝะฐ `__name__` ัƒ ะฒะฐัˆะพะผัƒ ั„ะฐะนะปั–, ัะบะฐ ัั‚ะฒะพั€ัŽั”ั‚ัŒัั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ Python, ะผะฐั‚ะธะผะต ะทะฝะฐั‡ะตะฝะฝั ั€ัะดะบะฐ `"__main__"`. ะžั‚ะถะต, ั†ะตะน ะฑะปะพะบ ะบะพะดัƒ: @@ -52,17 +52,17 @@ $ python myapp.py --- -ะฆะต ะฝะต ัั‚ะฐะฝะตั‚ัŒัั, ัะบั‰ะพ ะ’ะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต ั†ะตะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป). +ะฆะต ะฝะต ัั‚ะฐะฝะตั‚ัŒัั, ัะบั‰ะพ ะฒะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต ั†ะตะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป). -ะฏะบั‰ะพ ัƒ ะ’ะฐั ั” ั–ะฝัˆะธะน ั„ะฐะนะป, ะฝะฐะฟั€ะธะบะปะฐะด `importer.py`, ะท ะฝะฐัั‚ัƒะฟะฝะธะผ ะบะพะดะพะผ: +ะžั‚ะถะต, ัะบั‰ะพ ัƒ ะฒะฐั ั” ั–ะฝัˆะธะน ั„ะฐะนะป `importer.py` ะท: ```Python from myapp import app -# ะ”ะพะดะฐั‚ะบะพะฒะธะน ะบะพะด +# Some more code ``` -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัั‚ะฒะพั€ะตะฝะฐ ะทะผั–ะฝะฝะฐ ัƒ ั„ะฐะนะปั– `myapp.py` ะฝะต ะผะฐั‚ะธะผะต ะทะฝะฐั‡ะตะฝะฝั ะทะผั–ะฝะฝะพั— `__name__` ัะบ `"__main__"`. +ัƒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัั‚ะฒะพั€ะตะฝะฐ ะทะผั–ะฝะฝะฐ `__name__` ะฒัะตั€ะตะดะธะฝั– `myapp.py` ะฝะต ะผะฐั‚ะธะผะต ะทะฝะฐั‡ะตะฝะฝั `"__main__"`. ะžั‚ะถะต, ั€ัะดะพะบ: @@ -74,38 +74,39 @@ from myapp import app /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะ‘ั–ะปัŒัˆ ะดะตั‚ะฐะปัŒะฝัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะผะพะถะฝะฐ ะทะฝะฐะนั‚ะธ ะฒ <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">ะพั„ั–ั†ั–ะนะฝั–ะน ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Python</a>. +ะ”ะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั— ะดะธะฒั–ั‚ัŒัั <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">ะพั„ั–ั†ั–ะนะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ Python</a>. /// -## ะ—ะฐะฟัƒัะบ ะบะพะดัƒ ะท ะฒะฐัˆะธะผ ะดะตะฑะฐะณะตั€ะพะผ +## ะ—ะฐะฟัƒัะบ ะบะพะดัƒ ะท ะฒะฐัˆะธะผ ะดะตะฑะฐะณะตั€ะพะผ { #run-your-code-with-your-debugger } -ะžัะบั–ะปัŒะบะธ ะ’ะธ ะทะฐะฟัƒัะบะฐั”ั‚ะต ัะตั€ะฒะตั€ Uvicorn ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะท ะ’ะฐัˆะพะณะพ ะบะพะดัƒ, ะ’ะธ ะผะพะถะตั‚ะต ะทะฐะฟัƒัั‚ะธั‚ะธ ะฒะฐัˆัƒ Python ะฟั€ะพะณั€ะฐะผัƒ (ะฒะฐัˆ FastAPI ะดะพะดะฐั‚ะพะบ) ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะท ะดะตะฑะฐะณะตั€ะฐ. +ะžัะบั–ะปัŒะบะธ ะฒะธ ะทะฐะฟัƒัะบะฐั”ั‚ะต ัะตั€ะฒะตั€ Uvicorn ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะท ะฒะฐัˆะพะณะพ ะบะพะดัƒ, ะฒะธ ะผะพะถะตั‚ะต ะทะฐะฟัƒัั‚ะธั‚ะธ ะฒะฐัˆัƒ Python ะฟั€ะพะณั€ะฐะผัƒ (ะฒะฐัˆ FastAPI-ะดะพะดะฐั‚ะพะบ) ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะท ะดะตะฑะฐะณะตั€ะฐ. --- -ะะฐะฟั€ะธะบะปะฐะด, ัƒ Visual Studio Code ะ’ะธ ะผะพะถะตั‚ะต: +ะะฐะฟั€ะธะบะปะฐะด, ัƒ Visual Studio Code ะฒะธ ะผะพะถะตั‚ะต: -* ะŸะตั€ะตะนะดั–ั‚ัŒ ะฝะฐ ะฒะบะปะฐะดะบัƒ "Debug". -* ะะฐั‚ะธัะฝั–ั‚ัŒ "Add configuration...". -* ะ’ะธะฑะตั€ั–ั‚ัŒ "Python" +* ะŸะตั€ะตะนะดั–ั‚ัŒ ะฝะฐ ะฟะฐะฝะตะปัŒ ยซDebugยป. +* ยซAdd configuration...ยป. +* ะ’ะธะฑะตั€ั–ั‚ัŒ ยซPythonยป * ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ ะดะตะฑะฐะณะตั€ ะท ะพะฟั†ั–ั”ัŽ "`Python: Current File (Integrated Terminal)`". -ะฆะต ะทะฐะฟัƒัั‚ะธั‚ัŒ ัะตั€ะฒะตั€ ะท ะ’ะฐัˆะธะผ **FastAPI** ะบะพะดะพะผ, ะทัƒะฟะธะฝะธั‚ัŒัั ะฝะฐ ั‚ะพั‡ะบะฐั… ะทัƒะฟะธะฝัƒ ั‚ะพั‰ะพ. +ะŸั–ัะปั ั†ัŒะพะณะพ ะฒั–ะฝ ะทะฐะฟัƒัั‚ะธั‚ัŒ ัะตั€ะฒะตั€ ะท ะฒะฐัˆะธะผ ะบะพะดะพะผ **FastAPI**, ะทัƒะฟะธะฝะธั‚ัŒัั ะฝะฐ ั‚ะพั‡ะบะฐั… ะทัƒะฟะธะฝัƒ ั‚ะพั‰ะพ. ะžััŒ ัะบ ั†ะต ะผะพะถะต ะฒะธะณะปัะดะฐั‚ะธ: <img src="/img/tutorial/debugging/image01.png"> --- -ะฏะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต PyCharm, ะฒะธ ะผะพะถะตั‚ะต: -* ะ’ั–ะดะบั€ะธั‚ะธ ะผะตะฝัŽ "Run". -* ะ’ะธะฑั€ะฐั‚ะธ ะพะฟั†ั–ัŽ "Debug...". +ะฏะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต PyCharm, ะฒะธ ะผะพะถะตั‚ะต: + +* ะ’ั–ะดะบั€ะธั‚ะธ ะผะตะฝัŽ ยซRunยป. +* ะ’ะธะฑั€ะฐั‚ะธ ะพะฟั†ั–ัŽ ยซDebug...ยป. * ะŸะพั‚ั–ะผ ะท'ัะฒะธั‚ัŒัั ะบะพะฝั‚ะตะบัั‚ะฝะต ะผะตะฝัŽ. * ะ’ะธะฑั€ะฐั‚ะธ ั„ะฐะนะป ะดะปั ะฝะฐะปะฐะณะพะดะถะตะฝะฝั (ัƒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ, `main.py`). -ะฆะต ะทะฐะฟัƒัั‚ะธั‚ัŒ ัะตั€ะฒะตั€ ะท ะ’ะฐัˆะธะผ **FastAPI** ะบะพะดะพะผ, ะทัƒะฟะธะฝะธั‚ัŒัั ะฝะฐ ั‚ะพั‡ะบะฐั… ะทัƒะฟะธะฝัƒ ั‚ะพั‰ะพ. +ะŸั–ัะปั ั†ัŒะพะณะพ ะฒั–ะฝ ะทะฐะฟัƒัั‚ะธั‚ัŒ ัะตั€ะฒะตั€ ะท ะฒะฐัˆะธะผ ะบะพะดะพะผ **FastAPI**, ะทัƒะฟะธะฝะธั‚ัŒัั ะฝะฐ ั‚ะพั‡ะบะฐั… ะทัƒะฟะธะฝัƒ ั‚ะพั‰ะพ. ะžััŒ ัะบ ั†ะต ะผะพะถะต ะฒะธะณะปัะดะฐั‚ะธ: diff --git a/docs/uk/docs/tutorial/encoder.md b/docs/uk/docs/tutorial/encoder.md index f202c7989e..1b403d5bba 100644 --- a/docs/uk/docs/tutorial/encoder.md +++ b/docs/uk/docs/tutorial/encoder.md @@ -1,32 +1,32 @@ -# JSON Compatible Encoder +# JSON-ััƒะผั–ัะฝะธะน ะบะพะดัƒะฒะฐะปัŒะฝะธะบ { #json-compatible-encoder } -ะ†ัะฝัƒัŽั‚ัŒ ะฒะธะฟะฐะดะบะธ, ะบะพะปะธ ะฒะฐะผ ะผะพะถะต ะทะฝะฐะดะพะฑะธั‚ะธัั ะฟะตั€ะตั‚ะฒะพั€ะธั‚ะธ ั‚ะธะฟ ะดะฐะฝะธั… (ะฝะฐะฟั€ะธะบะปะฐะด, ะผะพะดะตะปัŒ Pydantic) ะฒ ั‰ะพััŒ ััƒะผั–ัะฝะต ะท JSON (ะฝะฐะฟั€ะธะบะปะฐะด, `dict`, `list`, ั– ั‚. ะด.). +ะ†ัะฝัƒัŽั‚ัŒ ะฒะธะฟะฐะดะบะธ, ะบะพะปะธ ะฒะฐะผ ะผะพะถะต ะทะฝะฐะดะพะฑะธั‚ะธัั ะฟะตั€ะตั‚ะฒะพั€ะธั‚ะธ ั‚ะธะฟ ะดะฐะฝะธั… (ะฝะฐะฟั€ะธะบะปะฐะด, ะผะพะดะตะปัŒ Pydantic) ะฝะฐ ั‰ะพััŒ ััƒะผั–ัะฝะต ะท JSON (ะฝะฐะฟั€ะธะบะปะฐะด, `dict`, `list` ั‚ะพั‰ะพ). ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะทะฑะตั€ะตะณั‚ะธ ั†ะต ะฒ ะฑะฐะทั– ะดะฐะฝะธั…. -ะ”ะปั ั†ัŒะพะณะพ, **FastAPI** ะฝะฐะดะฐั” `jsonable_encoder()` ั„ัƒะฝะบั†ั–ัŽ. +ะ”ะปั ั†ัŒะพะณะพ **FastAPI** ะฝะฐะดะฐั” ั„ัƒะฝะบั†ั–ัŽ `jsonable_encoder()`. -## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `jsonable_encoder` +## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `jsonable_encoder` { #using-the-jsonable-encoder } ะ”ะฐะฒะฐะนั‚ะต ัƒัะฒะธะผะพ, ั‰ะพ ัƒ ะฒะฐั ั” ะฑะฐะทะฐ ะดะฐะฝะธั… `fake_db`, ัะบะฐ ะฟั€ะธะนะผะฐั” ะปะธัˆะต ะดะฐะฝั–, ััƒะผั–ัะฝั– ะท JSON. ะะฐะฟั€ะธะบะปะฐะด, ะฒะพะฝะฐ ะฝะต ะฟั€ะธะนะผะฐั” ะพะฑ'ั”ะบั‚ะธ ั‚ะธะฟัƒ `datetime`, ะพัะบั–ะปัŒะบะธ ะฒะพะฝะธ ะฝะต ััƒะผั–ัะฝั– ะท JSON. -ะžั‚ะถะต, ะพะฑ'ั”ะบั‚ ั‚ะธะฟัƒ `datetime` ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตั‚ะฒะพั€ะธั‚ะธ ะฒ ั€ัะดะพะบ `str`, ัะบะธะน ะผั–ัั‚ะธั‚ัŒ ะดะฐะฝั– ะฒ <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ISO ั„ะพั€ะผะฐั‚ั–</a>. +ะžั‚ะถะต, ะพะฑ'ั”ะบั‚ ั‚ะธะฟัƒ `datetime` ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตั‚ะฒะพั€ะธั‚ะธ ะฝะฐ `str`, ัะบะธะน ะผั–ัั‚ะธั‚ัŒ ะดะฐะฝั– ะฒ <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ั„ะพั€ะผะฐั‚ั– ISO</a>. -ะขะธะผ ัะฐะผะธะผ ัะฟะพัะพะฑะพะผ ั†ั ะฑะฐะทะฐ ะดะฐะฝะธั… ะฝะต ะฟั€ะธะนะผะฐั‚ะธะผะต ะพะฑ'ั”ะบั‚ ั‚ะธะฟัƒ Pydantic model (ะพะฑ'ั”ะบั‚ ะท ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ), ะฐ ะปะธัˆะต `dict`. +ะขะฐะบ ัะฐะผะพ ั†ั ะฑะฐะทะฐ ะดะฐะฝะธั… ะฝะต ะฟั€ะธะนะผะฐั‚ะธะผะต ะผะพะดะตะปัŒ Pydantic (ะพะฑ'ั”ะบั‚ ะท ะฐั‚ั€ะธะฑัƒั‚ะฐะผะธ), ะฐ ะปะธัˆะต `dict`. ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `jsonable_encoder` ะดะปั ั†ัŒะพะณะพ. -ะ’ะพะฝะฐ ะฟั€ะธะนะผะฐั” ะพะฑ'ั”ะบั‚, ั‚ะฐะบะธะน ัะบ Pydantic model, ั– ะฟะพะฒะตั€ั‚ะฐั” ะนะพะณะพ ะฒะตั€ัั–ัŽ, ััƒะผั–ัะฝัƒ ะท JSON: +ะ’ะพะฝะฐ ะฟั€ะธะนะผะฐั” ะพะฑ'ั”ะบั‚, ั‚ะฐะบะธะน ัะบ ะผะพะดะตะปัŒ Pydantic, ั– ะฟะพะฒะตั€ั‚ะฐั” ะนะพะณะพ ะฒะตั€ัั–ัŽ, ััƒะผั–ัะฝัƒ ะท JSON: {* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *} -ะฃ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั– ะฒะพะฝะฐ ะบะพะฝะฒะตั€ั‚ัƒั” Pydantic model ัƒ `dict`, ะฐ `datetime` ัƒ `str`. +ะฃ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั– ะฒะพะฝะฐ ะบะพะฝะฒะตั€ั‚ัƒั” ะผะพะดะตะปัŒ Pydantic ัƒ `dict`, ะฐ `datetime` ัƒ `str`. -ะ ะตะทัƒะปัŒั‚ะฐั‚ ะฒะธะบะปะธะบัƒ ั†ั–ั”ั— ั„ัƒะฝะบั†ั–ั— - ั†ะต ั‰ะพััŒ, ั‰ะพ ะผะพะถะฝะฐ ะบะพะดัƒะฒะฐั‚ะธ ะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ ัั‚ะฐะฝะดะฐั€ั‚ัƒ Python <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>. +ะ ะตะทัƒะปัŒั‚ะฐั‚ ะฒะธะบะปะธะบัƒ ั†ั–ั”ั— ั„ัƒะฝะบั†ั–ั— โ€” ั†ะต ั‰ะพััŒ, ั‰ะพ ะผะพะถะฝะฐ ะบะพะดัƒะฒะฐั‚ะธ ะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ ัั‚ะฐะฝะดะฐั€ั‚ัƒ Python <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>. -ะ’ะพะฝะฐ ะฝะต ะฟะพะฒะตั€ั‚ะฐั” ะฒะตะปะธะบัƒ ัั‚ั€ะพะบัƒ `str`, ัะบะฐ ะผั–ัั‚ะธั‚ัŒ ะดะฐะฝั– ัƒ ั„ะพั€ะผะฐั‚ั– JSON (ัะบ ัั‚ั€ะพะบะฐ). ะ’ะพะฝะฐ ะฟะพะฒะตั€ั‚ะฐั” ัั‚ะฐะฝะดะฐั€ั‚ะฝัƒ ัั‚ั€ัƒะบั‚ัƒั€ัƒ ะดะฐะฝะธั… Python (ะฝะฐะฟั€ะธะบะปะฐะด `dict`) ั–ะท ะทะฝะฐั‡ะตะฝะฝัะผะธ ั‚ะฐ ะฟั–ะดะทะฝะฐั‡ะตะฝะฝัะผะธ, ัะบั– ั” ััƒะผั–ัะฝะธะผะธ ะท JSON. +ะ’ะพะฝะฐ ะฝะต ะฟะพะฒะตั€ั‚ะฐั” ะฒะตะปะธะบะธะน `str`, ัะบะธะน ะผั–ัั‚ะธั‚ัŒ ะดะฐะฝั– ัƒ ั„ะพั€ะผะฐั‚ั– JSON (ัะบ ั€ัะดะพะบ). ะ’ะพะฝะฐ ะฟะพะฒะตั€ั‚ะฐั” ัั‚ะฐะฝะดะฐั€ั‚ะฝัƒ ัั‚ั€ัƒะบั‚ัƒั€ัƒ ะดะฐะฝะธั… Python (ะฝะฐะฟั€ะธะบะปะฐะด, `dict`) ะทั– ะทะฝะฐั‡ะตะฝะฝัะผะธ ั‚ะฐ ะฟั–ะดะทะฝะฐั‡ะตะฝะฝัะผะธ, ัะบั– ั” ััƒะผั–ัะฝะธะผะธ ะท JSON. /// note | ะŸั€ะธะผั–ั‚ะบะฐ diff --git a/docs/uk/docs/tutorial/extra-data-types.md b/docs/uk/docs/tutorial/extra-data-types.md index 5da942b6e0..a3545e0746 100644 --- a/docs/uk/docs/tutorial/extra-data-types.md +++ b/docs/uk/docs/tutorial/extra-data-types.md @@ -1,13 +1,13 @@ -# ะ”ะพะดะฐั‚ะบะพะฒั– ั‚ะธะฟะธ ะดะฐะฝะธั… +# ะ”ะพะดะฐั‚ะบะพะฒั– ั‚ะธะฟะธ ะดะฐะฝะธั… { #extra-data-types } -ะ”ะพ ั†ัŒะพะณะพ ั‡ะฐััƒ, ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะปะธ ะทะฐะณะฐะปัŒะฝะพะฟะพัˆะธั€ะตะฝั– ั‚ะธะฟะธ ะดะฐะฝะธั…, ั‚ะฐะบั– ัะบ: +ะ”ะพ ั†ัŒะพะณะพ ั‡ะฐััƒ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะปะธ ะทะฐะณะฐะปัŒะฝะพะฟะพัˆะธั€ะตะฝั– ั‚ะธะฟะธ ะดะฐะฝะธั…, ั‚ะฐะบั– ัะบ: * `int` * `float` * `str` * `bool` -ะะปะต ะผะพะถะฝะฐ ั‚ะฐะบะพะถ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฑั–ะปัŒัˆ ัะบะปะฐะดะฝั– ั‚ะธะฟะธ ะดะฐะฝะธั…. +ะะปะต ะฒะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฑั–ะปัŒัˆ ัะบะปะฐะดะฝั– ั‚ะธะฟะธ ะดะฐะฝะธั…. ะ† ะฒะธ ะฒัะต ั‰ะต ะผะฐั‚ะธะผะตั‚ะต ั‚ั– ะถ ะผะพะถะปะธะฒะพัั‚ั–, ัะบั– ะฑัƒะปะธ ะฟะพะบะฐะทะฐะฝั– ะดะพ ั†ัŒะพะณะพ: @@ -17,30 +17,30 @@ * ะ’ะฐะปั–ะดะฐั†ั–ั ะดะฐะฝะธั…. * ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะฐ ะฐะฝะพั‚ะฐั†ั–ั ั‚ะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั. -## ะ†ะฝัˆั– ั‚ะธะฟะธ ะดะฐะฝะธั… +## ะ†ะฝัˆั– ั‚ะธะฟะธ ะดะฐะฝะธั… { #other-data-types } ะžััŒ ะดะพะดะฐั‚ะบะพะฒั– ั‚ะธะฟะธ ะดะฐะฝะธั… ะดะปั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั: * `UUID`: - * ะกั‚ะฐะฝะดะฐั€ั‚ะฝะธะน "ะฃะฝั–ะฒะตั€ัะฐะปัŒะฝะธะน ะฃะฝั–ะบะฐะปัŒะฝะธะน ะ†ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€", ัะบะธะน ั‡ะฐัั‚ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ัะบ ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ัƒ ะฑะฐะณะฐั‚ัŒะพั… ะฑะฐะทะฐั… ะดะฐะฝะธั… ั‚ะฐ ัะธัั‚ะตะผะฐั…. + * ะกั‚ะฐะฝะดะฐั€ั‚ะฝะธะน "ะฃะฝั–ะฒะตั€ัะฐะปัŒะฝะธะน ัƒะฝั–ะบะฐะปัŒะฝะธะน ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€", ัะบะธะน ั‡ะฐัั‚ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ัะบ ID ัƒ ะฑะฐะณะฐั‚ัŒะพั… ะฑะฐะทะฐั… ะดะฐะฝะธั… ั‚ะฐ ัะธัั‚ะตะผะฐั…. * ะฃ ะทะฐะฟะธั‚ะฐั… ั‚ะฐ ะฒั–ะดะฟะพะฒั–ะดัั… ะฑัƒะดะต ะฟั€ะตะดัั‚ะฐะฒะปะตะฝะธะน ัะบ `str`. * `datetime.datetime`: * ะŸะฐะนั‚ะพะฝั–ะฒััŒะบะธะน `datetime.datetime`. - * ะฃ ะทะฐะฟะธั‚ะฐั… ั‚ะฐ ะฒั–ะดะฟะพะฒั–ะดัั… ะฑัƒะดะต ะฟั€ะตะดัั‚ะฐะฒะปะตะฝะธะน ัะบ `str` ะฒ ั„ะพั€ะผะฐั‚ั– ISO 8601, ัะบ: `2008-09-15T15:53:00+05:00`. + * ะฃ ะทะฐะฟะธั‚ะฐั… ั‚ะฐ ะฒั–ะดะฟะพะฒั–ะดัั… ะฑัƒะดะต ะฟั€ะตะดัั‚ะฐะฒะปะตะฝะธะน ัะบ `str` ัƒ ั„ะพั€ะผะฐั‚ั– ISO 8601, ัะบ: `2008-09-15T15:53:00+05:00`. * `datetime.date`: * ะŸะฐะนั‚ะพะฝั–ะฒััŒะบะธะน `datetime.date`. - * ะฃ ะทะฐะฟะธั‚ะฐั… ั‚ะฐ ะฒั–ะดะฟะพะฒั–ะดัั… ะฑัƒะดะต ะฟั€ะตะดัั‚ะฐะฒะปะตะฝะธะน ัะบ `str` ะฒ ั„ะพั€ะผะฐั‚ั– ISO 8601, ัะบ: `2008-09-15`. + * ะฃ ะทะฐะฟะธั‚ะฐั… ั‚ะฐ ะฒั–ะดะฟะพะฒั–ะดัั… ะฑัƒะดะต ะฟั€ะตะดัั‚ะฐะฒะปะตะฝะธะน ัะบ `str` ัƒ ั„ะพั€ะผะฐั‚ั– ISO 8601, ัะบ: `2008-09-15`. * `datetime.time`: * ะŸะฐะนั‚ะพะฝั–ะฒััŒะบะธะน `datetime.time`. - * ะฃ ะทะฐะฟะธั‚ะฐั… ั‚ะฐ ะฒั–ะดะฟะพะฒั–ะดัั… ะฑัƒะดะต ะฟั€ะตะดัั‚ะฐะฒะปะตะฝะธะน ัะบ `str` ะฒ ั„ะพั€ะผะฐั‚ั– ISO 8601, ัะบ: `14:23:55.003`. + * ะฃ ะทะฐะฟะธั‚ะฐั… ั‚ะฐ ะฒั–ะดะฟะพะฒั–ะดัั… ะฑัƒะดะต ะฟั€ะตะดัั‚ะฐะฒะปะตะฝะธะน ัะบ `str` ัƒ ั„ะพั€ะผะฐั‚ั– ISO 8601, ัะบ: `14:23:55.003`. * `datetime.timedelta`: * ะŸะฐะนั‚ะพะฝั–ะฒััŒะบะธะน `datetime.timedelta`. * ะฃ ะทะฐะฟะธั‚ะฐั… ั‚ะฐ ะฒั–ะดะฟะพะฒั–ะดัั… ะฑัƒะดะต ะฟั€ะตะดัั‚ะฐะฒะปะตะฝะธะน ัะบ `float` ะทะฐะณะฐะปัŒะฝะพั— ะบั–ะปัŒะบะพัั‚ั– ัะตะบัƒะฝะด. - * Pydantic ั‚ะฐะบะพะถ ะดะพะทะฒะพะปัั” ะฟั€ะตะดัั‚ะฐะฒะปัั‚ะธ ั†ะต ัะบ "ISO 8601 time diff encoding", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#json_encoders" class="external-link" target="_blank">ะฑั–ะปัŒัˆะต ั–ะฝั„ะพั€ะผะฐั†ั–ั— ะดะธะฒะธััŒ ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—</a>. + * Pydantic ั‚ะฐะบะพะถ ะดะพะทะฒะพะปัั” ะฟั€ะตะดัั‚ะฐะฒะปัั‚ะธ ั†ะต ัะบ "ISO 8601 time diff encoding", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers" class="external-link" target="_blank">ะดะธะฒั–ั‚ัŒัั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั—</a>. * `frozenset`: * ะฃ ะทะฐะฟะธั‚ะฐั… ั– ะฒั–ะดะฟะพะฒั–ะดัั… ั†ะต ะฑัƒะดะต ะพะฑั€ะพะฑะปะตะฝะพ ั‚ะฐะบ ัะฐะผะพ, ัะบ ั– `set`: - * ะฃ ะทะฐะฟะธั‚ะฐั… ัะฟะธัะพะบ ะฑัƒะดะต ะทั‡ะธั‚ะฐะฝะพ, ะดัƒะฑะปั–ะบะฐั‚ะธ ะฑัƒะดัƒั‚ัŒ ะฒะธะดะฐะปะตะฝั– ั‚ะฐ ะฒั–ะฝ ะฑัƒะดะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะธะน ะฝะฐ `set`. - * ะฃ ะฒั–ะดะฟะพะฒั–ะดัั…, `set` ะฑัƒะดะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะธะน ะฝะฐ `list`. + * ะฃ ะทะฐะฟะธั‚ะฐั… ัะฟะธัะพะบ ะฑัƒะดะต ะทั‡ะธั‚ะฐะฝะพ, ะดัƒะฑะปั–ะบะฐั‚ะธ ะฑัƒะดะต ะฒะธะดะฐะปะตะฝะพ, ั– ะนะพะณะพ ะฑัƒะดะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะพ ะฝะฐ `set`. + * ะฃ ะฒั–ะดะฟะพะฒั–ะดัั… `set` ะฑัƒะดะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะพ ะฝะฐ `list`. * ะ—ะณะตะฝะตั€ะพะฒะฐะฝะฐ ัั…ะตะผะฐ ะฑัƒะดะต ะฒะบะฐะทัƒะฒะฐั‚ะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั `set` ั” ัƒะฝั–ะบะฐะปัŒะฝะธะผะธ (ะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ JSON Schema's `uniqueItems`). * `bytes`: * ะกั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ะŸะฐะนั‚ะพะฝั–ะฒััŒะบะธะน `bytes`. @@ -49,11 +49,11 @@ * `Decimal`: * ะกั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ะŸะฐะนั‚ะพะฝั–ะฒััŒะบะธะน `Decimal`. * ะฃ ะทะฐะฟะธั‚ะฐั… ั– ะฒั–ะดะฟะพะฒั–ะดัั… ั†ะต ะฑัƒะดะต ะพะฑั€ะพะฑะปะตะฝะพ ั‚ะฐะบ ัะฐะผะพ, ัะบ ั– `float`. -* ะ’ะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะฒั–ั€ะธั‚ะธ ะฒัั– ะดั–ะนัะฝั– ั‚ะธะฟะธ ะดะฐะฝะธั… Pydantic ั‚ัƒั‚: <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">ั‚ะธะฟะธ ะดะฐะฝะธั… Pydantic</a>. +* ะ’ะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะฒั–ั€ะธั‚ะธ ะฒัั– ะดั–ะนัะฝั– ั‚ะธะฟะธ ะดะฐะฝะธั… Pydantic ั‚ัƒั‚: <a href="https://docs.pydantic.dev/latest/usage/types/types/" class="external-link" target="_blank">ั‚ะธะฟะธ ะดะฐะฝะธั… Pydantic</a>. -## ะŸั€ะธะบะปะฐะด +## ะŸั€ะธะบะปะฐะด { #example } -ะžััŒ ะฟั€ะธะบะปะฐะด *path operation* ะท ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะดะตัะบั– ะท ะฒะธั‰ะตะทะฐะทะฝะฐั‡ะตะฝะธั… ั‚ะธะฟั–ะฒ. +ะžััŒ ะฟั€ะธะบะปะฐะด *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะท ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะดะตัะบั– ะท ะฒะธั‰ะตะทะฐะทะฝะฐั‡ะตะฝะธั… ั‚ะธะฟั–ะฒ. {* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *} diff --git a/docs/uk/docs/tutorial/first-steps.md b/docs/uk/docs/tutorial/first-steps.md index 3f861cb484..5f3750010c 100644 --- a/docs/uk/docs/tutorial/first-steps.md +++ b/docs/uk/docs/tutorial/first-steps.md @@ -1,126 +1,118 @@ -# ะŸะตั€ัˆั– ะบั€ะพะบะธ +# ะŸะตั€ัˆั– ะบั€ะพะบะธ { #first-steps } ะะฐะนะฟั€ะพัั‚ั–ัˆะธะน ั„ะฐะนะป FastAPI ะผะพะถะต ะฒะธะณะปัะดะฐั‚ะธ ั‚ะฐะบ: -{* ../../docs_src/first_steps/tutorial001.py *} +{* ../../docs_src/first_steps/tutorial001_py39.py *} ะกะบะพะฟั–ัŽะนั‚ะต ั†ะต ะดะพ ั„ะฐะนะปัƒ `main.py`. -ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ ัะตั€ะฒะตั€: +ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ live-ัะตั€ะฒะตั€: <div class="termy"> ```console -$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u> -<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font> -<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font> -<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files -<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> +$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u> - โ•ญโ”€ <font color="#8AE234"><b>Python module file</b></font> โ”€โ•ฎ - โ”‚ โ”‚ - โ”‚ ๐Ÿ main.py โ”‚ - โ”‚ โ”‚ - โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + <span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server ๐Ÿš€ -<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font> -<font color="#3465A4">INFO </font> Found importable FastAPI app + Searching for package file structure from directories + with <font color="#3465A4">__init__.py</font> files + Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> - โ•ญโ”€ <font color="#8AE234"><b>Importable FastAPI app</b></font> โ”€โ•ฎ - โ”‚ โ”‚ - โ”‚ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> โ”‚ - โ”‚ โ”‚ - โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + <span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> ๐Ÿ main.py -<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font> + <span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with + the following code: - <span style="background-color:#C4A000"><font color="#2E3436">โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ FastAPI CLI - Development mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ Serving at: http://127.0.0.1:8000 โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ API docs: http://127.0.0.1:8000/docs โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ Running in development mode, for production use: โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ</font></span> + <u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u> -<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] -<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit) -<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font> -<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>] -<font color="#4E9A06">INFO</font>: Waiting for application startup. -<font color="#4E9A06">INFO</font>: Application startup complete. + <span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font> + + <span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use: + <b>fastapi run</b> + + Logs: + + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories: + <b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C + to quit<b>)</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. ``` </div> -ะฃ ะบะพะฝัะพะปั– ะฑัƒะดะต ั€ัะดะพะบ ะฟั€ะธะฑะปะธะทะฝะพ ั‚ะฐะบะพะณะพ ะทะผั–ัั‚ัƒ: +ะฃ ะฒะธะฒะพะดั– ั” ั€ัะดะพะบ ะฟั€ะธะฑะปะธะทะฝะพ ั‚ะฐะบะพะณะพ ะทะผั–ัั‚ัƒ: ```hl_lines="4" INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` -ะฆะตะน ั€ัะดะพะบ ะฟะพะบะฐะทัƒั” URL, ะทะฐ ัะบะธะผ ะดะพะดะฐั‚ะพะบ ะทะฐะฟัƒัะบะฐั”ั‚ัŒัั ะฝะฐ ะฒะฐัˆั–ะน ะปะพะบะฐะปัŒะฝั–ะน ะผะฐัˆะธะฝั–. +ะฆะตะน ั€ัะดะพะบ ะฟะพะบะฐะทัƒั” URL, ะทะฐ ัะบะธะผ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ ะพะฑัะปัƒะณะพะฒัƒั”ั‚ัŒัั ะฝะฐ ะฒะฐัˆั–ะน ะปะพะบะฐะปัŒะฝั–ะน ะผะฐัˆะธะฝั–. -### ะŸะตั€ะตะฒั–ั€ั‚ะต +### ะŸะตั€ะตะฒั–ั€ั‚ะต { #check-it } ะ’ั–ะดะบั€ะธะนั‚ะต ะฑั€ะฐัƒะทะตั€ ั‚ะฐ ะฒะฒะตะดั–ั‚ัŒ ะฐะดั€ะตััƒ <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>. -ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ ั‚ะฐะบะต ะฟะพะฒั–ะดะพะผะปะตะฝะฝั ัƒ ั„ะพั€ะผะฐั‚ั– JSON: +ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต JSON-ะฒั–ะดะฟะพะฒั–ะดัŒ: ```JSON {"message": "Hello World"} ``` -### ะ†ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐ API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั +### ะ†ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐ API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั { #interactive-api-docs } -ะŸะตั€ะตะนะดะตะผะพ ััŽะดะธ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. +ะขะตะฟะตั€ ะฟะตั€ะตะนะดั–ั‚ัŒ ััŽะดะธ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. -ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ัั‚ะฒะพั€ะตะฝัƒ ะทะฐะฒะดัะบะธ <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>): +ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ะฝะฐะดะฐั”ั‚ัŒัั <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐ API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั +### ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐ API ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั { #alternative-api-docs } -ะขะตะฟะตั€ ะฟะตั€ะตะนะดะตะผะพ ััŽะดะธ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. +ะ ั‚ะตะฟะตั€ ะฟะตั€ะตะนะดั–ั‚ัŒ ััŽะดะธ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. -ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝัƒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ัั‚ะฒะพั€ะตะฝัƒ ะทะฐะฒะดัะบะธ <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>): +ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝัƒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ะฝะฐะดะฐั”ั‚ัŒัั <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -### OpenAPI +### OpenAPI { #openapi } -**FastAPI** ะณะตะฝะตั€ัƒั” "ัั…ะตะผัƒ" ะท ัƒัั–ะผ ะฒะฐัˆะธะผ API, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัั‚ะฐะฝะดะฐั€ั‚ **OpenAPI** ะดะปั ะฒะธะทะฝะฐั‡ะตะฝะฝั API. +**FastAPI** ะณะตะฝะตั€ัƒั” ยซัั…ะตะผัƒยป ะท ัƒัั–ะผ ะฒะฐัˆะธะผ API, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัั‚ะฐะฝะดะฐั€ั‚ **OpenAPI** ะดะปั ะฒะธะทะฝะฐั‡ะตะฝะฝั API. -#### "ะกั…ะตะผะฐ" +#### ยซะกั…ะตะผะฐยป { #schema } -"ะกั…ะตะผะฐ" - ั†ะต ะฒะธะทะฝะฐั‡ะตะฝะฝั ะฐะฑะพ ะพะฟะธั ั‡ะพะณะพััŒ. ะฆะต ะฝะต ะบะพะด, ัะบะธะน ะนะพะณะพ ั€ะตะฐะปั–ะทัƒั”, ะฐ ะฟั€ะพัั‚ะพ ะฐะฑัั‚ั€ะฐะบั‚ะฝะธะน ะพะฟะธั. +ยซะกั…ะตะผะฐยป โ€” ั†ะต ะฒะธะทะฝะฐั‡ะตะฝะฝั ะฐะฑะพ ะพะฟะธั ั‡ะพะณะพััŒ. ะฆะต ะฝะต ะบะพะด, ัะบะธะน ะนะพะณะพ ั€ะตะฐะปั–ะทัƒั”, ะฐ ะฟั€ะพัั‚ะพ ะฐะฑัั‚ั€ะฐะบั‚ะฝะธะน ะพะฟะธั. -#### API "ัั…ะตะผะฐ" +#### API ยซัั…ะตะผะฐยป { #api-schema } ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ, <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> ั” ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั”ัŽ, ัะบะฐ ะฒะธะทะฝะฐั‡ะฐั”, ัะบ ะพะฟะธัะฐั‚ะธ ัั…ะตะผัƒ ะฒะฐัˆะพะณะพ API. -ะฆะต ะฒะธะทะฝะฐั‡ะตะฝะฝั ัั…ะตะผะธ ะฒะบะปัŽั‡ะฐั” ัˆะปัั…ะธ (paths) ะฒะฐัˆะพะณะพ API, ะผะพะถะปะธะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ, ัะบั– ะฒะพะฝะธ ะฟั€ะธะนะผะฐัŽั‚ัŒ ั‚ะพั‰ะพ. +ะฆะต ะฒะธะทะฝะฐั‡ะตะฝะฝั ัั…ะตะผะธ ะฒะบะปัŽั‡ะฐั” ัˆะปัั…ะธ (paths) ะฒะฐัˆะพะณะพ API, ะผะพะถะปะธะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ, ัะบั– ะฒะพะฝะธ ะฟั€ะธะนะผะฐัŽั‚ัŒ, ั‚ะพั‰ะพ. -#### "ะกั…ะตะผะฐ" ะดะฐะฝะธั… +#### ยซะกั…ะตะผะฐยป ะดะฐะฝะธั… { #data-schema } -ะขะตั€ะผั–ะฝ "ัั…ะตะผะฐ" ั‚ะฐะบะพะถ ะผะพะถะต ะฒั–ะดะฝะพัะธั‚ะธัั ะดะพ ัั‚ั€ัƒะบั‚ัƒั€ะธ ะดะฐะฝะธั…, ะฝะฐะฟั€ะธะบะปะฐะด, JSON. +ะขะตั€ะผั–ะฝ ยซัั…ะตะผะฐยป ั‚ะฐะบะพะถ ะผะพะถะต ะฒั–ะดะฝะพัะธั‚ะธัั ะดะพ ั„ะพั€ะผะธ ะดะตัะบะธั… ะดะฐะฝะธั…, ะฝะฐะฟั€ะธะบะปะฐะด, ะฒะผั–ัั‚ัƒ JSON. -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั†ะต ะพะทะฝะฐั‡ะฐั” - ะฐั‚ั€ะธะฑัƒั‚ะธ JSON ั– ั‚ะธะฟะธ ะดะฐะฝะธั…, ัะบั– ะฒะพะฝะธ ะผะฐัŽั‚ัŒ ั‚ะพั‰ะพ. +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั†ะต ะพะทะฝะฐั‡ะฐั” ะฐั‚ั€ะธะฑัƒั‚ะธ JSON ั– ั‚ะธะฟะธ ะดะฐะฝะธั…, ัะบั– ะฒะพะฝะธ ะผะฐัŽั‚ัŒ, ั‚ะพั‰ะพ. -#### OpenAPI ั– JSON Schema +#### OpenAPI ั– JSON Schema { #openapi-and-json-schema } -OpenAPI ะพะฟะธััƒั” ัั…ะตะผัƒ ะดะปั ะฒะฐัˆะพะณะพ API. ะ† ั†ั ัั…ะตะผะฐ ะฒะบะปัŽั‡ะฐั” ะฒะธะทะฝะฐั‡ะตะฝะฝั (ะฐะฑะพ "ัั…ะตะผะธ") ะดะฐะฝะธั…, ั‰ะพ ะฝะฐะดัะธะปะฐัŽั‚ัŒัั ั‚ะฐ ะพั‚ั€ะธะผัƒัŽั‚ัŒัั ะฒะฐัˆะธะผ API ะทะฐ ะดะพะฟะพะผะพะณะพัŽ **JSON Schema**, ัั‚ะฐะฝะดะฐั€ั‚ัƒ ะดะปั ัั…ะตะผ ะดะฐะฝะธั… JSON. +OpenAPI ะพะฟะธััƒั” ัั…ะตะผัƒ API ะดะปั ะฒะฐัˆะพะณะพ API. ะ† ั†ั ัั…ะตะผะฐ ะฒะบะปัŽั‡ะฐั” ะฒะธะทะฝะฐั‡ะตะฝะฝั (ะฐะฑะพ ยซัั…ะตะผะธยป) ะดะฐะฝะธั…, ั‰ะพ ะฝะฐะดัะธะปะฐัŽั‚ัŒัั ั‚ะฐ ะพั‚ั€ะธะผัƒัŽั‚ัŒัั ะฒะฐัˆะธะผ API, ะทะฐ ะดะพะฟะพะผะพะณะพัŽ **JSON Schema**, ัั‚ะฐะฝะดะฐั€ั‚ัƒ ะดะปั ัั…ะตะผ ะดะฐะฝะธั… JSON. -#### ะ ะพะทะณะปัะฝะตะผะพ `openapi.json` +#### ะŸะตั€ะตะฒั–ั€ั‚ะต `openapi.json` { #check-the-openapi-json } -ะฏะบั‰ะพ ะฒะฐั ั†ั–ะบะฐะฒะธั‚ัŒ, ัะบ ะฒะธะณะปัะดะฐั” ะฒะธั…ั–ะดะฝะฐ ัั…ะตะผะฐ OpenAPI, ั‚ะพ FastAPI ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะณะตะฝะตั€ัƒั” JSON-ัั…ะตะผัƒ ะท ัƒัั–ะผะฐ ะพะฟะธัะฐะผะธ API. +ะฏะบั‰ะพ ะฒะฐั ั†ั–ะบะฐะฒะธั‚ัŒ, ัะบ ะฒะธะณะปัะดะฐั” ยซัะธั€ะธะนยป OpenAPI schema, FastAPI ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะณะตะฝะตั€ัƒั” JSON (schema) ะท ะพะฟะธัะฐะผะธ ะฒััŒะพะณะพ ะฒะฐัˆะพะณะพ API. -ะžะทะฝะฐะนะพะผะธั‚ะธัั ะผะพะถะฝะฐ ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>. +ะ’ะธ ะผะพะถะตั‚ะต ะฟะพะฑะฐั‡ะธั‚ะธ ั†ะต ะฝะฐะฟั€ัะผัƒ ั‚ัƒั‚: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>. -ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฟั€ะธะฑะปะธะทะฝะพ ั‚ะฐะบะธะน JSON: +ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต JSON, ั‰ะพ ะฟะพั‡ะธะฝะฐั”ั‚ัŒัั ะฟั€ะธะฑะปะธะทะฝะพ ั‚ะฐะบ: ```JSON { @@ -143,42 +135,79 @@ OpenAPI ะพะฟะธััƒั” ัั…ะตะผัƒ ะดะปั ะฒะฐัˆะพะณะพ API. ะ† ั†ั ัั…ะตะผะฐ ะฒะบ ... ``` -#### ะ”ะปั ั‡ะพะณะพ ะฟะพั‚ั€ั–ะฑะฝะธะน OpenAPI +#### ะ”ะปั ั‡ะพะณะพ ะฟะพั‚ั€ั–ะฑะฝะธะน OpenAPI { #what-is-openapi-for } -ะกั…ะตะผะฐ OpenAPI ั” ะพัะฝะพะฒะพัŽ ะดะปั ะพะฑะพั… ัะธัั‚ะตะผ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. +OpenAPI schema โ€” ั†ะต ั‚ะต, ะฝะฐ ั‡ะพะผัƒ ะฟั€ะฐั†ัŽัŽั‚ัŒ ะดะฒั– ะฒะบะปัŽั‡ะตะฝั– ัะธัั‚ะตะผะธ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. -ะ†ัะฝัƒัŽั‚ัŒ ะดะตััั‚ะบะธ ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะธั… ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ะทะฐัะฝะพะฒะฐะฝะธั… ะฝะฐ OpenAPI. ะ’ะธ ะผะพะถะตั‚ะต ะปะตะณะบะพ ะดะพะดะฐั‚ะธ ะฑัƒะดัŒ-ัะบะธะน ะท ะฝะธั… ะดะพ **FastAPI** ะดะพะดะฐั‚ะบัƒ. +ะขะฐะบะพะถ ั–ัะฝัƒัŽั‚ัŒ ะดะตััั‚ะบะธ ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒ, ั– ะฒัั– ะฒะพะฝะธ ะทะฐัะฝะพะฒะฐะฝั– ะฝะฐ OpenAPI. ะ’ะธ ะผะพะถะตั‚ะต ะปะตะณะบะพ ะดะพะดะฐั‚ะธ ะฑัƒะดัŒ-ัะบัƒ ะท ั†ะธั… ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒ ะดะพ ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ, ัั‚ะฒะพั€ะตะฝะพะณะพ ะท **FastAPI**. -ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ OpenAPI ะดะปั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพั— ะณะตะฝะตั€ะฐั†ั–ั— ะบะพะดัƒ ะดะปั ะบะปั–ั”ะฝั‚ั–ะฒ, ัะบั– ะฒะทะฐั”ะผะพะดั–ัŽั‚ัŒ ะท API. ะะฐะฟั€ะธะบะปะฐะด, ะดะปั ั„ั€ะพะฝั‚ะตะฝะด-, ะผะพะฑั–ะปัŒะฝะธั… ะฐะฑะพ IoT-ะดะพะดะฐั‚ะบั–ะฒ +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะนะพะณะพ ะดะปั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพั— ะณะตะฝะตั€ะฐั†ั–ั— ะบะพะดัƒ ะดะปั ะบะปั–ั”ะฝั‚ั–ะฒ, ัะบั– ะฒะทะฐั”ะผะพะดั–ัŽั‚ัŒ ะท ะฒะฐัˆะธะผ API. ะะฐะฟั€ะธะบะปะฐะด, ะดะปั ั„ั€ะพะฝั‚ะตะฝะด-, ะผะพะฑั–ะปัŒะฝะธั… ะฐะฑะพ IoT-ะทะฐัั‚ะพััƒะฝะบั–ะฒ. -## ะ ั‚ะตะฟะตั€ ะบั€ะพะบ ะทะฐ ะบั€ะพะบะพะผ +### ะ ะพะทะณะพั€ะฝั–ั‚ัŒ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ (ะฝะตะพะฑะพะฒสผัะทะบะพะฒะพ) { #deploy-your-app-optional } -### ะšั€ะพะบ 1: ั–ะผะฟะพั€ั‚ัƒั”ะผะพ `FastAPI` +ะ—ะฐ ะฑะฐะถะฐะฝะฝัะผ ะฒะธ ะผะพะถะตั‚ะต ั€ะพะทะณะพั€ะฝัƒั‚ะธ ะฒะฐัˆ FastAPI-ะทะฐัั‚ะพััƒะฝะพะบ ัƒ <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, ะฟะตั€ะตะนะดั–ั‚ัŒ ั– ะฟั€ะธั”ะดะฝะฐะนั‚ะตัั ะดะพ ัะฟะธัะบัƒ ะพั‡ั–ะบัƒะฒะฐะฝะฝั, ัะบั‰ะพ ะฒะธ ั†ัŒะพะณะพ ั‰ะต ะฝะต ะทั€ะพะฑะธะปะธ. ๐Ÿš€ -{* ../../docs_src/first_steps/tutorial001.py hl[1] *} +ะฏะบั‰ะพ ัƒ ะฒะฐั ะฒะถะต ั” ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั **FastAPI Cloud** (ะผะธ ะทะฐะฟั€ะพัะธะปะธ ะฒะฐั ะทั– ัะฟะธัะบัƒ ะพั‡ั–ะบัƒะฒะฐะฝะฝั ๐Ÿ˜‰), ะฒะธ ะผะพะถะตั‚ะต ั€ะพะทะณะพั€ะฝัƒั‚ะธ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ ะพะดะฝั–ั”ัŽ ะบะพะผะฐะฝะดะพัŽ. -`FastAPI` ั†ะต ะบะปะฐั ัƒ Python, ัะบะธะน ะฝะฐะดะฐั” ะฒััŽ ั„ัƒะฝะบั†ั–ะพะฝะฐะปัŒะฝั–ัั‚ัŒ ะดะปั API. +ะŸะตั€ะตะด ั€ะพะทะณะพั€ั‚ะฐะฝะฝัะผ ะฟะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ัƒะฒั–ะนัˆะปะธ: + +<div class="termy"> + +```console +$ fastapi login + +You are logged in to FastAPI Cloud ๐Ÿš€ +``` + +</div> + +ะŸะพั‚ั–ะผ ั€ะพะทะณะพั€ะฝั–ั‚ัŒ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ: + +<div class="termy"> + +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +โœ… Deployment successful! + +๐Ÿ” Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +</div> + +ะžััŒ ั– ะฒัะต! ะขะตะฟะตั€ ะฒะธ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ะดะพัั‚ัƒะฟ ะดะพ ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ ะทะฐ ั†ะธะผ URL. โœจ + +## ะŸั–ะดั–ะฑสผั”ะผะพ ะฟั–ะดััƒะผะบะธ, ะบั€ะพะบ ะทะฐ ะบั€ะพะบะพะผ { #recap-step-by-step } + +### ะšั€ะพะบ 1: ั–ะผะฟะพั€ั‚ัƒั”ะผะพ `FastAPI` { #step-1-import-fastapi } + +{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} + +`FastAPI` โ€” ั†ะต ะบะปะฐั ัƒ Python, ัะบะธะน ะฝะฐะดะฐั” ะฒััŽ ั„ัƒะฝะบั†ั–ะพะฝะฐะปัŒะฝั–ัั‚ัŒ ะดะปั ะฒะฐัˆะพะณะพ API. /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– -`FastAPI` ั†ะต ะบะปะฐั, ัะบะธะน ัƒัะฟะฐะดะบะพะฒัƒั”ั‚ัŒัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒั–ะด `Starlette`. +`FastAPI` โ€” ั†ะต ะบะปะฐั, ัะบะธะน ัƒัะฟะฐะดะบะพะฒัƒั”ั‚ัŒัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒั–ะด `Starlette`. ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒััŽ ั„ัƒะฝะบั†ั–ะพะฝะฐะปัŒะฝั–ัั‚ัŒ <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> ัƒ `FastAPI`. /// -### ะšั€ะพะบ 2: ัั‚ะฒะพั€ัŽั”ะผะพ ะตะบะทะตะผะฟะปัั€ `FastAPI` +### ะšั€ะพะบ 2: ัั‚ะฒะพั€ัŽั”ะผะพ ยซะตะบะทะตะผะฟะปัั€ยป `FastAPI` { #step-2-create-a-fastapi-instance } -{* ../../docs_src/first_steps/tutorial001.py hl[3] *} -ะ—ะผั–ะฝะฝะฐ `app` ั” ะตะบะทะตะผะฟะปัั€ะพะผ ะบะปะฐััƒ `FastAPI`. +{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *} -ะฆะต ะฑัƒะดะต ะณะพะปะพะฒะฝะฐ ั‚ะพั‡ะบะฐ ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ั– ะฒะทะฐั”ะผะพะดั–ั— ะท API. +ะขัƒั‚ ะทะผั–ะฝะฝะฐ `app` ะฑัƒะดะต ยซะตะบะทะตะผะฟะปัั€ะพะผยป ะบะปะฐััƒ `FastAPI`. -### ะšั€ะพะบ 3: ะฒะธะทะฝะฐั‡ั‚ะต ะพะฟะตั€ะฐั†ั–ัŽ ัˆะปัั…ัƒ (path operation) +ะฆะต ะฑัƒะดะต ะณะพะปะพะฒะฝะฐ ั‚ะพั‡ะบะฐ ะฒะทะฐั”ะผะพะดั–ั— ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ะฒััŒะพะณะพ ะฒะฐัˆะพะณะพ API. -#### ะจะปัั… (path) +### ะšั€ะพะบ 3: ัั‚ะฒะพั€ั–ั‚ัŒ *ะพะฟะตั€ะฐั†ั–ัŽ ัˆะปัั…ัƒ* { #step-3-create-a-path-operation } -"ะจะปัั…" ั†ะต ั‡ะฐัั‚ะธะฝะฐ URL, ัะบะฐ ะนะดะต ะพะดั€ะฐะทัƒ ะฟั–ัะปั ัะธะผะฒะพะปัƒ `/`. +#### ะจะปัั… { #path } + +ยซะจะปัั…ยป ั‚ัƒั‚ ะพะทะฝะฐั‡ะฐั” ะพัั‚ะฐะฝะฝัŽ ั‡ะฐัั‚ะธะฝัƒ URL, ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท ะฟะตั€ัˆะพะณะพ `/`. ะžั‚ะถะต, ัƒ ั‚ะฐะบะพะผัƒ URL, ัะบ: @@ -192,16 +221,17 @@ https://example.com/items/foo /items/foo ``` -/// info | ะ”ะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั +/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -"ะจะปัั…" (path) ั‚ะฐะบะพะถ ะทะฐะทะฒะธั‡ะฐะน ะฝะฐะทะธะฒะฐัŽั‚ัŒ "ะตะฝะดะฟะพั–ะฝั‚ะพะผ" (endpoint) ะฐะฑะพ "ะผะฐั€ัˆั€ัƒั‚ะพะผ" (route). +ยซะจะปัั…ยป ั‚ะฐะบะพะถ ะทะฐะทะฒะธั‡ะฐะน ะฝะฐะทะธะฒะฐัŽั‚ัŒ ยซะตะฝะดะฟะพั–ะฝั‚ะพะผยป ะฐะฑะพ ยซะผะฐั€ัˆั€ัƒั‚ะพะผยป. /// -ะŸั€ะธ ัั‚ะฒะพั€ะตะฝะฝั– API, "ัˆะปัั…" ั” ะพัะฝะพะฒะฝะธะผ ัะฟะพัะพะฑะพะผ ั€ะพะทะดั–ะปะตะฝะฝั "ะทะฐะฒะดะฐะฝัŒ" ั– "ั€ะตััƒั€ัั–ะฒ". -#### Operation +ะŸั–ะด ั‡ะฐั ัั‚ะฒะพั€ะตะฝะฝั API ยซัˆะปัั…ยป ั” ะพัะฝะพะฒะฝะธะผ ัะฟะพัะพะฑะพะผ ั€ะพะทะดั–ะปะธั‚ะธ ยซะทะฐะฒะดะฐะฝะฝัยป ั– ยซั€ะตััƒั€ัะธยป. -"ะžะฟะตั€ะฐั†ั–ั" (operation) ั‚ัƒั‚ ะพะทะฝะฐั‡ะฐั” ะพะดะธะฝ ะท "ะผะตั‚ะพะดั–ะฒ" HTTP. +#### ะžะฟะตั€ะฐั†ั–ั { #operation } + +ยซะžะฟะตั€ะฐั†ั–ัยป ั‚ัƒั‚ ะพะทะฝะฐั‡ะฐั” ะพะดะธะฝ ะท HTTP ยซะผะตั‚ะพะดั–ะฒยป. ะžะดะธะฝ ะท: @@ -217,46 +247,47 @@ https://example.com/items/foo * `PATCH` * `TRACE` -ะฃ HTTP-ะฟั€ะพั‚ะพะบะพะปั– ะผะพะถะฝะฐ ัะฟั–ะปะบัƒะฒะฐั‚ะธัั ะท ะบะพะถะฝะธะผ ัˆะปัั…ะพะผ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะพะดะธะฝ (ะฐะฑะพ ะบั–ะปัŒะบะฐ) ะท ั†ะธั… "ะผะตั‚ะพะดั–ะฒ". +ะฃ ะฟั€ะพั‚ะพะบะพะปั– HTTP ะฒะธ ะผะพะถะตั‚ะต ัะฟั–ะปะบัƒะฒะฐั‚ะธัั ะท ะบะพะถะฝะธะผ ัˆะปัั…ะพะผ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะพะดะธะฝ (ะฐะฑะพ ะบั–ะปัŒะบะฐ) ะท ั†ะธั… ยซะผะตั‚ะพะดั–ะฒยป. --- -ะŸั€ะธ ัั‚ะฒะพั€ะตะฝะฝั– API ะทะฐะทะฒะธั‡ะฐะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะบะพะฝะบั€ะตั‚ะฝั– ะผะตั‚ะพะดะธ HTTP ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฟะตะฒะฝะธั… ะดั–ะน. +ะŸั–ะด ั‡ะฐั ัั‚ะฒะพั€ะตะฝะฝั API ะทะฐะทะฒะธั‡ะฐะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ ั†ั– ะบะพะฝะบั€ะตั‚ะฝั– HTTP ะผะตั‚ะพะดะธ, ั‰ะพะฑ ะฒะธะบะพะฝะฐั‚ะธ ะฟะตะฒะฝัƒ ะดั–ัŽ. -ะฏะบ ะฟั€ะฐะฒะธะปะพ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ: +ะ—ะฐะทะฒะธั‡ะฐะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ: -* `POST`: ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ะดะฐะฝะธั…. -* `GET`: ะดะปั ั‡ะธั‚ะฐะฝะฝั ะดะฐะฝะธั…. -* `PUT`: ะดะปั ะพะฝะพะฒะปะตะฝะฝั ะดะฐะฝะธั…. -* `DELETE`: ะดะปั ะฒะธะดะฐะปะตะฝะฝั ะดะฐะฝะธั…. +* `POST`: ั‰ะพะฑ ัั‚ะฒะพั€ะธั‚ะธ ะดะฐะฝั–. +* `GET`: ั‰ะพะฑ ั‡ะธั‚ะฐั‚ะธ ะดะฐะฝั–. +* `PUT`: ั‰ะพะฑ ะพะฝะพะฒะธั‚ะธ ะดะฐะฝั–. +* `DELETE`: ั‰ะพะฑ ะฒะธะดะฐะปะธั‚ะธ ะดะฐะฝั–. -ะ’ OpenAPI ะบะพะถะตะฝ HTTP ะผะตั‚ะพะด ะฝะฐะทะธะฒะฐั”ั‚ัŒัั "ะพะฟะตั€ะฐั†ั–ั". +ะžั‚ะถะต, ะฒ OpenAPI ะบะพะถะตะฝ ะท HTTP ะผะตั‚ะพะดั–ะฒ ะฝะฐะทะธะฒะฐั”ั‚ัŒัั ยซะพะฟะตั€ะฐั†ั–ั”ัŽยป. -ะœะธ ั‚ะฐะบะพะถ ะฑัƒะดะตะผะพ ะดะพั‚ั€ะธะผัƒะฒะฐั‚ะธัั ั†ัŒะพะณะพ ั‚ะตั€ะผั–ะฝะฐ. +ะœะธ ั‚ะฐะบะพะถ ะฑัƒะดะตะผะพ ะฝะฐะทะธะฒะฐั‚ะธ ั—ั… ยซ**ะพะฟะตั€ะฐั†ั–ัะผะธ**ยป. -#### ะ’ะธะทะฝะฐั‡ั‚ะต ะดะตะบะพั€ะฐั‚ะพั€ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ (path operation decorator) +#### ะ’ะธะทะฝะฐั‡ั‚ะต *ะดะตะบะพั€ะฐั‚ะพั€ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* { #define-a-path-operation-decorator } -{* ../../docs_src/first_steps/tutorial001.py hl[6] *} -ะ”ะตะบะพั€ะฐั‚ะพั€ `@app.get("/")` ะฒะบะฐะทัƒั” **FastAPI**, ั‰ะพ ั„ัƒะฝะบั†ั–ั ะฝะธะถั‡ะต, ะฒั–ะดะฟะพะฒั–ะดะฐั” ะทะฐ ะพะฑั€ะพะฑะบัƒ ะทะฐะฟะธั‚ั–ะฒ, ัะบั– ะฝะฐะดั…ะพะดัั‚ัŒ ะดะพ ะฝะตั—: +{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *} -* ัˆะปัั… `/` +ะ”ะตะบะพั€ะฐั‚ะพั€ `@app.get("/")` ะฟะพะฒั–ะดะพะผะปัั” **FastAPI**, ั‰ะพ ั„ัƒะฝะบั†ั–ั ะพะดั€ะฐะทัƒ ะฝะธะถั‡ะต ะฒั–ะดะฟะพะฒั–ะดะฐั” ะทะฐ ะพะฑั€ะพะฑะบัƒ ะทะฐะฟะธั‚ั–ะฒ, ัะบั– ะฝะฐะดั…ะพะดัั‚ัŒ ะดะพ: + +* ัˆะปัั…ัƒ `/` * ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ <abbr title="an HTTP GET method"><code>get</code> ะพะฟะตั€ะฐั†ั–ัŽ</abbr> -/// info | `@decorator` ะ”ะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั +/// info | `@decorator` ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะกะธะฝั‚ะฐะบัะธั `@something` ัƒ Python ะฝะฐะทะธะฒะฐั”ั‚ัŒัั "ะดะตะบะพั€ะฐั‚ะพั€ะพะผ". +ะกะธะฝั‚ะฐะบัะธั `@something` ัƒ Python ะฝะฐะทะธะฒะฐั”ั‚ัŒัั ยซะดะตะบะพั€ะฐั‚ะพั€ะพะผยป. ะ’ะธ ั€ะพะทั‚ะฐัˆะพะฒัƒั”ั‚ะต ะนะพะณะพ ะฝะฐะด ั„ัƒะฝะบั†ั–ั”ัŽ. ะฏะบ ะณะฐั€ะฝะธะน ะดะตะบะพั€ะฐั‚ะธะฒะฝะธะน ะบะฐะฟะตะปัŽั… (ะผะฐะฑัƒั‚ัŒ, ะทะฒั–ะดั‚ะธ ะฟะพั…ะพะดะธั‚ัŒ ั‚ะตั€ะผั–ะฝ). -"ะ”ะตะบะพั€ะฐั‚ะพั€" ะฟั€ะธะนะผะฐั” ั„ัƒะฝะบั†ั–ัŽ ะฝะธะถั‡ะต ั– ะฒะธะบะพะฝัƒั” ะท ะฝะตัŽ ัะบัƒััŒ ะดั–ัŽ. +ยซะ”ะตะบะพั€ะฐั‚ะพั€ยป ะฑะตั€ะต ั„ัƒะฝะบั†ั–ัŽ ะฝะธะถั‡ะต ั– ะฒะธะบะพะฝัƒั” ะท ะฝะตัŽ ัะบัƒััŒ ะดั–ัŽ. ะฃ ะฝะฐัˆะพะผัƒ ะฒะธะฟะฐะดะบัƒ, ั†ะตะน ะดะตะบะพั€ะฐั‚ะพั€ ะฟะพะฒั–ะดะพะผะปัั” **FastAPI**, ั‰ะพ ั„ัƒะฝะบั†ั–ั ะฝะธะถั‡ะต ะฒั–ะดะฟะพะฒั–ะดะฐั” **ัˆะปัั…ัƒ** `/` ั– **ะพะฟะตั€ะฐั†ั–ั—** `get`. -ะฆะต ั– ั” "ะดะตะบะพั€ะฐั‚ะพั€ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ (path operation decorator)". +ะฆะต ั– ั” ยซ**ะดะตะบะพั€ะฐั‚ะพั€ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ**ยป. /// -ะœะพะถะฝะฐ ั‚ะฐะบะพะถ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะฟะตั€ะฐั†ั–ั—: +ะœะพะถะฝะฐ ั‚ะฐะบะพะถ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั–ะฝัˆั– ะพะฟะตั€ะฐั†ั–ั—: * `@app.post()` * `@app.put()` @@ -271,58 +302,79 @@ https://example.com/items/foo /// tip | ะŸะพั€ะฐะดะฐ -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะบะพะถะฝัƒ ะพะฟะตั€ะฐั†ั–ัŽ (HTTP-ะผะตั‚ะพะด) ะฝะฐ ัะฒั–ะน ั€ะพะทััƒะด. +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะบะพะถะฝัƒ ะพะฟะตั€ะฐั†ั–ัŽ (HTTP-ะผะตั‚ะพะด) ัะบ ะทะฐะฑะฐะถะฐั”ั‚ะต. -**FastAPI** ะฝะต ะฝะฐะฒ'ัะทัƒั” ะถะพะดะฝะพะณะพ ะฟะตะฒะฝะพะณะพ ะทะฝะฐั‡ะตะฝะฝั ะดะปั ะบะพะถะฝะพะณะพ ะผะตั‚ะพะดัƒ. +**FastAPI** ะฝะต ะฝะฐะฒสผัะทัƒั” ะถะพะดะฝะพะณะพ ะบะพะฝะบั€ะตั‚ะฝะพะณะพ ะทะฝะฐั‡ะตะฝะฝั. -ะะฐะฒะตะดะตะฝะฐ ั‚ัƒั‚ ั–ะฝั„ะพั€ะผะฐั†ั–ั ั” ั€ะตะบะพะผะตะฝะดะฐั†ั–ั”ัŽ, ะฐ ะฝะต ะพะฑะพะฒ'ัะทะบะพะฒะพัŽ ะฒะธะผะพะณะพัŽ. +ะะฐะฒะตะดะตะฝะฐ ั‚ัƒั‚ ั–ะฝั„ะพั€ะผะฐั†ั–ั ะฟะพะดะฐะฝะฐ ัะบ ะฝะฐัั‚ะฐะฝะพะฒะฐ, ะฐ ะฝะต ะฒะธะผะพะณะฐ. -ะะฐะฟั€ะธะบะปะฐะด, ะฟั–ะด ั‡ะฐั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั GraphQL ะทะฐะทะฒะธั‡ะฐะน ัƒัั– ะดั–ั— ะฒะธะบะพะฝัƒัŽั‚ัŒัั ั‚ั–ะปัŒะบะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `POST` ะพะฟะตั€ะฐั†ั–ะน. +ะะฐะฟั€ะธะบะปะฐะด, ะฟั–ะด ั‡ะฐั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั GraphQL ะฒะธ ะทะฐะทะฒะธั‡ะฐะน ะฒะธะบะพะฝัƒั”ั‚ะต ะฒัั– ะดั–ั—, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะปะธัˆะต `POST` ะพะฟะตั€ะฐั†ั–ั—. /// -### ะšั€ะพะบ 4: ะฒะธะทะฝะฐั‡ั‚ะต **ั„ัƒะฝะบั†ั–ัŽ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ (path operation function)** +### ะšั€ะพะบ 4: ะฒะธะทะฝะฐั‡ั‚ะต **ั„ัƒะฝะบั†ั–ัŽ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ** { #step-4-define-the-path-operation-function } -ะžััŒ "**ั„ัƒะฝะบั†ั–ั ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ**": +ะžััŒ ะฝะฐัˆะฐ ยซ**ั„ัƒะฝะบั†ั–ั ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ**ยป: * **ัˆะปัั…**: ั†ะต `/`. * **ะพะฟะตั€ะฐั†ั–ั**: ั†ะต `get`. -* **ั„ัƒะฝะบั†ั–ั**: ั†ะต ั„ัƒะฝะบั†ั–ั, ัะบะฐ ะทะฝะฐั…ะพะดะธั‚ัŒัั ะฝะธะถั‡ะต "ะดะตะบะพั€ะฐั‚ะพั€ะฐ" (ะฝะธะถั‡ะต `@app.get("/")`). +* **ั„ัƒะฝะบั†ั–ั**: ั†ะต ั„ัƒะฝะบั†ั–ั ะฝะธะถั‡ะต ยซะดะตะบะพั€ะฐั‚ะพั€ะฐยป (ะฝะธะถั‡ะต `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} -ะฆะต ะทะฒะธั‡ะฐะนะฝะฐ ั„ัƒะฝะบั†ั–ั Python. +ะฆะต ั„ัƒะฝะบั†ั–ั Python. -FastAPI ะฒะธะบะปะธะบะฐั‚ะธะผะต ั—ั— ั‰ะพั€ะฐะทัƒ, ะบะพะปะธ ะพั‚ั€ะธะผะฐั” ะทะฐะฟะธั‚ ะดะพ URL ั–ะท ัˆะปัั…ะพะผ "/", ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะพะฟะตั€ะฐั†ั–ัŽ `GET`. +**FastAPI** ะฒะธะบะปะธะบะฐั‚ะธะผะต ั—ั— ั‰ะพั€ะฐะทัƒ, ะบะพะปะธ ะพั‚ั€ะธะผะฐั” ะทะฐะฟะธั‚ ะดะพ URL ยซ`/`ยป, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะพะฟะตั€ะฐั†ั–ัŽ `GET`. -ะฃ ะดะฐะฝะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั†ะต ะฐัะธะฝั…ั€ะพะฝะฝะฐ ั„ัƒะฝะบั†ั–ั. +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั†ะต `async` ั„ัƒะฝะบั†ั–ั. --- ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ั—ั— ัะบ ะทะฒะธั‡ะฐะนะฝัƒ ั„ัƒะฝะบั†ั–ัŽ ะทะฐะผั–ัั‚ัŒ `async def`: -{* ../../docs_src/first_steps/tutorial003.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} /// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะฏะบั‰ะพ ะฝะต ะทะฝะฐั”ั‚ะต ะฒ ั‡ะพะผัƒ ั€ั–ะทะฝะธั†ั, ะฟะพะดะธะฒั–ั‚ัŒัั [ะšะพะฝะบัƒั€ะตะฝั‚ะฝั–ัั‚ัŒ: *"ะŸะพัะฟั–ัˆะฐั”ัˆ?"*](../async.md#in-a-hurry){.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`, ั–ั‚ะด. +ะ’ะธ ะผะพะถะตั‚ะต ะฟะพะฒะตั€ะฝัƒั‚ะธ `dict`, `list`, ะฐ ั‚ะฐะบะพะถ ะพะบั€ะตะผั– ะทะฝะฐั‡ะตะฝะฝั `str`, `int` ั‚ะพั‰ะพ. ะขะฐะบะพะถ ะผะพะถะฝะฐ ะฟะพะฒะตั€ะฝัƒั‚ะธ ะผะพะดะตะปั– Pydantic (ะฟั€ะพ ั†ะต ะฒะธ ะดั–ะทะฝะฐั”ั‚ะตััŒ ะฟั–ะทะฝั–ัˆะต). -ะ†ัะฝัƒั” ะฑะฐะณะฐั‚ะพ ั–ะฝัˆะธั… ะพะฑ'ั”ะบั‚ั–ะฒ ั– ะผะพะดะตะปะตะน, ัะบั– ะฑัƒะดัƒั‚ัŒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะพะฝะฒะตั€ั‚ะพะฒะฐะฝั– ะฒ JSON (ะทะพะบั€ะตะผะฐ ORM ั‚ะพั‰ะพ). ะกะฟั€ะพะฑัƒะนั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ัะฒะพั— ัƒะปัŽะฑะปะตะฝั–, ะฒะตะปะธะบะฐ ะนะผะพะฒั–ั€ะฝั–ัั‚ัŒ, ั‰ะพ ะฒะพะฝะธ ะฒะถะต ะฟั–ะดั‚ั€ะธะผัƒัŽั‚ัŒัั. +ะ†ัะฝัƒั” ะฑะฐะณะฐั‚ะพ ั–ะฝัˆะธั… ะพะฑสผั”ะบั‚ั–ะฒ ั– ะผะพะดะตะปะตะน, ัะบั– ะฑัƒะดัƒั‚ัŒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะพะฝะฒะตั€ั‚ะพะฒะฐะฝั– ะฒ JSON (ะทะพะบั€ะตะผะฐ ORM ั‚ะพั‰ะพ). ะกะฟั€ะพะฑัƒะนั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ัะฒะพั— ัƒะปัŽะฑะปะตะฝั– โ€” ะฒะตะปะธะบะฐ ะนะผะพะฒั–ั€ะฝั–ัั‚ัŒ, ั‰ะพ ะฒะพะฝะธ ะฒะถะต ะฟั–ะดั‚ั€ะธะผัƒัŽั‚ัŒัั. -## ะŸั–ะดั–ะฑ'ั”ะผะพ ะฟั–ะดััƒะผะบะธ +### ะšั€ะพะบ 6: ั€ะพะทะณะพั€ะฝั–ั‚ัŒ ะนะพะณะพ { #step-6-deploy-it } -* ะ†ะผะฟะพั€ั‚ัƒั”ะผะพ `FastAPI`. -* ะกั‚ะฒะพั€ัŽั”ะผะพ ะตะบะทะตะผะฟะปัั€ `app`. -* ะŸะธัˆะตะผะพ **ะดะตะบะพั€ะฐั‚ะพั€ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ** ัะบ `@app.get("/")`. -* ะŸะธัˆะตะผะพ **ั„ัƒะฝะบั†ั–ัŽ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ**; ะฝะฐะฟั€ะธะบะปะฐะด, `def root(): ...`. -* ะ—ะฐะฟัƒัะบะฐั”ะผะพ ัะตั€ะฒะตั€ ัƒ ั€ะตะถะธะผั– ั€ะพะทั€ะพะฑะบะธ `fastapi dev`. +ะ ะพะทะณะพั€ะฝั–ั‚ัŒ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ ัƒ **<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** ะพะดะฝั–ั”ัŽ ะบะพะผะฐะฝะดะพัŽ: `fastapi deploy`. ๐ŸŽ‰ + +#### ะŸั€ะพ FastAPI Cloud { #about-fastapi-cloud } + +**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** ัั‚ะฒะพั€ะตะฝะพ ั‚ะธะผ ัะฐะผะธะผ ะฐะฒั‚ะพั€ะพะผ ั– ะบะพะผะฐะฝะดะพัŽ, ัะบั– ัั‚ะพัั‚ัŒ ะทะฐ **FastAPI**. + +ะ’ั–ะฝ ัะฟั€ะพั‰ัƒั” ะฟั€ะพั†ะตั **ัั‚ะฒะพั€ะตะฝะฝั**, **ั€ะพะทะณะพั€ั‚ะฐะฝะฝั** ั‚ะฐ **ะดะพัั‚ัƒะฟัƒ** ะดะพ API ะท ะผั–ะฝั–ะผะฐะปัŒะฝะธะผะธ ะทัƒัะธะปะปัะผะธ. + +ะ’ั–ะฝ ะฟะตั€ะตะฝะพัะธั‚ัŒ ั‚ะพะน ัะฐะผะธะน **ะดะพัะฒั–ะด ั€ะพะทั€ะพะฑะฝะธะบะฐ** ะทั– ัั‚ะฒะพั€ะตะฝะฝั ะทะฐัั‚ะพััƒะฝะบั–ะฒ ะฝะฐ FastAPI ะฝะฐ **ั€ะพะทะณะพั€ั‚ะฐะฝะฝั** ั—ั… ัƒ ั…ะผะฐั€ั–. ๐ŸŽ‰ + +FastAPI Cloud โ€” ะพัะฝะพะฒะฝะธะน ัะฟะพะฝัะพั€ ั– ะดะถะตั€ะตะปะพ ั„ั–ะฝะฐะฝััƒะฒะฐะฝะฝั ะดะปั open source ะฟั€ะพั”ะบั‚ั–ะฒ *FastAPI and friends*. โœจ + +#### ะ ะพะทะณะพั€ั‚ะฐะฝะฝั ะฒ ั–ะฝัˆะธั… ั…ะผะฐั€ะฝะธั… ะฟั€ะพะฒะฐะนะดะตั€ะฐั… { #deploy-to-other-cloud-providers } + +FastAPI โ€” ั†ะต open source ั– ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ ัั‚ะฐะฝะดะฐั€ั‚ะฐั…. ะ’ะธ ะผะพะถะตั‚ะต ั€ะพะทะณะพั€ั‚ะฐั‚ะธ FastAPI-ะทะฐัั‚ะพััƒะฝะบะธ ัƒ ะฑัƒะดัŒ-ัะบะพะณะพ ั…ะผะฐั€ะฝะพะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ ะฝะฐ ะฒะฐัˆ ะฒะธะฑั–ั€. + +ะ”ะพั‚ั€ะธะผัƒะนั‚ะตัั ั–ะฝัั‚ั€ัƒะบั†ั–ะน ะฒะฐัˆะพะณะพ ั…ะผะฐั€ะฝะพะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ, ั‰ะพะฑ ั€ะพะทะณะพั€ะฝัƒั‚ะธ FastAPI-ะทะฐัั‚ะพััƒะฝะบะธ ะท ั—ั…ะฝัŒะพัŽ ะดะพะฟะพะผะพะณะพัŽ. ๐Ÿค“ + +## ะŸั–ะดั–ะฑสผั”ะผะพ ะฟั–ะดััƒะผะบะธ { #recap } + +* ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต `FastAPI`. +* ะกั‚ะฒะพั€ั–ั‚ัŒ ะตะบะทะตะผะฟะปัั€ `app`. +* ะะฐะฟะธัˆั–ั‚ัŒ **ะดะตะบะพั€ะฐั‚ะพั€ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ**, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะดะตะบะพั€ะฐั‚ะพั€ะธ ะฝะฐ ะบัˆั‚ะฐะปั‚ `@app.get("/")`. +* ะ’ะธะทะฝะฐั‡ั‚ะต **ั„ัƒะฝะบั†ั–ัŽ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ**; ะฝะฐะฟั€ะธะบะปะฐะด, `def root(): ...`. +* ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ ัะตั€ะฒะตั€ ั€ะพะทั€ะพะฑะบะธ ะบะพะผะฐะฝะดะพัŽ `fastapi dev`. +* ะ—ะฐ ะฑะฐะถะฐะฝะฝัะผ ั€ะพะทะณะพั€ะฝั–ั‚ัŒ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `fastapi deploy`. diff --git a/docs/uk/docs/tutorial/handling-errors.md b/docs/uk/docs/tutorial/handling-errors.md index 32de73b2a0..53b8b12f61 100644 --- a/docs/uk/docs/tutorial/handling-errors.md +++ b/docs/uk/docs/tutorial/handling-errors.md @@ -1,10 +1,10 @@ -# ะžะฑั€ะพะฑะบะฐ ะŸะพะผะธะปะพะบ +# ะžะฑั€ะพะฑะบะฐ ะฟะพะผะธะปะพะบ { #handling-errors } -ะ„ ะฑะฐะณะฐั‚ะพ ัะธั‚ัƒะฐั†ั–ะน, ะบะพะปะธ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะพะฒั–ะดะพะผะธั‚ะธ ะบะปั–ั”ะฝั‚ะฐ, ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ะ’ะฐัˆ API, ะฟั€ะพ ะฟะพะผะธะปะบัƒ. +ะ„ ะฑะฐะณะฐั‚ะพ ัะธั‚ัƒะฐั†ั–ะน, ะบะพะปะธ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะพะฒั–ะดะพะผะธั‚ะธ ะฟั€ะพ ะฟะพะผะธะปะบัƒ ะบะปั–ั”ะฝั‚ะฐ, ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ะฒะฐัˆ API. ะฆะธะผ ะบะปั–ั”ะฝั‚ะพะผ ะผะพะถะต ะฑัƒั‚ะธ ะฑั€ะฐัƒะทะตั€ ั–ะท ั„ั€ะพะฝั‚ะตะฝะดะพะผ, ะบะพะด ั–ะฝัˆะพะณะพ ั€ะพะทั€ะพะฑะฝะธะบะฐ, IoT-ะฟั€ะธัั‚ั€ั–ะน ั‚ะพั‰ะพ. -ะœะพะถะปะธะฒะพ, ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะพะฒั–ะดะพะผะธั‚ะธ ะบะปั–ั”ะฝั‚ะฐ, ั‰ะพ: +ะœะพะถะปะธะฒะพ, ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะพะฒั–ะดะพะผะธั‚ะธ ะบะปั–ั”ะฝั‚ะฐ, ั‰ะพ: * ะฃ ะฝัŒะพะณะพ ะฝะตะดะพัั‚ะฐั‚ะฝัŒะพ ะฟั€ะฐะฒ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ั†ั–ั”ั— ะพะฟะตั€ะฐั†ั–ั—. * ะ’ั–ะฝ ะฝะต ะผะฐั” ะดะพัั‚ัƒะฟัƒ ะดะพ ั†ัŒะพะณะพ ั€ะตััƒั€ััƒ. @@ -13,37 +13,37 @@ ะฃ ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบะฐั… ะทะฐะทะฒะธั‡ะฐะน ะฟะพะฒะตั€ั‚ะฐั”ั‚ัŒัั **HTTP ัั‚ะฐั‚ัƒั-ะบะพะด** ะฒ ะดั–ะฐะฟะฐะทะพะฝั– **400** (ะฒั–ะด 400 ะดะพ 499). -ะฆะต ัั…ะพะถะต ะฝะฐ HTTP ัั‚ะฐั‚ัƒั-ะบะพะดะธ 200 (ะฒั–ะด 200 ะดะพ 299). ะฆั– "200" ัั‚ะฐั‚ัƒั-ะบะพะดะธ ะพะทะฝะฐั‡ะฐัŽั‚ัŒ, ั‰ะพ ะทะฐะฟะธั‚ ะฟั€ะพะนัˆะพะฒ ัƒัะฟั–ัˆะฝะพ. +ะฆะต ัั…ะพะถะต ะฝะฐ HTTP ัั‚ะฐั‚ัƒั-ะบะพะดะธ 200 (ะฒั–ะด 200 ะดะพ 299). ะฆั– ยซ200ยป ัั‚ะฐั‚ัƒั-ะบะพะดะธ ะพะทะฝะฐั‡ะฐัŽั‚ัŒ, ั‰ะพ ัะบะธะผะพััŒ ั‡ะธะฝะพะผ ะทะฐะฟะธั‚ ะฑัƒะฒ ยซัƒัะฟั–ัˆะฝะธะผยป. ะกั‚ะฐั‚ัƒั-ะบะพะดะธ ะฒ ะดั–ะฐะฟะฐะทะพะฝั– 400 ะพะทะฝะฐั‡ะฐัŽั‚ัŒ, ั‰ะพ ัั‚ะฐะปะฐัั ะฟะพะผะธะปะบะฐ ะท ะฑะพะบัƒ ะบะปั–ั”ะฝั‚ะฐ. -ะŸะฐะผ'ัั‚ะฐั”ั‚ะต ะฒัั– ั†ั– ะฟะพะผะธะปะบะธ **404 Not Found** (ั– ะถะฐั€ั‚ะธ ะฟั€ะพ ะฝะธั…)? +ะŸะฐะผ'ัั‚ะฐั”ั‚ะต ะฒัั– ั†ั– ะฟะพะผะธะปะบะธ **ยซ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` โ€” ั†ะต ะทะฒะธั‡ะฐะนะฝะฐ ะฟะพะผะธะปะบะฐ Python ั–ะท ะดะพะดะฐั‚ะบะพะฒะธะผะธ ะดะฐะฝะธะผะธ, ัะบั– ัั‚ะพััƒัŽั‚ัŒัั API. -ะžัะบั–ะปัŒะบะธ ั†ะต ะฟะพะผะธะปะบะฐ Python, ะ’ะธ ะฝะต `ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต` ะนะพะณะพ, ะฐ `ะณะตะฝะตั€ัƒั”ั‚ะต` (ะณะตะฝะตั€ัƒั”ั‚ะต ะฟะพะผะธะปะบัƒ). +ะžัะบั–ะปัŒะบะธ ั†ะต ะฟะพะผะธะปะบะฐ Python, ะฒะธ ะฝะต `return` ั—ั—, ะฐ `raise` ั—ั—. -ะฆะต ั‚ะฐะบะพะถ ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ัะบั‰ะพ ะ’ะธ ะฟะตั€ะตะฑัƒะฒะฐั”ั‚ะต ะฒัะตั€ะตะดะธะฝั– ะดะพะฟะพะผั–ะถะฝะพั— ั„ัƒะฝะบั†ั–ั—, ัะบัƒ ะฒะธะบะปะธะบะฐั”ั‚ะต ะฒัะตั€ะตะดะธะฝั– ัะฒะพั”ั— *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ั– ั‚ะฐะผ ะณะตะฝะตั€ัƒั”ั‚ะต `HTTPException`, ะฒัะตั€ะตะดะธะฝั– ั†ั–ั”ั— ะดะพะฟะพะผั–ะถะฝะพั— ั„ัƒะฝะบั†ั–ั—, ั‚ะพ ั€ะตัˆั‚ะฐ ะบะพะดัƒ ะฒ *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะฝะต ะฑัƒะดะต ะฒะธะบะพะฝะฐะฝะฐ. ะ—ะฐะฟะธั‚ ะพะดั€ะฐะทัƒ ะทะฐะฒะตั€ัˆะธั‚ัŒัั, ั– HTTP-ะฟะพะผะธะปะบะฐ ะท `HTTPException` ะฑัƒะดะต ะฝะฐะดั–ัะปะฐะฝะฐ ะบะปั–ั”ะฝั‚ัƒ. +ะฆะต ั‚ะฐะบะพะถ ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ัะบั‰ะพ ะฒะธ ะฟะตั€ะตะฑัƒะฒะฐั”ั‚ะต ะฒัะตั€ะตะดะธะฝั– ะดะพะฟะพะผั–ะถะฝะพั— ั„ัƒะฝะบั†ั–ั—, ัะบัƒ ะฒะธะบะปะธะบะฐั”ั‚ะต ะฒัะตั€ะตะดะธะฝั– ัะฒะพั”ั— *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ั– ั‚ะฐะผ ะทะณะตะฝะตั€ัƒั”ั‚ะต `HTTPException` ะฒัะตั€ะตะดะธะฝั– ั†ั–ั”ั— ะดะพะฟะพะผั–ะถะฝะพั— ั„ัƒะฝะบั†ั–ั—, ั‚ะพ ั€ะตัˆั‚ะฐ ะบะพะดัƒ ะฒ *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะฝะต ะฑัƒะดะต ะฒะธะบะพะฝะฐะฝะฐ. ะ—ะฐะฟะธั‚ ะพะดั€ะฐะทัƒ ะทะฐะฒะตั€ัˆะธั‚ัŒัั, ั– HTTP-ะฟะพะผะธะปะบะฐ ะท `HTTPException` ะฑัƒะดะต ะฝะฐะดั–ัะปะฐะฝะฐ ะบะปั–ั”ะฝั‚ัƒ. -ะŸะตั€ะตะฒะฐะณะฐ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `ะณะตะฝะตั€ะฐั†ั–ั—` (raise) ะฟะพะผะธะปะบะธ ะทะฐะผั–ัั‚ัŒ `ะฟะพะฒะตั€ะฝะตะฝะฝั` ะทะฝะฐั‡ะตะฝะฝั (return) ัั‚ะฐะฝะต ะฑั–ะปัŒัˆ ะพั‡ะตะฒะธะดะฝะธะผ ะฒ ั€ะพะทะดั–ะปั– ะฟั€ะพ ะ—ะฐะปะตะถะฝะพัั‚ั– ั‚ะฐ ะ‘ะตะทะฟะตะบัƒ. +ะŸะตั€ะตะฒะฐะณะฐ ะณะตะฝะตั€ะฐั†ั–ั— ะฒะธะบะปัŽั‡ะตะฝะฝั ะทะฐะผั–ัั‚ัŒ ะฟะพะฒะตั€ะฝะตะฝะฝั ะทะฝะฐั‡ะตะฝะฝั ัั‚ะฐะฝะต ะฑั–ะปัŒัˆ ะพั‡ะตะฒะธะดะฝะพัŽ ะฒ ั€ะพะทะดั–ะปั– ะฟั€ะพ ะทะฐะปะตะถะฝะพัั‚ั– ั‚ะฐ ะฑะตะทะฟะตะบัƒ. -ะฃ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั–, ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ะทะฐะฟะธั‚ัƒั” ะตะปะตะผะตะฝั‚ ะทะฐ ID, ัะบะพะณะพ ะฝะต ั–ัะฝัƒั”, ะฑัƒะดะต ะทะณะตะฝะตั€ะพะฒะฐะฝะพ ะฟะพะผะธะปะบัƒ ะทั– ัั‚ะฐั‚ัƒั-ะบะพะดะพะผ `404`: +ะฃ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั–, ะบะพะปะธ ะบะปั–ั”ะฝั‚ ะทะฐะฟะธั‚ัƒั” ะตะปะตะผะตะฝั‚ ะทะฐ ID, ัะบะพะณะพ ะฝะต ั–ัะฝัƒั”, ะทะณะตะฝะตั€ัƒะนั‚ะต ะฒะธะบะปัŽั‡ะตะฝะฝั ะทั– ัั‚ะฐั‚ัƒั-ะบะพะดะพะผ `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"`), ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” ัั‚ะฐั‚ัƒั-ะบะพะด 200 ั– JSON ะฒั–ะดะฟะพะฒั–ะดัŒ: +ะฏะบั‰ะพ ะบะปั–ั”ะฝั‚ ั€ะพะฑะธั‚ัŒ ะทะฐะฟะธั‚ ะทะฐ ัˆะปัั…ะพะผ `http://example.com/items/foo` (ะดะต `item_id` `"foo"`), ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” HTTP ัั‚ะฐั‚ัƒั-ะบะพะด 200 ั– JSON ะฒั–ะดะฟะพะฒั–ะดัŒ: ```JSON { @@ -51,7 +51,7 @@ } ``` -ะะปะต ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ั€ะพะฑะธั‚ัŒ ะทะฐะฟะธั‚ ะฝะฐ `http://example.com/items/bar` (ะดะต `item_id` ะผะฐั” ะฝะต ั–ัะฝัƒัŽั‡ะต ะทะฝะฐั‡ะตะฝะฝั `"bar"`), ั‚ะพ ะพั‚ั€ะธะผะฐั” ัั‚ะฐั‚ัƒั-ะบะพะด 404 (ะฟะพะผะธะปะบะฐ "ะฝะต ะทะฝะฐะนะดะตะฝะพ") ั‚ะฐ ะฒั–ะดะฟะพะฒั–ะดัŒ: +ะะปะต ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ั€ะพะฑะธั‚ัŒ ะทะฐะฟะธั‚ ะฝะฐ `http://example.com/items/bar` (ะดะต `item_id` ะผะฐั” ะฝะต ั–ัะฝัƒัŽั‡ะต ะทะฝะฐั‡ะตะฝะฝั `"bar"`), ั‚ะพ ะพั‚ั€ะธะผะฐั” HTTP ัั‚ะฐั‚ัƒั-ะบะพะด 404 (ะฟะพะผะธะปะบะฐ ยซะฝะต ะทะฝะฐะนะดะตะฝะพยป) ั‚ะฐ JSON ะฒั–ะดะฟะพะฒั–ะดัŒ: ```JSON { @@ -61,7 +61,7 @@ /// tip | ะŸะพั€ะฐะดะฐ -ะŸั–ะด ั‡ะฐั ะฒะธะบะปะธะบัƒ `HTTPException` ะ’ะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ ะฑัƒะดัŒ-ัะบะต ะทะฝะฐั‡ะตะฝะฝั, ัะบะต ะผะพะถะต ะฑัƒั‚ะธ ะฟะตั€ะตั‚ะฒะพั€ะตะฝะต ะฒ JSON, ัะบ ะฟะฐั€ะฐะผะตั‚ั€ `detail`, ะฐ ะฝะต ะปะธัˆะต ั€ัะดะพะบ (`str`). +ะŸั–ะด ั‡ะฐั ะณะตะฝะตั€ะฐั†ั–ั— `HTTPException` ะฒะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ ะฑัƒะดัŒ-ัะบะต ะทะฝะฐั‡ะตะฝะฝั, ัะบะต ะผะพะถะต ะฑัƒั‚ะธ ะฟะตั€ะตั‚ะฒะพั€ะตะฝะต ะฒ JSON, ัะบ ะฟะฐั€ะฐะผะตั‚ั€ `detail`, ะฐ ะฝะต ะปะธัˆะต `str`. ะ’ะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ `dict`, `list` ั‚ะพั‰ะพ. @@ -69,33 +69,33 @@ /// -## ะ”ะพะดะฐะฒะฐะฝะฝั ะฒะปะฐัะฝะธั… ะทะฐะณะพะปะพะฒะบั–ะฒ +## ะ”ะพะดะฐะฒะฐะฝะฝั ะฒะปะฐัะฝะธั… ะทะฐะณะพะปะพะฒะบั–ะฒ { #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 } -ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะฒะปะฐัะฝั– ะพะฑั€ะพะฑะฝะธะบะธ ะฟะพะผะธะปะพะบ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ <a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">ั‚ะธั… ัะฐะผะธั… ัƒั‚ะธะปั–ั‚ ะพะฑั€ะพะฑะบะธ ะฟะพะผะธะปะพะบ ะทั– Starlette</a>. +ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะฒะปะฐัะฝั– ะพะฑั€ะพะฑะฝะธะบะธ ะฒะธะบะปัŽั‡ะตะฝัŒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ <a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">ั‚ะธั… ัะฐะผะธั… ัƒั‚ะธะปั–ั‚ ะดะปั ะฒะธะบะปัŽั‡ะตะฝัŒ ะทั– Starlette</a>. -ะŸั€ะธะฟัƒัั‚ะธะผะพ, ัƒ ะ’ะฐั ั” ะฒะปะฐัะฝะธะน ะพะฑสผั”ะบั‚ ะฟะพะผะธะปะบะธ `UnicornException`, ัะบะต ะ’ะธ (ะฐะฑะพ ะฑั–ะฑะปั–ะพั‚ะตะบะฐ, ัะบัƒ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต) ะผะพะถะต `ะทะณะตะฝะตั€ัƒะฒะฐั‚ะธ` (`raise`). +ะŸั€ะธะฟัƒัั‚ั–ะผะพ, ัƒ ะฒะฐั ั” ะฒะปะฐัะฝะต ะฒะธะบะปัŽั‡ะตะฝะฝั `UnicornException`, ัะบะต ะฒะธ (ะฐะฑะพ ะฑั–ะฑะปั–ะพั‚ะตะบะฐ, ัะบัƒ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต) ะผะพะถะตั‚ะต `raise`. -ะ† ะ’ะธ ั…ะพั‡ะตั‚ะต ะพะฑั€ะพะฑะปัั‚ะธ ั†ะต ะฒะธะบะปัŽั‡ะตะฝะฝั ะณะปะพะฑะฐะปัŒะฝะพ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ FastAPI. +ะ† ะฒะธ ั…ะพั‡ะตั‚ะต ะพะฑั€ะพะฑะปัั‚ะธ ั†ะต ะฒะธะบะปัŽั‡ะตะฝะฝั ะณะปะพะฑะฐะปัŒะฝะพ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ FastAPI. ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะฒะปะฐัะฝะธะน ะพะฑั€ะพะฑะฝะธะบ ะฒะธะบะปัŽั‡ะตะฝัŒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `@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`, *ะพะฟะตั€ะฐั†ั–ั ัˆะปัั…ัƒ* ะทะณะตะฝะตั€ัƒั” (`raise`) `UnicornException`. ะะปะต ะฒะพะฝะฐ ะฑัƒะดะต ะพะฑั€ะพะฑะปะตะฝะฐ ั„ัƒะฝะบั†ั–ั”ัŽ-ะพะฑั€ะพะฑะฝะธะบะพะผ `unicorn_exception_handler`. -ะžั‚ะถะต, ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะทั€ะพะทัƒะผั–ะปัƒ ะฟะพะผะธะปะบัƒ ะทั– HTTP-ัั‚ะฐั‚ัƒัะพะผ `418` ั– JSON-ะฒั–ะดะฟะพะฒั–ะดะดัŽ: +ะžั‚ะถะต, ะฒะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะทั€ะพะทัƒะผั–ะปัƒ ะฟะพะผะธะปะบัƒ ะทั– HTTP-ัั‚ะฐั‚ัƒัะพะผ `418` ั– JSON-ะฒะผั–ัั‚ะพะผ: ```JSON {"message": "Oops! yolo did something. There goes a rainbow..."} @@ -105,31 +105,31 @@ ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `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** ะผะฐั” ะบั–ะปัŒะบะฐ ะพะฑั€ะพะฑะฝะธะบั–ะฒ ะฟะพะผะธะปะพะบ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. +**FastAPI** ะผะฐั” ะบั–ะปัŒะบะฐ ะพะฑั€ะพะฑะฝะธะบั–ะฒ ะฒะธะบะปัŽั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. -ะฆั– ะพะฑั€ะพะฑะฝะธะบะธ ะฒั–ะดะฟะพะฒั–ะดะฐัŽั‚ัŒ ะทะฐ ะฟะพะฒะตั€ะฝะตะฝะฝั ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั… JSON-ะฒั–ะดะฟะพะฒั–ะดะตะน, ะบะพะปะธ ะ’ะธ `ะณะตะฝะตั€ัƒั”ั‚ะต` (`raise`) `HTTPException`, ะฐ ั‚ะฐะบะพะถ ะบะพะปะธ ะทะฐะฟะธั‚ ะผั–ัั‚ะธั‚ัŒ ะฝะตะบะพั€ะตะบั‚ะฝั– ะดะฐะฝั–. +ะฆั– ะพะฑั€ะพะฑะฝะธะบะธ ะฒั–ะดะฟะพะฒั–ะดะฐัŽั‚ัŒ ะทะฐ ะฟะพะฒะตั€ะฝะตะฝะฝั ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั… JSON-ะฒั–ะดะฟะพะฒั–ะดะตะน, ะบะพะปะธ ะฒะธ `raise` `HTTPException`, ะฐ ั‚ะฐะบะพะถ ะบะพะปะธ ะทะฐะฟะธั‚ ะผั–ัั‚ะธั‚ัŒ ะฝะตะบะพั€ะตะบั‚ะฝั– ะดะฐะฝั–. -ะ’ะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะฒะธะทะฝะฐั‡ะธั‚ะธ ั†ั– ะพะฑั€ะพะฑะฝะธะบะธ, ัั‚ะฒะพั€ะธะฒัˆะธ ะฒะปะฐัะฝั–. +ะ’ะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะฒะธะทะฝะฐั‡ะธั‚ะธ ั†ั– ะพะฑั€ะพะฑะฝะธะบะธ ะฒะธะบะปัŽั‡ะตะฝัŒ ะฒะปะฐัะฝะธะผะธ. -### ะŸะตั€ะตะฒะธะทะฝะฐั‡ะตะฝะฝั ะฟะพะผะธะปะพะบ ะฒะฐะปั–ะดะฐั†ั–ั— ะทะฐะฟะธั‚ัƒ +### ะŸะตั€ะตะฒะธะทะฝะฐั‡ะตะฝะฝั ะฒะธะบะปัŽั‡ะตะฝัŒ ะฒะฐะปั–ะดะฐั†ั–ั— ะทะฐะฟะธั‚ัƒ { #override-request-validation-exceptions } -ะšะพะปะธ ะทะฐะฟะธั‚ ะผั–ัั‚ะธั‚ัŒ ะฝะตะบะพั€ะตะบั‚ะฝั– ะดะฐะฝั–, **FastAPI** ะณะตะฝะตั€ัƒั” `RequestValidationError`. +ะšะพะปะธ ะทะฐะฟะธั‚ ะผั–ัั‚ะธั‚ัŒ ะฝะตะบะพั€ะตะบั‚ะฝั– ะดะฐะฝั–, **FastAPI** ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพ ะณะตะฝะตั€ัƒั” `RequestValidationError`. -ะ† ั‚ะฐะบะพะถ ะฒะบะปัŽั‡ะฐั” ะพะฑั€ะพะฑะฝะธะบ ะฟะพะผะธะปะพะบ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะดะปั ะฝัŒะพะณะพ. +ะ† ั‚ะฐะบะพะถ ะฒะบะปัŽั‡ะฐั” ะพะฑั€ะพะฑะฝะธะบ ะฒะธะบะปัŽั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะดะปั ะฝัŒะพะณะพ. -ะฉะพะฑ ะฟะตั€ะตะฒะธะทะฝะฐั‡ะธั‚ะธ ะนะพะณะพ, ั–ะผะฟะพั€ั‚ัƒะนั‚ะต `RequestValidationError` ั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะนะพะณะพ ะท `@app.exception_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 { @@ -146,55 +146,44 @@ } ``` -ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ั‚ะตะบัั‚ะพะฒัƒ ะฒะตั€ัั–ัŽ: +ะฒะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ั‚ะตะบัั‚ะพะฒัƒ ะฒะตั€ัั–ัŽ: ``` -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` ะฟั€ะพั‚ะธ `ValidationError` +### ะŸะตั€ะตะฒะธะทะฝะฐั‡ะตะฝะฝั ะพะฑั€ะพะฑะฝะธะบะฐ ะฟะพะผะธะปะพะบ `HTTPException` { #override-the-httpexception-error-handler } -/// warning | ะฃะฒะฐะณะฐ +ะะฝะฐะปะพะณั–ั‡ะฝะพ, ะฒะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะฒะธะทะฝะฐั‡ะธั‚ะธ ะพะฑั€ะพะฑะฝะธะบ `HTTPException`. -ะฆะต ั‚ะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั–, ัะบั– ะ’ะธ ะผะพะถะตั‚ะต ะฟั€ะพะฟัƒัั‚ะธั‚ะธ, ัะบั‰ะพ ะฒะพะฝะธ ะทะฐั€ะฐะท ะฝะต ะฒะฐะถะปะธะฒั– ะดะปั ะ’ะฐั. +ะะฐะฟั€ะธะบะปะฐะด, ะฒะธ ะผะพะถะตั‚ะต ะทะฐั…ะพั‚ั–ั‚ะธ ะฟะพะฒะตั€ะฝัƒั‚ะธ ะฒั–ะดะฟะพะฒั–ะดัŒ ัƒ ะฒะธะณะปัะดั– ะฟั€ะพัั‚ะพะณะพ ั‚ะตะบัั‚ัƒ ะทะฐะผั–ัั‚ัŒ JSON ะดะปั ั†ะธั… ะฟะพะผะธะปะพะบ: -/// - -`RequestValidationError` ั” ะฟั–ะดะบะปะฐัะพะผ Pydantic <a href="https://docs.pydantic.dev/latest/concepts/models/#error-handling" class="external-link" target="_blank">`ValidationError`</a>. - -**FastAPI** ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ะนะพะณะพ ะดะปั ั‚ะพะณะพ, ัะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ะผะพะดะตะปัŒ Pydantic ัƒ `response_model` ั– ัƒ ะฒะฐัˆะธั… ะดะฐะฝะธั… ั” ะฟะพะผะธะปะบะฐ, ะ’ะธ ะฟะพะฑะฐั‡ะธะปะธ ะฟะพะผะธะปะบัƒ ัƒ ัะฒะพั”ะผัƒ ะถัƒั€ะฝะฐะปั–. - -ะะปะต ะบะปั–ั”ะฝั‚/ะบะพั€ะธัั‚ัƒะฒะฐั‡ ะฝะต ะฟะพะฑะฐั‡ะธั‚ัŒ ั—ั—. ะะฐั‚ะพะผั–ัั‚ัŒ ะบะปั–ั”ะฝั‚ ะพั‚ั€ะธะผะฐั” "Internal Server Error" ะทั– ัั‚ะฐั‚ัƒัะพะผ HTTP `500`. - -ะขะฐะบ ะผะฐั” ะฑัƒั‚ะธ, ัะบั‰ะพ ัƒ ะ’ะฐั ะฒะธะฝะธะบะปะฐ `ValidationError` Pydantic ัƒ *ะฒั–ะดะฟะพะฒั–ะดั–* ะฐะฑะพ ะดะตั–ะฝะดะต ัƒ ะฒะฐัˆะพะผัƒ ะบะพะดั– (ะฝะต ัƒ *ะทะฐะฟะธั‚ั–* ะบะปั–ั”ะฝั‚ะฐ), ั†ะต ะฝะฐัะฟั€ะฐะฒะดั– ั” ะฟะพะผะธะปะบะพัŽ ัƒ ะ’ะฐัˆะพะผัƒ ะบะพะดั–. - -ะ† ะฟะพะบะธ ะ’ะธ ั—ั— ะฒะธะฟั€ะฐะฒะปัั”ั‚ะต, ะบะปั–ั”ะฝั‚ะธ/ะบะพั€ะธัั‚ัƒะฒะฐั‡ั– ะฝะต ะฟะพะฒะธะฝะฝั– ะผะฐั‚ะธ ะดะพัั‚ัƒะฟัƒ ะดะพ ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั— ะฟั€ะพ ะฟะพะผะธะปะบัƒ, ะพัะบั–ะปัŒะบะธ ั†ะต ะผะพะถะต ะฟั€ะธะทะฒะตัั‚ะธ ะดะพ ะฒั€ะฐะทะปะธะฒะพัั‚ั– ะฑะตะทะฟะตะบะธ. - -### ะŸะตั€ะตะฒะธะทะฝะฐั‡ะตะฝะฝั ะพะฑั€ะพะฑะฝะธะบะฐ ะฟะพะผะธะปะพะบ `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` ะผั–ัั‚ะธั‚ัŒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะฟั€ะพ ะฝะฐะทะฒัƒ ั„ะฐะนะปัƒ ั‚ะฐ ั€ัะดะพะบ, ะดะต ัั‚ะฐะปะฐัั ะฟะพะผะธะปะบะฐ ะฒะฐะปั–ะดะฐั†ั–ั—, ั‰ะพะฑ ะทะฐ ะฟะพั‚ั€ะตะฑะธ ะฒะธ ะผะพะณะปะธ ะฟะพะบะฐะทะฐั‚ะธ ั†ะต ัƒ ัะฒะพั—ั… ะปะพะณะฐั… ั–ะท ะฒั–ะดะฟะพะฒั–ะดะฝะพัŽ ั–ะฝั„ะพั€ะผะฐั†ั–ั”ัŽ. + +ะะปะต ั†ะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ัะบั‰ะพ ะฒะธ ะฟั€ะพัั‚ะพ ะฟะตั€ะตั‚ะฒะพั€ะธั‚ะต ั†ะต ะฝะฐ ั€ัะดะพะบ ั– ะฟะพะฒะตั€ะฝะตั‚ะต ั†ัŽ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะฝะฐะฟั€ัะผัƒ, ะฒะธ ะผะพะถะตั‚ะต ั€ะพะทะบั€ะธั‚ะธ ั‚ั€ะพั…ะธ ั–ะฝั„ะพั€ะผะฐั†ั–ั— ะฟั€ะพ ะฒะฐัˆัƒ ัะธัั‚ะตะผัƒ, ั‚ะพะผัƒ ั‚ัƒั‚ ะบะพะด ะฒะธั‚ัะณะฐั” ั‚ะฐ ะฟะพะบะฐะทัƒั” ะบะพะถะฝัƒ ะฟะพะผะธะปะบัƒ ะฝะตะทะฐะปะตะถะฝะพ. + +/// + +### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ั‚ั–ะปะฐ `RequestValidationError` { #use-the-requestvalidationerror-body } `RequestValidationError` ะผั–ัั‚ะธั‚ัŒ `body`, ัะบะธะน ะฒั–ะฝ ะพั‚ั€ะธะผะฐะฒ ั–ะท ะฝะตะบะพั€ะตะบั‚ะฝะธะผะธ ะดะฐะฝะธะผะธ. ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั†ะต ะฟั–ะด ั‡ะฐั ั€ะพะทั€ะพะฑะบะธ ัะฒะพะณะพ ะดะพะดะฐั‚ะบะฐ, ั‰ะพะฑ ะปะพะณัƒะฒะฐั‚ะธ ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ ั‚ะฐ ะฝะฐะปะฐะณะพะดะถัƒะฒะฐั‚ะธ ะนะพะณะพ, ะฟะพะฒะตั€ั‚ะฐั‚ะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะตะฒั– ั‚ะพั‰ะพ. -{* ../../docs_src/handling_errors/tutorial005.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} ะขะตะฟะตั€ ัะฟั€ะพะฑัƒะนั‚ะต ะฝะฐะดั–ัะปะฐั‚ะธ ะฝะตะบะพั€ะตะบั‚ะฝะธะน ะตะปะตะผะตะฝั‚, ะฝะฐะฟั€ะธะบะปะฐะด: @@ -204,8 +193,8 @@ path -> item_id "size": "XL" } ``` -ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะฒั–ะดะฟะพะฒั–ะดัŒ, ัะบะฐ ะฟะพะฒั–ะดะพะผะธั‚ัŒ ะ’ะฐะผ, ัะบั– ัะฐะผะต ะดะฐะฝั– ั” ะฝะตะบะพั€ะตะบั‚ะฝั– ัƒ ะฒะฐัˆะพะผัƒ ั‚ั–ะปั– ะทะฐะฟะธั‚ัƒ: +ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะฒั–ะดะฟะพะฒั–ะดัŒ, ัะบะฐ ะฟะพะฒั–ะดะพะผะธั‚ัŒ ะฒะฐะผ, ั‰ะพ ะดะฐะฝั– ั” ะฝะตะบะพั€ะตะบั‚ะฝะธะผะธ, ั– ะผั–ัั‚ะธั‚ะธะผะต ะพั‚ั€ะธะผะฐะฝะต ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ: ```JSON hl_lines="12-15" { @@ -226,30 +215,30 @@ path -> item_id } ``` -#### `HTTPException` FastAPI ะฟั€ะพั‚ะธ `HTTPException` Starlette +#### `HTTPException` FastAPI ะฟั€ะพั‚ะธ `HTTPException` Starlette { #fastapis-httpexception-vs-starlettes-httpexception } **FastAPI** ะผะฐั” ะฒะปะฐัะฝะธะน `HTTPException`. -ะ† ะบะปะฐั ะฟะพะผะธะปะบะธ `HTTPException` ะฒ **FastAPI** ัƒัะฟะฐะดะบะพะฒัƒั”ั‚ัŒัั ะฒั–ะด ะบะปะฐััƒ ะฟะพะผะธะปะบะธ `HTTPException` ะฒ Starlette. +ะ† ะบะปะฐั ะฟะพะผะธะปะบะธ `HTTPException` ะฒ **FastAPI** ัƒัะฟะฐะดะบะพะฒัƒั”ั‚ัŒัั ะฒั–ะด ะบะปะฐััƒ ะฟะพะผะธะปะบะธ `HTTPException` ัƒ Starlette. ะ„ะดะธะฝะฐ ั€ั–ะทะฝะธั†ั ะฟะพะปัะณะฐั” ะฒ ั‚ะพะผัƒ, ั‰ะพ `HTTPException` ะฒ **FastAPI** ะฟั€ะธะนะผะฐั” ะฑัƒะดัŒ-ัะบั– ะดะฐะฝั–, ัะบั– ะผะพะถะฝะฐ ะฟะตั€ะตั‚ะฒะพั€ะธั‚ะธ ะฝะฐ JSON, ะดะปั ะฟะพะปั `detail`, ั‚ะพะดั– ัะบ `HTTPException` ัƒ Starlette ะฟั€ะธะนะผะฐั” ั‚ั–ะปัŒะบะธ ั€ัะดะบะธ. -ะžั‚ะถะต, ะ’ะธ ะผะพะถะตั‚ะต ะฟั€ะพะดะพะฒะถัƒะฒะฐั‚ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `HTTPException` ะฒ **FastAPI** ัะบ ะทะฐะทะฒะธั‡ะฐะน ัƒ ัะฒะพั”ะผัƒ ะบะพะดั–. +ะžั‚ะถะต, ะฒะธ ะผะพะถะตั‚ะต ะฟั€ะพะดะพะฒะถัƒะฒะฐั‚ะธ ะณะตะฝะตั€ัƒะฒะฐั‚ะธ `HTTPException` **FastAPI** ัะบ ะทะฐะทะฒะธั‡ะฐะน ัƒ ัะฒะพั”ะผัƒ ะบะพะดั–. -ะะปะต ะบะพะปะธ ะ’ะธ ั€ะตั”ัั‚ั€ัƒั”ั‚ะต ะพะฑั€ะพะฑะฝะธะบ ะฒะธะบะปัŽั‡ะตะฝัŒ, ัะปั–ะด ั€ะตั”ัั‚ั€ัƒะฒะฐั‚ะธ ะนะพะณะพ ะดะปั `HTTPException` ะทั– Starlette. +ะะปะต ะบะพะปะธ ะฒะธ ั€ะตั”ัั‚ั€ัƒั”ั‚ะต ะพะฑั€ะพะฑะฝะธะบ ะฒะธะบะปัŽั‡ะตะฝัŒ, ัะปั–ะด ั€ะตั”ัั‚ั€ัƒะฒะฐั‚ะธ ะนะพะณะพ ะดะปั `HTTPException` ะทั– Starlette. -ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ัะบั‰ะพ ะฑัƒะดัŒ-ัะบะฐ ั‡ะฐัั‚ะธะฝะฐ ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพะณะพ ะบะพะดัƒ Starlette ะฐะฑะพ ั€ะพะทัˆะธั€ะตะฝะฝั ั‡ะธ ะฟะปะฐะณั–ะฝ Starlette ะทะณะตะฝะตั€ัƒั” (raise) `HTTPException`, ะ’ะฐัˆ ะพะฑั€ะพะฑะฝะธะบ ะทะผะพะถะต ะฟะตั€ะตั…ะพะฟะธั‚ะธ ั‚ะฐ ะพะฑั€ะพะฑะธั‚ะธ ั—ั—. +ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ัะบั‰ะพ ะฑัƒะดัŒ-ัะบะฐ ั‡ะฐัั‚ะธะฝะฐ ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพะณะพ ะบะพะดัƒ Starlette ะฐะฑะพ ั€ะพะทัˆะธั€ะตะฝะฝั ั‡ะธ ะฟะปะฐะณั–ะฝ Starlette ะทะณะตะฝะตั€ัƒั” Starlette `HTTPException`, ะฒะฐัˆ ะพะฑั€ะพะฑะฝะธะบ ะทะผะพะถะต ะฟะตั€ะตั…ะพะฟะธั‚ะธ ั‚ะฐ ะพะฑั€ะพะฑะธั‚ะธ ั—ั—. -ะฃ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั–, ั‰ะพะฑ ะผะฐั‚ะธ ะผะพะถะปะธะฒั–ัั‚ัŒ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะฑะธะดะฒะฐ `HTTPException` ะฒ ะพะดะฝะพะผัƒ ะบะพะดั–, ะฟะพะผะธะปะบะฐ Starlette ะฟะตั€ะตะนะผะตะฝะพะฒัƒั”ั‚ัŒัั ะฝะฐ `StarletteHTTPException`: +ะฃ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั–, ั‰ะพะฑ ะผะฐั‚ะธ ะผะพะถะปะธะฒั–ัั‚ัŒ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะฑะธะดะฒะฐ `HTTPException` ะฒ ะพะดะฝะพะผัƒ ะบะพะดั–, ะฒะธะบะปัŽั‡ะตะฝะฝั Starlette ะฟะตั€ะตะนะผะตะฝะพะฒัƒั”ั‚ัŒัั ะฝะฐ `StarletteHTTPException`: ```Python from starlette.exceptions import HTTPException as StarletteHTTPException ``` -### ะŸะพะฒั‚ะพั€ะฝะต ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะพะฑั€ะพะฑะฝะธะบั–ะฒ ะฟะพะผะธะปะพะบ **FastAPI** +### ะŸะพะฒั‚ะพั€ะฝะต ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะพะฑั€ะพะฑะฝะธะบั–ะฒ ะฒะธะบะปัŽั‡ะตะฝัŒ **FastAPI** { #reuse-fastapis-exception-handlers } -ะฏะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะพะผะธะปะบะธ ั€ะฐะทะพะผ ั–ะท ั‚ะฐะบะธะผะธ ะถ ะพะฑั€ะพะฑะฝะธะบะฐะผะธ ะฟะพะผะธะปะพะบ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ัะบ ัƒ **FastAPI**, ะ’ะธ ะผะพะถะตั‚ะต ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ ั‚ะฐ ะฟะพะฒั‚ะพั€ะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั—ั… ั–ะท `fastapi.exception_handlers`: +ะฏะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะธะบะปัŽั‡ะตะฝะฝั ั€ะฐะทะพะผ ั–ะท ั‚ะฐะบะธะผะธ ะถ ะพะฑั€ะพะฑะฝะธะบะฐะผะธ ะฒะธะบะปัŽั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ัะบ ัƒ **FastAPI**, ะฒะธ ะผะพะถะตั‚ะต ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ ั‚ะฐ ะฟะพะฒั‚ะพั€ะฝะพ ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ะพะฑั€ะพะฑะฝะธะบะธ ะฒะธะบะปัŽั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ั–ะท `fastapi.exception_handlers`: -{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *} +{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} -ะฃ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั– ะ’ะธ ะฟั€ะพัั‚ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `print` ะดะปั ะฒะธะฒะตะดะตะฝะฝั ะดัƒะถะต ั–ะฝั„ะพั€ะผะฐั‚ะธะฒะฝะพะณะพ ะฟะพะฒั–ะดะพะผะปะตะฝะฝั, ะฐะปะต ะ’ะธ ะทั€ะพะทัƒะผั–ะปะธ ะพัะฝะพะฒะฝัƒ ั–ะดะตัŽ. ะ’ะธ ะผะพะถะตั‚ะต ะพะฑั€ะพะฑะธั‚ะธ ะฟะพะผะธะปะบัƒ ั‚ะฐ ะฟะพะฒั‚ะพั€ะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะฑั€ะพะฑะฝะธะบะธ ะฟะพะผะธะปะพะบ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. +ะฃ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั– ะฒะธ ะฟั€ะพัั‚ะพ ะดั€ัƒะบัƒั”ั‚ะต ะฟะพะผะธะปะบัƒ ะท ะดัƒะถะต ะฒะธั€ะฐะทะฝะธะผ ะฟะพะฒั–ะดะพะผะปะตะฝะฝัะผ, ะฐะปะต ะฒะธ ะทั€ะพะทัƒะผั–ะปะธ ะพัะฝะพะฒะฝัƒ ั–ะดะตัŽ. ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะธะบะปัŽั‡ะตะฝะฝั, ะฐ ะฟะพั‚ั–ะผ ะฟั€ะพัั‚ะพ ะฟะพะฒั‚ะพั€ะฝะพ ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ะพะฑั€ะพะฑะฝะธะบะธ ะฒะธะบะปัŽั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. diff --git a/docs/uk/docs/tutorial/header-param-models.md b/docs/uk/docs/tutorial/header-param-models.md index 6f7b0bdae7..c080c19f06 100644 --- a/docs/uk/docs/tutorial/header-param-models.md +++ b/docs/uk/docs/tutorial/header-param-models.md @@ -1,26 +1,24 @@ -# ะœะพะดะตะปั– ะŸะฐั€ะฐะผะตั‚ั€ั–ะฒ ะ—ะฐะณะพะปะพะฒะบั–ะฒ +# ะœะพะดะตะปั– ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะณะพะปะพะฒะบั–ะฒ { #header-parameter-models } -ะฏะบั‰ะพ ัƒ ะ’ะฐั ั” ะณั€ัƒะฟะฐ ะฟะพะฒโ€™ัะทะฐะฝะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะณะพะปะพะฒะบั–ะฒ, ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ **Pydantic ะผะพะดะตะปัŒ** ะดะปั ั—ั… ะพะณะพะปะพัˆะตะฝะฝั. +ะฏะบั‰ะพ ัƒ ะ’ะฐั ั” ะณั€ัƒะฟะฐ ะฟะพะฒโ€™ัะทะฐะฝะธั… **ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะณะพะปะพะฒะบั–ะฒ**, ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ **Pydantic ะผะพะดะตะปัŒ** ะดะปั ั—ั… ะพะณะพะปะพัˆะตะฝะฝั. ะฆะต ะดะพะทะฒะพะปะธั‚ัŒ ะ’ะฐะผ ะฟะพะฒั‚ะพั€ะฝะพ **ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดะตะปัŒ** ะฒ **ั€ั–ะทะฝะธั… ะผั–ัั†ัั…**, ะฐ ั‚ะฐะบะพะถ ะพะณะพะปะพัะธั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ั— ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ะดะปั ะฒัั–ั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะพะดะฝะพั‡ะฐัะฝะพ. ๐Ÿ˜Ž -/// note | ะะพั‚ะฐั‚ะบะธ +/// note | ะŸั€ะธะผั–ั‚ะบะฐ ะฆั ะผะพะถะปะธะฒั–ัั‚ัŒ ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท ะฒะตั€ัั–ั— FastAPI `0.115.0`. ๐Ÿค“ /// -## ะŸะฐั€ะฐะผะตั‚ั€ะธ ะ—ะฐะณะพะปะพะฒะบั–ะฒ ะท ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝัะผ Pydantic Model +## ะŸะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะณะพะปะพะฒะบั–ะฒ ะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ Pydantic ะผะพะดะตะปั– { #header-parameters-with-a-pydantic-model } ะžะณะพะปะพัั–ั‚ัŒ ะฟะพั‚ั€ั–ะฑะฝั– **ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะณะพะปะพะฒะบั–ะฒ** ัƒ **Pydantic ะผะพะดะตะปั–**, ะฐ ะฟะพั‚ั–ะผ ะพะณะพะปะพัั–ั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ ัะบ `Header`: {* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *} -FastAPI ะฑัƒะดะต ะฒะธั‚ัะณัƒะฒะฐั‚ะธ ะดะฐะฝั– ะดะปั ะบะพะถะฝะพะณะพ ะฟะพะปั ะท ะทะฐะณะพะปะพะฒะบั–ะฒ ัƒ ะทะฐะฟะธั‚ั– ั‚ะฐ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ั—ั… ัƒ ัั‚ะฒะพั€ะตะฝัƒ ะ’ะฐะผะธ Pydantic ะผะพะดะตะปัŒ. - **FastAPI** ะฑัƒะดะต **ะฒะธั‚ัะณัƒะฒะฐั‚ะธ** ะดะฐะฝั– ะดะปั **ะบะพะถะฝะพะณะพ ะฟะพะปั** ะท **ะทะฐะณะพะปะพะฒะบั–ะฒ** ัƒ ะทะฐะฟะธั‚ั– ั‚ะฐ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ั—ั… ัƒ ัั‚ะฒะพั€ะตะฝัƒ ะ’ะฐะผะธ Pydantic ะผะพะดะตะปัŒ. -## ะŸะตั€ะตะฒั–ั€ะบะฐ ะฒ ะ”ะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +## ะŸะตั€ะตะฒั–ั€ะบะฐ ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— { #check-the-docs } ะ’ะธ ะผะพะถะตั‚ะต ะฟะพะฑะฐั‡ะธั‚ะธ ะฝะตะพะฑั…ั–ะดะฝั– ะทะฐะณะพะปะพะฒะบะธ ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั– ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะทะฐ ะฐะดั€ะตัะพัŽ `/docs`: @@ -28,7 +26,7 @@ FastAPI ะฑัƒะดะต ะฒะธั‚ัะณัƒะฒะฐั‚ะธ ะดะฐะฝั– ะดะปั ะบะพะถะฝะพะณะพ ะฟะพะปั ะท <img src="/img/tutorial/header-param-models/image01.png"> </div> -## ะ—ะฐะฑะพั€ะพะฝะฐ ะ”ะพะดะฐั‚ะบะพะฒะธั… ะ—ะฐะณะพะปะพะฒะบั–ะฒ +## ะ—ะฐะฑะพั€ะพะฝะธั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะทะฐะณะพะปะพะฒะบะธ { #forbid-extra-headers } ะฃ ะดะตัะบะธั… ะพัะพะฑะปะธะฒะธั… ะฒะธะฟะฐะดะบะฐั… (ะนะผะพะฒั–ั€ะฝะพ, ะฝะต ะดัƒะถะต ะฟะพัˆะธั€ะตะฝะธั…) ะ’ะธ ะผะพะถะตั‚ะต ะทะฐั…ะพั‚ั–ั‚ะธ **ะพะฑะผะตะถะธั‚ะธ** ะทะฐะณะพะปะพะฒะบะธ, ัะบั– ั…ะพั‡ะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ. @@ -38,7 +36,7 @@ FastAPI ะฑัƒะดะต ะฒะธั‚ัะณัƒะฒะฐั‚ะธ ะดะฐะฝั– ะดะปั ะบะพะถะฝะพะณะพ ะฟะพะปั ะท ะฏะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ **ะดะพะดะฐั‚ะบะพะฒั– ะทะฐะณะพะปะพะฒะบะธ**, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” **ะฟะพะผะธะปะบัƒ** ัƒ ะฒั–ะดะฟะพะฒั–ะดั–. -ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ ะทะฐะณะพะปะพะฒะพะบ `tool` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `plumbus`, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” **ะฟะพะผะธะปะบัƒ** ะท ะฟะพะฒั–ะดะพะผะปะตะฝะฝัะผ ะฟั€ะพ ั‚ะต, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะณะพะปะพะฒะบะฐ `tool` ะฝะต ะดะพะทะฒะพะปะตะฝะธะน: +ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ ะทะฐะณะพะปะพะฒะพะบ `tool` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `plumbus`, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” **ะฟะพะผะธะปะบัƒ** ัƒ ะฒั–ะดะฟะพะฒั–ะดั– ะท ะฟะพะฒั–ะดะพะผะปะตะฝะฝัะผ ะฟั€ะพ ั‚ะต, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะณะพะปะพะฒะบะฐ `tool` ะฝะต ะดะพะทะฒะพะปะตะฝะธะน: ```json { @@ -53,6 +51,22 @@ FastAPI ะฑัƒะดะต ะฒะธั‚ัะณัƒะฒะฐั‚ะธ ะดะฐะฝั– ะดะปั ะบะพะถะฝะพะณะพ ะฟะพะปั ะท } ``` -## ะŸั–ะดััƒะผะพะบ +## ะ’ะธะผะบะฝัƒั‚ะธ ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ะฟั–ะดะบั€ะตัะปะตะฝัŒ { #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/uk/docs/tutorial/header-params.md b/docs/uk/docs/tutorial/header-params.md index 09c70a4f61..f5a4ea18d1 100644 --- a/docs/uk/docs/tutorial/header-params.md +++ b/docs/uk/docs/tutorial/header-params.md @@ -1,14 +1,14 @@ -# Header-ะฟะฐั€ะฐะผะตั‚ั€ะธ +# ะŸะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะณะพะปะพะฒะบั–ะฒ { #header-parameters } -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะณะพะปะพะฒะบั–ะฒ, ั‚ะฐะบ ัะฐะผะพ ัะบ ะฒะธะทะฝะฐั‡ะฐั”ั‚ะต `Query`, `Path` ั– `Cookie` ะฟะฐั€ะฐะผะตั‚ั€ะธ. +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะณะพะปะพะฒะบั–ะฒ ั‚ะฐะบ ัะฐะผะพ, ัะบ ะฒะธะทะฝะฐั‡ะฐั”ั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ `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`. @@ -18,9 +18,9 @@ /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– -`Header`ั” "ัะตัั‚ั€ะธะฝััŒะบะธะผ" ะบะปะฐัะพะผ ะดะปั `Path`, `Query` ั– `Cookie`. ะ’ั–ะฝ ั‚ะฐะบะพะถ ัƒัะฟะฐะดะบะพะฒัƒั”ั‚ัŒัั ะฒั–ะด ะทะฐะณะฐะปัŒะฝะพะณะพ ะบะปะฐััƒ `Param`. +`Header` ั” ยซัะตัั‚ั€ะธะฝััŒะบะธะผยป ะบะปะฐัะพะผ ะดะปั `Path`, `Query` ั– `Cookie`. ะ’ั–ะฝ ั‚ะฐะบะพะถ ัƒัะฟะฐะดะบะพะฒัƒั”ั‚ัŒัั ะฒั–ะด ั‚ะพะณะพ ะถ ัะฟั–ะปัŒะฝะพะณะพ ะบะปะฐััƒ `Param`. -ะะปะต ะฟะฐะผโ€™ัั‚ะฐะนั‚ะต, ั‰ะพ ะฟั€ะธ ั–ะผะฟะพั€ั‚ั– `Query`, `Path`, `Header` ั‚ะฐ ั–ะฝัˆะธั… ั–ะท `fastapi`, ั‚ะพ ะฝะฐัะฟั€ะฐะฒะดั– ะฒะพะฝะธ ั” ั„ัƒะฝะบั†ั–ัะผะธ, ัะบั– ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒ ัะฟะตั†ั–ะฐะปัŒะฝั– ะบะปะฐัะธ. +ะะปะต ะฟะฐะผโ€™ัั‚ะฐะนั‚ะต, ั‰ะพ ะบะพะปะธ ะฒะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต `Query`, `Path`, `Header` ั‚ะฐ ั–ะฝัˆั– ะท `fastapi`, ั‚ะพ ะฝะฐัะฟั€ะฐะฒะดั– ั†ะต ั„ัƒะฝะบั†ั–ั—, ัะบั– ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒ ัะฟะตั†ั–ะฐะปัŒะฝั– ะบะปะฐัะธ. /// @@ -30,43 +30,43 @@ /// -## ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั +## ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั { #automatic-conversion } -`Header` ะผะฐั” ะดะพะดะฐั‚ะบะพะฒะธะน ั„ัƒะฝะบั†ั–ะพะฝะฐะป ะฟะพั€ั–ะฒะฝัะฝะพ ะท `Path`, `Query` ั‚ะฐ `Cookie`. +`Header` ะผะฐั” ั‚ั€ะพั…ะธ ะดะพะดะฐั‚ะบะพะฒะพั— ั„ัƒะฝะบั†ั–ะพะฝะฐะปัŒะฝะพัั‚ั– ะฟะพั€ั–ะฒะฝัะฝะพ ะท ั‚ะธะผ, ั‰ะพ ะฝะฐะดะฐัŽั‚ัŒ `Path`, `Query` ั‚ะฐ `Cookie`. -ะ‘ั–ะปัŒัˆั–ัั‚ัŒ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั… ะทะฐะณะพะปะพะฒะบั–ะฒ ั€ะพะทะดั–ะปััŽั‚ัŒัั ัะธะผะฒะพะปะพะผ ยซะดะตั„ั–ัยป, ั‚ะฐะบะพะถ ะฒั–ะดะพะผะธะผ ัะบ ยซะผั–ะฝัƒัยป (`-`). +ะ‘ั–ะปัŒัˆั–ัั‚ัŒ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั… ะทะฐะณะพะปะพะฒะบั–ะฒ ั€ะพะทะดั–ะปััŽั‚ัŒัั ัะธะผะฒะพะปะพะผ ยซะดะตั„ั–ัยป, ั‚ะฐะบะพะถ ะฒั–ะดะพะผะธะผ ัะบ ยซัะธะผะฒะพะป ะผั–ะฝัƒัะฐยป (`-`). ะะปะต ะทะผั–ะฝะฝะฐ, ั‚ะฐะบะฐ ัะบ `user-agent`, ั” ะฝะตะดั–ะนัะฝะพัŽ ะฒ Python. -ะขะพะผัƒ, ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, `Header` ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะตั€ะตั‚ะฒะพั€ัŽั” ัะธะผะฒะพะปะธ ะฟั–ะดะบั€ะตัะปะตะฝะฝั (`_`) ะฝะฐ ะดะตั„ั–ัะธ (`-`) ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ั‚ะฐ ะดะพะบัƒะผะตะฝั‚ัƒะฒะฐะฝะฝั ะทะฐะณะพะปะพะฒะบั–ะฒ. +ะขะพะผัƒ, ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, `Header` ะฟะตั€ะตั‚ะฒะพั€ัŽะฒะฐั‚ะธะผะต ัะธะผะฒะพะปะธ ะฟั–ะดะบั€ะตัะปะตะฝะฝั (`_`) ัƒ ะฝะฐะทะฒะฐั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะฝะฐ ะดะตั„ั–ัะธ (`-`), ั‰ะพะฑ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ั‚ะฐ ะดะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธ ะทะฐะณะพะปะพะฒะบะธ. -ะžัะบั–ะปัŒะบะธ ะทะฐะณะพะปะพะฒะบะธ HTTP ะฝะต ั‡ัƒั‚ะปะธะฒั– ะดะพ ั€ะตะณั–ัั‚ั€ัƒ, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ัั‚ะธะปัŒ Python ("snake_case"). +ะขะฐะบะพะถ ะทะฐะณะพะปะพะฒะบะธ HTTP ะฝะต ั‡ัƒั‚ะปะธะฒั– ะดะพ ั€ะตะณั–ัั‚ั€ัƒ, ั‚ะพะถ ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั—ั… ัƒ ัั‚ะฐะฝะดะฐั€ั‚ะฝะพะผัƒ ัั‚ะธะปั– Python (ั‚ะฐะบะพะถ ะฒั–ะดะพะผะพะผัƒ ัะบ ยซsnake_caseยป). -ะขะพะผัƒ ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `user_agent`, ัะบ ะทะฐะทะฒะธั‡ะฐะน ัƒ ะบะพะดั– Python, ะทะฐะผั–ัั‚ัŒ ั‚ะพะณะพ ั‰ะพะฑ ะฟะธัะฐั‚ะธ ะท ะฒะตะปะธะบะพั— ะปั–ั‚ะตั€ะธ, ัะบ `User_Agent` ะฐะฑะพ ั‰ะพััŒ ะฟะพะดั–ะฑะฝะต. +ะขะพะผัƒ ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `user_agent`, ัะบ ะทะฐะทะฒะธั‡ะฐะน ัƒ ะบะพะดั– Python, ะทะฐะผั–ัั‚ัŒ ั‚ะพะณะพ ั‰ะพะฑ ะฟะพั‚ั€ั–ะฑะฝะพ ะฑัƒะปะพ ะฟะธัะฐั‚ะธ ะท ะฒะตะปะธะบะพั— ะปั–ั‚ะตั€ะธ ะฟะตั€ัˆั– ะปั–ั‚ะตั€ะธ, ัะบ `User_Agent` ะฐะฑะพ ั‰ะพััŒ ะฟะพะดั–ะฑะฝะต. -ะฏะบั‰ะพ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะผะบะฝัƒั‚ะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ะฟั–ะดะบั€ะตัะปะตะฝัŒ ัƒ ะดะตั„ั–ัะธ, ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ `convert_underscores` ะฒ `Header` ะทะฝะฐั‡ะตะฝะฝั `False`: +ะฏะบั‰ะพ ะท ัะบะพั—ััŒ ะฟั€ะธั‡ะธะฝะธ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะผะบะฝัƒั‚ะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ะฟั–ะดะบั€ะตัะปะตะฝัŒ ะฝะฐ ะดะตั„ั–ัะธ, ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `convert_underscores` ัƒ `Header` ะฒ `False`: {* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *} -/// warning | ะฃะฒะฐะณะฐ +/// warning | ะŸะพะฟะตั€ะตะดะถะตะฝะฝั -ะŸะตั€ะตะด ั‚ะธะผ ัะบ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั `False` ะดะปั `convert_underscores` ะฟะฐะผโ€™ัั‚ะฐะนั‚ะต, ั‰ะพ ะดะตัะบั– HTTP-ะฟั€ะพะบัั– ั‚ะฐ ัะตั€ะฒะตั€ะธ ะฝะต ะฟั–ะดั‚ั€ะธะผัƒัŽั‚ัŒ ะทะฐะณะพะปะพะฒะบะธ ะท ะฟั–ะดะบั€ะตัะปะตะฝะฝัะผะธ. +ะŸะตั€ะตะด ั‚ะธะผ ัะบ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ `convert_underscores` ะฒ `False`, ะฟะฐะผโ€™ัั‚ะฐะนั‚ะต, ั‰ะพ ะดะตัะบั– HTTP-ะฟั€ะพะบัั– ั‚ะฐ ัะตั€ะฒะตั€ะธ ะทะฐะฑะพั€ะพะฝััŽั‚ัŒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะทะฐะณะพะปะพะฒะบั–ะฒ ั–ะท ะฟั–ะดะบั€ะตัะปะตะฝะฝัะผะธ. /// -## ะ”ัƒะฑะปัŒะพะฒะฐะฝั– ะทะฐะณะพะปะพะฒะบะธ +## ะ”ัƒะฑะปัŒะพะฒะฐะฝั– ะทะฐะณะพะปะพะฒะบะธ { #duplicate-headers } -ะœะพะถะปะธะฒะพ ะพั‚ั€ะธะผะฐั‚ะธ ะดัƒะฑะปัŒะพะฒะฐะฝั– ะทะฐะณะพะปะพะฒะบะธ, ั‚ะพะฑั‚ะพ ั‚ะพะน ัะฐะผะธะน ะทะฐะณะพะปะพะฒะพะบ ั–ะท ะบั–ะปัŒะบะพะผะฐ ะทะฝะฐั‡ะตะฝะฝัะผะธ. +ะœะพะถะปะธะฒะพ ะพั‚ั€ะธะผะฐั‚ะธ ะดัƒะฑะปัŒะพะฒะฐะฝั– ะทะฐะณะพะปะพะฒะบะธ. ะขะพะฑั‚ะพ ะพะดะธะฝ ั– ั‚ะพะน ัะฐะผะธะน ะทะฐะณะพะปะพะฒะพะบ ั–ะท ะบั–ะปัŒะบะพะผะฐ ะทะฝะฐั‡ะตะฝะฝัะผะธ. -ะฆะต ะผะพะถะฝะฐ ะฒะธะทะฝะฐั‡ะธั‚ะธ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัะฟะธัะพะบ ัƒ ั‚ะธะฟั–ะทะฐั†ั–ั— ะฟะฐั€ะฐะผะตั‚ั€ะฐ. +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ั‚ะฐะบั– ะฒะธะฟะฐะดะบะธ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัะฟะธัะพะบ ัƒ ั‚ะธะฟั–ะทะฐั†ั–ั—. -ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะฒัั– ะทะฝะฐั‡ะตะฝะฝั ะดัƒะฑะปัŒะพะฒะฐะฝะพะณะพ ะทะฐะณะพะปะพะฒะบะฐ ัƒ ะฒะธะณะปัะดั– `list` ัƒ Python. +ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะฒัั– ะทะฝะฐั‡ะตะฝะฝั ะดัƒะฑะปัŒะพะฒะฐะฝะพะณะพ ะทะฐะณะพะปะพะฒะบะฐ ัƒ ะฒะธะณะปัะดั– Python-`list`. -ะะฐะฟั€ะธะบะปะฐะด, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ะทะฐะณะพะปะพะฒะพะบ `X-Token`, ัะบะธะน ะผะพะถะต ะทโ€™ัะฒะปัั‚ะธัั ะฑั–ะปัŒัˆะต ะฝั–ะถ ะพะดะธะฝ ั€ะฐะท: +ะะฐะฟั€ะธะบะปะฐะด, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ะทะฐะณะพะปะพะฒะพะบ `X-Token`, ัะบะธะน ะผะพะถะต ะทโ€™ัะฒะปัั‚ะธัั ะฑั–ะปัŒัˆะต ะฝั–ะถ ะพะดะธะฝ ั€ะฐะท, ะฒะธ ะผะพะถะตั‚ะต ะฝะฐะฟะธัะฐั‚ะธ: {* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *} -ะฏะบั‰ะพ ะ’ะธ ะฒะทะฐั”ะผะพะดั–ั”ั‚ะต ะท ั†ั–ั”ัŽ ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ, ะฝะฐะดัะธะปะฐัŽั‡ะธ ะดะฒะฐ HTTP-ะทะฐะณะพะปะพะฒะบะธ, ะฝะฐะฟั€ะธะบะปะฐะด: +ะฏะบั‰ะพ ะฒะธ ะฒะทะฐั”ะผะพะดั–ั”ั‚ะต ะท ั†ั–ั”ัŽ *ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ*, ะฝะฐะดัะธะปะฐัŽั‡ะธ ะดะฒะฐ HTTP-ะทะฐะณะพะปะพะฒะบะธ, ะฝะฐะฟั€ะธะบะปะฐะด: ``` X-Token: foo @@ -84,8 +84,8 @@ X-Token: bar } ``` -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะžะณะพะปะพัˆัƒะนั‚ะต ะทะฐะณะพะปะพะฒะบะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Header`, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั‚ะพะน ัะฐะผะธะน ะฟั–ะดั…ั–ะด, ั‰ะพ ะน ะดะปั `Query`, `Path` ั‚ะฐ `Cookie`. +ะžะณะพะปะพัˆัƒะนั‚ะต ะทะฐะณะพะปะพะฒะบะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Header`, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั‚ะพะน ัะฐะผะธะน ะทะฐะณะฐะปัŒะฝะธะน ะฟั–ะดั…ั–ะด, ั‰ะพ ะน ะดะปั `Query`, `Path` ั‚ะฐ `Cookie`. -ะะต ั…ะฒะธะปัŽะนั‚ะตัั ะฟั€ะพ ะฟั–ะดะบั€ะตัะปะตะฝะฝั ัƒ ะทะผั–ะฝะฝะธั… โ€” **FastAPI** ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะพะฝะฒะตั€ั‚ัƒั” ั—ั…. +ะ† ะฝะต ั…ะฒะธะปัŽะนั‚ะตัั ะฟั€ะพ ะฟั–ะดะบั€ะตัะปะตะฝะฝั ัƒ ะฒะฐัˆะธั… ะทะผั–ะฝะฝะธั… โ€” **FastAPI** ะฟะพะดะฑะฐั” ะฟั€ะพ ั—ั… ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั. diff --git a/docs/uk/docs/tutorial/index.md b/docs/uk/docs/tutorial/index.md index 92c3e77a36..6848090ecc 100644 --- a/docs/uk/docs/tutorial/index.md +++ b/docs/uk/docs/tutorial/index.md @@ -1,29 +1,53 @@ -# ะขัƒั‚ะพั€ั–ะฐะป - ะŸะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ +# ะขัƒั‚ะพั€ั–ะฐะป - ะŸะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ { #tutorial-user-guide } ะฃ ั†ัŒะพะผัƒ ะฟะพัั–ะฑะฝะธะบัƒ ะฟะพะบะฐะทะฐะฝะพ, ัะบ ะบะพั€ะธัั‚ัƒะฒะฐั‚ะธัั **FastAPI** ะท ะฑั–ะปัŒัˆั–ัั‚ัŽ ะนะพะณะพ ั„ัƒะฝะบั†ั–ะน, ะบั€ะพะบ ะทะฐ ะบั€ะพะบะพะผ. ะšะพะถะตะฝ ั€ะพะทะดั–ะป ะฟะพัั‚ัƒะฟะพะฒะพ ะฝะฐะดะฑัƒะดะพะฒัƒั”ั‚ัŒัั ะฝะฐ ะฟะพะฟะตั€ะตะดะฝั–, ะฐะปะต ะฒั–ะฝ ัั‚ั€ัƒะบั‚ัƒั€ะพะฒะฐะฝะธะน ะฝะฐ ะพะบั€ะตะผั– ั‚ะตะผะธ, ั‰ะพะฑ ะฒะธ ะผะพะณะปะธ ะฟะตั€ะตะนั‚ะธ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะดะพ ะฑัƒะดัŒ-ัะบะพั— ะบะพะฝะบั€ะตั‚ะฝะพั—, ั‰ะพะฑ ะฒะธั€ั–ัˆะธั‚ะธ ะฒะฐัˆั– ะบะพะฝะบั€ะตั‚ะฝั– ะฟะพั‚ั€ะตะฑะธ API. -ะ’ั–ะฝ ั‚ะฐะบะพะถ ัั‚ะฒะพั€ะตะฝะธะน ัะบ ะดะพะฒั–ะดะฝะธะบ ะดะปั ั€ะพะฑะพั‚ะธ ัƒ ะผะฐะนะฑัƒั‚ะฝัŒะพะผัƒ. +ะขะฐะบะพะถ ะฒั–ะฝ ัั‚ะฒะพั€ะตะฝะธะน ัะบ ะดะพะฒั–ะดะฝะธะบ ะดะปั ั€ะพะฑะพั‚ะธ ัƒ ะผะฐะนะฑัƒั‚ะฝัŒะพะผัƒ, ั‚ะพะถ ะฒะธ ะผะพะถะตั‚ะต ะฟะพะฒะตั€ะฝัƒั‚ะธัั ั– ะฟะพะฑะฐั‡ะธั‚ะธ ัะฐะผะต ั‚ะต, ั‰ะพ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ. -ะขะพะถ ะฒะธ ะผะพะถะตั‚ะต ะฟะพะฒะตั€ะฝัƒั‚ะธัั ั– ะฟะพะฑะฐั‡ะธั‚ะธ ัะฐะผะต ั‚ะต, ั‰ะพ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ. - -## ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ ะบะพะด +## ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ ะบะพะด { #run-the-code } ะฃัั– ะฑะปะพะบะธ ะบะพะดัƒ ะผะพะถะฝะฐ ัะบะพะฟั–ัŽะฒะฐั‚ะธ ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ (ั†ะต ั„ะฐะบั‚ะธั‡ะฝะพ ะฟะตั€ะตะฒั–ั€ะตะฝั– ั„ะฐะนะปะธ Python). -ะฉะพะฑ ะทะฐะฟัƒัั‚ะธั‚ะธ ะฑัƒะดัŒ-ัะบะธะน ั–ะท ะฟั€ะธะบะปะฐะดั–ะฒ, ัะบะพะฟั–ัŽะนั‚ะต ะบะพะด ัƒ ั„ะฐะนะป `main.py` ั– ะทะฐะฟัƒัั‚ั–ั‚ัŒ `uvicorn` ะทะฐ ะดะพะฟะพะผะพะณะพัŽ: +ะฉะพะฑ ะทะฐะฟัƒัั‚ะธั‚ะธ ะฑัƒะดัŒ-ัะบะธะน ั–ะท ะฟั€ะธะบะปะฐะดั–ะฒ, ัะบะพะฟั–ัŽะนั‚ะต ะบะพะด ัƒ ั„ะฐะนะป `main.py` ั– ะทะฐะฟัƒัั‚ั–ั‚ัŒ `fastapi dev` ะทะฐ ะดะพะฟะพะผะพะณะพัŽ: <div class="termy"> ```console -$ uvicorn main:app --reload +$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u> -<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -<span style="color: green;">INFO</span>: Started reloader process [28720] -<span style="color: green;">INFO</span>: Started server process [28722] -<span style="color: green;">INFO</span>: Waiting for application startup. -<span style="color: green;">INFO</span>: Application startup complete. + <span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server ๐Ÿš€ + + Searching for package file structure from directories + with <font color="#3465A4">__init__.py</font> files + Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> ๐Ÿ main.py + + <span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with + the following code: + + <u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u> + + <span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font> + + <span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use: + <b>fastapi run</b> + + Logs: + + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories: + <b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C + to quit<b>)</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. ``` </div> @@ -34,45 +58,33 @@ $ uvicorn main:app --reload --- -## ะ’ัั‚ะฐะฝะพะฒะปะตะฝะฝั FastAPI +## ะ’ัั‚ะฐะฝะพะฒะปะตะฝะฝั FastAPI { #install-fastapi } ะŸะตั€ัˆะธะผ ะบั€ะพะบะพะผ ั” ะฒัั‚ะฐะฝะพะฒะปะตะฝะฝั FastAPI. -ะ”ะปั ั‚ัƒั‚ะพั€ั–ะฐะปัƒ ะฒะธ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะนะพะณะพ ะท ัƒัั–ะผะฐ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผะธ ะทะฐะปะตะถะฝะพัั‚ัะผะธ ั‚ะฐ ั„ัƒะฝะบั†ั–ัะผะธ: +ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ **ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ FastAPI**: <div class="termy"> ```console -$ pip install "fastapi[all]" +$ pip install "fastapi[standard]" ---> 100% ``` </div> -...ัะบะธะน ั‚ะฐะบะพะถ ะฒะบะปัŽั‡ะฐั” `uvicorn`, ัะบะธะน ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัะบ ัะตั€ะฒะตั€, ัะบะธะน ะทะฐะฟัƒัะบะฐั” ะฒะฐัˆ ะบะพะด. +/// note | ะŸั€ะธะผั–ั‚ะบะฐ -/// note +ะšะพะปะธ ะฒะธ ะฒัั‚ะฐะฝะพะฒะปัŽั”ั‚ะต ั‡ะตั€ะตะท `pip install "fastapi[standard]"`, ะฒั–ะฝ ะฟะพัั‚ะฐั‡ะฐั”ั‚ัŒัั ะท ะดะตัะบะธะผะธ ั‚ะธะฟะพะฒะธะผะธ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะผะธ ะทะฐะปะตะถะฝะพัั‚ัะผะธ, ะฒะบะปัŽั‡ะฝะพ ะท `fastapi-cloud-cli`, ัะบะธะน ะดะพะทะฒะพะปัั” ั€ะพะทะณะพั€ั‚ะฐั‚ะธ ะฒ <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>. -ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะนะพะณะพ ั‡ะฐัั‚ะธะฝะฐ ะทะฐ ั‡ะฐัั‚ะธะฝะพัŽ. +ะฏะบั‰ะพ ะฒะธ ะฝะต ั…ะพั‡ะตั‚ะต ะผะฐั‚ะธ ั†ั– ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒั– ะทะฐะปะตะถะฝะพัั‚ั–, ะฝะฐั‚ะพะผั–ัั‚ัŒ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ `pip install fastapi`. -ะฆะต ั‚ะต, ั‰ะพ ะฒะธ, ะนะผะพะฒั–ั€ะฝะพ, ะทั€ะพะฑะธะปะธ ะฑ, ะบะพะปะธ ะทะฐั…ะพั‡ะตั‚ะต ั€ะพะทะณะพั€ะฝัƒั‚ะธ ัะฒะพัŽ ะฟั€ะพะณั€ะฐะผัƒ ัƒ ะฒะธั€ะพะฑะฝะธั‡ะพะผัƒ ัะตั€ะตะดะพะฒะธั‰ั–: - -``` -pip install fastapi -``` - -ะขะฐะบะพะถ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ `uvicorn`, ั‰ะพะฑ ะฒั–ะฝ ะฟั€ะฐั†ัŽะฒะฐะฒ ัะบ ัะตั€ะฒะตั€: - -``` -pip install "uvicorn[standard]" -``` - -ะ† ั‚ะต ัะฐะผะต ะดะปั ะบะพะถะฝะพั— ะท ะพะฟั†ั–ะพะฝะฐะปัŒะฝะธั… ะทะฐะปะตะถะฝะพัั‚ะตะน, ัะบั– ะฒะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ. +ะฏะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ะทะฐะปะตะถะฝะพัั‚ั–, ะฐะปะต ะฑะตะท `fastapi-cloud-cli`, ะฒะธ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ั‡ะตั€ะตะท `pip install "fastapi[standard-no-fastapi-cloud-cli]"`. /// -## ะ ะพะทัˆะธั€ะตะฝะธะน ะฟะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ +## ะ ะพะทัˆะธั€ะตะฝะธะน ะฟะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ { #advanced-user-guide } ะ†ัะฝัƒั” ั‚ะฐะบะพะถ **ะ ะพะทัˆะธั€ะตะฝะธะน ะฟะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ**, ัะบะธะน ะฒะธ ะทะผะพะถะตั‚ะต ะฟั€ะพั‡ะธั‚ะฐั‚ะธ ะฟั–ะทะฝั–ัˆะต ะฟั–ัะปั ั†ัŒะพะณะพ **ะขัƒั‚ะพั€ั–ะฐะป - ะŸะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ**. @@ -80,4 +92,4 @@ pip install "uvicorn[standard]" ะะปะต ะฒะฐะผ ัะปั–ะด ัะฟะพั‡ะฐั‚ะบัƒ ะฟั€ะพั‡ะธั‚ะฐั‚ะธ **ะขัƒั‚ะพั€ั–ะฐะป - ะŸะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ** (ั‚ะต, ั‰ะพ ะฒะธ ะทะฐั€ะฐะท ั‡ะธั‚ะฐั”ั‚ะต). -ะ’ั–ะฝ ั€ะพะทั€ะพะฑะปะตะฝะธะน ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ, ั‰ะพ ะฒะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ ะฟะพะฒะฝัƒ ะฟั€ะพะณั€ะฐะผัƒ ะปะธัˆะต ะทะฐ ะดะพะฟะพะผะพะณะพัŽ **ะขัƒั‚ะพั€ั–ะฐะป - ะŸะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ**, ะฐ ะฟะพั‚ั–ะผ ั€ะพะทัˆะธั€ะธั‚ะธ ั—ั— ั€ั–ะทะฝะธะผะธ ัะฟะพัะพะฑะฐะผะธ, ะทะฐะปะตะถะฝะพ ะฒั–ะด ะฒะฐัˆะธั… ะฟะพั‚ั€ะตะฑ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะดะตัะบั– ะท ะดะพะดะฐั‚ะบะพะฒะธั… ั–ะดะตะน ะท **ะ ะพะทัˆะธั€ะตะฝะพะณะพ ะฟะพัั–ะฑะฝะธะบะฐ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ** . +ะ’ั–ะฝ ั€ะพะทั€ะพะฑะปะตะฝะธะน ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ, ั‰ะพ ะฒะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ ะฟะพะฒะฝัƒ ะฟั€ะพะณั€ะฐะผัƒ ะปะธัˆะต ะทะฐ ะดะพะฟะพะผะพะณะพัŽ **ะขัƒั‚ะพั€ั–ะฐะป - ะŸะพัั–ะฑะฝะธะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ**, ะฐ ะฟะพั‚ั–ะผ ั€ะพะทัˆะธั€ะธั‚ะธ ั—ั— ั€ั–ะทะฝะธะผะธ ัะฟะพัะพะฑะฐะผะธ, ะทะฐะปะตะถะฝะพ ะฒั–ะด ะฒะฐัˆะธั… ะฟะพั‚ั€ะตะฑ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะดะตัะบั– ะท ะดะพะดะฐั‚ะบะพะฒะธั… ั–ะดะตะน ะท **ะ ะพะทัˆะธั€ะตะฝะพะณะพ ะฟะพัั–ะฑะฝะธะบะฐ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ**. diff --git a/docs/uk/docs/tutorial/metadata.md b/docs/uk/docs/tutorial/metadata.md index 64e667ec69..cf17045628 100644 --- a/docs/uk/docs/tutorial/metadata.md +++ b/docs/uk/docs/tutorial/metadata.md @@ -1,26 +1,26 @@ -# ะœะตั‚ะฐะดะฐะฝั– ั‚ะฐ URL-ะฐะดั€ะตัะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +# ะœะตั‚ะฐะดะฐะฝั– ั‚ะฐ URL-ะฐะดั€ะตัะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— { #metadata-and-docs-urls } ะ’ะธ ะผะพะถะตั‚ะต ะฝะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ะบั–ะปัŒะบะฐ ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ะน ะผะตั‚ะฐะดะฐะฝะธั… ัƒ ะ’ะฐัˆะพะผัƒ ะดะพะดะฐั‚ะบัƒ **FastAPI**. -## ะœะตั‚ะฐะดะฐะฝั– ะดะปั API +## ะœะตั‚ะฐะดะฐะฝั– ะดะปั API { #metadata-for-api } ะ’ะธ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ั‚ะฐะบั– ะฟะพะปั, ัะบั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะฒ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OpenAPI ั‚ะฐ ะฒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะณะตะฝะตั€ะพะฒะฐะฝะธั… ั–ะฝั‚ะตั€ั„ะตะนัะฐั… ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— API: | ะŸะฐั€ะฐะผะตั‚ั€ | ะขะธะฟ | ะžะฟะธั | |------------|------|-------------| | `title` | `str` | ะะฐะทะฒะฐ API. | -| `summary` | `str` | ะšะพั€ะพั‚ะบะธะน ะพะฟะธั API. <small>ะ”ะพัั‚ัƒะฟะฝะพ ะท OpenAPI 3.1.0, FastAPI 0.99.0.</small> | -| `description` | `str` | ะ‘ั–ะปัŒัˆ ะดะตั‚ะฐะปัŒะฝะธะน ะพะฟะธั API. ะœะพะถะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Markdown. | +| `summary` | `str` | ะšะพั€ะพั‚ะบะธะน ะฟั–ะดััƒะผะพะบ API. <small>ะ”ะพัั‚ัƒะฟะฝะพ ะท OpenAPI 3.1.0, FastAPI 0.99.0.</small> | +| `description` | `str` | ะšะพั€ะพั‚ะบะธะน ะพะฟะธั API. ะœะพะถะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Markdown. | | `version` | `string` | ะ’ะตั€ัั–ั API. ะฆะต ะฒะตั€ัั–ั ะ’ะฐัˆะพะณะพ ะดะพะดะฐั‚ะบะฐ, ะฐ ะฝะต OpenAPI. ะะฐะฟั€ะธะบะปะฐะด, `2.5.0`. | -| `terms_of_service` | `str` | URL ะดะพ ัƒะผะพะฒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั API. ะฏะบั‰ะพ ะฒะบะฐะทะฐะฝะพ, ะผะฐั” ะฑัƒั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– URL. | -| `contact` | `dict` | ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะดะปั ะบะพะฝั‚ะฐะบั‚ัƒ ะท API. ะœะพะถะต ะผั–ัั‚ะธั‚ะธ ะบั–ะปัŒะบะฐ ะฟะพะปั–ะฒ. <details><summary><code>contact</code> ะฟะพะปั</summary><table><thead><tr><th>ะŸะฐั€ะฐะผะตั‚ั€</th><th>ะขะธะฟ</th><th>ะžะฟะธั</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>ะ†ะผ'ั ะบะพะฝั‚ะฐะบั‚ะฝะพั— ะพัะพะฑะธ ะฐะฑะพ ะพั€ะณะฐะฝั–ะทะฐั†ั–ั—.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL ะท ั–ะฝั„ะพั€ะผะฐั†ั–ั”ัŽ ะดะปั ะบะพะฝั‚ะฐะบั‚ัƒ. ะŸะพะฒะธะฝะตะฝ ะฑัƒั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>Email ะบะพะฝั‚ะฐะบั‚ะฝะพั— ะพัะพะฑะธ ะฐะฑะพ ะพั€ะณะฐะฝั–ะทะฐั†ั–ั—. ะŸะพะฒะธะฝะตะฝ ะฑัƒั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ.</td></tr></tbody></table></details> | -| `license_info` | `dict` | ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะปั–ั†ะตะฝะทั–ัŽ ะดะปั API. ะœะพะถะต ะผั–ัั‚ะธั‚ะธ ะบั–ะปัŒะบะฐ ะฟะพะปั–ะฒ. <details><summary><code>license_info</code> ะฟะพะปั</summary><table><thead><tr><th>ะŸะฐั€ะฐะผะตั‚ั€</th><th>ะขะธะฟ</th><th>ะžะฟะธั</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ะžะ‘ะžะ’'ะฏะ—ะšะžะ’ะž</strong> (ัะบั‰ะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ <code>license_info</code>). ะะฐะทะฒะฐ ะปั–ั†ะตะฝะทั–ั— ะดะปั API.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>ะ›ั–ั†ะตะฝะทั–ะนะฝะธะน ะฒะธั€ะฐะท ะทะฐ <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> ะดะปั API. ะŸะพะปะต <code>identifier</code> ะฒะทะฐั”ะผะพะฒะธะบะปัŽั‡ะฝะต ะท ะฟะพะปะตะผ <code>url</code>. <small>ะ”ะพัั‚ัƒะฟะฝะพ ะท OpenAPI 3.1.0, FastAPI 0.99.0.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL ะดะพ ะปั–ั†ะตะฝะทั–ั—, ัะบะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะดะปั API. ะŸะพะฒะธะฝะตะฝ ะฑัƒั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– URL.</td></tr></tbody></table></details> | +| `terms_of_service` | `str` | URL ะดะพ ัƒะผะพะฒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั API. ะฏะบั‰ะพ ะฒะบะฐะทะฐะฝะพ, ะผะฐั” ะฑัƒั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– URL. | +| `contact` | `dict` | ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะดะปั ะบะพะฝั‚ะฐะบั‚ัƒ ะท ะพะฟัƒะฑะปั–ะบะพะฒะฐะฝะธะผ API. ะœะพะถะต ะผั–ัั‚ะธั‚ะธ ะบั–ะปัŒะบะฐ ะฟะพะปั–ะฒ. <details><summary><code>contact</code> ะฟะพะปั</summary><table><thead><tr><th>ะŸะฐั€ะฐะผะตั‚ั€</th><th>ะขะธะฟ</th><th>ะžะฟะธั</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>ะ†ะดะตะฝั‚ะธั„ั–ะบะฐั†ั–ะนะฝะต ั–ะผ'ั ะบะพะฝั‚ะฐะบั‚ะฝะพั— ะพัะพะฑะธ ะฐะฑะพ ะพั€ะณะฐะฝั–ะทะฐั†ั–ั—.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL, ั‰ะพ ะฒะบะฐะทัƒั” ะฝะฐ ะบะพะฝั‚ะฐะบั‚ะฝัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ. <strong>ะœะะ„</strong> ะฑัƒั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>ะะดั€ะตัะฐ ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ ะบะพะฝั‚ะฐะบั‚ะฝะพั— ะพัะพะฑะธ ะฐะฑะพ ะพั€ะณะฐะฝั–ะทะฐั†ั–ั—. <strong>ะœะะ„</strong> ะฑัƒั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– ะฐะดั€ะตัะธ ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ.</td></tr></tbody></table></details> | +| `license_info` | `dict` | ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะปั–ั†ะตะฝะทั–ัŽ ะดะปั ะพะฟัƒะฑะปั–ะบะพะฒะฐะฝะพะณะพ API. ะœะพะถะต ะผั–ัั‚ะธั‚ะธ ะบั–ะปัŒะบะฐ ะฟะพะปั–ะฒ. <details><summary><code>license_info</code> ะฟะพะปั</summary><table><thead><tr><th>ะŸะฐั€ะฐะผะตั‚ั€</th><th>ะขะธะฟ</th><th>ะžะฟะธั</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ะžะ‘ะžะ’'ะฏะ—ะšะžะ’ะž</strong> (ัะบั‰ะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ <code>license_info</code>). ะะฐะทะฒะฐ ะปั–ั†ะตะฝะทั–ั— ะดะปั API.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>ะ›ั–ั†ะตะฝะทั–ะนะฝะธะน ะฒะธั€ะฐะท ะทะฐ <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> ะดะปั API. ะŸะพะปะต <code>identifier</code> ะฒะทะฐั”ะผะพะฒะธะบะปัŽั‡ะฝะต ะท ะฟะพะปะตะผ <code>url</code>. <small>ะ”ะพัั‚ัƒะฟะฝะพ ะท OpenAPI 3.1.0, FastAPI 0.99.0.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL ะดะพ ะปั–ั†ะตะฝะทั–ั—, ัะบะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะดะปั API. <strong>ะœะะ„</strong> ะฑัƒั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– URL.</td></tr></tbody></table></details> | ะ’ะธ ะผะพะถะตั‚ะต ะฝะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ั—ั… ะฝะฐัั‚ัƒะฟะฝะธะผ ั‡ะธะฝะพะผ: -{* ../../docs_src/metadata/tutorial001.py hl[3:16, 19:32] *} +{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ ะฃ ะฟะพะปั– `description` ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Markdown, ั– ะฒั–ะฝ ะฑัƒะดะต ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธัั ัƒ ั€ะตะทัƒะปัŒั‚ะฐั‚ั–. @@ -30,15 +30,15 @@ <img src="/img/tutorial/metadata/image01.png"> -## ะ†ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะปั–ั†ะตะฝะทั–ั— +## ะ†ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะปั–ั†ะตะฝะทั–ั— { #license-identifier } ะ— ะฟะพั‡ะฐั‚ะบัƒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั OpenAPI 3.1.0 ั‚ะฐ FastAPI 0.99.0 ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฝะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ `license_info` ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `identifier` ะทะฐะผั–ัั‚ัŒ `url`. ะะฐะฟั€ะธะบะปะฐะด: -{* ../../docs_src/metadata/tutorial001_1.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} -## ะœะตั‚ะฐะดะฐะฝั– ะดะปั ั‚ะตะณั–ะฒ +## ะœะตั‚ะฐะดะฐะฝั– ะดะปั ั‚ะตะณั–ะฒ { #metadata-for-tags } ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะผะตั‚ะฐะดะฐะฝั– ะดะปั ั€ั–ะทะฝะธั… ั‚ะตะณั–ะฒ, ัะบั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะดะปั ะณั€ัƒะฟัƒะฒะฐะฝะฝั ะพะฟะตั€ะฐั†ั–ะน ัˆะปัั…ั–ะฒ, ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `openapi_tags`. @@ -46,53 +46,53 @@ ะšะพะถะตะฝ ัะปะพะฒะฝะธะบ ะผะพะถะต ะผั–ัั‚ะธั‚ะธ: -* `name` (**ะพะฑะพะฒ'ัะทะบะพะฒะพ**): `str` ะท ั‚ั–ั”ัŽ ะถ ะฝะฐะทะฒะพัŽ ั‚ะตะณัƒ, ัะบัƒ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ัƒ ะฟะฐั€ะฐะผะตั‚ั€ั– `tags` ัƒ ะ’ะฐัˆะธั… *ะพะฟะตั€ะฐั†ั–ัั… ัˆะปัั…ัƒ* ั‚ะฐ `APIRouter`s. -* `description`: `str` ะท ะบะพั€ะพั‚ะบะธะผ ะพะฟะธัะพะผ ั‚ะตะณัƒ. ะœะพะถะต ะผั–ัั‚ะธั‚ะธ Markdown ั– ะฑัƒะดะต ะฒั–ะดะพะฑั€ะฐะถะตะฝะพ ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั– ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. -* `externalDocs`: `dict` ัะบะธะน ะพะฟะธััƒั” ะทะพะฒะฝั–ัˆะฝัŽ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะท ั‚ะฐะบะธะผะธ ะฟะพะปัะผะธ: +* `name` (**ะพะฑะพะฒ'ัะทะบะพะฒะพ**): `str` ะท ั‚ั–ั”ัŽ ะถ ะฝะฐะทะฒะพัŽ ั‚ะตะณัƒ, ัะบัƒ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ัƒ ะฟะฐั€ะฐะผะตั‚ั€ั– `tags` ัƒ ะ’ะฐัˆะธั… *ะพะฟะตั€ะฐั†ั–ัั… ัˆะปัั…ัƒ* ั‚ะฐ `APIRouter`s. +* `description`: `str` ะท ะบะพั€ะพั‚ะบะธะผ ะพะฟะธัะพะผ ั‚ะตะณัƒ. ะœะพะถะต ะผั–ัั‚ะธั‚ะธ Markdown ั– ะฑัƒะดะต ะฟะพะบะฐะทะฐะฝะพ ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั– ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. +* `externalDocs`: `dict`, ัะบะธะน ะพะฟะธััƒั” ะทะพะฒะฝั–ัˆะฝัŽ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะท ั‚ะฐะบะธะผะธ ะฟะพะปัะผะธ: * `description`: `str` ะท ะบะพั€ะพั‚ะบะธะผ ะพะฟะธัะพะผ ะทะพะฒะฝั–ัˆะฝัŒะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. - * `url` (**ะพะฑะพะฒ'ัะทะบะพะฒะพ**): `str`ะท URL-ะฐะดั€ะตัะพัŽ ะทะพะฒะฝั–ัˆะฝัŒะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. + * `url` (**ะพะฑะพะฒ'ัะทะบะพะฒะพ**): `str` ะท URL-ะฐะดั€ะตัะพัŽ ะทะพะฒะฝั–ัˆะฝัŒะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. -### ะกั‚ะฒะพั€ะตะฝะฝั ะผะตั‚ะฐะดะฐะฝะธั… ะดะปั ั‚ะตะณั–ะฒ +### ะกั‚ะฒะพั€ะตะฝะฝั ะผะตั‚ะฐะดะฐะฝะธั… ะดะปั ั‚ะตะณั–ะฒ { #create-metadata-for-tags } ะกะฟั€ะพะฑัƒะนะผะพ ั†ะต ะฝะฐ ะฟั€ะธะบะปะฐะดั– ะท ั‚ะตะณะฐะผะธ ะดะปั `users` ั‚ะฐ `items`. -ะกั‚ะฒะพั€ั–ั‚ัŒ ะผะตั‚ะฐะดะฐะฝั– ะดะปั ัะฒะพั—ั… ั‚ะตะณั–ะฒ ั– ะฟะตั€ะตะดะฐะนั‚ะต ั—ั… ัƒ ะฟะฐั€ะฐะผะตั‚ั€ `openapi_tags`: +ะกั‚ะฒะพั€ั–ั‚ัŒ ะผะตั‚ะฐะดะฐะฝั– ะดะปั ัะฒะพั—ั… ั‚ะตะณั–ะฒ ั– ะฟะตั€ะตะดะฐะนั‚ะต ั—ั… ัƒ ะฟะฐั€ะฐะผะตั‚ั€ `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_). /// tip | ะŸะพั€ะฐะดะฐ -ะะต ะพะฑะพะฒ'ัะทะบะพะฒะพ ะดะพะดะฐะฒะฐั‚ะธ ะผะตั‚ะฐะดะฐะฝั– ะดะปั ะฒัั–ั… ั‚ะตะณั–ะฒ, ัะบั– ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต. +ะ’ะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะดะพะดะฐะฒะฐั‚ะธ ะผะตั‚ะฐะดะฐะฝั– ะดะปั ะฒัั–ั… ั‚ะตะณั–ะฒ, ัะบั– ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต. /// -### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ั‚ะตะณั–ะฒ +### ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ัะฒะพั— ั‚ะตะณะธ { #use-your-tags } -ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ `tags` ะทั– ัะฒะพั—ะผะธ *ะพะฟะตั€ะฐั†ั–ัะผะธ ัˆะปัั…ัƒ* (ั– `APIRouter`) ะดะปั ะฟั€ะธะทะฝะฐั‡ะตะฝะฝั ั—ั… ะดะพ ั€ั–ะทะฝะธั… ั‚ะตะณั–ะฒ: +ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ `tags` ะทั– ัะฒะพั—ะผะธ *ะพะฟะตั€ะฐั†ั–ัะผะธ ัˆะปัั…ัƒ* (ั– `APIRouter`s), ั‰ะพะฑ ะฟั€ะธะทะฝะฐั‡ะธั‚ะธ ั—ั… ะดะพ ั€ั–ะทะฝะธั… ั‚ะตะณั–ะฒ: -{* ../../docs_src/metadata/tutorial004.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะ”ะตั‚ะฐะปัŒะฝั–ัˆะต ะฟั€ะพ ั‚ะตะณะธ ั‡ะธั‚ะฐะนั‚ะต ะฒ ั€ะพะทะดั–ะปั– [ะšะพะฝั„ั–ะณัƒั€ะฐั†ั–ั ัˆะปัั…ั–ะฒ ะพะฟะตั€ะฐั†ั–ะน](path-operation-configuration.md#tags){.internal-link target=_blank}. +ะ”ะตั‚ะฐะปัŒะฝั–ัˆะต ะฟั€ะพ ั‚ะตะณะธ ั‡ะธั‚ะฐะนั‚ะต ะฒ ั€ะพะทะดั–ะปั– [ะšะพะฝั„ั–ะณัƒั€ะฐั†ั–ั ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ](path-operation-configuration.md#tags){.internal-link target=_blank}. /// -### ะŸะตั€ะตะฒั–ั€ะบะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +### ะŸะตั€ะตะฒั–ั€ั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ { #check-the-docs } -ะฏะบั‰ะพ ะ’ะธ ะทะฐั€ะฐะท ะฟะตั€ะตะฒั–ั€ะธั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ะฒะพะฝะฐ ะฟะพะบะฐะถะต ะฒัั– ะดะพะดะฐั‚ะบะพะฒั– ะผะตั‚ะฐะดะฐะฝั–: +ะขะตะฟะตั€, ัะบั‰ะพ ะ’ะธ ะฟะตั€ะตะฒั–ั€ะธั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ะฒะพะฝะฐ ะฟะพะบะฐะถะต ะฒัั– ะดะพะดะฐั‚ะบะพะฒั– ะผะตั‚ะฐะดะฐะฝั–: <img src="/img/tutorial/metadata/image02.png"> -### ะŸะพั€ัะดะพะบ ั‚ะตะณั–ะฒ +### ะŸะพั€ัะดะพะบ ั‚ะตะณั–ะฒ { #order-of-tags } ะŸะพั€ัะดะพะบ ะบะพะถะฝะพะณะพ ัะปะพะฒะฝะธะบะฐ ะผะตั‚ะฐะดะฐะฝะธั… ั‚ะตะณัƒ ั‚ะฐะบะพะถ ะฒะธะทะฝะฐั‡ะฐั” ะฟะพั€ัะดะพะบ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั– ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. -ะะฐะฟั€ะธะบะปะฐะด, ั…ะพั‡ะฐ `users` ะผะฐะฒ ะฑะธ ะนั‚ะธ ะฟั–ัะปั `items` ะฒ ะฐะปั„ะฐะฒั–ั‚ะฝะพะผัƒ ะฟะพั€ัะดะบัƒ, ะฒั–ะฝ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั ะฟะตั€ะตะด ะฝะธะผะธ, ะพัะบั–ะปัŒะบะธ ะผะธ ะดะพะดะฐะปะธ ะนะพะณะพ ะผะตั‚ะฐะดะฐะฝั– ัะบ ะฟะตั€ัˆะธะน ัะปะพะฒะฝะธะบ ัƒ ัะฟะธัะบัƒ. +ะะฐะฟั€ะธะบะปะฐะด, ั…ะพั‡ะฐ `users` ะผะฐะฒ ะฑะธ ะนั‚ะธ ะฟั–ัะปั `items` ะฒ ะฐะปั„ะฐะฒั–ั‚ะฝะพะผัƒ ะฟะพั€ัะดะบัƒ, ะฒั–ะฝ ะฒั–ะดะพะฑั€ะฐะถะฐั”ั‚ัŒัั ะฟะตั€ะตะด ะฝะธะผะธ, ะพัะบั–ะปัŒะบะธ ะผะธ ะดะพะดะฐะปะธ ั—ั…ะฝั– ะผะตั‚ะฐะดะฐะฝั– ัะบ ะฟะตั€ัˆะธะน ัะปะพะฒะฝะธะบ ัƒ ัะฟะธัะบัƒ. -## URL ะดะปั OpenAPI +## URL ะดะปั OpenAPI { #openapi-url } ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ัั…ะตะผะฐ OpenAPI ะฝะฐะดะฐั”ั‚ัŒัั ะทะฐ ะฐะดั€ะตัะพัŽ `/openapi.json`. @@ -100,11 +100,11 @@ ะะฐะฟั€ะธะบะปะฐะด, ั‰ะพะฑ ะฝะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ะนะพะณะพ ะฝะฐ `/api/v1/openapi.json`: -{* ../../docs_src/metadata/tutorial002.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} ะฏะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะฟะพะฒะฝั–ัั‚ัŽ ะฒะธะผะบะฝัƒั‚ะธ ัั…ะตะผัƒ OpenAPI, ะ’ะธ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ `openapi_url=None`, ั†ะต ั‚ะฐะบะพะถ ะฒะธะผะบะฝะต ั–ะฝั‚ะตั€ั„ะตะนัะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—, ัะบั– ั—ั— ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ. -## URL-ะฐะดั€ะตัะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +## URL-ะฐะดั€ะตัะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— { #docs-urls } ะ’ะธ ะผะพะถะตั‚ะต ะฝะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ ะดะฒะฐ ั–ะฝั‚ะตั€ั„ะตะนัะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะดะปั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—, ัะบั– ะฒะบะปัŽั‡ะตะฝั–: @@ -117,4 +117,4 @@ ะะฐะฟั€ะธะบะปะฐะด, ั‰ะพะฑ ะฝะฐะปะฐัˆั‚ัƒะฒะฐั‚ะธ Swagger UI ะฝะฐ `/documentation` ั– ะฒะธะผะบะฝัƒั‚ะธ ReDoc: -{* ../../docs_src/metadata/tutorial003.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} diff --git a/docs/uk/docs/tutorial/middleware.md b/docs/uk/docs/tutorial/middleware.md index 13ce8573de..2d1580e49e 100644 --- a/docs/uk/docs/tutorial/middleware.md +++ b/docs/uk/docs/tutorial/middleware.md @@ -1,45 +1,43 @@ -# Middleware (ะŸั€ะพะผั–ะถะฝะธะน ัˆะฐั€) +# Middleware { #middleware } -ะฃ **FastAPI** ะผะพะถะฝะฐ ะดะพะดะฐะฒะฐั‚ะธ middleware (ะฟั€ะพะผั–ะถะฝะธะน ัˆะฐั€). +ะฃ **FastAPI** ะผะพะถะฝะฐ ะดะพะดะฐะฒะฐั‚ะธ middleware. -"Middleware" โ€” ั†ะต ั„ัƒะฝะบั†ั–ั, ัะบะฐ ะฟั€ะฐั†ัŽั” ะท ะบะพะถะฝะธะผ **ะทะฐะฟะธั‚ะพะผ** ะฟะตั€ะตะด ะนะพะณะพ ะพะฑั€ะพะฑะบะพัŽ ะฑัƒะดัŒ-ัะบะพัŽ ะบะพะฝะบั€ะตั‚ะฝะพัŽ *ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ* (*path operation*), ะฐ ั‚ะฐะบะพะถ ะท ะบะพะถะฝะพัŽ **ะฒั–ะดะฟะพะฒั–ะดะดัŽ** ะฟะตั€ะตะด ั—ั— ะฟะพะฒะตั€ะฝะตะฝะฝัะผ. +ยซMiddlewareยป โ€” ั†ะต ั„ัƒะฝะบั†ั–ั, ัะบะฐ ะฟั€ะฐั†ัŽั” ะท ะบะพะถะฝะธะผ **ะทะฐะฟะธั‚ะพะผ** ะฟะตั€ะตะด ะนะพะณะพ ะพะฑั€ะพะฑะบะพัŽ ะฑัƒะดัŒ-ัะบะพัŽ ะบะพะฝะบั€ะตั‚ะฝะพัŽ *ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ*. ะ ั‚ะฐะบะพะถ ะท ะบะพะถะฝะพัŽ **ะฒั–ะดะฟะพะฒั–ะดะดัŽ** ะฟะตั€ะตะด ั—ั— ะฟะพะฒะตั€ะฝะตะฝะฝัะผ. -* Middleware ะพั‚ั€ะธะผัƒั” ะบะพะถะตะฝ **ะทะฐะฟะธั‚**, ั‰ะพ ะฝะฐะดั…ะพะดะธั‚ัŒ ะดะพ ะ’ะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ. -* ะœะพะถะต ะฒะธะบะพะฝะฐั‚ะธ ะฟะตะฒะฝั– ะดั–ั— ั–ะท ั†ะธะผ **ะทะฐะฟะธั‚ะพะผ** ะฐะฑะพ ะทะฐะฟัƒัั‚ะธั‚ะธ ะฝะตะพะฑั…ั–ะดะฝะธะน ะบะพะด. -* ะ”ะฐะปั– ะฟะตั€ะตะดะฐั” **ะทะฐะฟะธั‚** ะดะปั ะพะฑั€ะพะฑะบะธ ะพัะฝะพะฒะฝะธะผ ะทะฐัั‚ะพััƒะฝะบะพะผ (*ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ*). -* ะžั‚ั€ะธะผัƒั” **ะฒั–ะดะฟะพะฒั–ะดัŒ**, ัั„ะพั€ะผะพะฒะฐะฝัƒ ะทะฐัั‚ะพััƒะฝะบะพะผ (*ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ*). -* ะœะพะถะต ะทะผั–ะฝะธั‚ะธ ั†ัŽ **ะฒั–ะดะฟะพะฒั–ะดัŒ** ะฐะฑะพ ะฒะธะบะพะฝะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒะธะน ะบะพะด. -* ะŸะพะฒะตั€ั‚ะฐั” **ะฒั–ะดะฟะพะฒั–ะดัŒ** ะบะปั–ั”ะฝั‚ัƒ. +* ะ’ะพะฝะฐ ะพั‚ั€ะธะผัƒั” ะบะพะถะตะฝ **ะทะฐะฟะธั‚**, ั‰ะพ ะฝะฐะดั…ะพะดะธั‚ัŒ ะดะพ ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ. +* ะŸะพั‚ั–ะผ ะฒะพะฝะฐ ะผะพะถะต ะฒะธะบะพะฝะฐั‚ะธ ะฟะตะฒะฝั– ะดั–ั— ั–ะท ั†ะธะผ **ะทะฐะฟะธั‚ะพะผ** ะฐะฑะพ ะทะฐะฟัƒัั‚ะธั‚ะธ ะฝะตะพะฑั…ั–ะดะฝะธะน ะบะพะด. +* ะ”ะฐะปั– ะฒะพะฝะฐ ะฟะตั€ะตะดะฐั” **ะทะฐะฟะธั‚** ะดะปั ะพะฑั€ะพะฑะบะธ ั€ะตัˆั‚ะพัŽ ะทะฐัั‚ะพััƒะฝะบัƒ (ัะบะพัŽััŒ *ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ*). +* ะŸะพั‚ั–ะผ ะฒะพะฝะฐ ะพั‚ั€ะธะผัƒั” **ะฒั–ะดะฟะพะฒั–ะดัŒ**, ัั„ะพั€ะผะพะฒะฐะฝัƒ ะทะฐัั‚ะพััƒะฝะบะพะผ (ัะบะพัŽััŒ *ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ*). +* ะ’ะพะฝะฐ ะผะพะถะต ะฒะธะบะพะฝะฐั‚ะธ ะฟะตะฒะฝั– ะดั–ั— ั–ะท ั†ั–ั”ัŽ **ะฒั–ะดะฟะพะฒั–ะดะดัŽ** ะฐะฑะพ ะทะฐะฟัƒัั‚ะธั‚ะธ ะฝะตะพะฑั…ั–ะดะฝะธะน ะบะพะด. +* ะŸะพั‚ั–ะผ ะฒะพะฝะฐ ะฟะพะฒะตั€ั‚ะฐั” **ะฒั–ะดะฟะพะฒั–ะดัŒ**. /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– -ะฏะบั‰ะพ ัƒ ะ’ะฐั ั” ะทะฐะปะตะถะฝะพัั‚ั– ะท `yield`, ะบะพะด ะฒะธั…ะพะดัƒ ะฒะธะบะพะฝะฐั”ั‚ัŒัั *ะฟั–ัะปั* middleware. +ะฏะบั‰ะพ ัƒ ะฒะฐั ั” ะทะฐะปะตะถะฝะพัั‚ั– ะท `yield`, ะบะพะด ะฒะธั…ะพะดัƒ ะฒะธะบะพะฝะฐั”ั‚ัŒัั *ะฟั–ัะปั* middleware. -ะฏะบั‰ะพ ะฑัƒะปะธ ะทะฐะฟะปะฐะฝะพะฒะฐะฝั– ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั– (background tasks - ั€ะพะทะณะปัะฝัƒั‚ะพ ะดะฐะปั–), ะฒะพะฝะธ ะฒะธะบะพะฝะฐัŽั‚ัŒัั *ะฟั–ัะปั* ะฒัั–ั… middleware. +ะฏะบั‰ะพ ะฑัƒะปะธ ะทะฐะฟะปะฐะฝะพะฒะฐะฝั– ั„ะพะฝะพะฒั– ะทะฐะดะฐั‡ั– (ั€ะพะทะณะปัะฝัƒั‚ะพ ะฒ ั€ะพะทะดั–ะปั– [Background Tasks](background-tasks.md){.internal-link target=_blank}, ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ั†ะต ะฟั–ะทะฝั–ัˆะต), ะฒะพะฝะธ ะฒะธะบะพะฝะฐัŽั‚ัŒัั *ะฟั–ัะปั* ะฒัั–ั… middleware. /// -## ะกั‚ะฒะพั€ะตะฝะฝั middleware +## ะกั‚ะฒะพั€ะตะฝะฝั middleware { #create-a-middleware } -ะฉะพะฑ ัั‚ะฒะพั€ะธั‚ะธ middleware, ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ะดะตะบะพั€ะฐั‚ะพั€ `@app.middleware("http")` ะฝะฐ ั„ัƒะฝะบั†ั–ั—. +ะฉะพะฑ ัั‚ะฒะพั€ะธั‚ะธ middleware, ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ะดะตะบะพั€ะฐั‚ะพั€ `@app.middleware("http")` ะฝะฐะด ั„ัƒะฝะบั†ั–ั”ัŽ. ะคัƒะฝะบั†ั–ั middleware ะพั‚ั€ะธะผัƒั”: -* `ะ—ะฐะฟะธั‚`. -* ะคัƒะฝะบั†ั–ัŽ `call_next`, ัะบะฐ ะฟั€ะธะนะผะฐั” `ะทะฐะฟะธั‚` ัะบ ะฟะฐั€ะฐะผะตั‚ั€. - * ะฆั ั„ัƒะฝะบั†ั–ั ะฟะตั€ะตะดะฐั” `ะทะฐะฟะธั‚` ะฒั–ะดะฟะพะฒั–ะดะฝั–ะน *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*. - * ะŸะพั‚ั–ะผ ะฒะพะฝะฐ ะฟะพะฒะตั€ั‚ะฐั” `ะฒั–ะดะฟะพะฒั–ะดัŒ`, ะทะณะตะฝะตั€ะพะฒะฐะฝัƒ ั†ั–ั”ัŽ *ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ*. +* `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 | ะŸะพั€ะฐะดะฐ -ะะต ะทะฐะฑัƒะฒะฐะนั‚ะต, ั‰ะพ ะฒะปะฐัะฝั– ะทะฐะณะพะปะพะฒะบะธ ะผะพะถะฝะฐ ะดะพะดะฐะฒะฐั‚ะธ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">ะฟั€ะตั„ั–ะบั 'X-'</a>. +ะŸะฐะผโ€™ัั‚ะฐะนั‚ะต, ั‰ะพ ะฒะปะฐัะฝั– ะฟั€ะพะฟั€ั–ั”ั‚ะฐั€ะฝั– ะทะฐะณะพะปะพะฒะบะธ ะผะพะถะฝะฐ ะดะพะดะฐะฒะฐั‚ะธ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะฟั€ะตั„ั–ะบั `X-`</a>. -ะะปะต ัะบั‰ะพ ัƒ ะ’ะฐั ั” ะฒะปะฐัะฝั– ะทะฐะณะพะปะพะฒะบะธ, ัะบั– ะ’ะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะฑั€ะฐัƒะทะตั€ะฝะธะน ะบะปั–ั”ะฝั‚ ะผั–ะณ ะฟะพะฑะฐั‡ะธั‚ะธ, ะฟะพั‚ั€ั–ะฑะฝะพ ะดะพะดะฐั‚ะธ ั—ั… ะดะพ ะ’ะฐัˆะพั— ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ั— CORS (ะดะธะฒ. [CORS (ะžะฑะผั–ะฝ ั€ะตััƒั€ัะฐะผะธ ะผั–ะถ ั€ั–ะทะฝะธะผะธ ะดะถะตั€ะตะปะฐะผะธ)](cors.md){.internal-link target=_blank} ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `expose_headers`, ะพะฟะธัะฐะฝะพะณะพ ะฒ <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Starlette ะฟะพ CORS</a>. +ะะปะต ัะบั‰ะพ ัƒ ะฒะฐั ั” ะฒะปะฐัะฝั– ะทะฐะณะพะปะพะฒะบะธ, ัะบั– ะฒะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะบะปั–ั”ะฝั‚ ัƒ ะฑั€ะฐัƒะทะตั€ั– ะผั–ะณ ะฟะพะฑะฐั‡ะธั‚ะธ, ะฟะพั‚ั€ั–ะฑะฝะพ ะดะพะดะฐั‚ะธ ั—ั… ะดะพ ะฒะฐัˆะธั… ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ะน CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `expose_headers`, ะพะฟะธัะฐะฝะพะณะพ ะฒ <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Starlette ะฟะพ CORS</a>. /// @@ -47,28 +45,50 @@ ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `from starlette.requests import Request`. -**FastAPI** ะฝะฐะดะฐั” ั†ะต ะดะปั ะ’ะฐัˆะพั— ะทั€ัƒั‡ะฝะพัั‚ั– ัะบ ั€ะพะทั€ะพะฑะฝะธะบะฐ. ะะปะต ะฒั–ะฝ ะฟะพั…ะพะดะธั‚ัŒ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะทั– Starlette. +**FastAPI** ะฝะฐะดะฐั” ั†ะต ะดะปั ะฒะฐัˆะพั— ะทั€ัƒั‡ะฝะพัั‚ั– ัะบ ั€ะพะทั€ะพะฑะฝะธะบะฐ. ะะปะต ะฒะพะฝะพ ะฟะพั…ะพะดะธั‚ัŒ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะทั– Starlette. /// -### ะ”ะพ ั– ะฟั–ัะปั `response`(`ะฒั–ะดะฟะพะฒั–ะดั–`) +### ะ”ะพ ั– ะฟั–ัะปั `response` { #before-and-after-the-response } -ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะบะพะด, ัะบะธะน ะฑัƒะดะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธัั ะท `ะทะฐะฟะธั‚ะพะผ` (`request`), ะดะพ ั‚ะพะณะพ, ัะบ ะนะพะณะพ ะพะฑั€ะพะฑะธั‚ัŒ ะฑัƒะดัŒ-ัะบะฐ *ะพะฟะตั€ะฐั†ั–ั ัˆะปัั…ัƒ* (*path operation*). +ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะบะพะด, ัะบะธะน ะฑัƒะดะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธัั ะท `request`, ะดะพ ั‚ะพะณะพ, ัะบ ะนะพะณะพ ะพั‚ั€ะธะผะฐั” ะฑัƒะดัŒ-ัะบะฐ *ะพะฟะตั€ะฐั†ั–ั ัˆะปัั…ัƒ*. -ะขะฐะบะพะถ ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะบะพะด, ัะบะธะน ะฑัƒะดะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธัั ะฟั–ัะปั ั‚ะพะณะพ, ัะบ `ะฒั–ะดะฟะพะฒั–ะดัŒ` (`response`) ะฑัƒะดะต ะทะณะตะฝะตั€ะพะฒะฐะฝะพ, ะฟะตั€ะตะด ั‚ะธะผ ัะบ ะนะพะณะพ ะฟะพะฒะตั€ะฝัƒั‚ะธ. +ะขะฐะบะพะถ ะฒะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะบะพะด, ัะบะธะน ะฑัƒะดะต ะฒะธะบะพะฝัƒะฒะฐั‚ะธัั ะฟั–ัะปั ั‚ะพะณะพ, ัะบ `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 | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ ะขัƒั‚ ะผะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ะผะพ <a href="https://docs.python.org/3/library/time.html#time.perf_counter" class="external-link" target="_blank">`time.perf_counter()`</a> ะทะฐะผั–ัั‚ัŒ `time.time()` ะพัะบั–ะปัŒะบะธ ะฒั–ะฝ ะผะพะถะต ะฑัƒั‚ะธ ะฑั–ะปัŒัˆ ั‚ะพั‡ะฝะธะผ ะดะปั ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบั–ะฒ. ๐Ÿค“ /// -## ะ†ะฝัˆั– middlewares +## ะŸะพั€ัะดะพะบ ะฒะธะบะพะฝะฐะฝะฝั ะบั–ะปัŒะบะพั… middleware { #multiple-middleware-execution-order } + +ะšะพะปะธ ะฒะธ ะดะพะดะฐั”ั‚ะต ะบั–ะปัŒะบะฐ middleware, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะฐะฑะพ ะดะตะบะพั€ะฐั‚ะพั€ `@app.middleware()` ะฐะฑะพ ะผะตั‚ะพะด `app.add_middleware()`, ะบะพะถะตะฝ ะฝะพะฒะธะน middleware ะพะฑะณะพั€ั‚ะฐั” ะทะฐัั‚ะพััƒะฝะพะบ, ัƒั‚ะฒะพั€ัŽัŽั‡ะธ ัั‚ะตะบ. ะžัั‚ะฐะฝะฝั–ะน ะดะพะดะฐะฝะธะน middleware ั” *ะทะพะฒะฝั–ัˆะฝั–ะผ*, ะฐ ะฟะตั€ัˆะธะน โ€” *ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะผ*. + +ะะฐ ัˆะปัั…ัƒ ะทะฐะฟะธั‚ัƒ ะฟะตั€ัˆะธะผ ะฒะธะบะพะฝัƒั”ั‚ัŒัั *ะทะพะฒะฝั–ัˆะฝั–ะน* middleware. + +ะะฐ ัˆะปัั…ัƒ ะฒั–ะดะฟะพะฒั–ะดั– ะฒั–ะฝ ะฒะธะบะพะฝัƒั”ั‚ัŒัั ะพัั‚ะฐะฝะฝั–ะผ. + +ะะฐะฟั€ะธะบะปะฐะด: + +```Python +app.add_middleware(MiddlewareA) +app.add_middleware(MiddlewareB) +``` + +ะฆะต ะฟั€ะธะทะฒะพะดะธั‚ัŒ ะดะพ ั‚ะฐะบะพะณะพ ะฟะพั€ัะดะบัƒ ะฒะธะบะพะฝะฐะฝะฝั: + +* **ะ—ะฐะฟะธั‚**: MiddlewareB โ†’ MiddlewareA โ†’ route + +* **ะ’ั–ะดะฟะพะฒั–ะดัŒ**: route โ†’ MiddlewareA โ†’ MiddlewareB + +ะขะฐะบะฐ ะฟะพะฒะตะดั–ะฝะบะฐ ัั‚ะตะบัƒ ะณะฐั€ะฐะฝั‚ัƒั”, ั‰ะพ middleware ะฒะธะบะพะฝัƒัŽั‚ัŒัั ัƒ ะฟะตั€ะตะดะฑะฐั‡ัƒะฒะฐะฝะพะผัƒ ั‚ะฐ ะบะตั€ะพะฒะฐะฝะพะผัƒ ะฟะพั€ัะดะบัƒ. + +## ะ†ะฝัˆั– middlewares { #other-middlewares } ะ’ะธ ะผะพะถะตั‚ะต ะฟั–ะทะฝั–ัˆะต ะฟั€ะพั‡ะธั‚ะฐั‚ะธ ะฑั–ะปัŒัˆะต ะฟั€ะพ ั–ะฝัˆั– middlewares ะฒ [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}. diff --git a/docs/uk/docs/tutorial/path-params-numeric-validations.md b/docs/uk/docs/tutorial/path-params-numeric-validations.md index 8ee4f480fe..f6aa920193 100644 --- a/docs/uk/docs/tutorial/path-params-numeric-validations.md +++ b/docs/uk/docs/tutorial/path-params-numeric-validations.md @@ -1,8 +1,8 @@ -# Path ะŸะฐั€ะฐะผะตั‚ั€ะธ ั‚ะฐ ะฒะฐะปั–ะดะฐั†ั–ั ั‡ะธัะปะพะฒะธั… ะดะฐะฝะธั… +# ะŸะฐั€ะฐะผะตั‚ั€ะธ ัˆะปัั…ัƒ ั‚ะฐ ะฒะฐะปั–ะดะฐั†ั–ั ั‡ะธัะปะพะฒะธั… ะดะฐะฝะธั… { #path-parameters-and-numeric-validations } -ะขะฐะบ ัะฐะผะพ ัะบ ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะฟะตั€ะตะฒั–ั€ะบะธ ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ะดะปั query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Query`, ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั‚ะพะน ัะฐะผะธะน ั‚ะธะฟ ะฟะตั€ะตะฒั–ั€ะพะบ ั– ะผะตั‚ะฐะดะฐะฝะธั… ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัˆะปัั…ัƒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Path`. +ะขะฐะบ ัะฐะผะพ ัะบ ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะฟะตั€ะตะฒั–ั€ะบะธ ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ะดะปั query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Query`, ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั‚ะพะน ัะฐะผะธะน ั‚ะธะฟ ะฟะตั€ะตะฒั–ั€ะพะบ ั– ะผะตั‚ะฐะดะฐะฝะธั… ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัˆะปัั…ัƒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Path`. -## ะ†ะผะฟะพั€ั‚ Path +## ะ†ะผะฟะพั€ั‚ `Path` { #import-path } ะกะฟะพั‡ะฐั‚ะบัƒ ั–ะผะฟะพั€ั‚ัƒะนั‚ะต `Path` ะท `fastapi` ั– ั–ะผะฟะพั€ั‚ัƒะนั‚ะต `Annotated`: @@ -10,70 +10,69 @@ /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -FastAPI ะดะพะดะฐะฒ ะฟั–ะดั‚ั€ะธะผะบัƒ `Annotated` (ั– ะฟะพั‡ะฐะฒ ั€ะตะบะพะผะตะฝะดัƒะฒะฐั‚ะธ ะนะพะณะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั) ัƒ ะฒะตั€ัั–ั— 0.95.0. +FastAPI ะดะพะดะฐะฒ ะฟั–ะดั‚ั€ะธะผะบัƒ `Annotated` (ั– ะฟะพั‡ะฐะฒ ั€ะตะบะพะผะตะฝะดัƒะฒะฐั‚ะธ ะนะพะณะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั) ัƒ ะฒะตั€ัั–ั— 0.95.0. -ะฏะบั‰ะพ ัƒ ะ’ะฐั ัั‚ะฐั€ะฐ ะฒะตั€ัั–ั, ะฟั€ะธ ัะฟั€ะพะฑั– ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `Annotated` ะผะพะถัƒั‚ัŒ ะฒะธะฝะธะบะฐั‚ะธ ะฟะพะผะธะปะบะธ. +ะฏะบั‰ะพ ัƒ ะฒะฐั ัั‚ะฐั€ะฐ ะฒะตั€ัั–ั, ะฟั€ะธ ัะฟั€ะพะฑั– ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `Annotated` ะผะพะถัƒั‚ัŒ ะฒะธะฝะธะบะฐั‚ะธ ะฟะพะผะธะปะบะธ. -ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะ’ะธ [ะพะฝะพะฒะธะปะธ ะฒะตั€ัั–ัŽ FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} ะฟั€ะธะฝะฐะนะผะฝั– ะดะพ ะฒะตั€ัั–ั— 0.95.1 ะฟะตั€ะตะด ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ `Annotated`. +ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ [ะพะฝะพะฒะธะปะธ ะฒะตั€ัั–ัŽ FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} ะฟั€ะธะฝะฐะนะผะฝั– ะดะพ ะฒะตั€ัั–ั— 0.95.1 ะฟะตั€ะตะด ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ `Annotated`. /// -## ะžะณะพะปะพัˆะตะฝะฝั ะผะตั‚ะฐะดะฐะฝะธั… +## ะžะณะพะปะพัˆะตะฝะฝั ะผะตั‚ะฐะดะฐะฝะธั… { #declare-metadata } ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฒัั– ั‚ั– ะถ ะฟะฐั€ะฐะผะตั‚ั€ะธ, ั‰ะพ ั– ะดะปั `Query`. -ะะฐะฟั€ะธะบะปะฐะด, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะผะตั‚ะฐะดะฐะฝะธั… `title` ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ `item_id`, ะ’ะธ ะผะพะถะตั‚ะต ะฝะฐะฟะธัะฐั‚ะธ: +ะะฐะฟั€ะธะบะปะฐะด, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะผะตั‚ะฐะดะฐะฝะธั… `title` ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ `item_id`, ะฒะธ ะผะพะถะตั‚ะต ะฝะฐะฟะธัะฐั‚ะธ: {* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *} /// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะŸะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ ะทะฐะฒะถะดะธ ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะพัะบั–ะปัŒะบะธ ะฒั–ะฝ ะผะฐั” ะฑัƒั‚ะธ ั‡ะฐัั‚ะธะฝะพัŽ ัˆะปัั…ัƒ. ะะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะ’ะธ ะพะณะพะปะพัะธั‚ะต ะนะพะณะพ ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `None` ะฐะฑะพ ะฒัั‚ะฐะฝะพะฒะธั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ โ€” ะฒั–ะฝ ะฒัะต ะพะดะฝะพ ะทะฐะปะธัˆะฐั‚ะธะผะตั‚ัŒัั ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ. +ะŸะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ ะทะฐะฒะถะดะธ ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะพัะบั–ะปัŒะบะธ ะฒั–ะฝ ะผะฐั” ะฑัƒั‚ะธ ั‡ะฐัั‚ะธะฝะพัŽ ัˆะปัั…ัƒ. ะะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะธ ะพะณะพะปะพัะธั‚ะต ะนะพะณะพ ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `None` ะฐะฑะพ ะฒัั‚ะฐะฝะพะฒะธั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ โ€” ั†ะต ะฝั– ะฝะฐ ั‰ะพ ะฝะต ะฒะฟะปะธะฝะต, ะฒั–ะฝ ะฒัะต ะพะดะฝะพ ะทะฐะฒะถะดะธ ะฑัƒะดะต ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ. /// -## ะฃะฟะพั€ัะดะบะพะฒัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ, ัะบ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ +## ะฃะฟะพั€ัะดะบะพะฒัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ, ัะบ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ { #order-the-parameters-as-you-need } -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ -ะฆะต, ะผะฐะฑัƒั‚ัŒ, ะฝะต ะฝะฐัั‚ั–ะปัŒะบะธ ะฒะฐะถะปะธะฒะพ ะฐะฑะพ ะฝะตะพะฑั…ั–ะดะฝะพ, ัะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`. +ะฆะต, ะผะฐะฑัƒั‚ัŒ, ะฝะต ะฝะฐัั‚ั–ะปัŒะบะธ ะฒะฐะถะปะธะฒะพ ะฐะฑะพ ะฝะตะพะฑั…ั–ะดะฝะพ, ัะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`. /// -ะŸั€ะธะฟัƒัั‚ะธะผะพ, ะ’ะธ ั…ะพั‡ะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ `q` ัะบ ะพะฑะพะฒโ€™ัะทะบะพะฒะธะน `str`. +ะŸั€ะธะฟัƒัั‚ะธะผะพ, ะฒะธ ั…ะพั‡ะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ `q` ัะบ ะพะฑะพะฒโ€™ัะทะบะพะฒะธะน `str`. -ะ† ะ’ะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฝั–ั‡ะพะณะพ ั–ะฝัˆะพะณะพ ะดะปั ั†ัŒะพะณะพ ะฟะฐั€ะฐะผะตั‚ั€ะฐ, ั‚ะพะผัƒ ะฝะตะผะฐั” ะฟะพั‚ั€ะตะฑะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Query`. +ะ† ะฒะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฝั–ั‡ะพะณะพ ั–ะฝัˆะพะณะพ ะดะปั ั†ัŒะพะณะพ ะฟะฐั€ะฐะผะตั‚ั€ะฐ, ั‚ะพะผัƒ ะฒะฐะผ ะฝะฐัะฟั€ะฐะฒะดั– ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Query`. -ะะปะต ะ’ะฐะผ ะฒัะต ะพะดะฝะพ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Path` ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ `item_id`. ะ† ะท ะฟะตะฒะฝะธั… ะฟั€ะธั‡ะธะฝ ะ’ะธ ะฝะต ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Annotated`. +ะะปะต ะฒะฐะผ ะฒัะต ะพะดะฝะพ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Path` ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ `item_id`. ะ† ะท ะฟะตะฒะฝะธั… ะฟั€ะธั‡ะธะฝ ะฒะธ ะฝะต ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Annotated`. Python ะฒะธะดะฐัั‚ัŒ ะฟะพะผะธะปะบัƒ, ัะบั‰ะพ ั€ะพะทะผั–ัั‚ะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะท "default" ะฟะตั€ะตะด ะทะฝะฐั‡ะตะฝะฝัะผ, ัะบะต ะฝะต ะผะฐั” "default". -ะะปะต ะ’ะธ ะผะพะถะตั‚ะต ะทะผั–ะฝะธั‚ะธ ะฟะพั€ัะดะพะบ ั– ั€ะพะทะผั–ัั‚ะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะฑะตะท ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ (ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ `q`) ะฟะตั€ัˆะธะผ. +ะะปะต ะฒะธ ะผะพะถะตั‚ะต ะทะผั–ะฝะธั‚ะธ ะฟะพั€ัะดะพะบ ั– ั€ะพะทะผั–ัั‚ะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะฑะตะท ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ (ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ `q`) ะฟะตั€ัˆะธะผ. +ะ”ะปั **FastAPI** ะฟะพั€ัะดะพะบ ะฝะต ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั. ะ’ั–ะฝ ะฒะธะทะฝะฐั‡ะฐั” ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐ ั—ั…ะฝั–ะผะธ ั–ะผะตะฝะฐะผะธ, ั‚ะธะฟะฐะผะธ ั‚ะฐ ะพะณะพะปะพัˆะตะฝะฝัะผะธ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ (`Query`, `Path` ั‚ะพั‰ะพ) ั– ะฝะต ะทะฒะตั€ั‚ะฐั” ัƒะฒะฐะณะธ ะฝะฐ ะฟะพั€ัะดะพะบ. -ะ”ะปั **FastAPI** ะฟะพั€ัะดะพะบ ะฝะต ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั. ะ’ั–ะฝ ะฒะธะทะฝะฐั‡ะฐั” ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐ ั—ั… ั–ะผะตะฝะฐะผะธ, ั‚ะธะฟะฐะผะธ ั‚ะฐ ะทะฝะฐั‡ะตะฝะฝัะผะธ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ (`Query`, `Path` ั‚ะพั‰ะพ) ั– ะฝะต ะทะฒะตั€ั‚ะฐั” ัƒะฒะฐะณะธ ะฝะฐ ะฟะพั€ัะดะพะบ. +ะขะพะผัƒ ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฒะฐัˆัƒ ั„ัƒะฝะบั†ั–ัŽ ั‚ะฐะบ: -ะขะพะผัƒ ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะ’ะฐัˆัƒ ั„ัƒะฝะบั†ั–ัŽ ั‚ะฐะบ: +{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} -{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} - -ะะปะต ะผะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ัะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`, ั†ั ะฟั€ะพะฑะปะตะผะฐ ะฝะต ะฒะธะฝะธะบะฝะต, ะพัะบั–ะปัŒะบะธ ะ’ะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `Query()` ะฐะฑะพ `Path()`. +ะะปะต ะผะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ัะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`, ั†ั–ั”ั— ะฟั€ะพะฑะปะตะผะธ ะฝะต ะฑัƒะดะต, ั†ะต ะฝะต ะผะฐั‚ะธะผะต ะทะฝะฐั‡ะตะฝะฝั, ะพัะบั–ะปัŒะบะธ ะฒะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั„ัƒะฝะบั†ั–ั— ะดะปั `Query()` ะฐะฑะพ `Path()`. {* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} -## ะฃะฟะพั€ัะดะบะพะฒัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐ ะฟะพั‚ั€ะตะฑะพัŽ, ั…ะธั‚ั€ะพั‰ั– +## ะฃะฟะพั€ัะดะบะพะฒัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ, ัะบ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ: ั…ะธั‚ั€ะพั‰ั– { #order-the-parameters-as-you-need-tricks } -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ -ะฆะต, ะผะฐะฑัƒั‚ัŒ, ะฝะต ะฝะฐัั‚ั–ะปัŒะบะธ ะฒะฐะถะปะธะฒะพ ะฐะฑะพ ะฝะตะพะฑั…ั–ะดะฝะพ, ัะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`. +ะฆะต, ะผะฐะฑัƒั‚ัŒ, ะฝะต ะฝะฐัั‚ั–ะปัŒะบะธ ะฒะฐะถะปะธะฒะพ ะฐะฑะพ ะฝะตะพะฑั…ั–ะดะฝะพ, ัะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`. /// ะžััŒ **ะฝะตะฒะตะปะธะบะฐ ั…ะธั‚ั€ั–ัั‚ัŒ**, ัะบะฐ ะผะพะถะต ัั‚ะฐั‚ะธ ะฒ ะฟั€ะธะณะพะดั–, ั…ะพั‡ะฐ ะฒะพะฝะฐ ั€ั–ะดะบะพ ะทะฝะฐะดะพะฑะธั‚ัŒัั. -ะฏะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต: +ะฏะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต: -* ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ `q` ะฑะตะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `Query` ะฐะฑะพ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ +* ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ `q` ะฑะตะท `Query` ั– ะฑะตะท ะถะพะดะฝะพะณะพ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ * ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ `item_id`, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `Path` * ั€ะพะทะผั–ัั‚ะธั‚ะธ ั—ั… ัƒ ั€ั–ะทะฝะพะผัƒ ะฟะพั€ัะดะบัƒ * ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Annotated` @@ -84,72 +83,72 @@ Python ะฒะธะดะฐัั‚ัŒ ะฟะพะผะธะปะบัƒ, ัะบั‰ะพ ั€ะพะทะผั–ัั‚ะธั‚ะธ ะทะฝะฐั‡ะตะฝ Python ะฝั–ั‡ะพะณะพ ะฝะต ะทั€ะพะฑะธั‚ัŒ ั–ะท ั†ั–ั”ัŽ `*`, ะฐะปะต ั€ะพะทะฟั–ะทะฝะฐั”, ั‰ะพ ะฒัั– ะฝะฐัั‚ัƒะฟะฝั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ัะปั–ะด ะฒะธะบะปะธะบะฐั‚ะธ ัะบ ะฐั€ะณัƒะผะตะฝั‚ะธ ะทะฐ ะบะปัŽั‡ะพะฒะธะผ ัะปะพะฒะพะผ (ะฟะฐั€ะธ ะบะปัŽั‡-ะทะฝะฐั‡ะตะฝะฝั), ั‚ะฐะบะพะถ ะฒั–ะดะพะผั– ัะบ <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>. ะะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะพะฝะธ ะฝะต ะผะฐัŽั‚ัŒ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} -### ะšั€ะฐั‰ะต ะท `Annotated` +### ะšั€ะฐั‰ะต ะท `Annotated` { #better-with-annotated } -ะœะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ัะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`, ะพัะบั–ะปัŒะบะธ ะ’ะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั„ัƒะฝะบั†ั–ั—, ั†ั–ั”ั— ะฟั€ะพะฑะปะตะผะธ ะฝะต ะฒะธะฝะธะบะฝะต, ั–, ัˆะฒะธะดัˆะต ะทะฐ ะฒัะต, ะ’ะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `*`. +ะœะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ัะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`, ะพัะบั–ะปัŒะบะธ ะฒะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ั„ัƒะฝะบั†ั–ั—, ั†ั–ั”ั— ะฟั€ะพะฑะปะตะผะธ ะฝะต ะฑัƒะดะต, ั–, ะนะผะพะฒั–ั€ะฝะพ, ะฒะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `*`. {* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} -## ะ’ะฐะปั–ะดะฐั†ั–ั ั‡ะธัะปะพะฒะธั… ะดะฐะฝะธั…: ะฑั–ะปัŒัˆะต ะฐะฑะพ ะดะพั€ั–ะฒะฝัŽั” +## ะ’ะฐะปั–ะดะฐั†ั–ั ั‡ะธัะปะพะฒะธั… ะดะฐะฝะธั…: ะฑั–ะปัŒัˆะต ะฐะฑะพ ะดะพั€ั–ะฒะฝัŽั” { #number-validations-greater-than-or-equal } -ะ—ะฐ ะดะพะฟะพะผะพะณะพัŽ `Query` ั– `Path` (ั‚ะฐ ั–ะฝัˆะธั…, ัะบั– ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฟั–ะทะฝั–ัˆะต) ะผะพะถะฝะฐ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั‡ะธัะปะพะฒั– ะพะฑะผะตะถะตะฝะฝั. +ะ—ะฐ ะดะพะฟะพะผะพะณะพัŽ `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 (ะผะตะฝัˆะต ะฐะฑะพ ะดะพั€ั–ะฒะฝัŽั”) +* `gt`: `g`reater `t`han +* `le`: `l`ess than or `e`qual {* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} -## ะ’ะฐะปั–ะดะฐั†ั–ั ั‡ะธัะปะพะฒะธั… ะดะฐะฝะธั…: float, ะฑั–ะปัŒัˆะต ะฝั–ะถ ั– ะผะตะฝัˆะต ะฝั–ะถ +## ะ’ะฐะปั–ะดะฐั†ั–ั ั‡ะธัะปะพะฒะธั… ะดะฐะฝะธั…: float, ะฑั–ะปัŒัˆะต ะฝั–ะถ ั– ะผะตะฝัˆะต ะฝั–ะถ { #number-validations-floats-greater-than-and-less-than } ะ’ะฐะปั–ะดะฐั†ั–ั ั‡ะธัะตะป ั‚ะฐะบะพะถ ะฟั€ะฐั†ัŽั” ะดะปั ะทะฝะฐั‡ะตะฝัŒ ั‚ะธะฟัƒ `float`. -ะžััŒ ะดะต ัั‚ะฐั” ะฒะฐะถะปะธะฒะพ ะผะฐั‚ะธ ะผะพะถะปะธะฒั–ัั‚ัŒ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ <abbr title="greater than (ะฑั–ะปัŒัˆะต ะฝั–ะถ)"><code>gt</code></abbr>, ะฐ ะฝะต ั‚ั–ะปัŒะบะธ <abbr title="greater than or equal (ะฑั–ะปัŒัˆะต ะฐะฑะพ ะดะพั€ั–ะฒะฝัŽั”)"><code>ge</code></abbr>. ะฆะต ะดะพะทะฒะพะปัั”, ะฝะฐะฟั€ะธะบะปะฐะด, ะฒะธะผะฐะณะฐั‚ะธ, ั‰ะพะฑ ะทะฝะฐั‡ะตะฝะฝั ะฑัƒะปะพ ะฑั–ะปัŒัˆะต `0`, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะพะฝะพ ะผะตะฝัˆะต `1`. +ะžััŒ ะดะต ัั‚ะฐั” ะฒะฐะถะปะธะฒะพ ะผะฐั‚ะธ ะผะพะถะปะธะฒั–ัั‚ัŒ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ <abbr title="greater than"><code>gt</code></abbr>, ะฐ ะฝะต ั‚ั–ะปัŒะบะธ <abbr title="greater than or equal"><code>ge</code></abbr>. ะฆะต ะดะพะทะฒะพะปัั”, ะฝะฐะฟั€ะธะบะปะฐะด, ะฒะธะผะฐะณะฐั‚ะธ, ั‰ะพะฑ ะทะฝะฐั‡ะตะฝะฝั ะฑัƒะปะพ ะฑั–ะปัŒัˆะต `0`, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะพะฝะพ ะผะตะฝัˆะต `1`. ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ะทะฝะฐั‡ะตะฝะฝั `0.5` ะฑัƒะดะต ะดะพะฟัƒัั‚ะธะผะธะผ. ะะปะต `0.0` ะฐะฑะพ `0` โ€” ะฝั–. -ะขะต ัะฐะผะต ัั‚ะพััƒั”ั‚ัŒัั <abbr title="less than (ะผะตะฝัˆะต ะฝั–ะถ)"><code>lt</code></abbr>. +ะขะต ัะฐะผะต ัั‚ะพััƒั”ั‚ัŒัั <abbr title="less than"><code>lt</code></abbr>. {* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ—ะฐ ะดะพะฟะพะผะพะณะพัŽ `Query`, `Path` (ั– ั–ะฝัˆะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ัะบั– ะ’ะธ ั‰ะต ะฝะต ะฑะฐั‡ะธะปะธ) ะผะพะถะฝะฐ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะผะตั‚ะฐะดะฐะฝั– ั‚ะฐ ะฟะตั€ะตะฒั–ั€ะบะธ ั€ัะดะบั–ะฒ, ั‚ะฐะบ ัะฐะผะพ ัะบ ัƒ [Query ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะฐ ะฒะฐะปั–ะดะฐั†ั–ั ั€ัะดะบั–ะฒ](query-params-str-validations.md){.internal-link target=_blank}. +ะ—ะฐ ะดะพะฟะพะผะพะณะพัŽ `Query`, `Path` (ั– ั–ะฝัˆะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ัะบั– ะฒะธ ั‰ะต ะฝะต ะฑะฐั‡ะธะปะธ) ะผะพะถะฝะฐ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะผะตั‚ะฐะดะฐะฝั– ั‚ะฐ ะฟะตั€ะตะฒั–ั€ะบะธ ั€ัะดะบั–ะฒ ั‚ะฐะบ ัะฐะผะพ ัะบ ัƒ [Query ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะฐ ะฒะฐะปั–ะดะฐั†ั–ั ั€ัะดะบั–ะฒ](query-params-str-validations.md){.internal-link target=_blank}. ะขะฐะบะพะถ ะผะพะถะฝะฐ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั‡ะธัะปะพะฒั– ะฟะตั€ะตะฒั–ั€ะบะธ: -* `gt`: `g`reater `t`han (ะฑั–ะปัŒัˆะต ะฝั–ะถ) -* `ge`: `g`reater than or `e`qual (ะฑั–ะปัŒัˆะต ะฐะฑะพ ะดะพั€ั–ะฒะฝัŽั”) -* `lt`: `l`ess `t`han (ะผะตะฝัˆะต ะฝั–ะถ) -* `le`: `l`ess than or `e`qual (ะผะตะฝัˆะต ะฐะฑะพ ะดะพั€ั–ะฒะฝัŽั”) +* `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`. +`Query`, `Path` ั‚ะฐ ั–ะฝัˆั– ะบะปะฐัะธ, ัะบั– ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฟั–ะทะฝั–ัˆะต, ั” ะฟั–ะดะบะปะฐัะฐะผะธ ัะฟั–ะปัŒะฝะพะณะพ ะบะปะฐััƒ `Param`. -ะ’ัั– ะฒะพะฝะธ ะผะฐัŽั‚ัŒ ะพะดะฝะฐะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะดะปั ะดะพะดะฐั‚ะบะพะฒะธั… ะฟะตั€ะตะฒั–ั€ะพะบ ั– ะผะตั‚ะฐะดะฐะฝะธั…, ัะบั– ะ’ะธ ะฒะถะต ะฑะฐั‡ะธะปะธ. +ะ’ัั– ะฒะพะฝะธ ะผะฐัŽั‚ัŒ ะพะดะฝะฐะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะดะปั ะดะพะดะฐั‚ะบะพะฒะธั… ะฟะตั€ะตะฒั–ั€ะพะบ ั– ะผะตั‚ะฐะดะฐะฝะธั…, ัะบั– ะฒะธ ะฒะถะต ะฑะฐั‡ะธะปะธ. /// /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– -ะšะพะปะธ ะ’ะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต `Query`, `Path` ั‚ะฐ ั–ะฝัˆั– ะท `fastapi`, ะฝะฐัะฟั€ะฐะฒะดั– ั†ะต ั„ัƒะฝะบั†ั–ั—. +ะšะพะปะธ ะฒะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต `Query`, `Path` ั‚ะฐ ั–ะฝัˆั– ะท `fastapi`, ะฝะฐัะฟั€ะฐะฒะดั– ั†ะต ั„ัƒะฝะบั†ั–ั—. ะŸั€ะธ ะฒะธะบะปะธะบัƒ ะฒะพะฝะธ ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒ ะตะบะทะตะผะฟะปัั€ะธ ะบะปะฐัั–ะฒ ะท ั‚ะฐะบะธะผะธ ะถ ั–ะผะตะฝะฐะผะธ. -ะขะพะฑั‚ะพ ะ’ะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต `Query`, ัะบะฐ ั” ั„ัƒะฝะบั†ั–ั”ัŽ. ะ ะบะพะปะธ ะ’ะธ ั—ั— ะฒะธะบะปะธะบะฐั”ั‚ะต, ะฒะพะฝะฐ ะฟะพะฒะตั€ั‚ะฐั” ะตะบะทะตะผะฟะปัั€ ะบะปะฐััƒ, ัะบะธะน ั‚ะตะถ ะฝะฐะทะธะฒะฐั”ั‚ัŒัั `Query`. +ะขะพะฑั‚ะพ ะฒะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต `Query`, ัะบะฐ ั” ั„ัƒะฝะบั†ั–ั”ัŽ. ะ ะบะพะปะธ ะฒะธ ั—ั— ะฒะธะบะปะธะบะฐั”ั‚ะต, ะฒะพะฝะฐ ะฟะพะฒะตั€ั‚ะฐั” ะตะบะทะตะผะฟะปัั€ ะบะปะฐััƒ, ัะบะธะน ั‚ะตะถ ะฝะฐะทะธะฒะฐั”ั‚ัŒัั `Query`. -ะฆั– ั„ัƒะฝะบั†ั–ั— ัั‚ะฒะพั€ะตะฝั– ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ (ะทะฐะผั–ัั‚ัŒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะบะปะฐัั–ะฒ ะฝะฐะฟั€ัะผัƒ), ั‰ะพะฑ ะ’ะฐัˆ ั€ะตะดะฐะบั‚ะพั€ ะฝะต ะฒั–ะดะทะฝะฐั‡ะฐะฒ ั—ั…ะฝั– ั‚ะธะฟะธ ัะบ ะฟะพะผะธะปะบะธ. +ะฆั– ั„ัƒะฝะบั†ั–ั— ัั‚ะฒะพั€ะตะฝั– ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ (ะทะฐะผั–ัั‚ัŒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะบะปะฐัั–ะฒ ะฝะฐะฟั€ัะผัƒ), ั‰ะพะฑ ะฒะฐัˆ ั€ะตะดะฐะบั‚ะพั€ ะฝะต ะฒั–ะดะทะฝะฐั‡ะฐะฒ ั—ั…ะฝั– ั‚ะธะฟะธ ัะบ ะฟะพะผะธะปะบะธ. -ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ะ’ะธ ะผะพะถะตั‚ะต ะบะพั€ะธัั‚ัƒะฒะฐั‚ะธัั ัะฒะพั—ะผ ะทะฒะธั‡ะฐะนะฝะธะผ ั€ะตะดะฐะบั‚ะพั€ะพะผ ั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ ะดะปั ะฟั€ะพะณั€ะฐะผัƒะฒะฐะฝะฝั ะฑะตะท ะดะพะดะฐั‚ะบะพะฒะธั… ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ ะดะปั ั–ะณะฝะพั€ัƒะฒะฐะฝะฝั ั‚ะฐะบะธั… ะฟะพะผะธะปะพะบ. +ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ะฒะธ ะผะพะถะตั‚ะต ะบะพั€ะธัั‚ัƒะฒะฐั‚ะธัั ัะฒะพั—ะผ ะทะฒะธั‡ะฐะนะฝะธะผ ั€ะตะดะฐะบั‚ะพั€ะพะผ ั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ ะดะปั ะฟั€ะพะณั€ะฐะผัƒะฒะฐะฝะฝั ะฑะตะท ะดะพะดะฐั‚ะบะพะฒะธั… ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝัŒ ะดะปั ั–ะณะฝะพั€ัƒะฒะฐะฝะฝั ั‚ะฐะบะธั… ะฟะพะผะธะปะพะบ. /// diff --git a/docs/uk/docs/tutorial/path-params.md b/docs/uk/docs/tutorial/path-params.md index da4ff2f9ea..0598905495 100644 --- a/docs/uk/docs/tutorial/path-params.md +++ b/docs/uk/docs/tutorial/path-params.md @@ -1,34 +1,34 @@ -# Path ะŸะฐั€ะฐะผะตั‚ั€ะธ +# ะŸะฐั€ะฐะผะตั‚ั€ะธ ัˆะปัั…ัƒ { #path-parameters } -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ "ะฟะฐั€ะฐะผะตั‚ั€ะธ" ะฐะฑะพ "ะทะผั–ะฝะฝั–" ัˆะปัั…ัƒ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัะธะฝั‚ะฐะบัะธั ั„ะพั€ะผะฐั‚ะพะฒะฐะฝะธั… ั€ัะดะบั–ะฒ: +ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ยซะฟะฐั€ะฐะผะตั‚ั€ะธยป ะฐะฑะพ ยซะทะผั–ะฝะฝั–ยป ัˆะปัั…ัƒ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั‚ะพะน ัะฐะผะธะน ัะธะฝั‚ะฐะบัะธั, ั‰ะพ ะน ัƒ ั„ะพั€ะผะฐั‚ะพะฒะฐะฝะธั… ั€ัะดะบะฐั… 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`. -ะฏะบั‰ะพ ะทะฐะฟัƒัั‚ะธั‚ะธ ั†ะตะน ะฟั€ะธะบะปะฐะด ั‚ะฐ ะฟะตั€ะตะนั‚ะธ ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, ั‚ะพ ะพั‚ั€ะธะผะฐั”ะผะพ ั‚ะฐะบัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ: +ะžั‚ะถะต, ัะบั‰ะพ ะฒะธ ะทะฐะฟัƒัั‚ะธั‚ะต ั†ะตะน ะฟั€ะธะบะปะฐะด ั– ะฟะตั€ะตะนะดะตั‚ะต ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, ั‚ะพ ะฟะพะฑะฐั‡ะธั‚ะต ะฒั–ะดะฟะพะฒั–ะดัŒ: ```JSON {"item_id":"foo"} ``` -## Path ะฟะฐั€ะฐะผะตั‚ั€ะธ ะท ั‚ะธะฟะฐะผะธ +## ะŸะฐั€ะฐะผะตั‚ั€ะธ ัˆะปัั…ัƒ ะท ั‚ะธะฟะฐะผะธ { #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`. +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ `item_id` ะพะณะพะปะพัˆะตะฝะพ ัะบ `int`. /// check | ะŸั€ะธะผั–ั‚ะบะฐ -ะฆะต ะดะฐัั‚ัŒ ะผะพะถะปะธะฒั–ัั‚ัŒ ะฟั–ะดั‚ั€ะธะผะบะธ ั€ะตะดะฐะบั‚ะพั€ะฐ ะฒัะตั€ะตะดะธะฝั– ั„ัƒะฝะบั†ั–ั— ะท ะฟะตั€ะตะฒั–ั€ะบะฐะผะธ ะฟะพะผะธะปะพะบ, ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั ั‚ะพั‰ะพ. +ะฆะต ะดะฐัั‚ัŒ ะฒะฐะผ ะฟั–ะดั‚ั€ะธะผะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ ะฒัะตั€ะตะดะธะฝั– ั„ัƒะฝะบั†ั–ั— ะท ะฟะตั€ะตะฒั–ั€ะบะฐะผะธ ะฟะพะผะธะปะพะบ, ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝัะผ ั‚ะพั‰ะพ. /// -## <abbr title="ะฐะฑะพ: ัะตั€ั–ะฐะปั–ะทะฐั†ั–ั, ะฟะฐั€ัะธะฝะณ, ะผะฐั€ัˆะฐะปั–ะทะฐั†ั–ั">ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั</abbr> ะดะฐะฝะธั… +## <abbr title="also known as: serialization, parsing, marshalling โ€“ ั‚ะฐะบะพะถ ะฒั–ะดะพะผะพ ัะบ: ัะตั€ั–ะฐะปั–ะทะฐั†ั–ั, ะฟะฐั€ัะธะฝะณ, ะผะฐั€ัˆะฐะปั–ะทะฐั†ั–ั">ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั</abbr> ะดะฐะฝะธั… { #data-conversion } -ะฏะบั‰ะพ ะทะฐะฟัƒัั‚ะธั‚ะธ ั†ะตะน ะฟั€ะธะบะปะฐะด ั– ะฟะตั€ะตะนั‚ะธ ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, ั‚ะพ ะพั‚ั€ะธะผะฐั”ั‚ะต ั‚ะฐะบัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ: +ะฏะบั‰ะพ ะฒะธ ะทะฐะฟัƒัั‚ะธั‚ะต ั†ะตะน ะฟั€ะธะบะปะฐะด ั– ะฒั–ะดะบั€ะธั”ั‚ะต ัƒ ะฑั€ะฐัƒะทะตั€ั– <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, ั‚ะพ ะฟะพะฑะฐั‡ะธั‚ะต ะฒั–ะดะฟะพะฒั–ะดัŒ: ```JSON {"item_id":3} @@ -36,15 +36,15 @@ /// check | ะŸั€ะธะผั–ั‚ะบะฐ -ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั, ัะบะต ะพั‚ั€ะธะผะฐะปะฐ (ั– ะฟะพะฒะตั€ะฝัƒะปะฐ) ะฒะฐัˆะฐ ั„ัƒะฝะบั†ั–ั, โ€” ั†ะต `3`. ะฆะต Python `int`, ะฐ ะฝะต ั€ัะดะพะบ `"3"`. +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั, ัะบะต ะพั‚ั€ะธะผะฐะปะฐ (ั– ะฟะพะฒะตั€ะฝัƒะปะฐ) ะฒะฐัˆะฐ ั„ัƒะฝะบั†ั–ั, โ€” ั†ะต `3`, ัะบ Python `int`, ะฐ ะฝะต ั€ัะดะพะบ `"3"`. -ะžั‚ะถะต, ะท ั‚ะฐะบะธะผ ะพะณะพะปะพัˆะตะฝะฝัะผ ั‚ะธะฟัƒ **FastAPI** ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธะบะพะฝัƒั” <abbr title="ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั€ัะดะบะฐ, ั‰ะพ ะฝะฐะดั…ะพะดะธั‚ัŒ ั–ะท HTTP-ะทะฐะฟะธั‚ัƒ, ัƒ ั‚ะธะฟะธ ะดะฐะฝะธั… Python">"ะฟะฐั€ัะธะฝะณ"</abbr> ะทะฐะฟะธั‚ั–ะฒ. +ะžั‚ะถะต, ะท ั‚ะฐะบะธะผ ะพะณะพะปะพัˆะตะฝะฝัะผ ั‚ะธะฟัƒ **FastAPI** ะฝะฐะดะฐั” ะฒะฐะผ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะธะน <abbr title="converting the string that comes from an HTTP request into Python data โ€“ ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั€ัะดะบะฐ, ั‰ะพ ะฝะฐะดั…ะพะดะธั‚ัŒ ั–ะท HTTP-ะทะฐะฟะธั‚ัƒ, ัƒ ะดะฐะฝั– Python">ยซparsingยป</abbr> ะทะฐะฟะธั‚ัƒ. /// -## <abbr title="ะะฑะพ ะฒะฐะปั–ะดะฐั†ั–ั">ะŸะตั€ะตะฒั–ั€ะบะฐ</abbr> ะดะฐะฝะธั… +## ะ’ะฐะปั–ะดะฐั†ั–ั ะดะฐะฝะธั… { #data-validation } -ะฏะบั‰ะพ ะถ ะฒั–ะดะบั€ะธั‚ะธ ัƒ ะฑั€ะฐัƒะทะตั€ั– ะฟะพัะธะปะฐะฝะฝั <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, ั‚ะพ ะฟะพะฑะฐั‡ะธะผะพ ั†ั–ะบะฐะฒัƒ HTTP-ะฟะพะผะธะปะบัƒ: +ะะปะต ัะบั‰ะพ ะฒะธ ะฟะตั€ะตะนะดะตั‚ะต ัƒ ะฑั€ะฐัƒะทะตั€ั– ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะณะฐั€ะฝัƒ HTTP-ะฟะพะผะธะปะบัƒ: ```JSON { @@ -61,144 +61,136 @@ ] } ``` -ั‚ะพะผัƒ ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั `"foo"`, ัะบะต ะฝะต ั” ั‚ะธะฟะพะผ `int`. -ะขะฐะบัƒ ัะฐะผัƒ ะฟะพะผะธะปะบัƒ ะพั‚ั€ะธะผะฐั”ะผะพ, ัะบั‰ะพ ะฟะตั€ะตะดะฐั‚ะธ `float` ะทะฐะผั–ัั‚ัŒ `int`, ัะบ ะฑะฐั‡ะธะผะพ, ัƒ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั–: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a> +ั‚ะพะผัƒ ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ `item_id` ะผะฐะฒ ะทะฝะฐั‡ะตะฝะฝั `"foo"`, ัะบะต ะฝะต ั” `int`. + +ะขะฐ ัะฐะผะฐ ะฟะพะผะธะปะบะฐ ะทโ€™ัะฒะธั‚ัŒัั, ัะบั‰ะพ ะฒะธ ะฟะตั€ะตะดะฐัั‚ะต `float` ะทะฐะผั–ัั‚ัŒ `int`, ัะบ ัƒ: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a> /// check | ะŸั€ะธะผั–ั‚ะบะฐ -ะžั‚ะถะต, **FastAPI** ะฝะฐะดะฐั” ะฟะตั€ะตะฒั–ั€ะบัƒ ั‚ะธะฟั–ะฒ ะท ั‚ะฐะบะธะผ ัะฐะผะธะผ ะพะณะพะปะพัˆะตะฝะฝัะผ ั‚ะธะฟัƒ ะฒ Python. +ะžั‚ะถะต, ะท ั‚ะธะผ ัะฐะผะธะผ ะพะณะพะปะพัˆะตะฝะฝัะผ ั‚ะธะฟัƒ ะฒ Python **FastAPI** ะฝะฐะดะฐั” ะฒะฐะผ ะฒะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั…. -ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะฟะพะผะธะปะบะฐ ั‚ะฐะบะพะถ ั‡ั–ั‚ะบะพ ะฒะบะฐะทัƒั” ัะฐะผะต ะฝะฐ ั‚ะต ะผั–ัั†ะต, ะดะต ะฒะฐะปั–ะดะฐั†ั–ั ะฝะต ะฟั€ะพะนัˆะปะฐ. +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะฟะพะผะธะปะบะฐ ั‚ะฐะบะพะถ ั‡ั–ั‚ะบะพ ะฒะบะฐะทัƒั” ัะฐะผะต ะผั–ัั†ะต, ะดะต ะฒะฐะปั–ะดะฐั†ั–ั ะฝะต ะฟั€ะพะนัˆะปะฐ. -ะฆะต ะฝะตะนะผะพะฒั–ั€ะฝะพ ะบะพั€ะธัะฝะพ ะฟั–ะด ั‡ะฐั ั€ะพะทั€ะพะฑะบะธ ั‚ะฐ ะดะตะฑะฐะณั–ะฝะณัƒ ะบะพะดัƒ, ั‰ะพ ะฒะทะฐั”ะผะพะดั–ั” ะท ะฒะฐัˆะธะผ API. +ะฆะต ะฝะตะนะผะพะฒั–ั€ะฝะพ ะบะพั€ะธัะฝะพ ะฟั–ะด ั‡ะฐั ั€ะพะทั€ะพะฑะบะธ ั‚ะฐ ะฝะฐะปะฐะณะพะดะถะตะฝะฝั ะบะพะดัƒ, ั‰ะพ ะฒะทะฐั”ะผะพะดั–ั” ะท ะฒะฐัˆะธะผ API. /// -## ะ”ะพะบัƒะผะตะฝั‚ะฐั†ั–ั +## ะ”ะพะบัƒะผะตะฝั‚ะฐั†ั–ั { #documentation } -ะขะตะฟะตั€ ะบะพะปะธ ะฒั–ะดะบั€ะธั”ั‚ะต ัะฒั–ะน ะฑั€ะฐัƒะทะตั€ ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, ั‚ะพ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะณะตะฝะตั€ะพะฒะฐะฝัƒ, ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ API-ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ: +ะ ะบะพะปะธ ะฒะธ ะฒั–ะดะบั€ะธั”ั‚ะต ัƒ ะฑั€ะฐัƒะทะตั€ั– <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ, ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ, API-ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะฝะฐ ะบัˆั‚ะฐะปั‚: <img src="/img/tutorial/path-params/image01.png"> /// check | ะŸั€ะธะผั–ั‚ะบะฐ -ะ—ะฝะพะฒัƒ ะถ ั‚ะฐะบะธ, ะปะธัˆะต ะท ั†ะธะผ ัะฐะผะธะผ ะพะณะพะปะพัˆะตะฝะฝัะผ ั‚ะธะฟัƒ ะฒ Python, FastAPI ะฝะฐะดะฐั” ะฒะฐะผ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ, ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ะท ั–ะฝั‚ะตะณั€ะฐั†ั–ั”ัŽ Swagger UI). - -ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ ะพะณะพะปะพัˆะตะฝะธะน ัะบ ั†ั–ะปะต ั‡ะธัะปะพ. +ะ—ะฝะพะฒัƒ ะถ ั‚ะฐะบะธ, ะปะธัˆะต ะท ั‚ะธะผ ัะฐะผะธะผ ะพะณะพะปะพัˆะตะฝะฝัะผ ั‚ะธะฟัƒ ะฒ Python **FastAPI** ะฝะฐะดะฐั” ะฒะฐะผ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ, ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ะท ั–ะฝั‚ะตะณั€ะฐั†ั–ั”ัŽ Swagger UI). +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ ะพะณะพะปะพัˆะตะฝะพ ัะบ ั†ั–ะปะต ั‡ะธัะปะพ. /// -## ะŸะตั€ะตะฒะฐะณะธ ัั‚ะฐะฝะดะฐั€ั‚ะธะทะฐั†ั–ั—, ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั +## ะŸะตั€ะตะฒะฐะณะธ ัั‚ะฐะฝะดะฐั€ั‚ั–ะฒ, ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั { #standards-based-benefits-alternative-documentation } ะ† ะพัะบั–ะปัŒะบะธ ะทะณะตะฝะตั€ะพะฒะฐะฝะฐ ัั…ะตะผะฐ ะฒั–ะดะฟะพะฒั–ะดะฐั” ัั‚ะฐะฝะดะฐั€ั‚ัƒ <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" class="external-link" target="_blank">OpenAPI</a>, ั–ัะฝัƒั” ะฑะฐะณะฐั‚ะพ ััƒะผั–ัะฝะธั… ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ. -ะ— ั†ั–ั”ั— ะฟั€ะธั‡ะธะฝะธ FastAPI ั‚ะฐะบะพะถ ะฝะฐะดะฐั” ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ API (ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ReDoc), ะดะพ ัะบะพั— ะผะพะถะฝะฐ ะพั‚ั€ะธะผะฐั‚ะธ ะดะพัั‚ัƒะฟ ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>: +ะงะตั€ะตะท ั†ะต **FastAPI** ั‚ะฐะบะพะถ ะฝะฐะดะฐั” ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝัƒ API-ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ (ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ReDoc), ะดะพ ัะบะพั— ะฒะธ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ะดะพัั‚ัƒะฟ ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>: <img src="/img/tutorial/path-params/image02.png"> -ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ั–ัะฝัƒั” ะฑะฐะณะฐั‚ะพ ััƒะผั–ัะฝะธั… ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ะฒะบะปัŽั‡ะฐัŽั‡ะธ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะดะปั ะณะตะฝะตั€ะฐั†ั–ั— ะบะพะดัƒ ะดะปั ะฑะฐะณะฐั‚ัŒะพั… ะผะพะฒ. +ะขะฐะบ ัะฐะผะพ, ั–ัะฝัƒั” ะฑะฐะณะฐั‚ะพ ััƒะผั–ัะฝะธั… ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ. ะ—ะพะบั€ะตะผะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะณะตะฝะตั€ะฐั†ั–ั— ะบะพะดัƒ ะดะปั ะฑะฐะณะฐั‚ัŒะพั… ะผะพะฒ. +## Pydantic { #pydantic } -## Pydantic +ะฃัั ะฒะฐะปั–ะดะฐั†ั–ั ะดะฐะฝะธั… ะฒะธะบะพะฝัƒั”ั‚ัŒัั ะทะฐ ะปะฐัˆั‚ัƒะฝะบะฐะผะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, ั‚ะพะถ ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะฒัั– ะฟะตั€ะตะฒะฐะณะธ ะฒั–ะด ะนะพะณะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั. ะ† ะฒะธ ะทะฝะฐั”ั‚ะต, ั‰ะพ ะฒะธ ะฒ ะฝะฐะดั–ะนะฝะธั… ั€ัƒะบะฐั…. -ะ’ัั ะฒะฐะปั–ะดะฐั†ั–ั ะดะฐะฝะธั… ะฒะธะบะพะฝัƒั”ั‚ัŒัั ะทะฐ ะปะฐัˆั‚ัƒะฝะบะฐะผะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, ั‚ะพะผัƒ ะ’ะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะฒัั– ะฟะตั€ะตะฒะฐะณะธ ะฒั–ะด ะนะพะณะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั. ะ† ะผะพะถะตั‚ะต ะฑัƒั‚ะธ ะฒะฟะตะฒะฝะตะฝั–, ั‰ะพ ะฒัะต ะฒ ะฝะฐะดั–ะนะฝะธั… ั€ัƒะบะฐั…. +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ั– ัะฐะผั– ะพะณะพะปะพัˆะตะฝะฝั ั‚ะธะฟั–ะฒ ะท `str`, `float`, `bool` ั‚ะฐ ะฑะฐะณะฐั‚ัŒะผะฐ ั–ะฝัˆะธะผะธ ัะบะปะฐะดะฝะธะผะธ ั‚ะธะฟะฐะผะธ ะดะฐะฝะธั…. -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ั– ัะฐะผั– ะพะณะพะปะพัˆะตะฝะฝั ั‚ะธะฟั–ะฒ ะท `str`, `float`, `bool` ั‚ะฐ ะฑะฐะณะฐั‚ัŒะผะฐ ั–ะฝัˆะธะผะธ ัะบะปะฐะดะฝะธะผะธ ั‚ะธะฟะฐะผะธ ะดะฐะฝะธั…. +ะ”ะตะบั–ะปัŒะบะฐ ะท ะฝะธั… ั€ะพะทะณะปัะดะฐัŽั‚ัŒัั ะฒ ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปะฐั… ะฟะพัั–ะฑะฝะธะบะฐ. -ะ”ะตะบั–ะปัŒะบะฐ ะท ะฝะธั… ะฑัƒะดัƒั‚ัŒ ั€ะพะทะณะปัะฝัƒั‚ั– ะฒ ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปะฐั… ะฟะพัั–ะฑะฝะธะบะฐ. +## ะŸะพั€ัะดะพะบ ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั { #order-matters } -## ะŸะพั€ัะดะพะบ ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั +ะŸั–ะด ั‡ะฐั ัั‚ะฒะพั€ะตะฝะฝั *ะพะฟะตั€ะฐั†ั–ะน ัˆะปัั…ัƒ* ะผะพะถัƒั‚ัŒ ะฒะธะฝะธะบะฐั‚ะธ ัะธั‚ัƒะฐั†ั–ั—, ะบะพะปะธ ัƒ ะฒะฐั ั” ั„ั–ะบัะพะฒะฐะฝะธะน ัˆะปัั…. -ะŸั€ะธ ัั‚ะฒะพั€ะตะฝะฝั– *ะพะฟะตั€ะฐั†ั–ะน ัˆะปัั…ัƒ* ะผะพะถัƒั‚ัŒ ะฒะธะฝะธะบะฐั‚ะธ ัะธั‚ัƒะฐั†ั–ั—, ะบะพะปะธ ัˆะปัั… ั„ั–ะบัะพะฒะฐะฝะธะน. +ะะฐะฟั€ะธะบะปะฐะด, `/users/me` โ€” ะฟั€ะธะฟัƒัั‚ั–ะผะพ, ั†ะต ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะฐะฝะธั… ะฟั€ะพ ะฟะพั‚ะพั‡ะฝะพะณะพ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ. -ะะฐะฟั€ะธะบะปะฐะด, `/users/me`. ะŸั€ะธะฟัƒัั‚ะธะผะพ, ั‰ะพ ั†ะต ัˆะปัั… ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะฐะฝะธั… ะฟั€ะพ ะฟะพั‚ะพั‡ะฝะพะณะพ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ. +ะ† ั‚ะพะดั– ัƒ ะฒะฐั ั‚ะฐะบะพะถ ะผะพะถะต ะฑัƒั‚ะธ ัˆะปัั… `/users/{user_id}` ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะฐะฝะธั… ะฟั€ะพ ะบะพะฝะบั€ะตั‚ะฝะพะณะพ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะทะฐ ะนะพะณะพ ID. -ะ ั‚ะฐะบะพะถ ัƒ ะฒะฐั ะผะพะถะต ะฑัƒั‚ะธ ัˆะปัั… `/users/{user_id}`, ั‰ะพะฑ ะพั‚ั€ะธะผะฐั‚ะธ ะดะฐะฝั– ะฟั€ะพ ะบะพะฝะบั€ะตั‚ะฝะพะณะพ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะทะฐ ะนะพะณะพ ID. +ะžัะบั–ะปัŒะบะธ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะพั†ั–ะฝัŽัŽั‚ัŒัั ะฟะพ ั‡ะตั€ะทั–, ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตะบะพะฝะฐั‚ะธัั, ั‰ะพ ัˆะปัั… ะดะปั `/users/me` ะพะณะพะปะพัˆะตะฝะพ ะฟะตั€ะตะด ัˆะปัั…ะพะผ ะดะปั `/users/{user_id}`: -ะžัะบั–ะปัŒะบะธ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะพั†ั–ะฝัŽัŽั‚ัŒัั ะฟะพ ั‡ะตั€ะทั–, ะ’ะธ ะฟะพะฒะธะฝะฝั– ะฟะตั€ะตะบะพะฝะฐั‚ะธัั, ั‰ะพ ัˆะปัั… ะดะปั `/users/me` ะพะณะพะปะพัˆะตะฝะธะน ะฟะตั€ะตะด ัˆะปัั…ะพะผ ะดะปั `/users/{user_id}`: +{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} -{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} +ะ†ะฝะฐะบัˆะต ัˆะปัั… ะดะปั `/users/{user_id}` ั‚ะฐะบะพะถ ะฒั–ะดะฟะพะฒั–ะดะฐั‚ะธะผะต `/users/me`, ยซะฒะฒะฐะถะฐัŽั‡ะธยป, ั‰ะพ ะพั‚ั€ะธะผัƒั” ะฟะฐั€ะฐะผะตั‚ั€ `user_id` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `"me"`. -ะ†ะฝะฐะบัˆะต ัˆะปัั… ะดะปั `/users/{user_id}` ั‚ะฐะบะพะถ ะฑัƒะดะต ะฒั–ะดะฟะพะฒั–ะดะฐั‚ะธ ะดะปั `/users/me`, "ะฒะฒะฐะถะฐัŽั‡ะธ", ั‰ะพ ะฒั–ะฝ ะพั‚ั€ะธะผัƒั” ะฟะฐั€ะฐะผะตั‚ั€ `user_id` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `"me"`. +ะขะฐะบ ัะฐะผะพ ะฒะธ ะฝะต ะผะพะถะตั‚ะต ะฟะตั€ะตะฒะธะทะฝะฐั‡ะธั‚ะธ ะพะฟะตั€ะฐั†ั–ัŽ ัˆะปัั…ัƒ: -ะะฝะฐะปะพะณั–ั‡ะฝะพ, ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะพะฟะตั€ะฐั†ั–ัŽ ัˆะปัั…ัƒ: +{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} -{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *} +ะ—ะฐะฒะถะดะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะตั‚ัŒัั ะฟะตั€ัˆะฐ, ะพัะบั–ะปัŒะบะธ ัˆะปัั… ะทะฑั–ะณะฐั”ั‚ัŒัั ะฟะตั€ัˆะธะผ. -ะŸะตั€ัˆะฐ ะพะฟะตั€ะฐั†ั–ั ะฑัƒะดะต ะทะฐะฒะถะดะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธัั, ะพัะบั–ะปัŒะบะธ ัˆะปัั… ะทะฑั–ะณะฐั”ั‚ัŒัั ะฟะตั€ัˆะธะผ. -## ะŸะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝั– ะทะฝะฐั‡ะตะฝะฝั +## ะŸะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝั– ะทะฝะฐั‡ะตะฝะฝั { #predefined-values } -ะฏะบั‰ะพ ัƒ ะฒะฐั ั” *ะพะฟะตั€ะฐั†ั–ั ัˆะปัั…ัƒ*, ัะบะฐ ะฟั€ะธะนะผะฐั” *ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ*, ะฐะปะต ะ’ะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะผะพะถะปะธะฒั– ะดะพะฟัƒัั‚ะธะผั– ะทะฝะฐั‡ะตะฝะฝั *ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ* ะฑัƒะปะธ ะฟะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝั–, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน Python <abbr title="ะฟะตั€ะตะปั–ั‡ะตะฝะฝั">Enum</abbr>. +ะฏะบั‰ะพ ัƒ ะฒะฐั ั” *ะพะฟะตั€ะฐั†ั–ั ัˆะปัั…ัƒ*, ัะบะฐ ะพั‚ั€ะธะผัƒั” *ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ*, ะฐะปะต ะฒะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะผะพะถะปะธะฒั– ะบะพั€ะตะบั‚ะฝั– ะทะฝะฐั‡ะตะฝะฝั *ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ* ะฑัƒะปะธ ะฟะพะฟะตั€ะตะดะฝัŒะพ ะฒะธะทะฝะฐั‡ะตะฝั–, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน Python <abbr title="Enumeration">`Enum`</abbr>. -### ะกั‚ะฒะพั€ะตะฝะฝั ะบะปะฐััƒ `Enum` +### ะกั‚ะฒะพั€ั–ั‚ัŒ ะบะปะฐั `Enum` { #create-an-enum-class } ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต `Enum` ั– ัั‚ะฒะพั€ั–ั‚ัŒ ะฟั–ะดะบะปะฐั, ั‰ะพ ะฝะฐัะปั–ะดัƒั”ั‚ัŒัั ะฒั–ะด `str` ั‚ะฐ `Enum`. -ะะฐัะปั–ะดัƒัŽั‡ะธ ะฒั–ะด `str`, ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั API ะทะผะพะถะต ะฒะธะทะฝะฐั‡ะธั‚ะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั ะฟะพะฒะธะฝะฝั– ะฑัƒั‚ะธ ั‚ะธะฟัƒ `string`, ั– ะฟั€ะฐะฒะธะปัŒะฝะพ ั—ั… ะฒั–ะดะพะฑั€ะฐะทะธั‚ัŒ. +ะ—ะฐะฒะดัะบะธ ะฝะฐัะปั–ะดัƒะฒะฐะฝะฝัŽ ะฒั–ะด `str` ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั API ะทะผะพะถะต ะฒะธะทะฝะฐั‡ะธั‚ะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั ะฟะพะฒะธะฝะฝั– ะฑัƒั‚ะธ ั‚ะธะฟัƒ `string`, ั– ะทะผะพะถะต ะบะพั€ะตะบั‚ะฝะพ ั—ั… ะฒั–ะดะพะฑั€ะฐะทะธั‚ะธ. -ะŸั–ัะปั ั†ัŒะพะณะพ ัั‚ะฒะพั€ั–ั‚ัŒ ะฐั‚ั€ะธะฑัƒั‚ะธ ะบะปะฐััƒ ะท ั„ั–ะบัะพะฒะฐะฝะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ, ัะบั– ะฑัƒะดัƒั‚ัŒ ะดะพัั‚ัƒะฟะฝะธะผะธ ะดะพะฟัƒัั‚ะธะผะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ: +ะŸั–ัะปั ั†ัŒะพะณะพ ัั‚ะฒะพั€ั–ั‚ัŒ ะฐั‚ั€ะธะฑัƒั‚ะธ ะบะปะฐััƒ ะท ั„ั–ะบัะพะฒะฐะฝะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ, ัะบั– ะฑัƒะดัƒั‚ัŒ ะดะพัั‚ัƒะฟะฝะธะผะธ ะบะพั€ะตะบั‚ะฝะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ: -{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} - -/// info | ะ”ะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั - -<a href="https://docs.python.org/3/library/enum.html" class="external-link" target="_blank">ะŸะตั€ะตะปั–ั‡ะตะฝะฝั (ะฐะฑะพ enums) ะดะพัั‚ัƒะฟะฝั– ะฒ Python</a> ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท ะฒะตั€ัั–ั— 3.4. - -/// +{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} /// tip | ะŸะพั€ะฐะดะฐ -ะฏะบั‰ะพ ะฒะฐะผ ั†ั–ะบะฐะฒะพ, "AlexNet", "ResNet" ั‚ะฐ "LeNet" โ€” ั†ะต ะฟั€ะพัั‚ะพ ะฝะฐะทะฒะธ ML ะผะพะดะตะปะตะน <abbr title="ะขะตั…ะฝั–ั‡ะฝะพ, ะฐั€ั…ั–ั‚ะตะบั‚ัƒั€ะธ Deep Learning ะผะพะดะตะปะตะน">Machine Learning</abbr>. +ะฏะบั‰ะพ ะฒะฐะผ ั†ั–ะบะฐะฒะพ, ยซAlexNetยป, ยซResNetยป ั‚ะฐ ยซLeNetยป โ€” ั†ะต ะฟั€ะพัั‚ะพ ะฝะฐะทะฒะธ Machine Learning <abbr title="Technically, Deep Learning model architectures โ€“ ั‚ะตั…ะฝั–ั‡ะฝะพ, ะฐั€ั…ั–ั‚ะตะบั‚ัƒั€ะธ ะผะพะดะตะปะตะน Deep Learning">models</abbr>. /// - -### ะžะณะพะปะพัั–ั‚ัŒ *ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ* +### ะžะณะพะปะพัั–ั‚ัŒ *ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ* { #declare-a-path-parameter } ะŸะพั‚ั–ะผ ัั‚ะฒะพั€ั–ั‚ัŒ *ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ* ะท ะฐะฝะพั‚ะฐั†ั–ั”ัŽ ั‚ะธะฟัƒ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัั‚ะฒะพั€ะตะฝะธะน ะฒะฐะผะธ ะบะปะฐั enum (`ModelName`): -{* ../../docs_src/path_params/tutorial005.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} -### ะŸะตั€ะตะฒั–ั€ะบะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +### ะŸะตั€ะตะฒั–ั€ั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ { #check-the-docs } -ะžัะบั–ะปัŒะบะธ ะดะพัั‚ัƒะฟะฝั– ะทะฝะฐั‡ะตะฝะฝั ะดะปั *ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ* ะฒะธะทะฝะฐั‡ะตะฝั– ะทะฐะทะดะฐะปะตะณั–ะดัŒ, ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั ะทะผะพะถะต ะบั€ะฐัะธะฒะพ ั—ั… ะฒั–ะดะพะฑั€ะฐะทะธั‚ะธ: +ะžัะบั–ะปัŒะบะธ ะดะพัั‚ัƒะฟะฝั– ะทะฝะฐั‡ะตะฝะฝั ะดะปั *ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ* ะฒะธะทะฝะฐั‡ะตะฝั– ะทะฐะทะดะฐะปะตะณั–ะดัŒ, ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั ะผะพะถะต ะบั€ะฐัะธะฒะพ ั—ั… ะฟะพะบะฐะทะฐั‚ะธ: <img src="/img/tutorial/path-params/image03.png"> -### ะ ะพะฑะพั‚ะฐ ะท *ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝัะผะธ* ัƒ Python +### ะ ะพะฑะพั‚ะฐ ะท Python *ะฟะตั€ะตะปั–ั‡ะตะฝะฝัะผะธ* { #working-with-python-enumerations } -ะ—ะฝะฐั‡ะตะฝะฝั *ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ* ะฑัƒะดะต ะตะปะตะผะตะฝั‚ะพะผ *ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั*. +ะ—ะฝะฐั‡ะตะฝะฝั *ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ* ะฑัƒะดะต *ะตะปะตะผะตะฝั‚ะพะผ ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั*. -#### ะŸะพั€ั–ะฒะฝัะฝะฝั *ะตะปะตะผะตะฝั‚ั–ะฒ ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั* +#### ะŸะพั€ั–ะฒะฝัะนั‚ะต *ะตะปะตะผะตะฝั‚ะธ ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั* { #compare-enumeration-members } -ะ’ะธ ะผะพะถะตั‚ะต ะฟะพั€ั–ะฒะฝัŽะฒะฐั‚ะธ ะนะพะณะพ ะท *ะตะปะตะผะตะฝั‚ะฐะผะธ ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั* ัƒ ัั‚ะฒะพั€ะตะฝะพะผัƒ ะฒะฐะผะธ enum `ModelName`: +ะ’ะธ ะผะพะถะตั‚ะต ะฟะพั€ั–ะฒะฝัŽะฒะฐั‚ะธ ะนะพะณะพ ะท *ะตะปะตะผะตะฝั‚ะพะผ ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั* ัƒ ัั‚ะฒะพั€ะตะฝะพะผัƒ ะฒะฐะผะธ enum `ModelName`: -{* ../../docs_src/path_params/tutorial005.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} -#### ะžั‚ั€ะธะผะฐะฝะฝั *ะทะฝะฐั‡ะตะฝะฝั ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั* +#### ะžั‚ั€ะธะผะฐะนั‚ะต *ะทะฝะฐั‡ะตะฝะฝั ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั* { #get-the-enumeration-value } ะ’ะธ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ั„ะฐะบั‚ะธั‡ะฝะต ะทะฝะฐั‡ะตะฝะฝั (ัƒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั†ะต `str`), ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `model_name.value`, ะฐะฑะพ ะทะฐะณะฐะปะพะผ `your_enum_member.value`: -{* ../../docs_src/path_params/tutorial005.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} /// tip | ะŸะพั€ะฐะดะฐ -ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ะดะพัั‚ัƒะฟ ะดะพ ะทะฝะฐั‡ะตะฝะฝั `"lenet"`, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `ModelName.lenet.value`. +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ะดะพัั‚ัƒะฟ ะดะพ ะทะฝะฐั‡ะตะฝะฝั `"lenet"` ั‡ะตั€ะตะท `ModelName.lenet.value`. /// +#### ะŸะพะฒะตั€ะฝั–ั‚ัŒ *ะตะปะตะผะตะฝั‚ะธ ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั* { #return-enumeration-members } -#### ะŸะพะฒะตั€ะฝะตะฝะฝั *ะตะปะตะผะตะฝั‚ั–ะฒ ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั* - -ะ’ะธ ะผะพะถะตั‚ะต ะฟะพะฒะตั€ั‚ะฐั‚ะธ *ะตะปะตะผะตะฝั‚ะธ ะฟะตั€ะตะปั–ั‡ัƒะฒะฐะฝะฝั* ะท ะฒะฐัˆะพั— *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะฝะฐะฒั–ั‚ัŒ ะฒะบะปะฐะดะตะฝั– ัƒ JSON-ั‚ั–ะปะพ (ะฝะฐะฟั€ะธะบะปะฐะด, `dict`). +ะ’ะธ ะผะพะถะตั‚ะต ะฟะพะฒะตั€ั‚ะฐั‚ะธ *ะตะปะตะผะตะฝั‚ะธ enum* ะท ะฒะฐัˆะพั— *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะฝะฐะฒั–ั‚ัŒ ะฒะบะปะฐะดะตะฝั– ัƒ JSON-ั‚ั–ะปะพ (ะฝะฐะฟั€ะธะบะปะฐะด, `dict`). ะ’ะพะฝะธ ะฑัƒะดัƒั‚ัŒ ะฟะตั€ะตั‚ะฒะพั€ะตะฝั– ะฝะฐ ะฒั–ะดะฟะพะฒั–ะดะฝั– ะทะฝะฐั‡ะตะฝะฝั (ัƒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั€ัะดะบะธ) ะฟะตั€ะตะด ะฟะพะฒะตั€ะฝะตะฝะฝัะผ ะบะปั–ั”ะฝั‚ัƒ: -{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} -ะะฐ ัั‚ะพั€ะพะฝั– ะบะปั–ั”ะฝั‚ะฐ ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะฒั–ะดะฟะพะฒั–ะดัŒ ัƒ ั„ะพั€ะผะฐั‚ั– JSON, ะฝะฐะฟั€ะธะบะปะฐะด: +ะะฐ ัั‚ะพั€ะพะฝั– ะบะปั–ั”ะฝั‚ะฐ ะฒะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะฒั–ะดะฟะพะฒั–ะดัŒ ัƒ ั„ะพั€ะผะฐั‚ั– JSON, ะฝะฐะฟั€ะธะบะปะฐะด: ```JSON { @@ -207,36 +199,35 @@ } ``` -## Path-ะฟะฐั€ะฐะผะตั‚ั€ะธ, ั‰ะพ ะผั–ัั‚ัั‚ัŒ ัˆะปัั…ะธ +## ะŸะฐั€ะฐะผะตั‚ั€ะธ ัˆะปัั…ัƒ, ั‰ะพ ะผั–ัั‚ัั‚ัŒ ัˆะปัั…ะธ { #path-parameters-containing-paths } -ะŸั€ะธะฟัƒัั‚ะธะผะพ, ัƒ ะฒะฐั ั” *ะพะฟะตั€ะฐั†ั–ั ัˆะปัั…ัƒ* ะท ะผะฐั€ัˆั€ัƒั‚ะพะผ `/files/{file_path}`. +ะŸั€ะธะฟัƒัั‚ั–ะผะพ, ัƒ ะฒะฐั ั” *ะพะฟะตั€ะฐั†ั–ั ัˆะปัั…ัƒ* ะทั– ัˆะปัั…ะพะผ `/files/{file_path}`. -ะะปะต ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ, ั‰ะพะฑ `file_path` ะผั–ัั‚ะธะฒ *ัˆะปัั…*, ะฝะฐะฟั€ะธะบะปะฐะด `home/johndoe/myfile.txt`. +ะะปะต ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ, ั‰ะพะฑ `file_path` ัะฐะผ ะผั–ัั‚ะธะฒ *ัˆะปัั…*, ะฝะฐะฟั€ะธะบะปะฐะด `home/johndoe/myfile.txt`. -ะžั‚ะถะต, URL ะดะปั ั†ัŒะพะณะพ ั„ะฐะนะปัƒ ะฒะธะณะปัะดะฐั‚ะธะผะต ั‚ะฐะบ: `/files/home/johndoe/myfile.txt`. +ะžั‚ะถะต, URL ะดะปั ั†ัŒะพะณะพ ั„ะฐะนะปัƒ ะฒะธะณะปัะดะฐั‚ะธะผะต ะฟั€ะธะฑะปะธะทะฝะพ ั‚ะฐะบ: `/files/home/johndoe/myfile.txt`. +### ะŸั–ะดั‚ั€ะธะผะบะฐ OpenAPI { #openapi-support } +OpenAPI ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ัะฟะพัั–ะฑ ะพะณะพะปะพัˆะตะฝะฝั *ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ*, ัะบะธะน ะผะฐั” ะผั–ัั‚ะธั‚ะธ ะฒัะตั€ะตะดะธะฝั– *ัˆะปัั…*, ะพัะบั–ะปัŒะบะธ ั†ะต ะผะพะถะต ะฟั€ะธะทะฒะตัั‚ะธ ะดะพ ัั†ะตะฝะฐั€ั–ั—ะฒ, ัะบั– ัะบะปะฐะดะฝะพ ั‚ะตัั‚ัƒะฒะฐั‚ะธ ั‚ะฐ ะฒะธะทะฝะฐั‡ะฐั‚ะธ. -### ะŸั–ะดั‚ั€ะธะผะบะฐ OpenAPI +ะŸั€ะพั‚ะต ะฒะธ ะฒัะต ะพะดะฝะพ ะผะพะถะตั‚ะต ะทั€ะพะฑะธั‚ะธ ั†ะต ะฒ **FastAPI**, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะพะดะธะฝ ั–ะท ะฒะฝัƒั‚ั€ั–ัˆะฝั–ั… ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ Starlette. -OpenAPI ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ัะฟะพัั–ะฑ ะพะณะพะปะพัˆะตะฝะฝั *ะฟะฐั€ะฐะผะตั‚ั€ะฐ ัˆะปัั…ัƒ*, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ *ัˆะปัั…* ะฒัะตั€ะตะดะธะฝั–, ะพัะบั–ะปัŒะบะธ ั†ะต ะผะพะถะต ะฟั€ะธะทะฒะตัั‚ะธ ะดะพ ัั†ะตะฝะฐั€ั–ั—ะฒ, ัะบั– ัะบะปะฐะดะฝะพ ั‚ะตัั‚ัƒะฒะฐั‚ะธ ั‚ะฐ ะฒะธะทะฝะฐั‡ะฐั‚ะธ. +ะ† ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั ะฒัะต ั‰ะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต, ั…ะพั‡ะฐ ะน ะฝะต ะดะพะดะฐะฒะฐั‚ะธะผะต ะถะพะดะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—, ัะบะฐ ะฑ ะบะฐะทะฐะปะฐ, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะผะฐั” ะผั–ัั‚ะธั‚ะธ ัˆะปัั…. -ะžะดะฝะฐะบ (ะพะดะฝะฐั‡ะต), ะ’ะธ ะฒัะต ะพะดะฝะพ ะผะพะถะตั‚ะต ะทั€ะพะฑะธั‚ะธ ั†ะต ะฒ **FastAPI**, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะพะดะธะฝ ั–ะท ะฒะฝัƒั‚ั€ั–ัˆะฝั–ั… ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ Starlette. +### ะšะพะฝะฒะตั€ั‚ะตั€ ัˆะปัั…ัƒ { #path-convertor } -ะ”ะพะบัƒะผะตะฝั‚ะฐั†ั–ั ะฒัะต ั‰ะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต, ั…ะพั‡ะฐ ะน ะฝะต ะดะพะดะฐะฒะฐั‚ะธะผะต ะพะฟะธััƒ ะฟั€ะพ ั‚ะต, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะฟะพะฒะธะฝะตะฝ ะผั–ัั‚ะธั‚ะธ ัˆะปัั…. - -### ะšะพะฝะฒะตั€ั‚ะตั€ ัˆะปัั…ัƒ - -ะ’ะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะพะฟั†ั–ัŽ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะทั– Starlette, ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ *ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ*, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ *ัˆะปัั…*, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ URL ะฝะฐ ะบัˆั‚ะฐะปั‚: +ะ’ะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะพะฟั†ั–ัŽ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะทั– Starlette, ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ *ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ*, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ *ัˆะปัั…*, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ URL ะฝะฐ ะบัˆั‚ะฐะปั‚: ``` /files/{file_path:path} ``` -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั–ะผ'ั ะฟะฐั€ะฐะผะตั‚ั€ะฐ โ€” `file_path`, ะฐ ะพัั‚ะฐะฝะฝั ั‡ะฐัั‚ะธะฝะฐ `:path` ะฒะบะฐะทัƒั” ะฝะฐ ั‚ะต, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะฟะพะฒะธะฝะตะฝ ะฒั–ะดะฟะพะฒั–ะดะฐั‚ะธ ะฑัƒะดัŒ-ัะบะพะผัƒ *ัˆะปัั…ัƒ*. -ะžั‚ะถะต, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ะนะพะณะพ ั‚ะฐะบ: +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั–ะผโ€™ั ะฟะฐั€ะฐะผะตั‚ั€ะฐ โ€” `file_path`, ะฐ ะพัั‚ะฐะฝะฝั ั‡ะฐัั‚ะธะฝะฐ `:path` ะฒะบะฐะทัƒั”, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะผะฐั” ะฒั–ะดะฟะพะฒั–ะดะฐั‚ะธ ะฑัƒะดัŒ-ัะบะพะผัƒ *ัˆะปัั…ัƒ*. -{* ../../docs_src/path_params/tutorial004.py hl[6] *} +ะžั‚ะถะต, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ะนะพะณะพ ั‚ะฐะบ: + +{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} /// tip | ะŸะพั€ะฐะดะฐ @@ -246,15 +237,15 @@ OpenAPI ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ัะฟะพัั–ะฑ ะพะณะพะปะพัˆะตะฝะฝั *ะฟะฐั€ะฐะผะต /// -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ— **FastAPI**, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะบะพั€ะพั‚ะบั–, ั–ะฝั‚ัƒั—ั‚ะธะฒะฝะพ ะทั€ะพะทัƒะผั–ะปั– ั‚ะฐ ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ะพะณะพะปะพัˆะตะฝะฝั ั‚ะธะฟั–ะฒ Python, ะ’ะธ ะพั‚ั€ะธะผัƒั”ั‚ะต: +ะ— **FastAPI**, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะบะพั€ะพั‚ะบั–, ั–ะฝั‚ัƒั—ั‚ะธะฒะฝะพ ะทั€ะพะทัƒะผั–ะปั– ั‚ะฐ ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ะพะณะพะปะพัˆะตะฝะฝั ั‚ะธะฟั–ะฒ Python, ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต: -* ะŸั–ะดั‚ั€ะธะผะบัƒ ะฒ ั€ะตะดะฐะบั‚ะพั€ั–: ะฟะตั€ะตะฒั–ั€ะบะฐ ะฟะพะผะธะปะพะบ, ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั ั‚ะพั‰ะพ. -* "<abbr title="ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั€ัะดะบะฐ, ั‰ะพ ะฝะฐะดั…ะพะดะธั‚ัŒ ะท HTTP-ะทะฐะฟะธั‚ัƒ, ัƒ ั‚ะธะฟะธ ะดะฐะฝะธั… Python">ะŸะฐั€ัะธะฝะณ</abbr>" ะดะฐะฝะธั… +* ะŸั–ะดั‚ั€ะธะผะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ: ะฟะตั€ะตะฒั–ั€ะบะฐ ะฟะพะผะธะปะพะบ, ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั ั‚ะพั‰ะพ. +* ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั ะดะฐะฝะธั… ยซ<abbr title="converting the string that comes from an HTTP request into Python data โ€“ ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั€ัะดะบะฐ, ั‰ะพ ะฝะฐะดั…ะพะดะธั‚ัŒ ะท HTTP-ะทะฐะฟะธั‚ัƒ, ัƒ ะดะฐะฝั– Python">parsing</abbr>ยป * ะ’ะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั… * ะะฝะพั‚ะฐั†ั–ัŽ API ั‚ะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะ† ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะพะณะพะปะพัะธั‚ะธ ั—ั… ะปะธัˆะต ะพะดะธะฝ ั€ะฐะท. -ะฆะต, ะนะผะพะฒั–ั€ะฝะพ, ะพัะฝะพะฒะฝะฐ ะฒะธะดะธะผะฐ ะฟะตั€ะตะฒะฐะณะฐ **FastAPI** ะฟะพั€ั–ะฒะฝัะฝะพ ะท ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะธะผะธ ั„ั€ะตะนะผะฒะพั€ะบะฐะผะธ (ะพะบั€ั–ะผ ะฒะธัะพะบะพั— ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั–). +ะฆะต, ะนะผะพะฒั–ั€ะฝะพ, ะพัะฝะพะฒะฝะฐ ะฒะธะดะธะผะฐ ะฟะตั€ะตะฒะฐะณะฐ **FastAPI** ะฟะพั€ั–ะฒะฝัะฝะพ ะท ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะธะผะธ ั„ั€ะตะนะผะฒะพั€ะบะฐะผะธ (ะพะบั€ั–ะผ ัะธั€ะพั— ะฟั€ะพะดัƒะบั‚ะธะฒะฝะพัั‚ั–). diff --git a/docs/uk/docs/tutorial/query-param-models.md b/docs/uk/docs/tutorial/query-param-models.md index 97eb82fa1b..a28bf6c27c 100644 --- a/docs/uk/docs/tutorial/query-param-models.md +++ b/docs/uk/docs/tutorial/query-param-models.md @@ -1,4 +1,4 @@ -# ะœะพะดะตะปั– Query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ +# ะœะพะดะตะปั– ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะฟะธั‚ัƒ { #query-parameter-models } ะฏะบั‰ะพ ัƒ ะ’ะฐั ั” ะณั€ัƒะฟะฐ **query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ**, ัะบั– ะฟะพะฒโ€™ัะทะฐะฝั– ะผั–ะถ ัะพะฑะพัŽ, ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ **Pydantic-ะผะพะดะตะปัŒ** ะดะปั ั—ั… ะพะณะพะปะพัˆะตะฝะฝั. @@ -10,7 +10,7 @@ /// -## Query ะฟะฐั€ะฐะผะตั‚ั€ะธ ะท Pydantic-ะผะพะดะตะปะปัŽ +## Query ะฟะฐั€ะฐะผะตั‚ั€ะธ ะท Pydantic-ะผะพะดะตะปะปัŽ { #query-parameters-with-a-pydantic-model } ะžะณะพะปะพัั–ั‚ัŒ **query ะฟะฐั€ะฐะผะตั‚ั€ะธ**, ัะบั– ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝั–, ัƒ **Pydantic-ะผะพะดะตะปั–**, ะฐ ะฟะพั‚ั–ะผ ะพะณะพะปะพัั–ั‚ัŒ ั†ะตะน ะฟะฐั€ะฐะผะตั‚ั€ ัะบ `Query`: @@ -18,7 +18,7 @@ **FastAPI** ะฑัƒะดะต **ะฒะธั‚ัะณัƒะฒะฐั‚ะธ** ะดะฐะฝั– ะดะปั **ะบะพะถะฝะพะณะพ ะฟะพะปั** ะท **query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ** ัƒ ะทะฐะฟะธั‚ั– ั‚ะฐ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ั—ั… ัƒ ะฒะธะทะฝะฐั‡ะตะฝัƒ ะฒะฐะผะธ Pydantic-ะผะพะดะตะปัŒ. -## ะŸะตั€ะตะฒั–ั€ั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ +## ะŸะตั€ะตะฒั–ั€ั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ { #check-the-docs } ะ’ะธ ะผะพะถะตั‚ะต ะฟะพะฑะฐั‡ะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฟะธั‚ัƒ ะฒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะทะฐ `/docs`: @@ -26,7 +26,7 @@ <img src="/img/tutorial/query-param-models/image01.png"> </div> -## ะ—ะฐะฑะพั€ะพะฝะฐ ะทะฐะนะฒะธั… Query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ +## ะ—ะฐะฑะพั€ะพะฝะฐ ะทะฐะนะฒะธั… Query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ { #forbid-extra-query-parameters } ะฃ ะดะตัะบะธั… ะพัะพะฑะปะธะฒะธั… ะฒะธะฟะฐะดะบะฐั… (ะนะผะพะฒั–ั€ะฝะพ, ะฝะต ะดัƒะถะต ะฟะพัˆะธั€ะตะฝะธั…) ะ’ะธ ะผะพะถะตั‚ะต ะทะฐั…ะพั‚ั–ั‚ะธ **ะพะฑะผะตะถะธั‚ะธ** query ะฟะฐั€ะฐะผะตั‚ั€ะธ, ัะบั– ะดะพะทะฒะพะปะตะฝะพ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ. @@ -34,7 +34,7 @@ {* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} -ะฏะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ **ะทะฐะนะฒั–** ะดะฐะฝั– ัƒ **query ะฟะฐั€ะฐะผะตั‚ั€ะฐั…**, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” **ะฟะพะผะธะปะบัƒ**. +ะฏะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ **ะทะฐะนะฒั–** ะดะฐะฝั– ัƒ **query ะฟะฐั€ะฐะผะตั‚ั€ะฐั…**, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” **ะฟะพะผะธะปะบัƒ** ะฒั–ะดะฟะพะฒั–ะดัŒ. ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ query ะฟะฐั€ะฐะผะตั‚ั€ `tool` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `plumbus`, ัะบ ัƒ ั†ัŒะพะผัƒ ะทะฐะฟะธั‚ั–: @@ -57,11 +57,11 @@ https://example.com/items/?limit=10&tool=plumbus } ``` -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #summary } ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ **Pydantic-ะผะพะดะตะปั–** ะดะปั ะพะณะพะปะพัˆะตะฝะฝั **query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ** ัƒ **FastAPI**. ๐Ÿ˜Ž -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ ะกะฟะพะนะปะตั€: ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Pydantic-ะผะพะดะตะปั– ะดะปั ะพะณะพะปะพัˆะตะฝะฝั cookie ั‚ะฐ ะทะฐะณะพะปะพะฒะบั–ะฒ, ะฐะปะต ะฟั€ะพ ั†ะต ะ’ะธ ะดั–ะทะฝะฐั”ั‚ะตัั ะฟั–ะทะฝั–ัˆะต ะฒ ั†ัŒะพะผัƒ ะฟะพัั–ะฑะฝะธะบัƒ. ๐Ÿคซ diff --git a/docs/uk/docs/tutorial/query-params-str-validations.md b/docs/uk/docs/tutorial/query-params-str-validations.md index cd3f4ad935..414987880b 100644 --- a/docs/uk/docs/tutorial/query-params-str-validations.md +++ b/docs/uk/docs/tutorial/query-params-str-validations.md @@ -1,26 +1,26 @@ -# Query ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะฐ ะฒะฐะปั–ะดะฐั†ั–ั ั€ัะดะบั–ะฒ +# Query ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะฐ ะฒะฐะปั–ะดะฐั†ั–ั ั€ัะดะบั–ะฒ { #query-parameters-and-string-validations } -**FastAPI** ะดะพะทะฒะพะปัั” ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ั‚ะฐ ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ัŽ ะดะปั ะ’ะฐัˆะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ. +**FastAPI** ะดะพะทะฒะพะปัั” ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ั‚ะฐ ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ัŽ ะดะปั ะฒะฐัˆะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ. ะ ะพะทะณะปัะฝะตะผะพ ั†ะตะน ะดะพะดะฐั‚ะพะบ ัะบ ะฟั€ะธะบะปะฐะด: {* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} -Query ะฟะฐั€ะฐะผะตั‚ั€ `q` ะผะฐั” ั‚ะธะฟ `str | None`, ั‰ะพ ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒั–ะฝ ะผะพะถะต ะฑัƒั‚ะธ ัะบ `str`, ั‚ะฐะบ ั– `None`. ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฒั–ะฝ ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั `None`, ั‚ะพะผัƒ FastAPI ั€ะพะทัƒะผั–ั”, ั‰ะพ ั†ะตะน ะฟะฐั€ะฐะผะตั‚ั€ ะฝะต ั” ะพะฑะพะฒ'ัะทะบะพะฒะธะผ. +Query ะฟะฐั€ะฐะผะตั‚ั€ `q` ะผะฐั” ั‚ะธะฟ `str | None`, ั‰ะพ ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒั–ะฝ ะผะฐั” ั‚ะธะฟ `str`, ะฐะปะต ั‚ะฐะบะพะถ ะผะพะถะต ะฑัƒั‚ะธ `None`, ั– ัะฟั€ะฐะฒะดั–, ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ โ€” `None`, ั‚ะพะถ FastAPI ะทะฝะฐั‚ะธะผะต, ั‰ะพ ะฒั–ะฝ ะฝะต ั” ะพะฑะพะฒ'ัะทะบะพะฒะธะผ. /// note | ะŸั€ะธะผั–ั‚ะบะฐ -FastAPI ะทะฝะฐั”, ั‰ะพ `q` ะฝะต ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะทะฐะฒะดัะบะธ ะทะฝะฐั‡ะตะฝะฝัŽ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `= None`. +FastAPI ะทะฝะฐั‚ะธะผะต, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั `q` ะฝะต ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะทะฐะฒะดัะบะธ ะทะฝะฐั‡ะตะฝะฝัŽ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `= None`. -ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `str | None` ะดะพะทะฒะพะปะธั‚ัŒ ะ’ะฐัˆะพะผัƒ ั€ะตะดะฐะบั‚ะพั€ัƒ ะบะพะดัƒ ะฝะฐะดะฐะฒะฐั‚ะธ ะบั€ะฐั‰ัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ ั‚ะฐ ะฒะธัะฒะปัั‚ะธ ะฟะพะผะธะปะบะธ. +ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `str | None` ะดะพะทะฒะพะปะธั‚ัŒ ะฒะฐัˆะพะผัƒ ั€ะตะดะฐะบั‚ะพั€ัƒ ะบะพะดัƒ ะฝะฐะดะฐะฒะฐั‚ะธ ะบั€ะฐั‰ัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ ั‚ะฐ ะฒะธัะฒะปัั‚ะธ ะฟะพะผะธะปะบะธ. /// -## ะ”ะพะดะฐั‚ะบะพะฒะฐ ะฒะฐะปั–ะดะฐั†ั–ั +## ะ”ะพะดะฐั‚ะบะพะฒะฐ ะฒะฐะปั–ะดะฐั†ั–ั { #additional-validation } -ะœะธ ั…ะพั‡ะตะผะพ, ั‰ะพะฑ ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ `q` ั” ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, **ะนะพะณะพ ะดะพะฒะถะธะฝะฐ ะฝะต ะฟะตั€ะตะฒะธั‰ัƒะฒะฐะปะฐ 50 ัะธะผะฒะพะปั–ะฒ**, ัะบั‰ะพ ะฒั–ะฝ ะฒัะต ะถ ะฑัƒะดะต ะฟะตั€ะตะดะฐะฝะธะน. +ะœะธ ั…ะพั‡ะตะผะพ, ั‰ะพะฑ ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ `q` ั” ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะบะพะปะธ ะนะพะณะพ ะฟะตั€ะตะดะฐัŽั‚ัŒ, **ะนะพะณะพ ะดะพะฒะถะธะฝะฐ ะฝะต ะฟะตั€ะตะฒะธั‰ัƒะฒะฐะปะฐ 50 ัะธะผะฒะพะปั–ะฒ**. -### ะ†ะผะฟะพั€ั‚ `Query` ั‚ะฐ `Annotated` +### ะ†ะผะฟะพั€ั‚ `Query` ั‚ะฐ `Annotated` { #import-query-and-annotated } ะฉะพะฑ ั†ะต ะทั€ะพะฑะธั‚ะธ, ัะฟะพั‡ะฐั‚ะบัƒ ั–ะผะฟะพั€ั‚ัƒั”ะผะพ: @@ -33,13 +33,13 @@ FastAPI ะทะฝะฐั”, ั‰ะพ `q` ะฝะต ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะทะฐะฒะดัะบะธ ะท FastAPI ะดะพะดะฐะฒ ะฟั–ะดั‚ั€ะธะผะบัƒ `Annotated` (ั– ะฟะพั‡ะฐะฒ ั€ะตะบะพะผะตะฝะดัƒะฒะฐั‚ะธ ะนะพะณะพ) ัƒ ะฒะตั€ัั–ั— 0.95.0. -ะฏะบั‰ะพ ัƒ ะ’ะฐั ัั‚ะฐั€ั–ัˆะฐ ะฒะตั€ัั–ั, ะฟั–ะด ั‡ะฐั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `Annotated` ะผะพะถัƒั‚ัŒ ะฒะธะฝะธะบะฐั‚ะธ ะฟะพะผะธะปะบะธ. +ะฏะบั‰ะพ ัƒ ะฒะฐั ัั‚ะฐั€ั–ัˆะฐ ะฒะตั€ัั–ั, ะฟั–ะด ั‡ะฐั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `Annotated` ะผะพะถัƒั‚ัŒ ะฒะธะฝะธะบะฐั‚ะธ ะฟะพะผะธะปะบะธ. -ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะ’ะธ [ะพะฝะพะฒะธะปะธ ะฒะตั€ัั–ัŽ FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} ะดะพ ะฟั€ะธะฝะฐะนะผะฝั– 0.95.1, ะฟะตั€ัˆ ะฝั–ะถ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Annotated`. +ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ [ะพะฝะพะฒะธะปะธ ะฒะตั€ัั–ัŽ FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} ะดะพ ะฟั€ะธะฝะฐะนะผะฝั– 0.95.1, ะฟะตั€ัˆ ะฝั–ะถ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Annotated`. /// -## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `Annotated` ัƒ ั‚ะธะฟั– ะฟะฐั€ะฐะผะตั‚ั€ะฐ `q` +## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `Annotated` ัƒ ั‚ะธะฟั– ะฟะฐั€ะฐะผะตั‚ั€ะฐ `q` { #use-annotated-in-the-type-for-the-q-parameter } ะŸะฐะผโ€™ัั‚ะฐั”ั‚ะต, ัะบ ั ั€ะฐะฝั–ัˆะต ั€ะพะทะฟะพะฒั–ะดะฐะฒ, ั‰ะพ `Annotated` ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะดะปั ะดะพะดะฐะฒะฐะฝะฝั ะผะตั‚ะฐะดะฐะฝะธั… ะดะพ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัƒ [ะ’ัั‚ัƒะฟั– ะดะพ ั‚ะธะฟั–ะฒ Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}? @@ -55,7 +55,7 @@ q: str | None = None //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python q: Union[str, None] = None @@ -73,7 +73,7 @@ q: Annotated[str | None] = None //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python q: Annotated[Union[str, None]] = None @@ -85,33 +85,33 @@ q: Annotated[Union[str, None]] = None ะ ั‚ะตะฟะตั€ ะฟะตั€ะตั…ะพะดะธะผะพ ะดะพ ั†ั–ะบะฐะฒะพะณะพ! ๐ŸŽ‰ -## ะ”ะพะดะฐะฒะฐะฝะฝั `Query` ะดะพ `Annotated` ัƒ ะฟะฐั€ะฐะผะตั‚ั€ `q` +## ะ”ะพะดะฐะฒะฐะฝะฝั `Query` ะดะพ `Annotated` ัƒ ะฟะฐั€ะฐะผะตั‚ั€ `q` { #add-query-to-annotated-in-the-q-parameter } -ะขะตะฟะตั€, ะบะพะปะธ ัƒ ะฝะฐั ั” `Annotated`, ะดะต ะผะธ ะผะพะถะตะผะพ ะดะพะดะฐะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ (ะทะพะบั€ะตะผะฐ ะฒะฐะปั–ะดะฐั†ั–ัŽ), ะดะพะดะฐะผะพ `Query` ะฒัะตั€ะตะดะธะฝัƒ `Annotated` ั– ะฒัั‚ะฐะฝะพะฒะธะผะพ ะฟะฐั€ะฐะผะตั‚ั€ `max_length` ัƒ `50`: +ะขะตะฟะตั€, ะบะพะปะธ ัƒ ะฝะฐั ั” `Annotated`, ะดะต ะผะธ ะผะพะถะตะผะพ ะดะพะดะฐะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ (ัƒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ โ€” ะดะพะดะฐั‚ะบะพะฒัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ), ะดะพะดะฐะผะพ `Query` ะฒัะตั€ะตะดะธะฝัƒ `Annotated` ั– ะฒัั‚ะฐะฝะพะฒะธะผะพ ะฟะฐั€ะฐะผะตั‚ั€ `max_length` ัƒ `50`: {* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *} ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ัƒัะต ั‰ะต `None`, ั‚ะพะผัƒ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะปะธัˆะฐั”ั‚ัŒัั ะฝะตะพะฑะพะฒ'ัะทะบะพะฒะธะผ. -ะะปะต ั‚ะตะฟะตั€, ะดะพะดะฐะฒัˆะธ `Query(max_length=50)` ะฒัะตั€ะตะดะธะฝัƒ `Annotated`, ะผะธ ะฟะพะฒั–ะดะพะผะปัั”ะผะพ FastAPI, ั‰ะพ ั…ะพั‡ะตะผะพ **ะดะพะดะฐั‚ะบะพะฒัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ** ะดะปั ั†ัŒะพะณะพ ะทะฝะฐั‡ะตะฝะฝั โ€” ะฒะพะฝะพ ะผะฐั” ะผั–ัั‚ะธั‚ะธ ะผะฐะบัะธะผัƒะผ 50 ัะธะผะฒะพะปั–ะฒ. ๐Ÿ˜Ž +ะะปะต ั‚ะตะฟะตั€, ะดะพะดะฐะฒัˆะธ `Query(max_length=50)` ะฒัะตั€ะตะดะธะฝัƒ `Annotated`, ะผะธ ะฟะพะฒั–ะดะพะผะปัั”ะผะพ FastAPI, ั‰ะพ ั…ะพั‡ะตะผะพ **ะดะพะดะฐั‚ะบะพะฒัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ** ะดะปั ั†ัŒะพะณะพ ะทะฝะฐั‡ะตะฝะฝั: ะผะธ ั…ะพั‡ะตะผะพ, ั‰ะพะฑ ะฒะพะฝะพ ะผะฐะปะพ ะผะฐะบัะธะผัƒะผ 50 ัะธะผะฒะพะปั–ะฒ. ๐Ÿ˜Ž -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ -ะœะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ะผะพ `Query()`, ะพัะบั–ะปัŒะบะธ ั†ะต **query ะฟะฐั€ะฐะผะตั‚ั€**. ะ”ะฐะปั– ะผะธ ั€ะพะทะณะปัะฝะตะผะพ ั–ะฝัˆั– ะฒะฐั€ั–ะฐะฝั‚ะธ, ัะบ-ะพั‚ `Path()`, `Body()`, `Header()` ั‚ะฐ `Cookie()`, ัะบั– ะฟั€ะธะนะผะฐัŽั‚ัŒ ั‚ั– ัะฐะผั– ะฐั€ะณัƒะผะตะฝั‚ะธ, ั‰ะพ ะน `Query()`. +ะขัƒั‚ ะผะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ะผะพ `Query()`, ะพัะบั–ะปัŒะบะธ ั†ะต **query ะฟะฐั€ะฐะผะตั‚ั€**. ะ”ะฐะปั– ะผะธ ั€ะพะทะณะปัะฝะตะผะพ ั–ะฝัˆั– ะฒะฐั€ั–ะฐะฝั‚ะธ, ัะบ-ะพั‚ `Path()`, `Body()`, `Header()` ั‚ะฐ `Cookie()`, ัะบั– ะฟั€ะธะนะผะฐัŽั‚ัŒ ั‚ั– ัะฐะผั– ะฐั€ะณัƒะผะตะฝั‚ะธ, ั‰ะพ ะน `Query()`. /// ะขะตะฟะตั€ FastAPI: -* **ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ** ะดะฐะฝั–, ั‰ะพะฑ ะฟะตั€ะตะบะพะฝะฐั‚ะธัั, ั‰ะพ ั—ั…ะฝั ะดะพะฒะถะธะฝะฐ ะฝะต ะฟะตั€ะตะฒะธั‰ัƒั” 50 ัะธะผะฒะพะปั–ะฒ +* **ะŸะตั€ะตะฒั–ั€ะธั‚ัŒ** ะดะฐะฝั–, ั‰ะพะฑ ะฟะตั€ะตะบะพะฝะฐั‚ะธัั, ั‰ะพ ั—ั…ะฝั ะผะฐะบัะธะผะฐะปัŒะฝะฐ ะดะพะฒะถะธะฝะฐ โ€” 50 ัะธะผะฒะพะปั–ะฒ * ะŸะพะบะฐะถe **ั‡ั–ั‚ะบัƒ ะฟะพะผะธะปะบัƒ** ะบะปั–ั”ะฝั‚ัƒ, ัะบั‰ะพ ะดะฐะฝั– ะฝะตะดั–ะนัะฝั– * **ะ—ะฐะดะพะบัƒะผะตะฝั‚ัƒั”** ะฟะฐั€ะฐะผะตั‚ั€ ะฒ OpenAPI-ัั…ะตะผั– *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* (ั‰ะพ ะฒั–ะดะพะฑั€ะฐะทะธั‚ัŒัั ะฒ **ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะณะตะฝะตั€ะพะฒะฐะฝั–ะน ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—**) -## ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะธะน (ะทะฐัั‚ะฐั€ั–ะปะธะน) ะผะตั‚ะพะด: Query ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ +## ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะธะน (ะทะฐัั‚ะฐั€ั–ะปะธะน) ะผะตั‚ะพะด: `Query` ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ { #alternative-old-query-as-the-default-value } -ะฃ ะฟะพะฟะตั€ะตะดะฝั–ั… ะฒะตั€ัั–ัั… FastAPI (ะดะพ <abbr title="ะดะพ 2023-03">0.95.0</abbr>) `Query` ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะฒัั ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ะฐ, ะฐ ะฝะต ะฒัะตั€ะตะดะธะฝั– `Annotated`. ะ’ะธ, ะนะผะพะฒั–ั€ะฝะพ, ะฟะพะฑะฐั‡ะธั‚ะต ะบะพะด, ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ั†ะตะน ะฟั–ะดั…ั–ะด, ั‚ะพะผัƒ ะฒะฐั€ั‚ะพ ั€ะพะทะณะปัะฝัƒั‚ะธ ะนะพะณะพ. +ะฃ ะฟะพะฟะตั€ะตะดะฝั–ั… ะฒะตั€ัั–ัั… FastAPI (ะดะพ <abbr title="before 2023-03 โ€“ ะดะพ 2023-03">0.95.0</abbr>) ะฟะพั‚ั€ั–ะฑะฝะพ ะฑัƒะปะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Query` ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฟะฐั€ะฐะผะตั‚ั€ะฐ, ะทะฐะผั–ัั‚ัŒ ั‚ะพะณะพ, ั‰ะพะฑ ะดะพะดะฐะฒะฐั‚ะธ ะนะพะณะพ ะฒ `Annotated`. ะ„ ะฒะธัะพะบะฐ ะนะผะพะฒั–ั€ะฝั–ัั‚ัŒ, ั‰ะพ ะฒะธ ะทัƒัั‚ั€ั–ะฝะตั‚ะต ะบะพะด ั–ะท ั‚ะฐะบะธะผ ะฟั–ะดั…ะพะดะพะผ, ั‚ะพะถ ั ะฟะพััะฝัŽ ะนะพะณะพ. -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ ะ”ะปั ะฝะพะฒะพะณะพ ะบะพะดัƒ ั‚ะฐ ะบะพะปะธ ั†ะต ะผะพะถะปะธะฒะพ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `Annotated`, ัะบ ะฟะพะบะฐะทะฐะฝะพ ะฒะธั‰ะต. ะฆะต ะผะฐั” ะฑะฐะณะฐั‚ะพ ะฟะตั€ะตะฒะฐะณ (ะฟะพััะฝะตะฝะธั… ะฝะธะถั‡ะต) ั– ะฝะต ะผะฐั” ะฝะตะดะพะปั–ะบั–ะฒ. ๐Ÿฐ @@ -121,7 +121,7 @@ q: Annotated[Union[str, None]] = None {* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *} -ะžัะบั–ะปัŒะบะธ ะฒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ (ะฑะตะท `Annotated`) ะฝะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะทะฐะผั–ะฝะธั‚ะธ `None` ัƒ ั„ัƒะฝะบั†ั–ั— ะฝะฐ `Query()`, ั‚ะตะฟะตั€ ะผะธ ะฟะพะฒะธะฝะฝั– ัะฒะฝะพ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ั‡ะตั€ะตะท ะฟะฐั€ะฐะผะตั‚ั€ `Query(default=None)`. ะฆะต ะฒะธะบะพะฝัƒั” ั‚ัƒ ัะฐะผัƒ ั€ะพะปัŒ ะฒะธะทะฝะฐั‡ะตะฝะฝั ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ (ะฟั€ะธะฝะฐะนะผะฝั– ะดะปั FastAPI). +ะžัะบั–ะปัŒะบะธ ะฒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ (ะฑะตะท `Annotated`) ะฝะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะทะฐะผั–ะฝะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `None` ัƒ ั„ัƒะฝะบั†ั–ั— ะฝะฐ `Query()`, ั‚ะตะฟะตั€ ะผะธ ะฟะพะฒะธะฝะฝั– ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ั‡ะตั€ะตะท ะฟะฐั€ะฐะผะตั‚ั€ `Query(default=None)`. ะฆะต ะฒะธะบะพะฝัƒั” ั‚ัƒ ัะฐะผัƒ ั€ะพะปัŒ ะฒะธะทะฝะฐั‡ะตะฝะฝั ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ (ะฟั€ะธะฝะฐะนะผะฝั– ะดะปั FastAPI). ะขะฐะบะธะผ ั‡ะธะฝะพะผ: @@ -135,9 +135,10 @@ q: str | None = Query(default=None) ```Python q: str | None = None ``` -ะะปะต ัƒ ะฒะตั€ัั–ั— ะท `Query` ะผะธ ัะฒะฝะพ ะฒะบะฐะทัƒั”ะผะพ, ั‰ะพ ั†ะต query ะฟะฐั€ะฐะผะตั‚ั€. -ะ”ะฐะปั– ะผะธ ะผะพะถะตะผะพ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ `Query` ะดะพะดะฐั‚ะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ, ะทะพะบั€ะตะผะฐ `max_length`, ัะบะธะน ะทะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั ะดะพ ั€ัะดะบั–ะฒ: +ะะปะต ัƒ ะฒะตั€ัั–ั— ะท `Query` ะผะธ ัะฒะฝะพ ะฒะบะฐะทัƒั”ะผะพ, ั‰ะพ ั†ะต query ะฟะฐั€ะฐะผะตั‚ั€. + +ะ”ะฐะปั– ะผะธ ะผะพะถะตะผะพ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ `Query` ะดะพะดะฐั‚ะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ. ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ โ€” ะฟะฐั€ะฐะผะตั‚ั€ `max_length`, ัะบะธะน ะทะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั ะดะพ ั€ัะดะบั–ะฒ: ```Python q: str | None = Query(default=None, max_length=50) @@ -145,11 +146,11 @@ q: str | None = Query(default=None, max_length=50) ะฆะต ะทะฐะฑะตะทะฟะตั‡ะธั‚ัŒ ะฒะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั…, ะฒะธะฒะตะดะต ะทั€ะพะทัƒะผั–ะปัƒ ะฟะพะผะธะปะบัƒ ัƒ ั€ะฐะทั– ะฝะตะดั–ะนัะฝะธั… ะดะฐะฝะธั… ั– ะทะฐะดะพะบัƒะผะตะฝั‚ัƒั” ะฟะฐั€ะฐะผะตั‚ั€ ัƒ ัั…ะตะผั– OpenAPI *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*. -### `Query` ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฐะฑะพ ะฒัะตั€ะตะดะธะฝั– `Annotated` +### `Query` ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฐะฑะพ ะฒัะตั€ะตะดะธะฝั– `Annotated` { #query-as-the-default-value-or-in-annotated } ะ’ะฐะถะปะธะฒะพ ะฟะฐะผโ€™ัั‚ะฐั‚ะธ, ัะบั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Query` ะฒัะตั€ะตะดะธะฝั– `Annotated`, ะฝะต ะผะพะถะฝะฐ ะทะฐะดะฐะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `default` ัƒ `Query`. -ะ—ะฐะผั–ัั‚ัŒ ั†ัŒะพะณะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ัƒ ัะฐะผั–ะน ั„ัƒะฝะบั†ั–ั—. ะ†ะฝะฐะบัˆะต ั†ะต ะฑัƒะดะต ะฝะตะปะพะณั–ั‡ะฝะพ. +ะ—ะฐะผั–ัั‚ัŒ ั†ัŒะพะณะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ั„ะฐะบั‚ะธั‡ะฝะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฟะฐั€ะฐะผะตั‚ั€ะฐ ั„ัƒะฝะบั†ั–ั—. ะ†ะฝะฐะบัˆะต ั†ะต ะฑัƒะดะต ะฝะตะฟะพัะปั–ะดะพะฒะฝะพ. ะะฐะฟั€ะธะบะปะฐะด, ั†ะตะน ะฒะฐั€ั–ะฐะฝั‚ ั” ะฝะตะบะพั€ะตะบั‚ะฝะธะผ: @@ -159,39 +160,39 @@ q: Annotated[str, Query(default="rick")] = "morty" ...ั‚ะพะผัƒ, ั‰ะพ ะฝะต ะทั€ะพะทัƒะผั–ะปะพ, ัะบะต ะทะฝะฐั‡ะตะฝะฝั ะผะฐั” ะฑัƒั‚ะธ ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: `"rick"` ั‡ะธ `"morty"`. -ะšะพั€ะตะบั‚ะฝั– ะฒะฐั€ั–ะฐะฝั‚ะธ: +ะขะพะถ ะฒะธ ะฑัƒะดะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ (ะฑะฐะถะฐะฝะพ): ```Python q: Annotated[str, Query()] = "rick" ``` -...ะฐะฑะพ ัƒ ัั‚ะฐั€ะธั… ะบะพะดะพะฒะธั… ะฑะฐะทะฐั… ะ’ะธ ะทะฝะฐะนะดะตั‚ะต: +...ะฐะฑะพ ัƒ ัั‚ะฐั€ะธั… ะบะพะดะพะฒะธั… ะฑะฐะทะฐั… ะฒะธ ะทะฝะฐะนะดะตั‚ะต: ```Python q: str = Query(default="rick") ``` -### ะŸะตั€ะตะฒะฐะณะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `Annotated` +### ะŸะตั€ะตะฒะฐะณะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `Annotated` { #advantages-of-annotated } **ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `Annotated` ั” ั€ะตะบะพะผะตะฝะดะพะฒะฐะฝะธะผ** ะทะฐะผั–ัั‚ัŒ ะทะฐะดะฐะฝะฝั ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ัƒ ะฟะฐั€ะฐะผะตั‚ั€ะฐั… ั„ัƒะฝะบั†ั–ั—, ะพัะบั–ะปัŒะบะธ ะฒะพะฝะพ **ะบั€ะฐั‰ะต** ะท ะบั–ะปัŒะบะพั… ะฟั€ะธั‡ะธะฝ. ๐Ÿค“ -ะ—ะฝะฐั‡ะตะฝะฝั **ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ** ะฟะฐั€ะฐะผะตั‚ั€ะฐ **ั„ัƒะฝะบั†ั–ั—** ั” ะนะพะณะพ **ั„ะฐะบั‚ะธั‡ะฝะธะผ ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ**, ั‰ะพ ั” ะฑั–ะปัŒัˆ ั–ะฝั‚ัƒั—ั‚ะธะฒะฝะธะผ ัƒ Python ะทะฐะณะฐะปะพะผ. ๐Ÿ˜Œ +ะ—ะฝะฐั‡ะตะฝะฝั **ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ** ะฟะฐั€ะฐะผะตั‚ั€ะฐ **ั„ัƒะฝะบั†ั–ั—** ั” **ั„ะฐะบั‚ะธั‡ะฝะธะผ ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ**, ั‰ะพ ั” ะฑั–ะปัŒัˆ ั–ะฝั‚ัƒั—ั‚ะธะฒะฝะธะผ ัƒ Python ะทะฐะณะฐะปะพะผ. ๐Ÿ˜Œ -ะ’ะธ ะผะพะถะตั‚ะต **ะฒะธะบะปะธะบะฐั‚ะธ** ั‚ัƒ ัะฐะผัƒ ั„ัƒะฝะบั†ั–ัŽ **ะฒ ั–ะฝัˆะธั… ะผั–ัั†ัั…** ะฑะตะท FastAPI, ั– ะฒะพะฝะฐ **ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ะพั‡ั–ะบัƒะฒะฐะฝะพ**. ะฏะบั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ั” **ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ** (ะฑะตะท ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ), ะ’ะฐัˆ **ั€ะตะดะฐะบั‚ะพั€** ะฟะพะฒั–ะดะพะผะธั‚ัŒ ะฟั€ะพ ะฟะพะผะธะปะบัƒ, ะฐ **Python** ั‚ะฐะบะพะถ ะฒะธะดะฐัั‚ัŒ ะฟะพะผะธะปะบัƒ, ัะบั‰ะพ ะ’ะธ ะฒะธะบะพะฝะฐั”ั‚ะต ั„ัƒะฝะบั†ั–ัŽ ะฑะตะท ะฟะตั€ะตะดะฐะฒะฐะฝะฝั ั†ัŒะพะณะพ ะฟะฐั€ะฐะผะตั‚ั€ะฐ. +ะ’ะธ ะผะพะถะตั‚ะต **ะฒะธะบะปะธะบะฐั‚ะธ** ั‚ัƒ ัะฐะผัƒ ั„ัƒะฝะบั†ั–ัŽ **ะฒ ั–ะฝัˆะธั… ะผั–ัั†ัั…** ะฑะตะท FastAPI, ั– ะฒะพะฝะฐ **ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ะพั‡ั–ะบัƒะฒะฐะฝะพ**. ะฏะบั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ั” **ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ** (ะฑะตะท ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ), ะฒะฐัˆ **ั€ะตะดะฐะบั‚ะพั€** ะฟะพะฒั–ะดะพะผะธั‚ัŒ ะฟั€ะพ ะฟะพะผะธะปะบัƒ, ะฐ **Python** ั‚ะฐะบะพะถ ะฒะธะดะฐัั‚ัŒ ะฟะพะผะธะปะบัƒ, ัะบั‰ะพ ะฒะธ ะฒะธะบะพะฝะฐั”ั‚ะต ั„ัƒะฝะบั†ั–ัŽ ะฑะตะท ะฟะตั€ะตะดะฐะฒะฐะฝะฝั ั†ัŒะพะณะพ ะฟะฐั€ะฐะผะตั‚ั€ะฐ. -ะฏะบั‰ะพ ะ’ะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`, ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต **(ัั‚ะฐั€ะธะน) ัั‚ะธะปัŒ ะทะฝะฐั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ**, ั‚ะพ ะฟั€ะธ ะฒะธะบะปะธะบัƒ ั†ั–ั”ั— ั„ัƒะฝะบั†ั–ั— ะฑะตะท FastAPI **ะฒ ั–ะฝัˆะธั… ะผั–ัั†ัั…**, ะฟะพั‚ั€ั–ะฑะฝะพ **ะฝะต ะทะฐะฑัƒั‚ะธ** ะฟะตั€ะตะดะฐั‚ะธ ั—ะน ะฐั€ะณัƒะผะตะฝั‚ะธ, ั–ะฝะฐะบัˆะต ะทะฝะฐั‡ะตะฝะฝั ะฑัƒะดัƒั‚ัŒ ะฒั–ะดั€ั–ะทะฝัั‚ะธัั ะฒั–ะด ะพั‡ั–ะบัƒะฒะฐะฝะธั… (ะฝะฐะฟั€ะธะบะปะฐะด, ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต `QueryInfo` ะฐะฑะพ ะฟะพะดั–ะฑะฝะต ะทะฐะผั–ัั‚ัŒ `str`). ะ’ะฐัˆ ั€ะตะดะฐะบั‚ะพั€ ะฝะต ะฟะพะฒั–ะดะพะผะธั‚ัŒ ะฟั€ะพ ะฟะพะผะธะปะบัƒ, ั– Python ั‚ะฐะบะพะถ ะฝะต ะฒะธะดะฐัั‚ัŒ ะฟะพะผะธะปะบัƒ ะฟั€ะธ ะทะฐะฟัƒัะบัƒ ั„ัƒะฝะบั†ั–ั—, ะฟะพะบะธ ะฝะต ะฒะธะฝะธะบะฝะต ะฟะพะผะธะปะบะฐ ะฟั–ะด ั‡ะฐั ะฒะธะบะพะฝะฐะฝะฝั ะพะฟะตั€ะฐั†ั–ะน ัƒัะตั€ะตะดะธะฝั–. +ะฏะบั‰ะพ ะฒะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Annotated`, ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต **(ัั‚ะฐั€ะธะน) ัั‚ะธะปัŒ ะทะฝะฐั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ**, ั‚ะพ ะฟั€ะธ ะฒะธะบะปะธะบัƒ ั†ั–ั”ั— ั„ัƒะฝะบั†ั–ั— ะฑะตะท FastAPI **ะฒ ั–ะฝัˆะธั… ะผั–ัั†ัั…**, ะฟะพั‚ั€ั–ะฑะฝะพ **ะฟะฐะผโ€™ัั‚ะฐั‚ะธ** ะฟะตั€ะตะดะฐั‚ะธ ั—ะน ะฐั€ะณัƒะผะตะฝั‚ะธ, ั‰ะพะฑ ะฒะพะฝะฐ ะฟั€ะฐั†ัŽะฒะฐะปะฐ ะบะพั€ะตะบั‚ะฝะพ, ั–ะฝะฐะบัˆะต ะทะฝะฐั‡ะตะฝะฝั ะฑัƒะดัƒั‚ัŒ ะฒั–ะดั€ั–ะทะฝัั‚ะธัั ะฒั–ะด ะพั‡ั–ะบัƒะฒะฐะฝะธั… (ะฝะฐะฟั€ะธะบะปะฐะด, ะฒะธ ะพั‚ั€ะธะผะฐั”ั‚ะต `QueryInfo` ะฐะฑะพ ั‰ะพััŒ ะฟะพะดั–ะฑะฝะต ะทะฐะผั–ัั‚ัŒ `str`). ะ† ะฒะฐัˆ ั€ะตะดะฐะบั‚ะพั€ ะฝะต ะฟะพะฒั–ะดะพะผะธั‚ัŒ ะฟั€ะพ ะฟะพะผะธะปะบัƒ, ั– Python ะฝะต ัะบะฐั€ะถะธั‚ะธะผะตั‚ัŒัั ะฟั–ะด ั‡ะฐั ะทะฐะฟัƒัะบัƒ ั†ั–ั”ั— ั„ัƒะฝะบั†ั–ั— โ€” ะปะธัˆะต ะบะพะปะธ ะพะฟะตั€ะฐั†ั–ั— ะฒัะตั€ะตะดะธะฝั– ะทะฐะฒะตั€ัˆะฐั‚ัŒัั ะฟะพะผะธะปะบะพัŽ. -ะžัะบั–ะปัŒะบะธ `Annotated` ะผะพะถะต ะผั–ัั‚ะธั‚ะธ ะบั–ะปัŒะบะฐ ะฐะฝะพั‚ะฐั†ั–ะน ะผะตั‚ะฐะดะฐะฝะธั…, ะ’ะธ ะฝะฐะฒั–ั‚ัŒ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ัƒ ัะฐะผัƒ ั„ัƒะฝะบั†ั–ัŽ ะท ั–ะฝัˆะธะผะธ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ, ั‚ะฐะบะธะผะธ ัะบ <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. ๐Ÿš€ +ะžัะบั–ะปัŒะบะธ `Annotated` ะผะพะถะต ะผั–ัั‚ะธั‚ะธ ะบั–ะปัŒะบะฐ ะฐะฝะพั‚ะฐั†ั–ะน ะผะตั‚ะฐะดะฐะฝะธั…, ั‚ะตะฟะตั€ ะฒะธ ะฝะฐะฒั–ั‚ัŒ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ัƒ ัะฐะผัƒ ั„ัƒะฝะบั†ั–ัŽ ะท ั–ะฝัˆะธะผะธ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ, ั‚ะฐะบะธะผะธ ัะบ <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. ๐Ÿš€ -## ะ”ะพะดะฐะฒะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะธั… ะฒะฐะปั–ะดะฐั†ั–ะน +## ะ”ะพะดะฐะฒะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะธั… ะฒะฐะปั–ะดะฐั†ั–ะน { #add-more-validations } ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `min_length`: {* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} -## ะ”ะพะดะฐะฒะฐะฝะฝั ั€ะตะณัƒะปัั€ะฝะธั… ะฒะธั€ะฐะทั–ะฒ +## ะ”ะพะดะฐะฒะฐะฝะฝั ั€ะตะณัƒะปัั€ะฝะธั… ะฒะธั€ะฐะทั–ะฒ { #add-regular-expressions } -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ <abbr title="ะ ะตะณัƒะปัั€ะฝะธะน ะฒะธั€ะฐะท (regex ะฐะฑะพ regexp) โ€” ั†ะต ะฟะพัะปั–ะดะพะฒะฝั–ัั‚ัŒ ัะธะผะฒะพะปั–ะฒ, ัะบะฐ ะฒะธะทะฝะฐั‡ะฐั” ัˆะฐะฑะปะพะฝ ะดะปั ะฟะพัˆัƒะบัƒ ะฒ ั€ัะดะบะฐั….">ั€ะตะณัƒะปัั€ะฝะธะน ะฒะธั€ะฐะท</abbr> pattern, ัะบะพะผัƒ ะผะฐั” ะฒั–ะดะฟะพะฒั–ะดะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€: +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ <abbr title="A regular expression, regex or regexp is a sequence of characters that define a search pattern for strings. โ€“ ะ ะตะณัƒะปัั€ะฝะธะน ะฒะธั€ะฐะท (regex ะฐะฑะพ regexp) โ€” ั†ะต ะฟะพัะปั–ะดะพะฒะฝั–ัั‚ัŒ ัะธะผะฒะพะปั–ะฒ, ัะบะฐ ะฒะธะทะฝะฐั‡ะฐั” ัˆะฐะฑะปะพะฝ ะดะปั ะฟะพัˆัƒะบัƒ ะฒ ั€ัะดะบะฐั….">regular expression</abbr> `pattern`, ัะบะพะผัƒ ะผะฐั” ะฒั–ะดะฟะพะฒั–ะดะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -201,41 +202,27 @@ q: str = Query(default="rick") * `fixedquery`: ั‚ะพั‡ะฝะพ ะฒั–ะดะฟะพะฒั–ะดะฐั” ะทะฝะฐั‡ะตะฝะฝัŽ `fixedquery`. * `$`: ะทะฐะบั–ะฝั‡ัƒั”ั‚ัŒัั ั‚ัƒั‚, ะฟั–ัะปั `fixedquery` ะฝะตะผะฐั” ะถะพะดะฝะธั… ัะธะผะฒะพะปั–ะฒ. -ะฏะบั‰ะพ ะ’ะธ ะฟะพั‡ัƒะฒะฐั”ั‚ะตัั ั€ะพะทะณัƒะฑะปะตะฝะพ ั‰ะพะดะพ **"ั€ะตะณัƒะปัั€ะฝะธั… ะฒะธั€ะฐะทั–ะฒ"**, ะฝะต ั…ะฒะธะปัŽะนั‚ะตัั. ะ’ะพะฝะธ ั” ัะบะปะฐะดะฝะพัŽ ั‚ะตะผะพัŽ ะดะปั ะฑะฐะณะฐั‚ัŒะพั… ะปัŽะดะตะน. ะ’ะธ ะฒัะต ะพะดะฝะพ ะผะพะถะตั‚ะต ะทั€ะพะฑะธั‚ะธ ะฑะฐะณะฐั‚ะพ ั€ะตั‡ะตะน ะฑะตะท ั—ั… ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั. +ะฏะบั‰ะพ ะฒะธ ะฟะพั‡ัƒะฒะฐั”ั‚ะตัั ั€ะพะทะณัƒะฑะปะตะฝะพ ั‰ะพะดะพ **ยซregular expressionยป**, ะฝะต ั…ะฒะธะปัŽะนั‚ะตัั. ะฆะต ัะบะปะฐะดะฝะฐ ั‚ะตะผะฐ ะดะปั ะฑะฐะณะฐั‚ัŒะพั… ะปัŽะดะตะน. ะ’ะธ ะฒัะต ะพะดะฝะพ ะผะพะถะตั‚ะต ั€ะพะฑะธั‚ะธ ะฑะฐะณะฐั‚ะพ ั€ะตั‡ะตะน ะฑะตะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ั€ะตะณัƒะปัั€ะฝะธั… ะฒะธั€ะฐะทั–ะฒ. -ะะปะต ั‚ะตะฟะตั€ ะ’ะธ ะทะฝะฐั”ั‚ะต, ั‰ะพ ะบะพะปะธ ะฒะพะฝะธ ะทะฝะฐะดะพะฑะปัั‚ัŒัั, ั—ั… ะผะพะถะฝะฐ ะทะฐัั‚ะพัะพะฒัƒะฒะฐั‚ะธ ัƒ **FastAPI**. +ะขะตะฟะตั€ ะฒะธ ะทะฝะฐั”ั‚ะต, ั‰ะพ ะบะพะปะธ ะฒะพะฝะธ ะทะฝะฐะดะพะฑะปัั‚ัŒัั, ั—ั… ะผะพะถะฝะฐ ะทะฐัั‚ะพัะพะฒัƒะฒะฐั‚ะธ ัƒ **FastAPI**. -### Pydantic v1 `regex` ะทะฐะผั–ัั‚ัŒ `pattern` +## ะ—ะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ { #default-values } -ะ”ะพ ะฒะตั€ัั–ั— Pydantic 2 ั– FastAPI 0.100.0 ะฟะฐั€ะฐะผะตั‚ั€ ะฝะฐะทะธะฒะฐะฒัั `regex` ะทะฐะผั–ัั‚ัŒ `pattern`, ะฐะปะต ั‚ะตะฟะตั€ ะฒั–ะฝ ะทะฐัั‚ะฐั€ั–ะฒ. +ะ’ะธ ะผะพะถะตั‚ะต, ะทะฒั–ัะฝะพ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฒั–ะดะผั–ะฝะฝั– ะฒั–ะด `None`. -ะ’ะธ ะฒัะต ั‰ะต ะผะพะถะตั‚ะต ะทัƒัั‚ั€ั–ั‚ะธ ะบะพะด, ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ะนะพะณะพ: - -//// tab | Pydantic v1 - -{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *} - -//// - -ะะปะต ะผะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ะฒั–ะฝ ั” ะทะฐัั‚ะฐั€ั–ะปะธะผ ั– ะนะพะณะพ ัะปั–ะด ะพะฝะพะฒะธั‚ะธ ะดะพ ะฝะพะฒะพะณะพ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `pattern`. ๐Ÿค“ - -## ะ—ะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ - -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฒั–ะดะผั–ะฝะฝั– ะฒั–ะด `None`. - -ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ `q` ะท `min_length` `3` ั– ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `"fixedquery"`: +ะŸั€ะธะฟัƒัั‚ั–ะผะพ, ั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะพะณะพะปะพัะธั‚ะธ query ะฟะฐั€ะฐะผะตั‚ั€ `q` ะท `min_length` `3` ั– ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `"fixedquery"`: {* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} -/// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– +/// note | ะŸั€ะธะผั–ั‚ะบะฐ ะะฐัะฒะฝั–ัั‚ัŒ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฑัƒะดัŒ-ัะบะพะณะพ ั‚ะธะฟัƒ, ะฒะบะปัŽั‡ะฐัŽั‡ะธ `None`, ั€ะพะฑะธั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ (not required). /// -## ะžะฑะพะฒโ€™ัะทะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ +## ะžะฑะพะฒโ€™ัะทะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ { #required-parameters } -ะฏะบั‰ะพ ะฝะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะบะฐะทัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะฟะตั€ะตะฒั–ั€ะบะธ ะฐะฑะพ ะผะตั‚ะฐะดะฐะฝั–, ะผะธ ะผะพะถะตะผะพ ะทั€ะพะฑะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `q` ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะฟั€ะพัั‚ะพ ะฝะต ะพะณะพะปะพัˆัƒัŽั‡ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฝะฐะฟั€ะธะบะปะฐะด: +ะฏะบั‰ะพ ะฝะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะฒะฐะปั–ะดะฐั†ั–ั— ะฐะฑะพ ะผะตั‚ะฐะดะฐะฝั–, ะผะธ ะผะพะถะตะผะพ ะทั€ะพะฑะธั‚ะธ query ะฟะฐั€ะฐะผะตั‚ั€ `q` ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะฟั€ะพัั‚ะพ ะฝะต ะฒะบะฐะทัƒัŽั‡ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฝะฐะฟั€ะธะบะปะฐะด: ```Python q: str @@ -247,43 +234,39 @@ q: str q: str | None = None ``` -ะะปะต ั‚ะตะฟะตั€ ะผะธ ะพะณะพะปะพัˆัƒั”ะผะพ ะนะพะณะพ ะท `Query`, ะฝะฐะฟั€ะธะบะปะฐะด: - -//// tab | Annotated +ะะปะต ั‚ะตะฟะตั€ ะผะธ ะพะณะพะปะพัˆัƒั”ะผะพ ะนะพะณะพ ะท `Query`, ะฝะฐะฟั€ะธะบะปะฐะด ั‚ะฐะบ: ```Python q: Annotated[str | None, Query(min_length=3)] = None ``` -//// - -ะขะพะผัƒ, ัะบั‰ะพ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะทั€ะพะฑะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `Query`, ะฟั€ะพัั‚ะพ ะฝะต ะฒะบะฐะทัƒะนั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: +ะขะพะผัƒ, ัะบั‰ะพ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะพะณะพะปะพัะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ัะบ ะพะฑะพะฒโ€™ัะทะบะพะฒะต ะฟั–ะด ั‡ะฐั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `Query`, ะฟั€ะพัั‚ะพ ะฝะต ะฒะบะฐะทัƒะนั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### ะžะฑะพะฒโ€™ัะทะบะพะฒะต ะทะฝะฐั‡ะตะฝะฝั, ัะบะต ะผะพะถะต ะฑัƒั‚ะธ `None` +### ะžะฑะพะฒโ€™ัะทะบะพะฒะธะน, ะผะพะถะต ะฑัƒั‚ะธ `None` { #required-can-be-none } -ะ’ะธ ะผะพะถะตั‚ะต ะฒะบะฐะทะฐั‚ะธ, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะผะพะถะต ะฟั€ะธะนะผะฐั‚ะธ `None`, ะฐะปะต ะฟั€ะธ ั†ัŒะพะผัƒ ะทะฐะปะธัˆะฐั”ั‚ัŒัั ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ. ะฆะต ะทะผัƒัะธั‚ัŒ ะบะปั–ั”ะฝั‚ั–ะฒ ะฝะฐะดั–ัะปะฐั‚ะธ ะทะฝะฐั‡ะตะฝะฝั, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะพะฝะพ ะดะพั€ั–ะฒะฝัŽั” `None`. +ะ’ะธ ะผะพะถะตั‚ะต ะฒะบะฐะทะฐั‚ะธ, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะผะพะถะต ะฟั€ะธะนะผะฐั‚ะธ `None`, ะฐะปะต ะฟั€ะธ ั†ัŒะพะผัƒ ะทะฐะปะธัˆะฐั”ั‚ัŒัั ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ. ะฆะต ะทะผัƒัะธั‚ัŒ ะบะปั–ั”ะฝั‚ั–ะฒ ะฝะฐะดั–ัะปะฐั‚ะธ ะทะฝะฐั‡ะตะฝะฝั, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะทะฝะฐั‡ะตะฝะฝั ะดะพั€ั–ะฒะฝัŽั” `None`. -ะฉะพะฑ ะทั€ะพะฑะธั‚ะธ ั†ะต, ะพะณะพะปะพัั–ั‚ัŒ, ั‰ะพ `None` ั” ะดะพะฟัƒัั‚ะธะผะธะผ ั‚ะธะฟะพะผ, ะฐะปะต ะฝะต ะฒะบะฐะทัƒะนั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: +ะฉะพะฑ ะทั€ะพะฑะธั‚ะธ ั†ะต, ะพะณะพะปะพัั–ั‚ัŒ, ั‰ะพ `None` ั” ะดะพะฟัƒัั‚ะธะผะธะผ ั‚ะธะฟะพะผ, ะฐะปะต ะฟั€ะพัั‚ะพ ะฝะต ะฒะบะฐะทัƒะนั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: {* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} -## ะกะฟะธัะพะบ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะฟะธั‚ัƒ / ะบั–ะปัŒะบะฐ ะทะฝะฐั‡ะตะฝัŒ +## ะกะฟะธัะพะบ query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ / ะบั–ะปัŒะบะฐ ะทะฝะฐั‡ะตะฝัŒ { #query-parameter-list-multiple-values } -ะฏะบั‰ะพ ะ’ะธ ะฒะธะทะฝะฐั‡ะฐั”ั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Query`, ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะดะพะทะฒะพะปะธั‚ะธ ะพั‚ั€ะธะผะฐะฝะฝั ัะฟะธัะบัƒ ะทะฝะฐั‡ะตะฝัŒ, ั‚ะพะฑั‚ะพ ะดะพะทะฒะพะปะธั‚ะธ ะพั‚ั€ะธะผะฐะฝะฝั ะบั–ะปัŒะบะพั… ะทะฝะฐั‡ะตะฝัŒ. +ะšะพะปะธ ะฒะธ ัะฒะฝะพ ะฒะธะทะฝะฐั‡ะฐั”ั‚ะต query ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `Query`, ะฒะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ, ั‰ะพ ะฒั–ะฝ ะผะฐั” ะฟั€ะธะนะผะฐั‚ะธ ัะฟะธัะพะบ ะทะฝะฐั‡ะตะฝัŒ, ะฐะฑะพ, ั–ะฝัˆะธะผะธ ัะปะพะฒะฐะผะธ, ะบั–ะปัŒะบะฐ ะทะฝะฐั‡ะตะฝัŒ. -ะะฐะฟั€ะธะบะปะฐะด, ั‰ะพะฑ ะดะพะทะฒะพะปะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ัƒ ะทะฐะฟะธั‚ัƒ `q` ะท'ัะฒะปัั‚ะธัั ะบั–ะปัŒะบะฐ ั€ะฐะทั–ะฒ ะฒ URL, ะผะพะถะฝะฐ ะฝะฐะฟะธัะฐั‚ะธ: +ะะฐะฟั€ะธะบะปะฐะด, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ query ะฟะฐั€ะฐะผะตั‚ั€ `q`, ัะบะธะน ะผะพะถะต ะทโ€™ัะฒะปัั‚ะธัั ะฒ URL ะบั–ะปัŒะบะฐ ั€ะฐะทั–ะฒ, ะผะพะถะฝะฐ ะฝะฐะฟะธัะฐั‚ะธ: {* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *} -ะขะพะดั–, ัƒ ะฒะธะฟะฐะดะบัƒ ะทะฐะฟะธั‚ัƒ ะทะฐ URL: +ะขะพะดั–, ัƒ ะฒะธะฟะฐะดะบัƒ URL: ``` http://localhost:8000/items/?q=foo&q=bar ``` -ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะบั–ะปัŒะบะฐ ะทะฝะฐั‡ะตะฝัŒ *query ะฟะฐั€ะฐะผะตั‚ั€ะฐ* `q` (`foo` ั– `bar`) ัƒ ะฒะธะณะปัะดั– ัะฟะธัะบัƒ `list` ะฒ Python ัƒ ะ’ะฐัˆั–ะน *ั„ัƒะฝะบั†ั–ั— ะพะฑั€ะพะฑะบะธ ัˆะปัั…ัƒ*, ัƒ *ะฟะฐั€ะฐะผะตั‚ั€ั– ั„ัƒะฝะบั†ั–ั—* `q`. +ะฒะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะบั–ะปัŒะบะฐ ะทะฝะฐั‡ะตะฝัŒ `q` *query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ* (`foo` ั– `bar`) ัƒ ะฒะธะณะปัะดั– Python `list` ัƒ ะฒะฐัˆั–ะน *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ัƒ *ะฟะฐั€ะฐะผะตั‚ั€ั– ั„ัƒะฝะบั†ั–ั—* `q`. ะžั‚ะถะต, ะฒั–ะดะฟะพะฒั–ะดัŒ ะฝะฐ ั†ะตะน URL ะฑัƒะดะต: @@ -296,9 +279,9 @@ http://localhost:8000/items/?q=foo&q=bar } ``` -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ -ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ ะท ั‚ะธะฟะพะผ `list`, ัะบ ัƒ ะฝะฐะฒะตะดะตะฝะพะผัƒ ะฒะธั‰ะต ะฟั€ะธะบะปะฐะดั–, ะฟะพั‚ั€ั–ะฑะฝะพ ัะฒะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Query`, ั–ะฝะฐะบัˆะต ะฒั–ะฝ ะฑัƒะดะต ั–ะฝั‚ะตั€ะฟั€ะตั‚ะพะฒะฐะฝะธะน ัะบ ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ. +ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ query ะฟะฐั€ะฐะผะตั‚ั€ ะท ั‚ะธะฟะพะผ `list`, ัะบ ัƒ ะฝะฐะฒะตะดะตะฝะพะผัƒ ะฒะธั‰ะต ะฟั€ะธะบะปะฐะดั–, ะฟะพั‚ั€ั–ะฑะฝะพ ัะฒะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Query`, ั–ะฝะฐะบัˆะต ะฒั–ะฝ ะฑัƒะดะต ั–ะฝั‚ะตั€ะฟั€ะตั‚ะพะฒะฐะฝะธะน ัะบ ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ. /// @@ -306,19 +289,19 @@ http://localhost:8000/items/?q=foo&q=bar <img src="/img/tutorial/query-params-str-validations/image02.png"> -### ะกะฟะธัะพะบ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะทะฐะฟะธั‚ัƒ / ะบั–ะปัŒะบะฐ ะทะฝะฐั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ +### ะกะฟะธัะพะบ query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ / ะบั–ะปัŒะบะฐ ะทะฝะฐั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ { #query-parameter-list-multiple-values-with-defaults } -ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะดะปั `list`, ัะบั‰ะพ ะถะพะดะฝะต ะทะฝะฐั‡ะตะฝะฝั ะฝะต ะฑัƒะปะพ ะฟะตั€ะตะดะฐะฝะต: +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `list`, ัะบั‰ะพ ะถะพะดะฝะต ะทะฝะฐั‡ะตะฝะฝั ะฝะต ะฑัƒะปะพ ะฟะตั€ะตะดะฐะฝะต: {* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} -ะฏะบั‰ะพ ะ’ะธ ะฟะตั€ะตะนะดะตั‚ะต ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ: +ะฏะบั‰ะพ ะฒะธ ะฟะตั€ะตะนะดะตั‚ะต ะทะฐ ะฟะพัะธะปะฐะฝะฝัะผ: ``` http://localhost:8000/items/ ``` -ั‚ะพ ะทะฝะฐั‡ะตะฝะฝั `q` ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฑัƒะดะต: `["foo", "bar"]`, ั– ะ’ะฐัˆะฐ ะฒั–ะดะฟะพะฒั–ะดัŒ ะฒะธะณะปัะดะฐั‚ะธะผะต ั‚ะฐะบ: +ั‚ะพ ะทะฝะฐั‡ะตะฝะฝั `q` ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฑัƒะดะต: `["foo", "bar"]`, ั– ะฒะฐัˆะฐ ะฒั–ะดะฟะพะฒั–ะดัŒ ะฒะธะณะปัะดะฐั‚ะธะผะต ั‚ะฐะบ: ```JSON { @@ -329,35 +312,35 @@ http://localhost:8000/items/ } ``` -#### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ั‚ั–ะปัŒะบะธ `list` +#### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ั‚ั–ะปัŒะบะธ `list` { #using-just-list } -ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `list` ะฑะตะท ัƒั‚ะพั‡ะฝะตะฝะฝั ั‚ะธะฟัƒ, ะทะฐะผั–ัั‚ัŒ `list[str]`: +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `list` ะฝะฐะฟั€ัะผัƒ ะทะฐะผั–ัั‚ัŒ `list[str]`: {* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} -/// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– +/// note | ะŸั€ะธะผั–ั‚ะบะฐ ะœะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ะฒ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ FastAPI ะฝะต ะฟะตั€ะตะฒั–ั€ัั‚ะธะผะต ะฒะผั–ัั‚ ัะฟะธัะบัƒ. -ะะฐะฟั€ะธะบะปะฐะด, `list[int]` ะฟะตั€ะตะฒั–ั€ัั‚ะธะผะต (ั– ะดะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธะผะต), ั‰ะพ ะฒัั– ะตะปะตะผะตะฝั‚ะธ ัะฟะธัะบัƒ ั” ั†ั–ะปะธะผะธ ั‡ะธัะปะฐะผะธ. ะะปะต `list` ะฑะตะท ัƒั‚ะพั‡ะฝะตะฝะฝั ั†ัŒะพะณะพ ะฝะต ั€ะพะฑะธั‚ะธะผะต. +ะะฐะฟั€ะธะบะปะฐะด, `list[int]` ะฟะตั€ะตะฒั–ั€ัั‚ะธะผะต (ั– ะดะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธะผะต), ั‰ะพ ะฒะผั–ัั‚ ัะฟะธัะบัƒ โ€” ั†ั–ะปั– ั‡ะธัะปะฐ. ะะปะต `list` ะฑะตะท ัƒั‚ะพั‡ะฝะตะฝะฝั ั†ัŒะพะณะพ ะฝะต ั€ะพะฑะธั‚ะธะผะต. /// -## ะ”ะพะดะฐะฒะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะธั… ะผะตั‚ะฐะดะฐะฝะธั… +## ะžะณะพะปะพัะธั‚ะธ ะฑั–ะปัŒัˆะต ะผะตั‚ะฐะดะฐะฝะธั… { #declare-more-metadata } ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะฑั–ะปัŒัˆะต ั–ะฝั„ะพั€ะผะฐั†ั–ั— ะฟั€ะพ ะฟะฐั€ะฐะผะตั‚ั€. -ะฆั ั–ะฝั„ะพั€ะผะฐั†ั–ั ะฑัƒะดะต ะฒะบะปัŽั‡ะตะฝะฐ ัƒ ะทะณะตะฝะตั€ะพะฒะฐะฝะธะน OpenAPI ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะฐะฝะฐ ะฒ ั–ะฝั‚ะตั€ั„ะตะนัะฐั… ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ั‚ะฐ ะทะพะฒะฝั–ัˆะฝั–ั… ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐั…. +ะฆั ั–ะฝั„ะพั€ะผะฐั†ั–ั ะฑัƒะดะต ะฒะบะปัŽั‡ะตะฝะฐ ัƒ ะทะณะตะฝะตั€ะพะฒะฐะฝะธะน OpenAPI ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะฐะฝะฐ ั–ะฝั‚ะตั€ั„ะตะนัะฐะผะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ั‚ะฐ ะทะพะฒะฝั–ัˆะฝั–ะผะธ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ. -/// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– +/// note | ะŸั€ะธะผั–ั‚ะบะฐ ะœะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ั€ั–ะทะฝั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะผะพะถัƒั‚ัŒ ะผะฐั‚ะธ ั€ั–ะทะฝะธะน ั€ั–ะฒะตะฝัŒ ะฟั–ะดั‚ั€ะธะผะบะธ OpenAPI. -ะ”ะตัะบั– ะท ะฝะธั… ะผะพะถัƒั‚ัŒ ั‰ะต ะฝะต ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฒััŽ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ, ั…ะพั‡ะฐ ะฒ ะฑั–ะปัŒัˆะพัั‚ั– ะฒะธะฟะฐะดะบั–ะฒ ั†ั ั„ัƒะฝะบั†ั–ั ะฒะถะต ะทะฐะฟะปะฐะฝะพะฒะฐะฝะฐ ะดะปั ั€ะพะทั€ะพะฑะบะธ. +ะ”ะตัะบั– ะท ะฝะธั… ะผะพะถัƒั‚ัŒ ั‰ะต ะฝะต ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธ ะฒััŽ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ, ั…ะพั‡ะฐ ะฒ ะฑั–ะปัŒัˆะพัั‚ั– ะฒะธะฟะฐะดะบั–ะฒ ะฒั–ะดััƒั‚ะฝัŽ ั„ัƒะฝะบั†ั–ัŽ ะฒะถะต ะทะฐะฟะปะฐะฝะพะฒะฐะฝะพ ะดะพ ั€ะตะฐะปั–ะทะฐั†ั–ั—. /// -ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ `title` : +ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ `title`: {* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *} @@ -365,9 +348,9 @@ http://localhost:8000/items/ {* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *} -## ะะปั–ะฐัะธ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ +## ะะปั–ะฐัะธ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ { #alias-parameters } -ะฃัะฒั–ั‚ัŒ, ั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะฟะฐั€ะฐะผะตั‚ั€ ะฝะฐะทะธะฒะฐะฒัั `item-query`. +ะฃัะฒั–ั‚ัŒ, ั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะฟะฐั€ะฐะผะตั‚ั€ ะฝะฐะทะธะฒะฐะฒัั `item-query`. ะะฐะฟั€ะธะบะปะฐะด: @@ -379,19 +362,19 @@ http://127.0.0.1:8000/items/?item-query=foobaritems ะะฐะนะฑะปะธะถั‡ะธะน ะดะพะฟัƒัั‚ะธะผะธะน ะฒะฐั€ั–ะฐะฝั‚ โ€” `item_query`. -ะŸั€ะพั‚ะต ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ, ั‰ะพะฑ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะปะธัˆะฐะฒัั ัะฐะผะต `item-query`... +ะŸั€ะพั‚ะต ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ, ั‰ะพะฑ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะปะธัˆะฐะฒัั ัะฐะผะต `item-query`... ะฃ ั‚ะฐะบะพะผัƒ ะฒะธะฟะฐะดะบัƒ ะผะพะถะฝะฐ ะพะณะพะปะพัะธั‚ะธ `alias`, ั– ัะฐะผะต ะฒั–ะฝ ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธัั ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะทะฝะฐั‡ะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ะฐ: {* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *} -## ะ’ะธะฒะตะดะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัะบ ะทะฐัั‚ะฐั€ั–ะปะธั… +## ะŸะพะทะฝะฐั‡ะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัะบ ะทะฐัั‚ะฐั€ั–ะปะธั… { #deprecating-parameters } -ะŸั€ะธะฟัƒัั‚ะธะผะพ, ั‰ะพ ะ’ะธ ะฑั–ะปัŒัˆะต ะฝะต ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั†ะตะน ะฟะฐั€ะฐะผะตั‚ั€. +ะŸั€ะธะฟัƒัั‚ั–ะผะพ, ั‰ะพ ะฒะฐะผ ะฑั–ะปัŒัˆะต ะฝะต ะฟะพะดะพะฑะฐั”ั‚ัŒัั ั†ะตะน ะฟะฐั€ะฐะผะตั‚ั€. -ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะทะฐะปะธัˆะธั‚ะธ ะนะพะณะพ ะฝะฐ ะดะตัะบะธะน ั‡ะฐั, ะพัะบั–ะปัŒะบะธ ะฝะธะผ ะบะพั€ะธัั‚ัƒัŽั‚ัŒัั ะบะปั–ั”ะฝั‚ะธ, ะฐะปะต ะ’ะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั ั‡ั–ั‚ะบะพ ะฟะพะบะฐะทัƒะฒะฐะปะฐ, ั‰ะพ ะฒั–ะฝ ั” <abbr title="ะทะฐัั‚ะฐั€ั–ะปะธะน, ะฝะต ั€ะตะบะพะผะตะฝะดัƒั”ั‚ัŒัั ะดะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั">ะทะฐัั‚ะฐั€ั–ะปะธะผ</abbr>. +ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะทะฐะปะธัˆะธั‚ะธ ะนะพะณะพ ะฝะฐ ะดะตัะบะธะน ั‡ะฐั, ะพัะบั–ะปัŒะบะธ ะฝะธะผ ะบะพั€ะธัั‚ัƒัŽั‚ัŒัั ะบะปั–ั”ะฝั‚ะธ, ะฐะปะต ะฒะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั ั‡ั–ั‚ะบะพ ะฟะพะบะฐะทัƒะฒะฐะปะฐ, ั‰ะพ ะฒั–ะฝ ั” <abbr title="obsolete, recommended not to use it โ€“ ะทะฐัั‚ะฐั€ั–ะปะธะน, ะฝะต ั€ะตะบะพะผะตะฝะดัƒั”ั‚ัŒัั ะดะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั">deprecated</abbr>. -ะขะพะดั– ะ’ะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `deprecated=True` ะดะพ `Query`: +ะขะพะดั– ะฟะตั€ะตะดะฐะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ `deprecated=True` ะดะพ `Query`: {* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *} @@ -399,27 +382,27 @@ http://127.0.0.1:8000/items/?item-query=foobaritems <img src="/img/tutorial/query-params-str-validations/image01.png"> -## ะ’ะธะฝัั‚ะพะบ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะท OpenAPI +## ะ’ะธะฝัั‚ะพะบ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ะท OpenAPI { #exclude-parameters-from-openapi } -ะฉะพะฑ ะฒะธะบะปัŽั‡ะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ ะทั– ะทะณะตะฝะตั€ะพะฒะฐะฝะพั— ัั…ะตะผะธ OpenAPI (ั–, ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ, ะท ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะธั… ัะธัั‚ะตะผ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—), ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `include_in_schema` ะดะปั `Query` ะฒ `False`: +ะฉะพะฑ ะฒะธะบะปัŽั‡ะธั‚ะธ query ะฟะฐั€ะฐะผะตั‚ั€ ะทั– ะทะณะตะฝะตั€ะพะฒะฐะฝะพั— ัั…ะตะผะธ OpenAPI (ั–, ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ, ะท ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะธั… ัะธัั‚ะตะผ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—), ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `include_in_schema` ะดะปั `Query` ะฒ `False`: {* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *} -## ะšะฐัั‚ะพะผะฝะฐ ะฒะฐะปั–ะดะฐั†ั–ั +## ะšะฐัั‚ะพะผะฝะฐ ะฒะฐะปั–ะดะฐั†ั–ั { #custom-validation } -ะœะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะฒะธะฟะฐะดะบะธ, ะบะพะปะธ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟั€ะพะฒะตัั‚ะธ **ะบะฐัั‚ะพะผะฝัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ**, ัะบัƒ ะฝะต ะผะพะถะฝะฐ ั€ะตะฐะปั–ะทัƒะฒะฐั‚ะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ะฟะพะบะฐะทะฐะฝะธั… ะฒะธั‰ะต. +ะœะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะฒะธะฟะฐะดะบะธ, ะบะพะปะธ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟั€ะพะฒะตัั‚ะธ **ะบะฐัั‚ะพะผะฝัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ**, ัะบัƒ ะฝะต ะผะพะถะฝะฐ ั€ะตะฐะปั–ะทัƒะฒะฐั‚ะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ะฟะพะบะฐะทะฐะฝะธั… ะฒะธั‰ะต. -ะฃ ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบะฐั… ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ **ะบะฐัั‚ะพะผะฝัƒ ั„ัƒะฝะบั†ั–ัŽ ะฒะฐะปั–ะดะฐั†ั–ั—**, ัะบะฐ ะฑัƒะดะต ะทะฐัั‚ะพัะพะฒะฐะฝะฐ ะฟั–ัะปั ะทะฒะธั‡ะฐะนะฝะพั— ะฒะฐะปั–ะดะฐั†ั–ั— (ะฝะฐะฟั€ะธะบะปะฐะด, ะฟั–ัะปั ะฟะตั€ะตะฒั–ั€ะบะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั ั” ั‚ะธะฟะพะผ `str`). +ะฃ ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบะฐั… ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ **ะบะฐัั‚ะพะผะฝัƒ ั„ัƒะฝะบั†ั–ัŽ-ะฒะฐะปั–ะดะฐั‚ะพั€**, ัะบะฐ ะฑัƒะดะต ะทะฐัั‚ะพัะพะฒะฐะฝะฐ ะฟั–ัะปั ะทะฒะธั‡ะฐะนะฝะพั— ะฒะฐะปั–ะดะฐั†ั–ั— (ะฝะฐะฟั€ะธะบะปะฐะด, ะฟั–ัะปั ะฟะตั€ะตะฒั–ั€ะบะธ, ั‰ะพ ะทะฝะฐั‡ะตะฝะฝั ั” ั‚ะธะฟะพะผ `str`). ะฆะต ะผะพะถะฝะฐ ะดะพััะณั‚ะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-after-validator" class="external-link" target="_blank">Pydantic's `AfterValidator`</a> ะฒ ัะตั€ะตะดะธะฝั– `Annotated`. -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ Pydantic ั‚ะฐะบะพะถ ะผะฐั” <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-before-validator" class="external-link" target="_blank">`BeforeValidator`</a> ั‚ะฐ ั–ะฝัˆั–. ๐Ÿค“ /// -ะะฐะฟั€ะธะบะปะฐะด, ั†ะตะน ะบะฐัั‚ะพะผะฝะธะน ะฒะฐะปั–ะดะฐั‚ะพั€ ะฟะตั€ะตะฒั–ั€ัั”, ั‡ะธ ะฟะพั‡ะธะฝะฐั”ั‚ัŒัั ID ะตะปะตะผะตะฝั‚ะฐ ะท `isbn-` ะดะปั ะฝะพะผะตั€ะฐ ะบะฝะธะณะธ <abbr title="ISBN ะพะทะฝะฐั‡ะฐั” ะœั–ะถะฝะฐั€ะพะดะฝะธะน ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ะฝะพะผะตั€ ะบะฝะธะณะธ">ISBN</abbr> ะฐะฑะพ ะท `imdb-` ะดะปั ID URL ั„ั–ะปัŒะผัƒ ะฝะฐ <abbr title="IMDB (Internet Movie Database) ั†ะต ะฒะตะฑัะฐะนั‚ ะท ั–ะฝั„ะพั€ะผะฐั†ั–ั”ัŽ ะฟั€ะพ ั„ั–ะปัŒะผะธ">IMDB</abbr>: +ะะฐะฟั€ะธะบะปะฐะด, ั†ะตะน ะบะฐัั‚ะพะผะฝะธะน ะฒะฐะปั–ะดะฐั‚ะพั€ ะฟะตั€ะตะฒั–ั€ัั”, ั‡ะธ ะฟะพั‡ะธะฝะฐั”ั‚ัŒัั ID ะตะปะตะผะตะฝั‚ะฐ ะท `isbn-` ะดะปั ะฝะพะผะตั€ะฐ ะบะฝะธะณะธ <abbr title="ISBN means International Standard Book Number โ€“ ISBN ะพะทะฝะฐั‡ะฐั” ะœั–ะถะฝะฐั€ะพะดะฝะธะน ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ะฝะพะผะตั€ ะบะฝะธะณะธ">ISBN</abbr> ะฐะฑะพ ะท `imdb-` ะดะปั ID URL ั„ั–ะปัŒะผัƒ ะฝะฐ <abbr title="IMDB (Internet Movie Database) is a website with information about movies โ€“ IMDB (Internet Movie Database) ั†ะต ะฒะตะฑัะฐะนั‚ ะท ั–ะฝั„ะพั€ะผะฐั†ั–ั”ัŽ ะฟั€ะพ ั„ั–ะปัŒะผะธ">IMDB</abbr>: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -429,49 +412,49 @@ Pydantic ั‚ะฐะบะพะถ ะผะฐั” <a href="https://docs.pydantic.dev/latest/concepts/va /// -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ -ะฏะบั‰ะพ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพะฝะฐั‚ะธ ะฑัƒะดัŒ-ัะบัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ, ัะบะฐ ะฒะธะผะฐะณะฐั” ะฒะทะฐั”ะผะพะดั–ั— ะท ะฑัƒะดัŒ-ัะบะธะผ **ะทะพะฒะฝั–ัˆะฝั–ะผ ะบะพะผะฟะพะฝะตะฝั‚ะพะผ**, ั‚ะฐะบะธะผ ัะบ ะฑะฐะทะฐ ะดะฐะฝะธั… ั‡ะธ ั–ะฝัˆะธะน API, ะฒะธ ะฟะพะฒะธะฝะฝั– ะทะฐะผั–ัั‚ัŒ ั†ัŒะพะณะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ **FastAPI Dependencies**. ะ’ะธ ะดั–ะทะฝะฐั”ั‚ะตััŒ ะฟั€ะพ ะฝะธั… ะฟั–ะทะฝั–ัˆะต. +ะฏะบั‰ะพ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพะฝะฐั‚ะธ ะฑัƒะดัŒ-ัะบัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ, ัะบะฐ ะฒะธะผะฐะณะฐั” ะฒะทะฐั”ะผะพะดั–ั— ะท ะฑัƒะดัŒ-ัะบะธะผ **ะทะพะฒะฝั–ัˆะฝั–ะผ ะบะพะผะฟะพะฝะตะฝั‚ะพะผ**, ั‚ะฐะบะธะผ ัะบ ะฑะฐะทะฐ ะดะฐะฝะธั… ั‡ะธ ั–ะฝัˆะธะน API, ะทะฐะผั–ัั‚ัŒ ั†ัŒะพะณะพ ัะปั–ะด ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ **FastAPI Dependencies** โ€” ะฒะธ ะดั–ะทะฝะฐั”ั‚ะตััŒ ะฟั€ะพ ะฝะธั… ะฟั–ะทะฝั–ัˆะต. -ะฆั– ะบะฐัั‚ะพะผะฝั– ะฒะฐะปั–ะดะฐั‚ะพั€ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะดะปั ั€ะตั‡ะตะน, ัะบั– ะผะพะถะฝะฐ ะฟะตั€ะตะฒั–ั€ะธั‚ะธ ะปะธัˆะต ะท **ั‚ะธะผะธ ะดะฐะฝะธะผะธ**, ั‰ะพ ะฝะฐะดะฐะฝั– ะฒ ะทะฐะฟะธั‚ั–. +ะฆั– ะบะฐัั‚ะพะผะฝั– ะฒะฐะปั–ะดะฐั‚ะพั€ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะดะปั ั€ะตั‡ะตะน, ัะบั– ะผะพะถะฝะฐ ะฟะตั€ะตะฒั–ั€ะธั‚ะธ ะปะธัˆะต ะท **ั‚ั–ะธะผะธ ัะฐะผะธะผะธ ะดะฐะฝะธะผะธ**, ั‰ะพ ะฝะฐะดะฐะฝั– ะฒ ะทะฐะฟะธั‚ั–. /// -### ะ—ั€ะพะทัƒะผั–ะนั‚ะต ั†ะตะน ะบะพะด +### ะ—ั€ะพะทัƒะผั–ะนั‚ะต ั†ะตะน ะบะพะด { #understand-that-code } -ะ“ะพะปะพะฒะฝะธะน ะผะพะผะตะฝั‚ โ€“ ั†ะต ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั **`AfterValidator` ะท ั„ัƒะฝะบั†ั–ั”ัŽ ะฒัะตั€ะตะดะธะฝั– `Annotated`**. ะœะพะถะตั‚ะต ะฟั€ะพะฟัƒัั‚ะธั‚ะธ ั†ัŽ ั‡ะฐัั‚ะธะฝัƒ, ัะบั‰ะพ ั…ะพั‡ะตั‚ะต. ๐Ÿคธ +ะ“ะพะปะพะฒะฝะธะน ะผะพะผะตะฝั‚ โ€” ั†ะต ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั **`AfterValidator` ะท ั„ัƒะฝะบั†ั–ั”ัŽ ะฒัะตั€ะตะดะธะฝั– `Annotated`**. ะœะพะถะตั‚ะต ะฟั€ะพะฟัƒัั‚ะธั‚ะธ ั†ัŽ ั‡ะฐัั‚ะธะฝัƒ, ัะบั‰ะพ ั…ะพั‡ะตั‚ะต. ๐Ÿคธ --- -ะะปะต ัะบั‰ะพ ะ’ะฐะผ ั†ั–ะบะฐะฒะพ ั€ะพะทั–ะฑั€ะฐั‚ะธัั ะฒ ั†ัŒะพะผัƒ ะบะพะฝะบั€ะตั‚ะฝะพะผัƒ ะฟั€ะธะบะปะฐะดั– ะบะพะดัƒ ั– ะ’ะฐะผ ั‰ะต ะฝะต ะฝะฐะฑั€ะธะดะปะพ, ะพััŒ ะบั–ะปัŒะบะฐ ะดะพะดะฐั‚ะบะพะฒะธั… ะดะตั‚ะฐะปะตะน. +ะะปะต ัะบั‰ะพ ะฒะฐะผ ั†ั–ะบะฐะฒะพ ั€ะพะทั–ะฑั€ะฐั‚ะธัั ะฒ ั†ัŒะพะผัƒ ะบะพะฝะบั€ะตั‚ะฝะพะผัƒ ะฟั€ะธะบะปะฐะดั– ะบะพะดัƒ ั– ะฒะฐะผ ั‰ะต ะฝะต ะฝะฐะฑั€ะธะดะปะพ, ะพััŒ ะบั–ะปัŒะบะฐ ะดะพะดะฐั‚ะบะพะฒะธั… ะดะตั‚ะฐะปะตะน. -#### ะ ัะดะพะบ ั–ะท `value.startswith()` +#### ะ ัะดะพะบ ั–ะท `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()` ะผะธ ะพั‚ั€ะธะผัƒั”ะผะพ <abbr title="ะžะฑ'ั”ะบั‚, ัะบะธะน ะผะพะถะฝะฐ ะฟะตั€ะตะฑะธั€ะฐั‚ะธ ะฒ ั†ะธะบะปั–, ัะบ-ะพั‚ ัะฟะธัะพะบ ั‡ะธ ะผะฝะพะถะธะฝัƒ.">ั–ั‚ะตั€ะฐะฑะตะปัŒะฝะธะน ะพะฑ'ั”ะบั‚</abbr> ั–ะท ะบะพั€ั‚ะตะถะฐะผะธ, ั‰ะพ ะผั–ัั‚ัั‚ัŒ ะบะปัŽั‡ ั– ะทะฝะฐั‡ะตะฝะฝั ะดะปั ะบะพะถะฝะพะณะพ ะตะปะตะผะตะฝั‚ะฐ ัะปะพะฒะฝะธะบะฐ. +ะ—ะฐ ะดะพะฟะพะผะพะณะพัŽ `data.items()` ะผะธ ะพั‚ั€ะธะผัƒั”ะผะพ <abbr title="Something we can iterate on with a for loop, like a list, set, etc. โ€“ ะžะฑ'ั”ะบั‚, ัะบะธะน ะผะพะถะฝะฐ ะฟะตั€ะตะฑะธั€ะฐั‚ะธ ะฒ ั†ะธะบะปั–, ัะบ-ะพั‚ ัะฟะธัะพะบ ั‡ะธ ะผะฝะพะถะธะฝัƒ.">iterable object</abbr> ั–ะท ะบะพั€ั‚ะตะถะฐะผะธ, ั‰ะพ ะผั–ัั‚ัั‚ัŒ ะบะปัŽั‡ ั– ะทะฝะฐั‡ะตะฝะฝั ะดะปั ะบะพะถะฝะพะณะพ ะตะปะตะผะตะฝั‚ะฐ ัะปะพะฒะฝะธะบะฐ. -ะœะธ ะฟะตั€ะตั‚ะฒะพั€ัŽั”ะผะพ ั†ะตะน ั–ั‚ะตั€ะฐะฑะตะปัŒะฝะธะน ะพะฑ'ั”ะบั‚ ัƒ ะทะฒะธั‡ะฐะนะฝะธะน `list` ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `list(data.items())`. +ะœะธ ะฟะตั€ะตั‚ะฒะพั€ัŽั”ะผะพ ั†ะตะน iterable object ัƒ ะทะฒะธั‡ะฐะนะฝะธะน `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 ะตะปะตะผะตะฝั‚ะฐ, ะฒั–ะฝ ะฒัะต ะพะดะฝะพ ะพั‚ั€ะธะผะฐั” ะฒะธะฟะฐะดะบะพะฒัƒ ั€ะตะบะพะผะตะฝะดะฐั†ั–ัŽ. -...ั– ะฒัะต ั†ะต ั€ะตะฐะปั–ะทะพะฒะฐะฝะพ ะฒ **ะพะดะฝะพะผัƒ ั€ัะดะบัƒ ะบะพะดัƒ**. ๐Ÿคฏ ะฅั–ะฑะฐ ะฝะต ะฟั€ะตะบั€ะฐัะฝะธะน Python? ๐Ÿ +...ะผะธ ั€ะพะฑะธะผะพ ะฒัะต ั†ะต ะฒ **ะพะดะฝะพะผัƒ ะฟั€ะพัั‚ะพะผัƒ ั€ัะดะบัƒ**. ๐Ÿคฏ ะฅั–ะฑะฐ ะฒะธ ะฝะต ะปัŽะฑะธั‚ะต Python? ๐Ÿ {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะฒะฐะปั–ะดะฐั†ั–ั— ั‚ะฐ ะผะตั‚ะฐั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะดะปั ัะฒะพั—ั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ. +ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะฒะฐะปั–ะดะฐั†ั–ั— ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั– ะดะปั ะฒะฐัˆะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ. -ะ—ะฐะณะฐะปัŒะฝั– ะฒะฐะปั–ะดะฐั†ั–ั— ั‚ะฐ ะผะตั‚ะฐั–ะฝั„ะพั€ะผะฐั†ั–ั: +ะ—ะฐะณะฐะปัŒะฝั– ะฒะฐะปั–ะดะฐั†ั–ั— ั‚ะฐ ะผะตั‚ะฐะดะฐะฝั–: * `alias` * `title` @@ -486,6 +469,6 @@ Pydantic ั‚ะฐะบะพะถ ะผะฐั” <a href="https://docs.pydantic.dev/latest/concepts/va ะšะฐัั‚ะพะผะฝั– ะฒะฐะปั–ะดะฐั†ั–ั— ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `AfterValidator`. -ะฃ ั†ะธั… ะฟั€ะธะบะปะฐะดะฐั… ะ’ะธ ะฟะพะฑะฐั‡ะธะปะธ, ัะบ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ั— ะดะปั ะทะฝะฐั‡ะตะฝัŒ `str`. +ะฃ ั†ะธั… ะฟั€ะธะบะปะฐะดะฐั… ะฒะธ ะฟะพะฑะฐั‡ะธะปะธ, ัะบ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ั— ะดะปั ะทะฝะฐั‡ะตะฝัŒ `str`. ะ”ะธะฒั–ั‚ัŒัั ะฝะฐัั‚ัƒะฟะฝั– ั€ะพะทะดั–ะปะธ, ั‰ะพะฑ ะดั–ะทะฝะฐั‚ะธัั, ัะบ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ั— ะดะปั ั–ะฝัˆะธั… ั‚ะธะฟั–ะฒ, ะฝะฐะฟั€ะธะบะปะฐะด ั‡ะธัะตะป. diff --git a/docs/uk/docs/tutorial/query-params.md b/docs/uk/docs/tutorial/query-params.md index 4b75ecab9a..a9068aa8f9 100644 --- a/docs/uk/docs/tutorial/query-params.md +++ b/docs/uk/docs/tutorial/query-params.md @@ -1,10 +1,10 @@ -# Query ะŸะฐั€ะฐะผะตั‚ั€ะธ +# Query ะฟะฐั€ะฐะผะตั‚ั€ะธ { #query-parameters } -ะšะพะปะธ ะ’ะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ั–ะฝัˆั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ัƒะฝะบั†ั–ั—, ัะบั– ะฝะต ั” ั‡ะฐัั‚ะธะฝะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัˆะปัั…ัƒ, ะฒะพะฝะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ั–ะฝั‚ะตั€ะฟั€ะตั‚ัƒัŽั‚ัŒัั ัะบ "query" ะฟะฐั€ะฐะผะตั‚ั€ะธ. +ะšะพะปะธ ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ั–ะฝัˆั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ัƒะฝะบั†ั–ั—, ัะบั– ะฝะต ั” ั‡ะฐัั‚ะธะฝะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัˆะปัั…ัƒ, ะฒะพะฝะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ั–ะฝั‚ะตั€ะฟั€ะตั‚ัƒัŽั‚ัŒัั ัะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ยซqueryยป. -{* ../../docs_src/query_params/tutorial001.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} -Query ะฟะฐั€ะฐะผะตั‚ั€ะธ โ€” ั†ะต ะฝะฐะฑั–ั€ ะฟะฐั€ ะบะปัŽั‡-ะทะฝะฐั‡ะตะฝะฝั, ั‰ะพ ะนะดัƒั‚ัŒ ะฟั–ัะปั ัะธะผะฒะพะปัƒ `?` ะฒ URL, ั€ะพะทะดั–ะปะตะฝั– ัะธะผะฒะพะปะฐะผะธ `&`. +Query โ€” ั†ะต ะฝะฐะฑั–ั€ ะฟะฐั€ ะบะปัŽั‡-ะทะฝะฐั‡ะตะฝะฝั, ั‰ะพ ะนะดัƒั‚ัŒ ะฟั–ัะปั ัะธะผะฒะพะปัƒ `?` ะฒ URL, ั€ะพะทะดั–ะปะตะฝั– ัะธะผะฒะพะปะฐะผะธ `&`. ะะฐะฟั€ะธะบะปะฐะด, ะฒ URL: @@ -12,41 +12,41 @@ Query ะฟะฐั€ะฐะผะตั‚ั€ะธ โ€” ั†ะต ะฝะฐะฑั–ั€ ะฟะฐั€ ะบะปัŽั‡-ะทะฝะฐั‡ะตะฝะฝั, http://127.0.0.1:8000/items/?skip=0&limit=10 ``` -...query ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ั”: +...ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ query ั”: * `skip`: ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `0` * `limit`: ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `10` -ะžัะบั–ะปัŒะบะธ ะฒะพะฝะธ ั” ั‡ะฐัั‚ะธะฝะพัŽ URL, ะฒะพะฝะธ "ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ" ั” ั€ัะดะบะฐะผะธ. +ะžัะบั–ะปัŒะบะธ ะฒะพะฝะธ ั” ั‡ะฐัั‚ะธะฝะพัŽ URL, ะฒะพะฝะธ ยซะฟั€ะธั€ะพะดะฝะพยป ั” ั€ัะดะบะฐะผะธ. -ะะปะต ะบะพะปะธ ะ’ะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ั—ั… ั–ะท ั‚ะธะฟะฐะผะธ Python (ัƒ ะฝะฐะฒะตะดะตะฝะพะผัƒ ะฟั€ะธะบะปะฐะดั– ัะบ `int`), ะฒะพะฝะธ ะฟะตั€ะตั‚ะฒะพั€ัŽัŽั‚ัŒัั ะฝะฐ ั†ะตะน ั‚ะธะฟ ั– ะฟั€ะพั…ะพะดัั‚ัŒ ะฟะตั€ะตะฒั–ั€ะบัƒ ะฒั–ะดะฟะพะฒั–ะดะฝะพัั‚ั–. +ะะปะต ะบะพะปะธ ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ั—ั… ั–ะท ั‚ะธะฟะฐะผะธ Python (ัƒ ะฝะฐะฒะตะดะตะฝะพะผัƒ ะฟั€ะธะบะปะฐะดั– ัะบ `int`), ะฒะพะฝะธ ะฟะตั€ะตั‚ะฒะพั€ัŽัŽั‚ัŒัั ะฝะฐ ั†ะตะน ั‚ะธะฟ ั– ะฟั€ะพั…ะพะดัั‚ัŒ ะฟะตั€ะตะฒั–ั€ะบัƒ ะฒั–ะดะฟะพะฒั–ะดะฝะพัั‚ั–. -ะฃะฒะตััŒ ั‚ะพะน ัะฐะผะธะน ะฟั€ะพั†ะตั, ัะบะธะน ะทะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั ะดะพ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัˆะปัั…ัƒ, ั‚ะฐะบะพะถ ะทะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั ะดะพ query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ: +ะฃะฒะตััŒ ั‚ะพะน ัะฐะผะธะน ะฟั€ะพั†ะตั, ัะบะธะน ะทะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั ะดะพ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัˆะปัั…ัƒ, ั‚ะฐะบะพะถ ะทะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั ะดะพ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ query: -* ะŸั–ะดั‚ั€ะธะผะบะฐ ะฒ ั€ะตะดะฐะบั‚ะพั€ั– (ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั, ะฟะตั€ะตะฒั–ั€ะบะฐ ะฟะพะผะธะปะพะบ) -* <abbr title="ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั€ัะดะบะฐ, ั‰ะพ ะฝะฐะดั…ะพะดะธั‚ัŒ ะท HTTP-ะทะฐะฟะธั‚ัƒ, ัƒ ั‚ะธะฟะธ ะดะฐะฝะธั… Python">"ะŸะฐั€ัะธะฝะณ"</abbr> ะดะฐะฝะธั… +* ะŸั–ะดั‚ั€ะธะผะบะฐ ะฒ ั€ะตะดะฐะบั‚ะพั€ั– (ะพั‡ะตะฒะธะดะฝะพ) +* <abbr title="converting the string that comes from an HTTP request into Python data โ€“ ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั€ัะดะบะฐ, ั‰ะพ ะฝะฐะดั…ะพะดะธั‚ัŒ ะท HTTP-ะทะฐะฟะธั‚ัƒ, ัƒ ะดะฐะฝั– Python">ยซparsingยป</abbr> ะดะฐะฝะธั… * ะ’ะฐะปั–ะดะฐั†ั–ั ะดะฐะฝะธั… * ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั +## ะ—ะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ { #defaults } -## ะ—ะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ - -ะžัะบั–ะปัŒะบะธ query ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฝะต ั” ั„ั–ะบัะพะฒะฐะฝะพัŽ ั‡ะฐัั‚ะธะฝะพัŽ ัˆะปัั…ัƒ, ะฒะพะฝะธ ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผะธ ั‚ะฐ ะผะฐั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. +ะžัะบั–ะปัŒะบะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ query ะฝะต ั” ั„ั–ะบัะพะฒะฐะฝะพัŽ ั‡ะฐัั‚ะธะฝะพัŽ ัˆะปัั…ัƒ, ะฒะพะฝะธ ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผะธ ั‚ะฐ ะผะฐั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. ะฃ ะฝะฐะฒะตะดะตะฝะพะผัƒ ะฒะธั‰ะต ะฟั€ะธะบะปะฐะดั– ะฒะพะฝะธ ะผะฐัŽั‚ัŒ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: `skip=0` ั– `limit=10`. -ะžั‚ะถะต, ั€ะตะทัƒะปัŒั‚ะฐั‚ ะฟะตั€ะตั…ะพะดัƒ ะทะฐ URL: +ะžั‚ะถะต, ะฟะตั€ะตั…ั–ะด ะทะฐ 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 @@ -54,12 +54,12 @@ http://127.0.0.1:8000/items/?skip=20 ะ—ะฝะฐั‡ะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัƒ ะฒะฐัˆั–ะน ั„ัƒะฝะบั†ั–ั— ะฑัƒะดัƒั‚ัŒ ั‚ะฐะบะธะผะธ: -* `skip=20`: ะพัะบั–ะปัŒะบะธ ะ’ะธ ะฒะบะฐะทะฐะปะธ ะนะพะณะพ ะฒ URL +* `skip=20`: ะพัะบั–ะปัŒะบะธ ะฒะธ ะฒะบะฐะทะฐะปะธ ะนะพะณะพ ะฒ URL * `limit=10`: ะพัะบั–ะปัŒะบะธ ั†ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ -## ะะตะพะฑะพะฒ'ัะทะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ +## ะะตะพะฑะพะฒ'ัะทะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ { #optional-parameters } -ะะฝะฐะปะพะณั–ั‡ะฝะพ, ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒั– query ะฟะฐั€ะฐะผะตั‚ั€ะธ, ะฒัั‚ะฐะฝะพะฒะธะฒัˆะธ ะดะปั ะฝะธั… ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `None`: +ะขะฐะบ ัะฐะผะพ ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ query, ะฒัั‚ะฐะฝะพะฒะธะฒัˆะธ ะดะปั ะฝะธั… ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `None`: {* ../../docs_src/query_params/tutorial002_py310.py hl[7] *} @@ -67,18 +67,17 @@ http://127.0.0.1:8000/items/?skip=20 /// check | ะŸั€ะธะผั–ั‚ะบะฐ -ะขะฐะบะพะถ ะทะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ **FastAPI** ะดะพัั‚ะฐั‚ะฝัŒะพ ั€ะพะทัƒะผะฝะธะน, ั‰ะพะฑ ะฒะธะทะฝะฐั‡ะธั‚ะธ, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ `item_id` ั” ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ัˆะปัั…ัƒ, ะฐ `q` โ€” ะฝั–, ะพั‚ะถะต, ั†ะต query ะฟะฐั€ะฐะผะตั‚ั€. +ะขะฐะบะพะถ ะทะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ **FastAPI** ะดะพัั‚ะฐั‚ะฝัŒะพ ั€ะพะทัƒะผะฝะธะน, ั‰ะพะฑ ะฒะธะทะฝะฐั‡ะธั‚ะธ, ั‰ะพ ะฟะฐั€ะฐะผะตั‚ั€ ัˆะปัั…ัƒ `item_id` ั” ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ัˆะปัั…ัƒ, ะฐ `q` โ€” ะฝั–, ะพั‚ะถะต, ั†ะต ะฟะฐั€ะฐะผะตั‚ั€ query. /// -## ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั‚ะธะฟัƒ Query ะฟะฐั€ะฐะผะตั‚ั€ะฐ +## ะŸะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั‚ะธะฟัƒ ะฟะฐั€ะฐะผะตั‚ั€ะฐ query { #query-parameter-type-conversion } ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะธะฟัƒ `bool`, ั– ะฒะพะฝะธ ะฑัƒะดัƒั‚ัŒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะพะฝะฒะตั€ั‚ะพะฒะฐะฝั–: {* ../../docs_src/query_params/tutorial003_py310.py hl[7] *} -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ, ัะบั‰ะพ ะ’ะธ ะทะฒะตั€ะฝะตั‚ะตััŒ ะดะพ: - +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ, ัะบั‰ะพ ะฒะธ ะฟะตั€ะตะนะดะตั‚ะต ะทะฐ: ``` http://127.0.0.1:8000/items/foo?short=1 @@ -108,38 +107,38 @@ http://127.0.0.1:8000/items/foo?short=on http://127.0.0.1:8000/items/foo?short=yes ``` -ะฐะฑะพ ะฑัƒะดัŒ-ัะบะธะน ั–ะฝัˆะธะน ะฒะฐั€ั–ะฐะฝั‚ ะฝะฐะฟะธัะฐะฝะฝั (ะฒะตะปะธะบั– ะปั–ั‚ะตั€ะธ, ะฟะตั€ัˆะฐ ะปั–ั‚ะตั€ะฐ ะฒะตะปะธะบะฐ ั‚ะพั‰ะพ), ะฒะฐัˆะฐ ั„ัƒะฝะบั†ั–ั ะฟะพะฑะฐั‡ะธั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `short` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `True` ะท ั‚ะธะฟะพะผ ะดะฐะฝะธั… `bool`. ะ’ ั–ะฝัˆะพะผัƒ ะฒะธะฟะฐะดะบัƒ โ€“ `False`. - -## ะšั–ะปัŒะบะฐ path ั– query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ - -ะ’ะธ ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะบั–ะปัŒะบะฐ path ั– query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ั– **FastAPI** ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธะทะฝะฐั‡ะธั‚ัŒ, ัะบะธะน ะท ะฝะธั… ะดะพ ั‡ะพะณะพ ะฝะฐะปะตะถะธั‚ัŒ. +ะฐะฑะพ ะฑัƒะดัŒ-ัะบะธะน ั–ะฝัˆะธะน ะฒะฐั€ั–ะฐะฝั‚ ะฝะฐะฟะธัะฐะฝะฝั (ะฒะตะปะธะบั– ะปั–ั‚ะตั€ะธ, ะฟะตั€ัˆะฐ ะปั–ั‚ะตั€ะฐ ะฒะตะปะธะบะฐ ั‚ะพั‰ะพ), ะฒะฐัˆะฐ ั„ัƒะฝะบั†ั–ั ะฟะพะฑะฐั‡ะธั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `short` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `True` ั‚ะธะฟัƒ `bool`. ะ’ ั–ะฝัˆะพะผัƒ ะฒะธะฟะฐะดะบัƒ โ€” `False`. -ะะต ะฟะพั‚ั€ั–ะฑะฝะพ ะดะพั‚ั€ะธะผัƒะฒะฐั‚ะธััŒ ะฟะตะฒะฝะพะณะพ ะฟะพั€ัะดะบัƒ ั—ั… ะพะณะพะปะพัˆะตะฝะฝั. +## ะšั–ะปัŒะบะฐ path ั– query ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ { #multiple-path-and-query-parameters } + +ะ’ะธ ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัˆะปัั…ัƒ ั‚ะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ query, **FastAPI** ะทะฝะฐั”, ัะบะธะน ะท ะฝะธั… ัะบะธะน. + +ะ† ะฒะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั—ั… ัƒ ัะบะพะผัƒััŒ ะบะพะฝะบั€ะตั‚ะฝะพะผัƒ ะฟะพั€ัะดะบัƒ. ะ’ะพะฝะธ ะฒะธะทะฝะฐั‡ะฐัŽั‚ัŒัั ะทะฐ ะฝะฐะทะฒะพัŽ: {* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} -## ะžะฑะพะฒโ€™ัะทะบะพะฒั– Query ะฟะฐั€ะฐะผะตั‚ั€ะธ +## ะžะฑะพะฒโ€™ัะทะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ query { #required-query-parameters } -ะฏะบั‰ะพ ะ’ะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ัะบั– ะฝะต ั” path-ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ (ัƒ ั†ัŒะพะผัƒ ั€ะพะทะดั–ะปั– ะผะธ ะฑะฐั‡ะธะปะธ ะฟะพะบะธ ั‰ะพ ะปะธัˆะต path ะฟะฐั€ะฐะผะตั‚ั€ะธ), ั‚ะพะดั– ะฒะพะฝะธ ัั‚ะฐัŽั‚ัŒ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผะธ. +ะšะพะปะธ ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะดะปั ะฝะต-path-ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ (ะฟะพะบะธ ั‰ะพ ะผะธ ะฑะฐั‡ะธะปะธ ะปะธัˆะต ะฟะฐั€ะฐะผะตั‚ั€ะธ query), ั‚ะพะดั– ะฒะพะฝะธ ะฝะต ั” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผะธ. -ะฏะบั‰ะพ ะ’ะธ ะฝะต ั…ะพั‡ะตั‚ะต ะฒะบะฐะทัƒะฒะฐั‚ะธ ะบะพะฝะบั€ะตั‚ะฝั– ะทะฝะฐั‡ะตะฝะฝั, ะฐะปะต ั…ะพั‡ะตั‚ะต ะทั€ะพะฑะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ะพะฟั†ั–ะพะฝะฐะปัŒะฝะธะผ, ะทะฐะดะฐะนั‚ะต `None` ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. +ะฏะบั‰ะพ ะฒะธ ะฝะต ั…ะพั‡ะตั‚ะต ะทะฐะดะฐะฒะฐั‚ะธ ะบะพะฝะบั€ะตั‚ะฝะต ะทะฝะฐั‡ะตะฝะฝั, ะฐ ะฟั€ะพัั‚ะพ ะทั€ะพะฑะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะทะฐะดะฐะนั‚ะต `None` ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. -ะะปะต ัะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะทั€ะพะฑะธั‚ะธ query ะฟะฐั€ะฐะผะตั‚ั€ ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะฟั€ะพัั‚ะพ ะฝะต ะฒะบะฐะทัƒะนั‚ะต ะดะปั ะฝัŒะพะณะพ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: +ะะปะต ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะทั€ะพะฑะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ query ะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะฟั€ะพัั‚ะพ ะฝะต ะฒะบะฐะทัƒะนั‚ะต ะดะปั ะฝัŒะพะณะพ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: -{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} -ะขัƒั‚ `needy` โ€“ ะพะฑะพะฒโ€™ัะทะบะพะฒะธะน query ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟัƒ `str`. +ะขัƒั‚ ะฟะฐั€ะฐะผะตั‚ั€ query `needy` โ€” ะพะฑะพะฒโ€™ัะทะบะพะฒะธะน ะฟะฐั€ะฐะผะตั‚ั€ query ั‚ะธะฟัƒ `str`. -ะฏะบั‰ะพ ะ’ะธ ะฒั–ะดะบั€ะธั”ั‚ะต ัƒ ะฑั€ะฐัƒะทะตั€ั– URL-ะฐะดั€ะตััƒ: +ะฏะบั‰ะพ ะฒะธ ะฒั–ะดะบั€ะธั”ั‚ะต ัƒ ะฑั€ะฐัƒะทะตั€ั– URL-ะฐะดั€ะตััƒ: ``` 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 { @@ -172,20 +171,18 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy } ``` - -ะ—ะฒะธั‡ะฐะนะฝะพ, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ะดะตัะบั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ัะบ ะพะฑะพะฒโ€™ัะทะบะพะฒั–, ั–ะฝัˆั– ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฐ ั‰ะต ะดะตัะบั– โ€” ะฟะพะฒะฝั–ัั‚ัŽ ะพะฟั†ั–ะพะฝะฐะปัŒะฝั–: +ะ† ะทะฒั–ัะฝะพ, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ะดะตัะบั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ัะบ ะพะฑะพะฒโ€™ัะทะบะพะฒั–, ะดะตัะบั– โ€” ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฐ ะดะตัะบั– โ€” ะฟะพะฒะฝั–ัั‚ัŽ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒั–: {* ../../docs_src/query_params/tutorial006_py310.py hl[8] *} -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั” 3 query ะฟะฐั€ะฐะผะตั‚ั€ะธ: +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ ั” 3 ะฟะฐั€ะฐะผะตั‚ั€ะธ query: * `needy`, ะพะฑะพะฒโ€™ัะทะบะพะฒะธะน `str`. * `skip`, `int` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `0`. -* `limit`, ะพะฟั†ั–ะพะฝะฐะปัŒะฝะธะน `int`. +* `limit`, ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะน `int`. +/// tip | ะŸะพั€ะฐะดะฐ -/// tip | ะŸั–ะดะบะฐะทะบะฐ - -ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Enum`-ะธ, ั‚ะฐะบ ัะฐะผะพ ัะบ ั– ะท [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}. +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Enum` ั‚ะฐะบ ัะฐะผะพ, ัะบ ั– ะท [Path Parameters](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 18b7cc01c9..a6ff70dc09 100644 --- a/docs/uk/docs/tutorial/request-files.md +++ b/docs/uk/docs/tutorial/request-files.md @@ -1,56 +1,56 @@ -# ะ—ะฐะฟะธั‚ ั„ะฐะนะปั–ะฒ +# ะ—ะฐะฟะธั‚ ั„ะฐะนะปั–ะฒ { #request-files } ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะทะฝะฐั‡ะธั‚ะธ ั„ะฐะนะปะธ, ัะบั– ะฑัƒะดัƒั‚ัŒ ะทะฐะฒะฐะฝั‚ะฐะถัƒะฒะฐั‚ะธัั ะบะปั–ั”ะฝั‚ะพะผ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `File`. /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะฉะพะฑ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ั„ะฐะนะปะธ, ัะฟะพั‡ะฐั‚ะบัƒ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">python-multipart</a>. +ะฉะพะฑ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ั„ะฐะนะปะธ, ัะฟะพั‡ะฐั‚ะบัƒ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>. -ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะ’ะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ ั‚ะฐ ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฟะฐะบะตั‚, ะฝะฐะฟั€ะธะบะปะฐะด: +ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฟะฐะบะตั‚, ะฝะฐะฟั€ะธะบะปะฐะด: ```console $ pip install python-multipart ``` -ะฆะต ะฝะตะพะฑั…ั–ะดะฝะพ, ะพัะบั–ะปัŒะบะธ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ั„ะฐะนะปะธ ะฟะตั€ะตะดะฐัŽั‚ัŒัั ัƒ ะฒะธะณะปัะดั– "ั„ะพั€ะผะฐั‚ะพะฒะฐะฝะธั… ะดะฐะฝะธั… ั„ะพั€ะผะธ". +ะฆะต ะฝะตะพะฑั…ั–ะดะฝะพ, ะพัะบั–ะปัŒะบะธ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ั„ะฐะนะปะธ ะฟะตั€ะตะดะฐัŽั‚ัŒัั ัƒ ะฒะธะณะปัะดั– ยซform dataยป. /// -## ะ†ะผะฟะพั€ั‚ `File` +## ะ†ะผะฟะพั€ั‚ `File` { #import-file } ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต `File` ั‚ะฐ `UploadFile` ะท `fastapi`: {* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} -## ะ’ะธะทะฝะฐั‡ะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `File` +## ะ’ะธะทะฝะฐั‡ะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `File` { #define-file-parameters } -ะกั‚ะฒะพั€ั–ั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ะฐะนะปั–ะฒ ั‚ะฐะบ ัะฐะผะพ ัะบ ะ’ะธ ะฑ ัั‚ะฒะพั€ัŽะฒะฐะปะธ `Body` ะฐะฑะพ `Form`: +ะกั‚ะฒะพั€ั–ั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ะฐะนะปั–ะฒ ั‚ะฐะบ ัะฐะผะพ ัะบ ะฒะธ ะฑ ัั‚ะฒะพั€ัŽะฒะฐะปะธ `Body` ะฐะฑะพ `Form`: {* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -`File` โ€” ั†ะต ะบะปะฐั, ัะบะธะน ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ัƒัะฟะฐะดะบะพะฒัƒั” `Form`. +`File` โ€” ั†ะต ะบะปะฐั, ัะบะธะน ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ัƒัะฟะฐะดะบะพะฒัƒั” `Form`. -ะะปะต ะฟะฐะผโ€™ัั‚ะฐะนั‚ะต, ั‰ะพ ะบะพะปะธ ะ’ะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต `Query`, `Path`, `File` ั‚ะฐ ั–ะฝัˆั– ะท `fastapi`, ั†ะต ะฝะฐัะฟั€ะฐะฒะดั– ั„ัƒะฝะบั†ั–ั—, ัะบั– ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒ ัะฟะตั†ั–ะฐะปัŒะฝั– ะบะปะฐัะธ. +ะะปะต ะฟะฐะผโ€™ัั‚ะฐะนั‚ะต, ั‰ะพ ะบะพะปะธ ะฒะธ ั–ะผะฟะพั€ั‚ัƒั”ั‚ะต `Query`, `Path`, `File` ั‚ะฐ ั–ะฝัˆั– ะท `fastapi`, ั†ะต ะฝะฐัะฟั€ะฐะฒะดั– ั„ัƒะฝะบั†ั–ั—, ัะบั– ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒ ัะฟะตั†ั–ะฐะปัŒะฝั– ะบะปะฐัะธ. /// -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ -ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ ั‚ั–ะปะฐ ั„ะฐะนะปั–ะฒ, ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `File`, ั‚ะพะผัƒ ั‰ะพ ั–ะฝะฐะบัˆะต ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฑัƒะดัƒั‚ัŒ ั–ะฝั‚ะตั€ะฟั€ะตั‚ะพะฒะฐะฝั– ัะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฟะธั‚ัƒ ะฐะฑะพ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ั–ะปะฐ (JSON). +ะฉะพะฑ ะพะณะพะปะพัะธั‚ะธ ั‚ั–ะปะฐ ั„ะฐะนะปั–ะฒ, ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `File`, ั‚ะพะผัƒ ั‰ะพ ั–ะฝะฐะบัˆะต ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฑัƒะดัƒั‚ัŒ ั–ะฝั‚ะตั€ะฟั€ะตั‚ะพะฒะฐะฝั– ัะบ ะฟะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฟะธั‚ัƒ ะฐะฑะพ ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ั–ะปะฐ (JSON). /// -ะคะฐะนะปะธ ะฑัƒะดัƒั‚ัŒ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ัƒ ะฒะธะณะปัะดั– "ั„ะพั€ะผะฐั‚ะพะฒะฐะฝะธั… ะดะฐะฝะธั… ั„ะพั€ะผะธ". +ะคะฐะนะปะธ ะฑัƒะดัƒั‚ัŒ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ัƒ ะฒะธะณะปัะดั– ยซform dataยป. -ะฏะบั‰ะพ ะ’ะธ ะพะณะพะปะพัะธั‚ะต ั‚ะธะฟ ะฟะฐั€ะฐะผะตั‚ั€ะฐ ั„ัƒะฝะบั†ั–ั— ะพะฑั€ะพะฑะฝะธะบะฐ ะผะฐั€ัˆั€ัƒั‚ัƒ ัะบ `bytes`, **FastAPI** ะฟั€ะพั‡ะธั‚ะฐั” ั„ะฐะนะป ะทะฐ ะ’ะฐั, ั– ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะนะพะณะพ ะฒะผั–ัั‚ ัƒ ะฒะธะณะปัะดั– `bytes`. +ะฏะบั‰ะพ ะฒะธ ะพะณะพะปะพัะธั‚ะต ั‚ะธะฟ ะฟะฐั€ะฐะผะตั‚ั€ะฐ *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ัะบ `bytes`, **FastAPI** ะฟั€ะพั‡ะธั‚ะฐั” ั„ะฐะนะป ะทะฐ ะฒะฐั, ั– ะฒะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ะนะพะณะพ ะฒะผั–ัั‚ ัƒ ะฒะธะณะปัะดั– `bytes`. -ะžะดะฝะฐะบ ะผะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ะฒะตััŒ ะฒะผั–ัั‚ ะฑัƒะดะต ะทะฑะตั€ะตะถะตะฝะพ ะฒ ะฟะฐะผ'ัั‚ั–. ะฆะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ะดะพะฑั€ะต ะดะปั ะผะฐะปะธั… ั„ะฐะนะปั–ะฒ. +ะœะฐะนั‚ะต ะฝะฐ ัƒะฒะฐะทั–, ั‰ะพ ั†ะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒะตััŒ ะฒะผั–ัั‚ ะฑัƒะดะต ะทะฑะตั€ะตะถะตะฝะพ ะฒ ะฟะฐะผ'ัั‚ั–. ะฆะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ะดะพะฑั€ะต ะดะปั ะผะฐะปะธั… ั„ะฐะนะปั–ะฒ. -ะะปะต ะฒ ะดะตัะบะธั… ะฒะธะฟะฐะดะบะฐั… ะ’ะฐะผ ะผะพะถะต ะทะฝะฐะดะพะฑะธั‚ะธัั `UploadFile`. +ะะปะต ั” ะบั–ะปัŒะบะฐ ะฒะธะฟะฐะดะบั–ะฒ, ัƒ ัะบะธั… ะฒะฐะผ ะผะพะถะต ะฑัƒั‚ะธ ะบะพั€ะธัะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `UploadFile`. -## ะŸะฐั€ะฐะผะตั‚ั€ะธ ั„ะฐะนะปัƒ ะท `UploadFile` +## ะŸะฐั€ะฐะผะตั‚ั€ะธ ั„ะฐะนะปัƒ ะท `UploadFile` { #file-parameters-with-uploadfile } ะ’ะธะทะฝะฐั‡ั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ ั„ะฐะนะปัƒ ะท ั‚ะธะฟะพะผ `UploadFile`: @@ -59,38 +59,39 @@ $ pip install python-multipart ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `UploadFile` ะผะฐั” ะบั–ะปัŒะบะฐ ะฟะตั€ะตะฒะฐะณ ะฟะตั€ะตะด `bytes`: * ะ’ะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `File()` ัƒ ะทะฝะฐั‡ะตะฝะฝั– ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฟะฐั€ะฐะผะตั‚ั€ะฐ. -* ะ’ะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั "ะฑัƒั„ะตั€ะธะทะพะฒะฐะฝะธะน" ั„ะฐะนะป: - * ะคะฐะนะป ะทะฑะตั€ั–ะณะฐั”ั‚ัŒัั ะฒ ะฟะฐะผ'ัั‚ั– ะดะพ ะดะพััะณะฝะตะฝะฝั ะฟะตะฒะฝะพะณะพ ะพะฑะผะตะถะตะฝะฝั, ะฟั–ัะปั ั‡ะพะณะพ ะฒั–ะฝ ะทะฐะฟะธััƒั”ั‚ัŒัั ะฝะฐ ะดะธัะบ. -* ะฆะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒั–ะฝ ะดะพะฑั€ะต ะฟั€ะฐั†ัŽั” ะดะปั ะฒะตะปะธะบะธั… ั„ะฐะนะปั–ะฒ, ั‚ะฐะบะธั… ัะบ ะทะพะฑั€ะฐะถะตะฝะฝั, ะฒั–ะดะตะพ, ะฒะตะปะธะบั– ะดะฒั–ะนะบะพะฒั– ั„ะฐะนะปะธ ั‚ะพั‰ะพ, ะฝะต ัะฟะพะถะธะฒะฐัŽั‡ะธ ะฒััŽ ะฟะฐะผ'ัั‚ัŒ. -ะ’ะธ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ะผะตั‚ะฐะดะฐะฝั– ะฟั€ะพ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะธะน ั„ะฐะนะป. -* ะ’ั–ะฝ ะผะฐั” <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `ะฐัะธะฝั…ั€ะพะฝะฝะธะน ั„ะฐะนะปะพะฒะธะน ั–ะฝั‚ะตั€ั„ะตะนั` interface. -* ะ’ั–ะฝ ะฝะฐะดะฐั” ั„ะฐะบั‚ะธั‡ะฝะธะน ะพะฑ'ั”ะบั‚ Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a>, ัะบะธะน ะผะพะถะฝะฐ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ั–ะฝัˆะธะผ ะฑั–ะฑะปั–ะพั‚ะตะบะฐะผ. +* ะ’ะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ยซspooledยป ั„ะฐะนะป: + * ะคะฐะนะป ะทะฑะตั€ั–ะณะฐั”ั‚ัŒัั ะฒ ะฟะฐะผ'ัั‚ั– ะดะพ ะดะพััะณะฝะตะฝะฝั ะผะฐะบัะธะผะฐะปัŒะฝะพะณะพ ะพะฑะผะตะถะตะฝะฝั ั€ะพะทะผั–ั€ัƒ, ะฟั–ัะปั ั‡ะพะณะพ ะฒั–ะฝ ะฑัƒะดะต ะทะฑะตั€ะตะถะตะฝะธะน ะฝะฐ ะดะธัะบัƒ. +* ะฆะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒั–ะฝ ะดะพะฑั€ะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ะดะปั ะฒะตะปะธะบะธั… ั„ะฐะนะปั–ะฒ, ั‚ะฐะบะธั… ัะบ ะทะพะฑั€ะฐะถะตะฝะฝั, ะฒั–ะดะตะพ, ะฒะตะปะธะบั– ะดะฒั–ะนะบะพะฒั– ั„ะฐะนะปะธ ั‚ะพั‰ะพ, ะฝะต ัะฟะพะถะธะฒะฐัŽั‡ะธ ะฒััŽ ะฟะฐะผ'ัั‚ัŒ. +* ะ’ะธ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ะผะตั‚ะฐะดะฐะฝั– ะฟั€ะพ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะธะน ั„ะฐะนะป. +* ะ’ั–ะฝ ะผะฐั” <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `async` ั–ะฝั‚ะตั€ั„ะตะนั. +* ะ’ั–ะฝ ะฝะฐะดะฐั” ั„ะฐะบั‚ะธั‡ะฝะธะน ะพะฑ'ั”ะบั‚ Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a>, ัะบะธะน ะผะพะถะฝะฐ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ั–ะฝัˆะธะผ ะฑั–ะฑะปั–ะพั‚ะตะบะฐะผ, ั‰ะพ ะพั‡ั–ะบัƒัŽั‚ัŒ file-like ะพะฑ'ั”ะบั‚. -### `UploadFile` +### `UploadFile` { #uploadfile } `UploadFile` ะผะฐั” ั‚ะฐะบั– ะฐั‚ั€ะธะฑัƒั‚ะธ: * `filename`: ะ ัะดะพะบ `str` ะท ะพั€ะธะณั–ะฝะฐะปัŒะฝะพัŽ ะฝะฐะทะฒะพัŽ ั„ะฐะนะปัƒ, ัะบะธะน ะฑัƒะฒ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะธะน (ะฝะฐะฟั€ะธะบะปะฐะด, `myimage.jpg`). -* `content_type`: ะ ัะดะพะบ `str` ะท MIME-ั‚ะธะฟะพะผ (ะฝะฐะฟั€ะธะบะปะฐะด, `image/jpeg`). -* `file`: ะžะฑ'ั”ะบั‚ <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">SpooledTemporaryFile</a> (<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">ั„ะฐะนะปะพะฟะพะดั–ะฑะฝะธะน</a> ะพะฑ'ั”ะบั‚). ะฆะต ั„ะฐะบั‚ะธั‡ะฝะธะน ั„ะฐะนะปะพะฒะธะน ะพะฑ'ั”ะบั‚ Python, ัะบะธะน ะผะพะถะฝะฐ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฟะตั€ะตะดะฐะฒะฐั‚ะธ ั–ะฝัˆะธะผ ั„ัƒะฝะบั†ั–ัะผ ะฐะฑะพ ะฑั–ะฑะปั–ะพั‚ะตะบะฐะผ, ั‰ะพ ะพั‡ั–ะบัƒัŽั‚ัŒ "ั„ะฐะนะปะพะฟะพะดั–ะฑะฝะธะน" ะพะฑ'ั”ะบั‚. +* `content_type`: ะ ัะดะพะบ `str` ะท ั‚ะธะฟะพะผ ะฒะผั–ัั‚ัƒ (MIME type / media type) (ะฝะฐะฟั€ะธะบะปะฐะด, `image/jpeg`). +* `file`: <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> (<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> ะพะฑ'ั”ะบั‚). ะฆะต ั„ะฐะบั‚ะธั‡ะฝะธะน ั„ะฐะนะปะพะฒะธะน ะพะฑ'ั”ะบั‚ 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)` ะฟะพะฒะตั€ะฝะต ะบัƒั€ัะพั€ ะฝะฐ ะฟะพั‡ะฐั‚ะพะบ ั„ะฐะนะปัƒ. - * This is especially useful if you run `await myfile.read()` once and then need to read the contents again. ะฆะต ะพัะพะฑะปะธะฒะพ ะบะพั€ะธัะฝะพ, ัะบั‰ะพ ะ’ะธ ะฒะธะบะพะฝัƒั”ั‚ะต await `await myfile.read()` ะพะดะธะฝ ั€ะฐะท, ะฐ ะฟะพั‚ั–ะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะทะฝะพะฒัƒ ะฟั€ะพั‡ะธั‚ะฐั‚ะธ ะฒะผั–ัั‚. +* `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() @@ -98,57 +99,57 @@ contents = myfile.file.read() /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– `async` -ะšะพะปะธ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `async` ะผะตั‚ะพะดะธ, **FastAPI** ะฒะธะบะพะฝัƒั” ั„ะฐะนะปะพะฒั– ะพะฟะตั€ะฐั†ั–ั— ัƒ ะฟัƒะปั– ะฟะพั‚ะพะบั–ะฒ ั‚ะฐ ะพั‡ั–ะบัƒั” ั—ั… ะทะฐะฒะตั€ัˆะตะฝะฝั. +ะšะพะปะธ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `async` ะผะตั‚ะพะดะธ, **FastAPI** ะฒะธะบะพะฝัƒั” ั„ะฐะนะปะพะฒั– ะผะตั‚ะพะดะธ ัƒ ะฟัƒะปั– ะฟะพั‚ะพะบั–ะฒ ั– ะพั‡ั–ะบัƒั” ะฝะฐ ะฝะธั…. /// /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– Starlette -`UploadFile` ัƒ **FastAPI** ัƒัะฟะฐะดะบะพะฒัƒั”ั‚ัŒัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒั–ะด `UploadFile` ัƒ **Starlette**, ะฐะปะต ะดะพะดะฐั” ะดะตัะบั– ะฝะตะพะฑั…ั–ะดะฝั– ั‡ะฐัั‚ะธะฝะธ, ั‰ะพะฑ ะทั€ะพะฑะธั‚ะธ ะนะพะณะพ ััƒะผั–ัะฝะธะผ ั–ะท **Pydantic** ั‚ะฐ ั–ะฝัˆะธะผะธ ะบะพะผะฟะพะฝะตะฝั‚ะฐะผะธ FastAPI. +`UploadFile` ัƒ **FastAPI** ัƒัะฟะฐะดะบะพะฒัƒั”ั‚ัŒัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒั–ะด `UploadFile` ัƒ **Starlette**, ะฐะปะต ะดะพะดะฐั” ะดะตัะบั– ะฝะตะพะฑั…ั–ะดะฝั– ั‡ะฐัั‚ะธะฝะธ, ั‰ะพะฑ ะทั€ะพะฑะธั‚ะธ ะนะพะณะพ ััƒะผั–ัะฝะธะผ ั–ะท **Pydantic** ั‚ะฐ ั–ะฝัˆะธะผะธ ั‡ะฐัั‚ะธะฝะฐะผะธ FastAPI. /// -## ะฉะพ ั‚ะฐะบะต "Form Data" +## ะฉะพ ั‚ะฐะบะต ยซForm Dataยป { #what-is-form-data } -ะกะฟะพัั–ะฑ, ัƒ ัะบะธะน HTML-ั„ะพั€ะผะธ (`<form></form>`) ะฝะฐะดัะธะปะฐัŽั‚ัŒ ะดะฐะฝั– ะฝะฐ ัะตั€ะฒะตั€, ะทะฐะทะฒะธั‡ะฐะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” "ัะฟะตั†ั–ะฐะปัŒะฝะต" ะบะพะดัƒะฒะฐะฝะฝั, ะฒั–ะดะผั–ะฝะฝะต ะฒั–ะด JSON. +ะกะฟะพัั–ะฑ, ัƒ ัะบะธะน HTML-ั„ะพั€ะผะธ (`<form></form>`) ะฝะฐะดัะธะปะฐัŽั‚ัŒ ะดะฐะฝั– ะฝะฐ ัะตั€ะฒะตั€, ะทะฐะทะฒะธั‡ะฐะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ยซัะฟะตั†ั–ะฐะปัŒะฝะตยป ะบะพะดัƒะฒะฐะฝะฝั ะดะปั ั†ะธั… ะดะฐะฝะธั…, ะฒั–ะดะผั–ะฝะฝะต ะฒั–ะด JSON. -**FastAPI** ะทะฐะฑะตะทะฟะตั‡ัƒั” ะฟั€ะฐะฒะธะปัŒะฝะต ะทั‡ะธั‚ัƒะฒะฐะฝะฝั ั†ะธั… ะดะฐะฝะธั… ะท ะฒั–ะดะฟะพะฒั–ะดะฝะพั— ั‡ะฐัั‚ะธะฝะธ ะทะฐะฟะธั‚ัƒ, ะฐ ะฝะต ะท JSON. +**FastAPI** ะทะฐะฑะตะทะฟะตั‡ะธั‚ัŒ ะทั‡ะธั‚ัƒะฒะฐะฝะฝั ั†ะธั… ะดะฐะฝะธั… ะท ะฟั€ะฐะฒะธะปัŒะฝะพะณะพ ะผั–ัั†ั, ะฐ ะฝะต ะท JSON. /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– -ะ”ะฐะฝั– ะท ั„ะพั€ะผ ะทะฐะทะฒะธั‡ะฐะน ะบะพะดัƒัŽั‚ัŒัั ะทะฐ ะดะพะฟะพะผะพะณะพัŽ "media type" `application/x-www-form-urlencoded`, ัะบั‰ะพ ะฒะพะฝะธ ะฝะต ะผั–ัั‚ัั‚ัŒ ั„ะฐะนะปั–ะฒ. +ะ”ะฐะฝั– ะท ั„ะพั€ะผ ะทะฐะทะฒะธั‡ะฐะน ะบะพะดัƒัŽั‚ัŒัั ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ยซmedia typeยป `application/x-www-form-urlencoded`, ัะบั‰ะพ ะฒะพะฝะธ ะฝะต ะผั–ัั‚ัั‚ัŒ ั„ะฐะนะปั–ะฒ. -ะะปะต ัะบั‰ะพ ั„ะพั€ะผะฐ ะผั–ัั‚ะธั‚ัŒ ั„ะฐะนะปะธ, ะฒะพะฝะฐ ะบะพะดัƒั”ั‚ัŒัั ัƒ ั„ะพั€ะผะฐั‚ั– `multipart/form-data`. ะฏะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `File`, **FastAPI** ะฒะธะทะฝะฐั‡ะธั‚ัŒ, ั‰ะพ ะฟะพั‚ั€ั–ะฑะฝะพ ะพั‚ั€ะธะผะฐั‚ะธ ั„ะฐะนะปะธ ะท ะฒั–ะดะฟะพะฒั–ะดะฝะพั— ั‡ะฐัั‚ะธะฝะธ ั‚ั–ะปะฐ ะทะฐะฟะธั‚ัƒ. +ะะปะต ัะบั‰ะพ ั„ะพั€ะผะฐ ะผั–ัั‚ะธั‚ัŒ ั„ะฐะนะปะธ, ะฒะพะฝะฐ ะบะพะดัƒั”ั‚ัŒัั ัะบ `multipart/form-data`. ะฏะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `File`, **FastAPI** ะทะฝะฐั‚ะธะผะต, ั‰ะพ ะฟะพั‚ั€ั–ะฑะฝะพ ะพั‚ั€ะธะผะฐั‚ะธ ั„ะฐะนะปะธ ะท ะฟั€ะฐะฒะธะปัŒะฝะพั— ั‡ะฐัั‚ะธะฝะธ ั‚ั–ะปะฐ. -ะฉะพะฑ ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ั†ั– ั‚ะธะฟะธ ะบะพะดัƒะฒะฐะฝะฝั ั‚ะฐ ั„ะพั€ะผะพะฒั– ะฟะพะปั, ะพะทะฝะฐะนะพะผั‚ะตัั ะท <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั”ัŽ MDN</abbr> ั‰ะพะดะพ <code>POST</code></a>. +ะฏะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ั†ั– ั‚ะธะฟะธ ะบะพะดัƒะฒะฐะฝะฝั ั‚ะฐ ั„ะพั€ะผะพะฒั– ะฟะพะปั, ะพะทะฝะฐะนะพะผั‚ะตัั ะท <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs ะดะปั <code>POST</code></a>. /// -/// warning | ะฃะฒะฐะณะฐ +/// warning | ะŸะพะฟะตั€ะตะดะถะตะฝะฝั -ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `File` ั– `Form` ะฒ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะฐะปะต ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฟะพะปั `Body`, ัะบั– ะผะฐัŽั‚ัŒ ะฝะฐะดั…ะพะดะธั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– JSON, ะพัะบั–ะปัŒะบะธ ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ ะฑัƒะดะต ะทะฐะบะพะดะพะฒะฐะฝะต ัƒ ั„ะพั€ะผะฐั‚ั– `multipart/form-data`, ะฐ ะฝะต `application/json`. +ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `File` ั– `Form` ะฒ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะฐะปะต ะฒะธ ะฝะต ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ะฟะพะปั `Body`, ัะบั– ะฒะธ ะพั‡ั–ะบัƒั”ั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ัะบ JSON, ะพัะบั–ะปัŒะบะธ ะทะฐะฟะธั‚ ะผะฐั‚ะธะผะต ั‚ั–ะปะพ, ะทะฐะบะพะดะพะฒะฐะฝะต ัะบ `multipart/form-data`, ะฐ ะฝะต `application/json`. -ะฆะต ะฝะต ะพะฑะผะตะถะตะฝะฝั **FastAPI**, ะฐ ะพัะพะฑะปะธะฒั–ัั‚ัŒ ะฟั€ะพั‚ะพะบะพะปัƒ HTTP. +ะฆะต ะฝะต ะพะฑะผะตะถะตะฝะฝั **FastAPI**, ะฐ ั‡ะฐัั‚ะธะฝะฐ ะฟั€ะพั‚ะพะบะพะปัƒ HTTP. /// -## ะžะฟั†ั–ะพะฝะฐะปัŒะฝะต ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะคะฐะนะปั–ะฒ +## ะะตะพะฑะพะฒโ€™ัะทะบะพะฒะต ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ั„ะฐะนะปัƒ { #optional-file-upload } -ะคะฐะนะป ะผะพะถะฝะฐ ะทั€ะพะฑะธั‚ะธ ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ั– ะฒัั‚ะฐะฝะพะฒะปัŽัŽั‡ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `None`: +ะ’ะธ ะผะพะถะตั‚ะต ะทั€ะพะฑะธั‚ะธ ั„ะฐะนะป ะฝะตะพะฑะพะฒโ€™ัะทะบะพะฒะธะผ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ั– ะฒัั‚ะฐะฝะพะฒะธะฒัˆะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ `None`: {* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} -## `UploadFile` ั–ะท ะ”ะพะดะฐั‚ะบะพะฒะธะผะธ ะœะตั‚ะฐ ะ”ะฐะฝะธะผะธ +## `UploadFile` ั–ะท ะดะพะดะฐั‚ะบะพะฒะธะผะธ ะผะตั‚ะฐะดะฐะฝะธะผะธ { #uploadfile-with-additional-metadata } -ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `File()` ั€ะฐะทะพะผ ั–ะท `UploadFile`, ะฝะฐะฟั€ะธะบะปะฐะด, ะดะปั ะฒัั‚ะฐะฝะพะฒะปะตะฝะฝั ะดะพะดะฐั‚ะบะพะฒะธั… ะผะตั‚ะฐะดะฐะฝะธั…: +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `File()` ั€ะฐะทะพะผ ั–ะท `UploadFile`, ะฝะฐะฟั€ะธะบะปะฐะด, ั‰ะพะฑ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะผะตั‚ะฐะดะฐะฝั–: {* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} -## ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะšั–ะปัŒะบะพั… ะคะฐะนะปั–ะฒ +## ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะบั–ะปัŒะบะพั… ั„ะฐะนะปั–ะฒ { #multiple-file-uploads } ะœะพะถะฝะฐ ะทะฐะฒะฐะฝั‚ะฐะถัƒะฒะฐั‚ะธ ะบั–ะปัŒะบะฐ ั„ะฐะนะปั–ะฒ ะพะดะฝะพั‡ะฐัะฝะพ. -ะ’ะพะฝะธ ะฑัƒะดัƒั‚ัŒ ะฟะพะฒโ€™ัะทะฐะฝั– ะท ะพะดะฝะธะผ ั– ั‚ะธะผ ัะฐะผะธะผ "form field", ัะบะธะน ะฟะตั€ะตะดะฐั”ั‚ัŒัั ัƒ ะฒะธะณะปัะดั– "form data". +ะ’ะพะฝะธ ะฑัƒะดัƒั‚ัŒ ะฟะพะฒโ€™ัะทะฐะฝั– ะท ะพะดะฝะธะผ ั– ั‚ะธะผ ัะฐะผะธะผ ยซform fieldยป, ัะบะธะน ะฟะตั€ะตะดะฐั”ั‚ัŒัั ัƒ ะฒะธะณะปัะดั– ยซform dataยป. ะฉะพะฑ ั†ะต ั€ะตะฐะปั–ะทัƒะฒะฐั‚ะธ, ะฟะพั‚ั€ั–ะฑะฝะพ ะพะณะพะปะพัะธั‚ะธ ัะฟะธัะพะบ `bytes` ะฐะฑะพ `UploadFile`: @@ -160,16 +161,16 @@ contents = myfile.file.read() ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `from starlette.responses import HTMLResponse`. -**FastAPI** ะฝะฐะดะฐั” ั‚ั– ะถ ัะฐะผั– `starlette.responses`, ั‰ะพ ะน `fastapi.responses`, ะดะปั ะทั€ัƒั‡ะฝะพัั‚ั– ั€ะพะทั€ะพะฑะฝะธะบั–ะฒ. ะžะดะฝะฐะบ ะฑั–ะปัŒัˆั–ัั‚ัŒ ะดะพัั‚ัƒะฟะฝะธั… ะฒั–ะดะฟะพะฒั–ะดะตะน ะฝะฐะดั…ะพะดัั‚ัŒ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒั–ะด Starlette. +**FastAPI** ะฝะฐะดะฐั” ั‚ั– ะถ ัะฐะผั– `starlette.responses`, ั‰ะพ ะน `fastapi.responses`, ะฟั€ะพัั‚ะพ ะดะปั ะทั€ัƒั‡ะฝะพัั‚ั– ะดะปั ะฒะฐั, ั€ะพะทั€ะพะฑะฝะธะบะฐ. ะะปะต ะฑั–ะปัŒัˆั–ัั‚ัŒ ะดะพัั‚ัƒะฟะฝะธั… ะฒั–ะดะฟะพะฒั–ะดะตะน ะฝะฐะดั…ะพะดัั‚ัŒ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒั–ะด Starlette. /// -### ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะดะตะบั–ะปัŒะบะพั… ั„ะฐะนะปั–ะฒ ั–ะท ะดะพะดะฐั‚ะบะพะฒะธะผะธ ะผะตั‚ะฐะดะฐะฝะธะผะธ +### ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะบั–ะปัŒะบะพั… ั„ะฐะนะปั–ะฒ ั–ะท ะดะพะดะฐั‚ะบะพะฒะธะผะธ ะผะตั‚ะฐะดะฐะฝะธะผะธ { #multiple-file-uploads-with-additional-metadata } -ะขะฐะบ ัะฐะผะพ ัะบ ั– ั€ะฐะฝั–ัˆะต, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `File()`, ั‰ะพะฑ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฝะฐะฒั–ั‚ัŒ ะดะปั `UploadFile`: +ะขะฐะบ ัะฐะผะพ ัะบ ั– ั€ะฐะฝั–ัˆะต, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `File()`, ั‰ะพะฑ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฝะฐะฒั–ั‚ัŒ ะดะปั `UploadFile`: {* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `File`, `bytes`ั‚ะฐ `UploadFile`, ั‰ะพะฑ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั„ะฐะนะปะธ ะดะปั ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ัƒ ะทะฐะฟะธั‚ะฐั…, ัะบั– ะฝะฐะดัะธะปะฐัŽั‚ัŒัั ัƒ ะฒะธะณะปัะดั– form data. +ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `File`, `bytes` ั‚ะฐ `UploadFile`, ั‰ะพะฑ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั„ะฐะนะปะธ ะดะปั ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะฒ ะทะฐะฟะธั‚ั–, ะฝะฐะดั–ัะปะฐะฝั– ัƒ ะฒะธะณะปัะดั– form data. diff --git a/docs/uk/docs/tutorial/request-form-models.md b/docs/uk/docs/tutorial/request-form-models.md index 7f5759e79a..1bfd368d60 100644 --- a/docs/uk/docs/tutorial/request-form-models.md +++ b/docs/uk/docs/tutorial/request-form-models.md @@ -1,12 +1,12 @@ -# ะœะพะดะตะปั– ั„ะพั€ะผ (Form Models) +# ะœะพะดะตะปั– ั„ะพั€ะผ { #form-models } -ะฃ FastAPI ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ **Pydantic-ะผะพะดะตะปั–** ะดะปั ะพะณะพะปะพัˆะตะฝะฝั **ะฟะพะปั–ะฒ ั„ะพั€ะผะธ**. +ะฃ FastAPI ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ **Pydantic-ะผะพะดะตะปั–** ะดะปั ะพะณะพะปะพัˆะตะฝะฝั **ะฟะพะปั–ะฒ ั„ะพั€ะผะธ**. /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะฉะพะฑ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั„ะพั€ะผะธ, ัะฟะพั‡ะฐั‚ะบัƒ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">python-multipart</a>. +ะฉะพะฑ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั„ะพั€ะผะธ, ัะฟะพั‡ะฐั‚ะบัƒ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>. -ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะ’ะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฑั–ะฑะปั–ะพั‚ะตะบัƒ, ะฝะฐะฟั€ะธะบะปะฐะด: +ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ ะนะพะณะพ, ะฝะฐะฟั€ะธะบะปะฐะด: ```console $ pip install python-multipart @@ -14,21 +14,21 @@ $ pip install python-multipart /// -/// note | ะŸั–ะดะบะฐะทะบะฐ +/// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะฆั ั„ัƒะฝะบั†ั–ั ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั, ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท FastAPI ะฒะตั€ัั–ั— `0.113.0`. ๐Ÿค“ +ะฆะต ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั, ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท 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`: @@ -36,13 +36,13 @@ $ pip install python-multipart <img src="/img/tutorial/request-form-models/image01.png"> </div> -## ะ—ะฐะฑะพั€ะพะฝะฐ ะดะพะดะฐั‚ะบะพะฒะธั… ะฟะพะปั–ะฒ ั„ะพั€ะผะธ +## ะ—ะฐะฑะพั€ะพะฝั–ั‚ัŒ ะดะพะดะฐั‚ะบะพะฒั– ะฟะพะปั ั„ะพั€ะผะธ { #forbid-extra-form-fields } -ะฃ ะดะตัะบะธั… ะพัะพะฑะปะธะฒะธั… ะฒะธะฟะฐะดะบะฐั… (ะนะผะพะฒั–ั€ะฝะพ, ั€ั–ะดะบะพ) ะ’ะธ ะผะพะถะตั‚ะต **ะพะฑะผะตะถะธั‚ะธ** ั„ะพั€ะผัƒ ะปะธัˆะต ั‚ะธะผะธ ะฟะพะปัะผะธ, ัะบั– ะฑัƒะปะธ ะพะณะพะปะพัˆะตะฝั– ะฒ Pydantic-ะผะพะดะตะปั–, ั– **ะทะฐะฑะพั€ะพะฝะธั‚ะธ** ะฑัƒะดัŒ-ัะบั– **ะดะพะดะฐั‚ะบะพะฒั–** ะฟะพะปั. +ะฃ ะดะตัะบะธั… ะพัะพะฑะปะธะฒะธั… ะฒะธะฟะฐะดะบะฐั… (ะนะผะพะฒั–ั€ะฝะพ, ะฝะต ะดัƒะถะต ะฟะพัˆะธั€ะตะฝะธั…) ะฒะธ ะผะพะถะตั‚ะต **ะพะฑะผะตะถะธั‚ะธ** ะฟะพะปั ั„ะพั€ะผะธ ะปะธัˆะต ั‚ะธะผะธ, ัะบั– ะฑัƒะปะธ ะพะณะพะปะพัˆะตะฝั– ะฒ Pydantic-ะผะพะดะตะปั–. ะ† **ะทะฐะฑะพั€ะพะฝะธั‚ะธ** ะฑัƒะดัŒ-ัะบั– **ะดะพะดะฐั‚ะบะพะฒั–** ะฟะพะปั. -/// note | ะŸั–ะดะบะฐะทะบะฐ +/// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะฆั ั„ัƒะฝะบั†ั–ั ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั, ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท FastAPI ะฒะตั€ัั–ั— `0.114.0`. ๐Ÿค“ +ะฆะต ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั, ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท FastAPI ะฒะตั€ัั–ั— `0.114.0`. ๐Ÿค“ /// @@ -52,7 +52,7 @@ $ pip install python-multipart ะฏะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– ะดะฐะฝั–, ะฒั–ะฝ ะพั‚ั€ะธะผะฐั” **ะฒั–ะดะฟะพะฒั–ะดัŒ ะท ะฟะพะผะธะปะบะพัŽ**. -ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ ะฝะฐัั‚ัƒะฟะฝั– ะฟะพะปั ั„ะพั€ะผะธ: +ะะฐะฟั€ะธะบะปะฐะด, ัะบั‰ะพ ะบะปั–ั”ะฝั‚ ัะฟั€ะพะฑัƒั” ะฝะฐะดั–ัะปะฐั‚ะธ ะฟะพะปั ั„ะพั€ะผะธ: * `username`: `Rick` * `password`: `Portal Gun` @@ -73,6 +73,6 @@ $ pip install python-multipart } ``` -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #summary } -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Pydantic-ะผะพะดะตะปั– ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ะฟะพะปั–ะฒ ั„ะพั€ะผะธ ัƒ FastAPI. ๐Ÿ˜Ž +ะฃ FastAPI ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Pydantic-ะผะพะดะตะปั– ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ะฟะพะปั–ะฒ ั„ะพั€ะผะธ. ๐Ÿ˜Ž diff --git a/docs/uk/docs/tutorial/request-forms-and-files.md b/docs/uk/docs/tutorial/request-forms-and-files.md index a089ef945a..e809bee225 100644 --- a/docs/uk/docs/tutorial/request-forms-and-files.md +++ b/docs/uk/docs/tutorial/request-forms-and-files.md @@ -1,10 +1,10 @@ -# ะ—ะฐะฟะธั‚ะธ ะท ั„ะพั€ะผะฐะผะธ ั‚ะฐ ั„ะฐะนะปะฐะผะธ +# ะ—ะฐะฟะธั‚ะธ ะท ั„ะพั€ะผะฐะผะธ ั‚ะฐ ั„ะฐะนะปะฐะผะธ { #request-forms-and-files } -ะฃ FastAPI ะ’ะธ ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ั„ะฐะนะปะธ ั‚ะฐ ะฟะพะปั ั„ะพั€ะผะธ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `File` ั– `Form`. +ะ’ะธ ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะฒะธะทะฝะฐั‡ะฐั‚ะธ ั„ะฐะนะปะธ ั‚ะฐ ะฟะพะปั ั„ะพั€ะผะธ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `File` ั– `Form`. /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะฉะพะฑ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ั„ะฐะนะปะธ ั‚ะฐ/ะฐะฑะพ ะดะฐะฝั– ั„ะพั€ะผะธ, ัะฟะพั‡ะฐั‚ะบัƒ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">python-multipart</a>. +ะฉะพะฑ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ั„ะฐะนะปะธ ั‚ะฐ/ะฐะฑะพ ะดะฐะฝั– ั„ะพั€ะผะธ, ัะฟะพั‡ะฐั‚ะบัƒ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>. ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะ’ะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฑั–ะฑะปั–ะพั‚ะตะบัƒ, ะฝะฐะฟั€ะธะบะปะฐะด: @@ -14,21 +14,21 @@ $ pip install python-multipart /// -## ะ†ะผะฟะพั€ั‚ `File` ั‚ะฐ `Form` +## ะ†ะผะฟะพั€ั‚ `File` ั‚ะฐ `Form` { #import-file-and-form } {* ../../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_an_py39.py hl[10:12] *} -ะคะฐะนะปะธ ั‚ะฐ ะฟะพะปั ั„ะพั€ะผะธ ะฑัƒะดัƒั‚ัŒ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ัะบ ั„ะพั€ะผะพะฒั– ะดะฐะฝั–, ั– ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ัะบ ั„ะฐะนะปะธ, ั‚ะฐะบ ั– ะฒะฒะตะดะตะฝั– ะบะพั€ะธัั‚ัƒะฒะฐั‡ะตะผ ะฟะพะปั. +ะคะฐะนะปะธ ั‚ะฐ ะฟะพะปั ั„ะพั€ะผะธ ะฑัƒะดัƒั‚ัŒ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝั– ัะบ ั„ะพั€ะผะพะฒั– ะดะฐะฝั–, ั– ะ’ะธ ะพั‚ั€ะธะผะฐั”ั‚ะต ั„ะฐะนะปะธ ั‚ะฐ ะฟะพะปั ั„ะพั€ะผะธ. ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะดะตัะบั– ั„ะฐะนะปะธ ัะบ `bytes`, ะฐ ะดะตัะบั– ัะบ `UploadFile`. -/// warning | ะฃะฒะฐะณะฐ +/// warning | ะŸะพะฟะตั€ะตะดะถะตะฝะฝั ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `File` ั– `Form` ะฒ ะพะฟะตั€ะฐั†ั–ั— *ัˆะปัั…ัƒ*, ะฐะปะต ะฝะต ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ `Body`-ะฟะพะปั, ัะบั– ะพั‡ั–ะบัƒั”ั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– JSON, ะพัะบั–ะปัŒะบะธ ะทะฐะฟะธั‚ ะผะฐั‚ะธะผะต ั‚ั–ะปะพ, ะทะฐะบะพะดะพะฒะฐะฝะต ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `multipart/form-data`, ะฐ ะฝะต `application/json`. @@ -36,6 +36,6 @@ $ pip install python-multipart /// -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `File` ั‚ะฐ `Form` ั€ะฐะทะพะผ, ะบะพะปะธ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะดะฐะฝั– ั„ะพั€ะผะธ ั‚ะฐ ั„ะฐะนะปะธ ะฒ ะพะดะฝะพะผัƒ ะทะฐะฟะธั‚ั–. +ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `File` ั‚ะฐ `Form` ั€ะฐะทะพะผ, ะบะพะปะธ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะดะฐะฝั– ั‚ะฐ ั„ะฐะนะปะธ ะฒ ะพะดะฝะพะผัƒ ะทะฐะฟะธั‚ั–. diff --git a/docs/uk/docs/tutorial/request-forms.md b/docs/uk/docs/tutorial/request-forms.md index 10c58a73e4..2a22ad922a 100644 --- a/docs/uk/docs/tutorial/request-forms.md +++ b/docs/uk/docs/tutorial/request-forms.md @@ -1,12 +1,12 @@ -# ะ”ะฐะฝั– ั„ะพั€ะผะธ +# ะ”ะฐะฝั– ั„ะพั€ะผะธ { #form-data } -ะฏะบั‰ะพ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะฟะพะปั ั„ะพั€ะผะธ ะทะฐะผั–ัั‚ัŒ JSON, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Form`. +ะฏะบั‰ะพ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะฟะพะปั ั„ะพั€ะผะธ ะทะฐะผั–ัั‚ัŒ JSON, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `Form`. /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฉะพะฑ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั„ะพั€ะผะธ, ัะฟะพั‡ะฐั‚ะบัƒ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>. -ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะ’ะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ั– ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฑั–ะฑะปั–ะพั‚ะตะบัƒ, ะฝะฐะฟั€ะธะบะปะฐะด: +ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ั– ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฑั–ะฑะปั–ะพั‚ะตะบัƒ, ะฝะฐะฟั€ะธะบะปะฐะด: ```console $ pip install python-multipart @@ -14,23 +14,23 @@ $ pip install python-multipart /// -## ะ†ะผะฟะพั€ั‚ `Form` +## ะ†ะผะฟะพั€ั‚ `Form` { #import-form } ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต `Form` ะท `fastapi`: {* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} -## ะžะณะพะปะพัˆะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `Form` +## ะžะณะพะปะพัˆะตะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `Form` { #define-form-parameters } -ะกั‚ะฒะพั€ัŽะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ะพั€ะผะธ ั‚ะฐะบ ัะฐะผะพ ัะบ ะ’ะธ ะฑ ัั‚ะฒะพั€ัŽะฒะฐะปะธ `Body` ะฐะฑะพ `Query`: +ะกั‚ะฒะพั€ัŽะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ะธ ั„ะพั€ะผะธ ั‚ะฐะบ ัะฐะผะพ ัะบ ะฒะธ ะฑ ัั‚ะฒะพั€ัŽะฒะฐะปะธ `Body` ะฐะฑะพ `Query`: {* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} ะะฐะฟั€ะธะบะปะฐะด, ะพะดะธะฝ ะทั– ัะฟะพัะพะฑั–ะฒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OAuth2 (ั‚ะฐะบ ะทะฒะฐะฝะธะน "password flow") ะฒะธะผะฐะณะฐั” ะฝะฐะดัะธะปะฐั‚ะธ `username` ั‚ะฐ `password` ัะบ ะฟะพะปั ั„ะพั€ะผะธ. -<abbr title="ะกะฟะตั†ะธั„ั–ะบะฐั†ั–ั">spec</abbr> ะฒะธะผะฐะณะฐั”, ั‰ะพะฑ ั†ั– ะฟะพะปั ะผะฐะปะธ ั‚ะพั‡ะฝั– ะฝะฐะทะฒะธ `username` ั– `password` ั‚ะฐ ะฝะฐะดัะธะปะฐะปะธัั ัƒ ะฒะธะณะปัะดั– ะฟะพะปั–ะฒ ั„ะพั€ะผะธ, ะฐ ะฝะต JSON. +<abbr title="specification">spec</abbr> ะฒะธะผะฐะณะฐั”, ั‰ะพะฑ ั†ั– ะฟะพะปั ะผะฐะปะธ ั‚ะพั‡ะฝั– ะฝะฐะทะฒะธ `username` ั– `password` ั‚ะฐ ะฝะฐะดัะธะปะฐะปะธัั ัƒ ะฒะธะณะปัะดั– ะฟะพะปั–ะฒ ั„ะพั€ะผะธ, ะฐ ะฝะต JSON. -ะ— `Form` ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั‚ั– ะถ ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ั—, ั‰ะพ ั– ะท `Body` (ั‚ะฐ `Query`, `Path`, `Cookie`), ะฒะบะปัŽั‡ะฐัŽั‡ะธ ะฒะฐะปั–ะดะฐั†ั–ัŽ, ะฟั€ะธะบะปะฐะดะธ, ะฟัะตะฒะดะพะฝั–ะผะธ (ะฝะฐะฟั€ะธะบะปะฐะด, `user-name` ะทะฐะผั–ัั‚ัŒ `username`) ั‚ะพั‰ะพ. +ะ— `Form` ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัˆัƒะฒะฐั‚ะธ ั‚ั– ะถ ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ั—, ั‰ะพ ั– ะท `Body` (ั‚ะฐ `Query`, `Path`, `Cookie`), ะฒะบะปัŽั‡ะฐัŽั‡ะธ ะฒะฐะปั–ะดะฐั†ั–ัŽ, ะฟั€ะธะบะปะฐะดะธ, ะฟัะตะฒะดะพะฝั–ะผะธ (ะฝะฐะฟั€ะธะบะปะฐะด, `user-name` ะทะฐะผั–ัั‚ัŒ `username`) ั‚ะพั‰ะพ. /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั @@ -44,7 +44,7 @@ $ pip install python-multipart /// -## ะŸั€ะพ "ะฟะพะปั ั„ะพั€ะผะธ" +## ะŸั€ะพ "ะฟะพะปั ั„ะพั€ะผะธ" { #about-form-fields } HTML-ั„ะพั€ะผะธ (`<form></form>`) ะฝะฐะดัะธะปะฐัŽั‚ัŒ ะดะฐะฝั– ะฝะฐ ัะตั€ะฒะตั€ ัƒ "ัะฟะตั†ั–ะฐะปัŒะฝะพะผัƒ" ะบะพะดัƒะฒะฐะฝะฝั–, ัะบะต ะฒั–ะดั€ั–ะทะฝัั”ั‚ัŒัั ะฒั–ะด JSON. @@ -56,18 +56,18 @@ HTML-ั„ะพั€ะผะธ (`<form></form>`) ะฝะฐะดัะธะปะฐัŽั‚ัŒ ะดะฐะฝั– ะฝะฐ ัะตั€ะฒะต ะะปะต ัะบั‰ะพ ั„ะพั€ะผะฐ ะผั–ัั‚ะธั‚ัŒ ั„ะฐะนะปะธ, ะฒะพะฝะฐ ะบะพะดัƒั”ั‚ัŒัั ัะบ `multipart/form-data`. ะ’ะธ ะดั–ะทะฝะฐั”ั‚ะตัั ะฟั€ะพ ะพะฑั€ะพะฑะบัƒ ั„ะฐะนะปั–ะฒ ัƒ ะฝะฐัั‚ัƒะฟะฝะพะผัƒ ั€ะพะทะดั–ะปั–. -ะฏะบั‰ะพ ะ’ะธ ั…ะพั‡ะตั‚ะต ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ั†ั– ะบะพะดัƒะฒะฐะฝะฝั ั‚ะฐ ะฟะพะปั ั„ะพั€ะผ, ะทะฒะตั€ะฝั–ั‚ัŒัั ะดะพ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> ะฒะตะฑะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะดะปั <code>POST</code></a>. +ะฏะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ั†ั– ะบะพะดัƒะฒะฐะฝะฝั ั‚ะฐ ะฟะพะปั ั„ะพั€ะผ, ะทะฒะตั€ะฝั–ั‚ัŒัั ะดะพ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> ะฒะตะฑะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะดะปั <code>POST</code></a>. /// /// warning | ะŸะพะฟะตั€ะตะดะถะตะฝะฝั -ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `Form` ะฒ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะฐะปะต ะฝะต ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะพะณะพะปะพัะธั‚ะธ ะฟะพะปั `Body`, ัะบั– ะ’ะธ ะพั‡ั–ะบัƒั”ั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– JSON, ะพัะบั–ะปัŒะบะธ ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ ะฑัƒะดะต ะทะฐะบะพะดะพะฒะฐะฝะพ ัƒ ั„ะพั€ะผะฐั‚ั– `application/x-www-form-urlencoded`, ะฐ ะฝะต `application/json`. +ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ะบั–ะปัŒะบะฐ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ `Form` ะฒ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ะฐะปะต ะฝะต ะผะพะถะตั‚ะต ะพะดะฝะพั‡ะฐัะฝะพ ะพะณะพะปะพัะธั‚ะธ ะฟะพะปั `Body`, ัะบั– ะฒะธ ะพั‡ั–ะบัƒั”ั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ัƒ ั„ะพั€ะผะฐั‚ั– JSON, ะพัะบั–ะปัŒะบะธ ะทะฐะฟะธั‚ ะผะฐั‚ะธะผะต ั‚ั–ะปะพ, ะทะฐะบะพะดะพะฒะฐะฝะต ัะบ `application/x-www-form-urlencoded`, ะฐ ะฝะต `application/json`. ะฆะต ะฝะต ะพะฑะผะตะถะตะฝะฝั **FastAPI**, ะฐ ั‡ะฐัั‚ะธะฝะฐ HTTP-ะฟั€ะพั‚ะพะบะพะปัƒ. /// -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `Form` ะดะปั ะพะณะพะปะพัˆะตะฝะฝั ะฒั…ั–ะดะฝะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ ัƒ ะฒะธะณะปัะดั– ะดะฐะฝะธั… ั„ะพั€ะผะธ. diff --git a/docs/uk/docs/tutorial/response-model.md b/docs/uk/docs/tutorial/response-model.md index def1f8a2d5..2fcad94387 100644 --- a/docs/uk/docs/tutorial/response-model.md +++ b/docs/uk/docs/tutorial/response-model.md @@ -1,36 +1,35 @@ -# ะœะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั– โ€” ะขะธะฟ, ั‰ะพ ะฟะพะฒะตั€ั‚ะฐั”ั‚ัŒัั +# ะœะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั– โ€” ะขะธะฟ, ั‰ะพ ะฟะพะฒะตั€ั‚ะฐั”ั‚ัŒัั { #response-model-return-type } -ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ั‚ะธะฟ, ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะตั‚ัŒัั ัƒ ะฒั–ะดะฟะพะฒั–ะดั–, ะทะฐ ะดะพะฟะพะผะพะณะพัŽ *ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟัƒ, ั‰ะพ ะฟะพะฒะตั€ั‚ะฐั”ั‚ัŒัั* *ั„ัƒะฝะบั†ั–ั”ัŽ ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ* (path operation) +ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ั‚ะธะฟ, ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะตั‚ัŒัั ัƒ ะฒั–ะดะฟะพะฒั–ะดั–, ะฐะฝะพั‚ัƒะฒะฐะฒัˆะธ **ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั** *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*. -**ะะฝะพั‚ะฐั†ั–ัŽ ั‚ะธะฟัƒ** ะผะพะถะฝะฐ ะฒะบะฐะทะฐั‚ะธ ั‚ะฐะบ ัะฐะผะพ ัะบ ั– ะดะปั ะฒั…ั–ะดะฝะธั… **ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ** ั„ัƒะฝะบั†ั–ั—: ั†ะต ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะผะพะดะตะปั– Pydantic, ัะฟะธัะบะธ (lists), ัะปะพะฒะฝะธะบะธ (dictionaries), ัะบะฐะปัั€ะฝั– ะทะฝะฐั‡ะตะฝะฝั, ัะบ-ะพั‚ ั†ั–ะปั– ั‡ะธัะปะฐ (integers), ะฑัƒะปะตะฒั– ะทะฝะฐั‡ะตะฝะฝั (booleans) ั‚ะพั‰ะพ. +**ะะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ** ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั‚ะฐะบ ัะฐะผะพ, ัะบ ั– ะดะปั ะฒั…ั–ะดะฝะธั… ะดะฐะฝะธั… ัƒ **ะฟะฐั€ะฐะผะตั‚ั€ะฐั…** ั„ัƒะฝะบั†ั–ั—: ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะผะพะดะตะปั– Pydantic, ัะฟะธัะบะธ, ัะปะพะฒะฝะธะบะธ, ัะบะฐะปัั€ะฝั– ะทะฝะฐั‡ะตะฝะฝั, ัะบ-ะพั‚ ั†ั–ะปั– ั‡ะธัะปะฐ, ะฑัƒะปะตะฒั– ะทะฝะฐั‡ะตะฝะฝั ั‚ะพั‰ะพ. {* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} -FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะต ั†ะตะน ั‚ะธะฟ, ั‰ะพะฑ: +FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะต ั†ะตะน ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั, ั‰ะพะฑ: * **ะŸะตั€ะตะฒั–ั€ะธั‚ะธ ะฟั€ะฐะฒะธะปัŒะฝั–ัั‚ัŒ** ะฟะพะฒะตั€ะฝะตะฝะธั… ะดะฐะฝะธั…. - * ะฏะบั‰ะพ ะดะฐะฝั– ะฝะต ะฒะฐะปั–ะดะฝั– (ะฝะฐะฟั€ะธะบะปะฐะด, ะฒั–ะดััƒั‚ะฝั” ะฟะพะปะต), ั†ะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะ’ะฐัˆ ะบะพะด ะดะพะดะฐั‚ะบัƒ ะฟั€ะฐั†ัŽั” ะฝะตะบะพั€ะตะบั‚ะฝะพ ั– ะฝะต ะฟะพะฒะตั€ั‚ะฐั” ั‚ะต, ั‰ะพ ะฟะพะฒะธะฝะตะฝ. ะฃ ั‚ะฐะบะพะผัƒ ะฒะธะฟะฐะดะบัƒ FastAPI ะฟะพะฒะตั€ะฝะต ะฟะพะผะธะปะบัƒ ัะตั€ะฒะตั€ะฐ, ะทะฐะผั–ัั‚ัŒ ั‚ะพะณะพ ั‰ะพะฑ ะฒั–ะดะดะฐั‚ะธ ะฝะตะดะพะฟัƒัั‚ะธะผั– ะดะฐะฝั–. ะขะฐะบ ะ’ะธ ั‚ะฐ ะ’ะฐัˆั– ะบะปั–ั”ะฝั‚ะธ ะฑัƒะดะตั‚ะต ะฒะฟะตะฒะฝะตะฝั–, ั‰ะพ ะพั‚ั€ะธะผัƒั”ั‚ะต ะพั‡ั–ะบัƒะฒะฐะฝั– ะดะฐะฝั– ัƒ ะฟั€ะฐะฒะธะปัŒะฝะพะผัƒ ั„ะพั€ะผะฐั‚ั–. - -* ะ”ะพะดะฐั‚ะธ **JSON Schema** ะฒั–ะดะฟะพะฒั–ะดั– ะดะพ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OpenAPI ะฒ *ะพะฟะตั€ะฐั†ั–ัั… ัˆะปัั…ัƒ*. + * ะฏะบั‰ะพ ะดะฐะฝั– ะฝะต ะฒะฐะปั–ะดะฝั– (ะฝะฐะฟั€ะธะบะปะฐะด, ะฒั–ะดััƒั‚ะฝั” ะฟะพะปะต), ั†ะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ *ะฒะฐัˆ* ะบะพะด ะทะฐัั‚ะพััƒะฝะบัƒ ะทะปะฐะผะฐะฝะธะน, ะฝะต ะฟะพะฒะตั€ั‚ะฐั” ั‚ะต, ั‰ะพ ะฟะพะฒะธะฝะตะฝ, ั– ะฑัƒะดะต ะฟะพะฒะตั€ะฝัƒั‚ะพ ะฟะพะผะธะปะบัƒ ัะตั€ะฒะตั€ะฐ ะทะฐะผั–ัั‚ัŒ ะฝะตะบะพั€ะตะบั‚ะฝะธั… ะดะฐะฝะธั…. ะขะฐะบ ะฒะธ ั‚ะฐ ะฒะฐัˆั– ะบะปั–ั”ะฝั‚ะธ ะผะพะถะตั‚ะต ะฑัƒั‚ะธ ะฒะฟะตะฒะฝะตะฝั–, ั‰ะพ ะพั‚ั€ะธะผะฐั”ั‚ะต ะดะฐะฝั– ะน ะพั‡ั–ะบัƒะฒะฐะฝัƒ ัั‚ั€ัƒะบั‚ัƒั€ัƒ ะดะฐะฝะธั…. +* ะ”ะพะดะฐั‚ะธ **JSON Schema** ะดะปั ะฒั–ะดะฟะพะฒั–ะดั– ะฒ OpenAPI *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*. * ะฆะต ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะฐะฝะพ ะฒ **ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝั–ะน ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—**. - * ะ ั‚ะฐะบะพะถ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ, ัะบั– ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะณะตะฝะตั€ัƒัŽั‚ัŒ ะบะปั–ั”ะฝั‚ััŒะบะธะน ะบะพะด. + * ะฆะต ั‚ะฐะบะพะถ ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะฐะฝะพ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ, ัะบั– ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะณะตะฝะตั€ัƒัŽั‚ัŒ ะบะปั–ั”ะฝั‚ััŒะบะธะน ะบะพะด. ะะปะต ะฝะฐะนะณะพะปะพะฒะฝั–ัˆะต: -* FastAPI **ะพะฑะผะตะถะธั‚ัŒ ั‚ะฐ ะฒั–ะดั„ั–ะปัŒั‚ั€ัƒั”** ะฒะธั…ั–ะดะฝั– ะดะฐะฝั– ะฒั–ะดะฟะพะฒั–ะดะฝะพ ะดะพ ั‚ะธะฟัƒ, ะฒะบะฐะทะฐะฝะพะณะพ ัƒ ะฒั–ะดะฟะพะฒั–ะดั–. - * ะฆะต ะพัะพะฑะปะธะฒะพ ะฒะฐะถะปะธะฒะพ ะดะปั **ะฑะตะทะฟะตะบะธ**. ะ”ะตั‚ะฐะปั– ะฝะธะถั‡ะต. +* ะฆะต **ะพะฑะผะตะถะธั‚ัŒ ั‚ะฐ ะฒั–ะดั„ั–ะปัŒั‚ั€ัƒั”** ะฒะธั…ั–ะดะฝั– ะดะฐะฝั– ะดะพ ั‚ะพะณะพ, ั‰ะพ ะฒะธะทะฝะฐั‡ะตะฝะพ ะฒ ั‚ะธะฟั– ะฟะพะฒะตั€ะฝะตะฝะฝั. + * ะฆะต ะพัะพะฑะปะธะฒะพ ะฒะฐะถะปะธะฒะพ ะดะปั **ะฑะตะทะฟะตะบะธ**, ะฝะธะถั‡ะต ะผะธ ะฟะพะฑะฐั‡ะธะผะพ ะฟั€ะพ ั†ะต ะฑั–ะปัŒัˆะต. -## ะŸะฐั€ะฐะผะตั‚ั€ `response_model` +## ะŸะฐั€ะฐะผะตั‚ั€ `response_model` { #response-model-parameter } -ะ†ะฝะพะดั– ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฐะฑะพ ะทั€ัƒั‡ะฝะพ ะฟะพะฒะตั€ั‚ะฐั‚ะธ ั–ะฝัˆั– ั‚ะธะฟะธ ะดะฐะฝะธั…, ะฝั–ะถ ั‚ั–, ั‰ะพ ะทะฐะทะฝะฐั‡ะตะฝั– ัะบ ั‚ะธะฟ ะฒั–ะดะฟะพะฒั–ะดั–. +ะ„ ะฒะธะฟะฐะดะบะธ, ะบะพะปะธ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฐะฑะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฟะพะฒะตั€ั‚ะฐั‚ะธ ะดะฐะฝั–, ัะบั– ะฝะต ะทะพะฒัั–ะผ ะฒั–ะดะฟะพะฒั–ะดะฐัŽั‚ัŒ ั‚ะพะผัƒ, ั‰ะพ ะพะณะพะปะพัˆะตะฝะพ ั‚ะธะฟะพะผ. -ะะฐะฟั€ะธะบะปะฐะด, ะ’ะธ ะผะพะถะตั‚ะต **ะฟะพะฒะตั€ั‚ะฐั‚ะธ ัะปะพะฒะฝะธะบ** ะฐะฑะพ ะพะฑโ€™ั”ะบั‚ ะฑะฐะทะธ ะดะฐะฝะธั…, ะฐะปะต **ะพะณะพะปะพัะธั‚ะธ ะผะพะดะตะปัŒ Pydantic** ัะบ ะผะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั–. ะขะพะดั– ะผะพะดะตะปัŒ Pydantic ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะพะฑั€ะพะฑะปัั‚ะธะผะต ะฒะฐะปั–ะดะฐั†ั–ัŽ, ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ั‚ะพั‰ะพ. +ะะฐะฟั€ะธะบะปะฐะด, ะฒะธ ะผะพะถะตั‚ะต ะทะฐั…ะพั‚ั–ั‚ะธ **ะฟะพะฒะตั€ั‚ะฐั‚ะธ ัะปะพะฒะฝะธะบ** ะฐะฑะพ ะพะฑโ€™ั”ะบั‚ ะฑะฐะทะธ ะดะฐะฝะธั…, ะฐะปะต **ะพะณะพะปะพัะธั‚ะธ ะนะพะณะพ ัะบ ะผะพะดะตะปัŒ Pydantic**. ะขะฐะบะธะผ ั‡ะธะฝะพะผ ะผะพะดะตะปัŒ Pydantic ะฒะธะบะพะฝัƒะฒะฐั‚ะธะผะต ะฒััŽ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะดะฐะฝะธั…, ะฒะฐะปั–ะดะฐั†ั–ัŽ ั‚ะพั‰ะพ ะดะปั ะพะฑโ€™ั”ะบั‚ะฐ, ัะบะธะน ะฒะธ ะฟะพะฒะตั€ะฝัƒะปะธ (ะฝะฐะฟั€ะธะบะปะฐะด, ัะปะพะฒะฝะธะบะฐ ะฐะฑะพ ะพะฑโ€™ั”ะบั‚ะฐ ะฑะฐะทะธ ะดะฐะฝะธั…). -ะฏะบั‰ะพ ะ’ะธ ะดะพะดะฐัั‚ะต ะฐะฝะพั‚ะฐั†ั–ัŽ ั‚ะธะฟัƒ ะดะปั ะฟะพะฒะตั€ะฝะตะฝะฝั, ั€ะตะดะฐะบั‚ะพั€ ะบะพะดัƒ ะฐะฑะพ mypy ะผะพะถัƒั‚ัŒ ะฟะพัะบะฐั€ะถะธั‚ะธัั, ั‰ะพ ั„ัƒะฝะบั†ั–ั ะฟะพะฒะตั€ั‚ะฐั” ั–ะฝัˆะธะน ั‚ะธะฟ (ะฝะฐะฟั€ะธะบะปะฐะด, dict ะทะฐะผั–ัั‚ัŒ Item). +ะฏะบั‰ะพ ะฒะธ ะดะพะดะฐะปะธ ะฐะฝะพั‚ะฐั†ั–ัŽ ั‚ะธะฟัƒ ะฟะพะฒะตั€ะฝะตะฝะฝั, ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ั‚ะฐ ั€ะตะดะฐะบั‚ะพั€ะธ ัะบะฐั€ะถะธั‚ะธะผัƒั‚ัŒัั (ะบะพั€ะตะบั‚ะฝะพัŽ) ะฟะพะผะธะปะบะพัŽ, ะฟะพะฒั–ะดะพะผะปััŽั‡ะธ, ั‰ะพ ะฒะฐัˆะฐ ั„ัƒะฝะบั†ั–ั ะฟะพะฒะตั€ั‚ะฐั” ั‚ะธะฟ (ะฝะฐะฟั€ะธะบะปะฐะด, dict), ัะบะธะน ะฒั–ะดั€ั–ะทะฝัั”ั‚ัŒัั ะฒั–ะด ั‚ะพะณะพ, ั‰ะพ ะฒะธ ะพะณะพะปะพัะธะปะธ (ะฝะฐะฟั€ะธะบะปะฐะด, ะผะพะดะตะปัŒ Pydantic). -ะฃ ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบะฐั… ะผะพะถะฝะฐ ัะบะพั€ะธัั‚ะฐั‚ะธัั ะฟะฐั€ะฐะผะตั‚ั€ะพะผ `response_model` ะฒ ะดะตะบะพั€ะฐั‚ะพั€ั– ะผะฐั€ัˆั€ัƒั‚ัƒ (ะฝะฐะฟั€ะธะบะปะฐะด, @app.get()). +ะฃ ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบะฐั… ะผะพะถะฝะฐ ัะบะพั€ะธัั‚ะฐั‚ะธัั ะฟะฐั€ะฐะผะตั‚ั€ะพะผ *ะดะตะบะพั€ะฐั‚ะพั€ะฐ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* `response_model` ะทะฐะผั–ัั‚ัŒ ั‚ะธะฟัƒ ะฟะพะฒะตั€ะฝะตะฝะฝั. -ะŸะฐั€ะฐะผะตั‚ั€ `response_model` ะฟั€ะฐั†ัŽั” ะท ะฑัƒะดัŒ-ัะบะธะผ *ะพะฟะตั€ะฐั‚ะพั€ะพะผ ัˆะปัั…ัƒ*: +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `response_model` ัƒ ะฑัƒะดัŒ-ัะบั–ะน ะท *ะพะฟะตั€ะฐั†ั–ะน ัˆะปัั…ัƒ*: * `@app.get()` * `@app.post()` @@ -42,33 +41,33 @@ FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะต ั†ะตะน ั‚ะธะฟ, ั‰ะพะฑ: /// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ `response_model` ั” ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ะผะตั‚ะพะดัƒ-ะดะตะบะพั€ะฐั‚ะพั€ะฐ (`get`, `post`, ั‚ะพั‰ะพ), ะฐ ะฝะต *ั„ัƒะฝะบั†ั–ั”ัŽ ะพะฟะตั€ะฐั†ั–ั”ัŽ ัˆะปัั…ัƒ* (path operation function), ัะบ ั†ะต ั€ะพะฑะธั‚ัŒัั ะท ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ะฐะฑะพ ั‚ั–ะปะพะผ ะทะฐะฟะธั‚ัƒ. +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ `response_model` ั” ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ะผะตั‚ะพะดัƒ ยซะดะตะบะพั€ะฐั‚ะพั€ะฐยป (`get`, `post` ั‚ะพั‰ะพ). ะ ะฝะต ะฒะฐัˆะพั— *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ัะบ ัƒัั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะฐ ั‚ั–ะปะพ. /// -`response_model` ะฟั€ะธะนะผะฐั” ั‚ะฐะบะธะน ัะฐะผะธะน ั‚ะธะฟ, ัะบะธะน ะ’ะธ ะฑ ะฒะบะฐะทะฐะปะธ ะดะปั ะฟะพะปั ะผะพะดะตะปั– Pydantic. ะขะพะฑั‚ะพ ั†ะต ะผะพะถะต ะฑัƒั‚ะธ ัะบ Pydantic-ะผะพะดะตะปัŒ, ั‚ะฐะบ ั–, ะฝะฐะฟั€ะธะบะปะฐะด, `list` ั–ะท ะผะพะดะตะปะตะน Pydantic โ€” `List[Item]`. +`response_model` ะฟั€ะธะนะผะฐั” ั‚ะฐะบะธะน ัะฐะผะธะน ั‚ะธะฟ, ัะบะธะน ะฒะธ ะฑ ะฒะบะฐะทะฐะปะธ ะดะปั ะฟะพะปั ะผะพะดะตะปั– Pydantic, ั‚ะพะฑั‚ะพ ั†ะต ะผะพะถะต ะฑัƒั‚ะธ ะผะพะดะตะปัŒ Pydantic, ะฐะปะต ั‚ะฐะบะพะถ ั†ะต ะผะพะถะต ะฑัƒั‚ะธ, ะฝะฐะฟั€ะธะบะปะฐะด, `list` ะผะพะดะตะปะตะน Pydantic, ัะบ-ะพั‚ `List[Item]`. -FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะต `response_model` ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—, ะฒะฐะปั–ะดะฐั†ั–ั— ะดะฐะฝะธั… ั‚ะฐ โ€” ะฝะฐะนะฒะฐะถะปะธะฒั–ัˆะต โ€” **ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั‚ะฐ ั„ั–ะปัŒั‚ั€ะฐั†ั–ั— ะฒะธั…ั–ะดะฝะธั… ะดะฐะฝะธั…** ะทะณั–ะดะฝะพ ะท ะพะณะพะปะพัˆะตะฝะธะผ ั‚ะธะฟะพะผ. +FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะต ั†ะตะน `response_model` ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฒัั–ั”ั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะดะฐะฝะธั…, ะฒะฐะปั–ะดะฐั†ั–ั— ั‚ะพั‰ะพ, ะฐ ั‚ะฐะบะพะถ ะดะปั **ะฟะตั€ะตั‚ะฒะพั€ะตะฝะฝั ั‚ะฐ ั„ั–ะปัŒั‚ั€ะฐั†ั–ั— ะฒะธั…ั–ะดะฝะธั… ะดะฐะฝะธั…** ะดะพ ะพะณะพะปะพัˆะตะฝะพะณะพ ั‚ะธะฟัƒ. /// tip | ะŸะพั€ะฐะดะฐ -ะฏะบั‰ะพ ัƒ ะ’ะฐั ัƒะฒั–ะผะบะฝะตะฝะพ ััƒะฒะพั€ัƒ ะฟะตั€ะตะฒั–ั€ะบัƒ ั‚ะธะฟั–ะฒ ัƒ ั€ะตะดะฐะบั‚ะพั€ั–, mypy ั‚ะพั‰ะพ, ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั„ัƒะฝะบั†ั–ั— ัะบ `Any`. +ะฏะบั‰ะพ ัƒ ะฒะฐั ัƒะฒั–ะผะบะฝะตะฝะพ ััƒะฒะพั€ัƒ ะฟะตั€ะตะฒั–ั€ะบัƒ ั‚ะธะฟั–ะฒ ัƒ ั€ะตะดะฐะบั‚ะพั€ั–, mypy ั‚ะพั‰ะพ, ะฒะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั„ัƒะฝะบั†ั–ั— ัะบ `Any`. -ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ะ’ะธ ะฟะพะฒั–ะดะพะผะปัั”ั‚ะต ั€ะตะดะฐะบั‚ะพั€ัƒ, ั‰ะพ ัะฒั–ะดะพะผะพ ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต ะฑัƒะดัŒ-ั‰ะพ. ะะปะต FastAPI ัƒัะต ะพะดะฝะพ ะฒะธะบะพะฝัƒะฒะฐั‚ะธะผะต ัั‚ะฒะพั€ะตะฝะฝั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—, ะฒะฐะปั–ะดะฐั†ั–ัŽ, ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ั‚ะพั‰ะพ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `response_model`. +ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ะฒะธ ะฟะพะฒั–ะดะพะผะปัั”ั‚ะต ั€ะตะดะฐะบั‚ะพั€ัƒ, ั‰ะพ ัะฒั–ะดะพะผะพ ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต ะฑัƒะดัŒ-ั‰ะพ. ะะปะต FastAPI ัƒัะต ะพะดะฝะพ ะฒะธะบะพะฝัƒะฒะฐั‚ะธะผะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะดะฐะฝะธั…, ะฒะฐะปั–ะดะฐั†ั–ัŽ, ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ั‚ะพั‰ะพ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ `response_model`. /// -### ะŸั€ั–ะพั€ะธั‚ะตั‚ `response_model` +### ะŸั€ั–ะพั€ะธั‚ะตั‚ `response_model` { #response-model-priority } -ะฏะบั‰ะพ ะ’ะธ ะฒะบะฐะทัƒั”ั‚ะต ั– ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั, ั– `response_model`, ั‚ะพ FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะต `response_model` ะท ะฟั€ั–ะพั€ะธั‚ะตั‚ะพะผ. +ะฏะบั‰ะพ ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ั– ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั, ั– `response_model`, ั‚ะพ `response_model` ะผะฐั‚ะธะผะต ะฟั€ั–ะพั€ะธั‚ะตั‚ ั– ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะฐะฝะธะน FastAPI. -ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะฟั€ะฐะฒะธะปัŒะฝั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ะดะพ ะฒะฐัˆะธั… ั„ัƒะฝะบั†ั–ะน, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะพะฝะธ ะฟะพะฒะตั€ั‚ะฐัŽั‚ัŒ ั‚ะธะฟ, ะฒั–ะดะผั–ะฝะฝะธะน ะฒั–ะด `response_model`. ะฆะต ะฑัƒะดะต ะบะพั€ะธัะฝะพ ะดะปั ั€ะตะดะฐะบั‚ะพั€ั–ะฒ ะบะพะดัƒ ั‚ะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ั‚ะฐะบะธั… ัะบ mypy. ะ† ะฟั€ะธ ั†ัŒะพะผัƒ FastAPI ะฟั€ะพะดะพะฒะถะธั‚ัŒ ะฒะธะบะพะฝัƒะฒะฐั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั…, ะณะตะฝะตั€ัƒะฒะฐั‚ะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ั‚ะพั‰ะพ ะฝะฐ ะพัะฝะพะฒั– `response_model`. +ะขะฐะบะธะผ ั‡ะธะฝะพะผ ะฒะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ะฟั€ะฐะฒะธะปัŒะฝั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ะดะพ ะฒะฐัˆะธั… ั„ัƒะฝะบั†ั–ะน, ะฝะฐะฒั–ั‚ัŒ ะบะพะปะธ ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต ั‚ะธะฟ, ะฒั–ะดะผั–ะฝะฝะธะน ะฒั–ะด ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั–, ั‰ะพะฑ ั†ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะปะธ ั€ะตะดะฐะบั‚ะพั€ ั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะฝะฐ ะบัˆั‚ะฐะปั‚ mypy. ะ† ะฟั€ะธ ั†ัŒะพะผัƒ FastAPI ะฒัะต ะพะดะฝะพ ะฒะธะบะพะฝัƒะฒะฐั‚ะธะผะต ะฒะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั…, ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ั‚ะพั‰ะพ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `response_model`. -ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `response_model=None`, ั‰ะพะฑ ะฒะธะผะบะฝัƒั‚ะธ ัั‚ะฒะพั€ะตะฝะฝั ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั– ะดะปั ั†ั–ั”ั— *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*. ะฆะต ะผะพะถะต ะทะฝะฐะดะพะฑะธั‚ะธัั, ัะบั‰ะพ ะ’ะธ ะดะพะดะฐั”ั‚ะต ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ะดะพ ะพะฑ'ั”ะบั‚ั–ะฒ, ัะบั– ะฝะต ั” ะดะพะฟัƒัั‚ะธะผะธะผะธ ะฟะพะปัะผะธ Pydantic โ€” ะฟั€ะธะบะปะฐะด ั†ัŒะพะณะพ ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฒ ะพะดะฝะพะผัƒ ะท ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปั–ะฒ. +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `response_model=None`, ั‰ะพะฑ ะฒะธะผะบะฝัƒั‚ะธ ัั‚ะฒะพั€ะตะฝะฝั ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั– ะดะปั ั†ั–ั”ั— *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*; ั†ะต ะผะพะถะต ะทะฝะฐะดะพะฑะธั‚ะธัั, ัะบั‰ะพ ะฒะธ ะดะพะดะฐั”ั‚ะต ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ะดะปั ั€ะตั‡ะตะน, ัะบั– ะฝะต ั” ะฒะฐะปั–ะดะฝะธะผะธ ะฟะพะปัะผะธ Pydantic, ะฟั€ะธะบะปะฐะด ั†ัŒะพะณะพ ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต ะฒ ะพะดะฝะพะผัƒ ะท ั€ะพะทะดั–ะปั–ะฒ ะฝะธะถั‡ะต. -## ะŸะพะฒะตั€ะฝัƒั‚ะธ ั‚ั– ัะฐะผั– ะฒั…ั–ะดะฝั– ะดะฐะฝั– +## ะŸะพะฒะตั€ะฝัƒั‚ะธ ั‚ั– ัะฐะผั– ะฒั…ั–ะดะฝั– ะดะฐะฝั– { #return-the-same-input-data } -ะขัƒั‚ ะผะธ ะพะณะพะปะพัˆัƒั”ะผะพ ะผะพะดะตะปัŒ `UserIn`, ัะบะฐ ะผั–ัั‚ะธั‚ัŒ ะทะฒะธั‡ะฐะนะฝะธะน ั‚ะตะบัั‚ะพะฒะธะน ะฟะฐั€ะพะปัŒ: +ะขัƒั‚ ะผะธ ะพะณะพะปะพัˆัƒั”ะผะพ ะผะพะดะตะปัŒ `UserIn`, ะฒะพะฝะฐ ะผั–ัั‚ะธั‚ะธะผะต ะฟะฐั€ะพะปัŒ ัƒ ะฒั–ะดะบั€ะธั‚ะพะผัƒ ะฒะธะณะปัะดั–: {* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *} @@ -76,7 +75,7 @@ FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะต `response_model` ะดะปั ัั‚ะฒะพั€ะต ะฉะพะฑ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `EmailStr`, ัะฟะพั‡ะฐั‚ะบัƒ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>. -ะŸะตั€ะตะบะพะฝะฐะนั‚ะตััŒ, ั‰ะพ ะ’ะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฟะฐะบะตั‚, ะฝะฐะฟั€ะธะบะปะฐะด: +ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฟะฐะบะตั‚, ะฝะฐะฟั€ะธะบะปะฐะด: ```console $ pip install email-validator @@ -90,29 +89,29 @@ $ pip install "pydantic[email]" /// -ะ† ะผะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ะผะพ ั†ัŽ ะผะพะดะตะปัŒ, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ั– ะฒั…ั–ะดะฝั–, ั– ะฒะธั…ั–ะดะฝั– ะดะฐะฝั–: +ะ† ะผะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ะผะพ ั†ัŽ ะผะพะดะตะปัŒ, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ะฝะฐัˆั– ะฒั…ั–ะดะฝั– ะดะฐะฝั–, ั– ั†ัŽ ะถ ะผะพะดะตะปัŒ, ั‰ะพะฑ ะพะณะพะปะพัะธั‚ะธ ะฝะฐัˆั– ะฒะธั…ั–ะดะฝั– ะดะฐะฝั–: {* ../../docs_src/response_model/tutorial002_py310.py hl[16] *} -ะขะตะฟะตั€, ะบะพะปะธ ะฑั€ะฐัƒะทะตั€ ัั‚ะฒะพั€ัŽั” ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะท ะฟะฐั€ะพะปะตะผ, API ะฟะพะฒะตั€ะฝะต ั‚ะพะน ัะฐะผะธะน ะฟะฐั€ะพะปัŒ ัƒ ะฒั–ะดะฟะพะฒั–ะดั–. +ะขะตะฟะตั€, ั‰ะพั€ะฐะทัƒ ะบะพะปะธ ะฑั€ะฐัƒะทะตั€ ัั‚ะฒะพั€ัŽั” ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะท ะฟะฐั€ะพะปะตะผ, 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] *} @@ -120,107 +119,107 @@ $ pip install "pydantic[email]" {* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} -ะขะฐะบะธะผ ั‡ะธะฝะพะผ, **FastAPI** ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒั–ะดั„ั–ะปัŒั‚ั€ัƒั” ะฒัั– ะดะฐะฝั–, ัะบั– ะฝะต ะฒะบะฐะทะฐะฝั– ัƒ ะฒะธั…ั–ะดะฝั–ะน ะผะพะดะตะปั– (ะทะฐ ะดะพะฟะพะผะพะณะพัŽ Pydantic). +ะขะฐะบะธะผ ั‡ะธะฝะพะผ, **FastAPI** ะฟะพะดะฑะฐั” ะฟั€ะพ ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ะฒัั–ั… ะดะฐะฝะธั…, ัะบั– ะฝะต ะพะณะพะปะพัˆะตะฝั– ัƒ ะฒะธั…ั–ะดะฝั–ะน ะผะพะดะตะปั– (ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ Pydantic). -### `response_model` ะฐะฑะพ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั +### `response_model` ะฐะฑะพ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั { #response-model-or-return-type } -ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ, ะพัะบั–ะปัŒะบะธ ะดะฒั– ะผะพะดะตะปั– ั€ั–ะทะฝั–, ัะบั‰ะพ ะผะธ ะฐะฝะพั‚ัƒั”ะผะพ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั„ัƒะฝะบั†ั–ั— ัะบ `UserOut`, ั€ะตะดะฐะบั‚ะพั€ ั– ั‚ะฐะบั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ, ัะบ mypy, ะฒะธะดะฐะดัƒั‚ัŒ ะฟะพะผะธะปะบัƒ, ะฑะพ ั„ะฐะบั‚ะธั‡ะฝะพ ะผะธ ะฟะพะฒะตั€ั‚ะฐั”ะผะพ ั–ะฝัˆะธะน ั‚ะธะฟ. +ะฃ ั†ัŒะพะผัƒ ะฒะธะฟะฐะดะบัƒ, ะพัะบั–ะปัŒะบะธ ะดะฒั– ะผะพะดะตะปั– ั€ั–ะทะฝั–, ัะบั‰ะพ ะผะธ ะฐะฝะพั‚ัƒั”ะผะพ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั„ัƒะฝะบั†ั–ั— ัะบ `UserOut`, ั€ะตะดะฐะบั‚ะพั€ ั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ัะบะฐั€ะถะธั‚ะธะผัƒั‚ัŒัั, ั‰ะพ ะผะธ ะฟะพะฒะตั€ั‚ะฐั”ะผะพ ะฝะตะฒะฐะปั–ะดะฝะธะน ั‚ะธะฟ, ะฐะดะถะต ั†ะต ั€ั–ะทะฝั– ะบะปะฐัะธ. -ะขะพะผัƒ ะฒ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั– ะผะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ะผะพ ะฟะฐั€ะฐะผะตั‚ั€ `response_model`, ะฐ ะฝะต ะฐะฝะพั‚ะฐั†ั–ัŽ ั‚ะธะฟัƒ ะฟะพะฒะตั€ะฝะตะฝะฝั. +ะกะฐะผะต ั‚ะพะผัƒ ะฒ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั– ะฝะฐะผ ั‚ั€ะตะฑะฐ ะพะณะพะปะพัะธั‚ะธ ั†ะต ั‡ะตั€ะตะท ะฟะฐั€ะฐะผะตั‚ั€ `response_model`. -...ะฐะปะต ั‡ะธั‚ะฐะนั‚ะต ะดะฐะปั–, ั‰ะพะฑ ะดั–ะทะฝะฐั‚ะธัั, ัะบ ะพะฑั–ะนั‚ะธ ั†ะต ะพะฑะผะตะถะตะฝะฝั. +...ะฐะปะต ั‡ะธั‚ะฐะนั‚ะต ะดะฐะปั– ะฝะธะถั‡ะต, ั‰ะพะฑ ะฟะพะฑะฐั‡ะธั‚ะธ, ัะบ ั†ะต ะพะฑั–ะนั‚ะธ. -## ะขะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั– ั„ั–ะปัŒั‚ั€ะฐั†ั–ั ะดะฐะฝะธั… +## ะขะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั– ั„ั–ะปัŒั‚ั€ะฐั†ั–ั ะดะฐะฝะธั… { #return-type-and-data-filtering } -ะŸั€ะพะดะพะฒะถะธะผะพ ะท ะฟะพะฟะตั€ะตะดะฝัŒะพะณะพ ะฟั€ะธะบะปะฐะดัƒ. ะœะธ ั…ะพั‚ั–ะปะธ **ะฐะฝะพั‚ัƒะฒะฐั‚ะธ ั„ัƒะฝะบั†ั–ัŽ ะพะดะฝะธะผ ั‚ะธะฟะพะผ**, ะฐะปะต ะฟั€ะธ ั†ัŒะพะผัƒ ะฟะพะฒะตั€ั‚ะฐั‚ะธ ะท ะฝะตั— ะฑั–ะปัŒัˆะต ะดะฐะฝะธั…. +ะŸั€ะพะดะพะฒะถะธะผะพ ะท ะฟะพะฟะตั€ะตะดะฝัŒะพะณะพ ะฟั€ะธะบะปะฐะดัƒ. ะœะธ ั…ะพั‚ั–ะปะธ **ะฐะฝะพั‚ัƒะฒะฐั‚ะธ ั„ัƒะฝะบั†ั–ัŽ ะพะดะฝะธะผ ั‚ะธะฟะพะผ**, ะฐะปะต ั…ะพั‚ั–ะปะธ ะผะฐั‚ะธ ะทะผะพะณัƒ ะฟะพะฒะตั€ั‚ะฐั‚ะธ ะท ั„ัƒะฝะบั†ั–ั— ั‚ะต, ั‰ะพ ะฝะฐัะฟั€ะฐะฒะดั– ะผั–ัั‚ะธั‚ัŒ **ะฑั–ะปัŒัˆะต ะดะฐะฝะธั…**. -ะœะธ ั…ะพั‡ะตะผะพ, ั‰ะพะฑ FastAPI ะฟั€ะพะดะพะฒะถัƒะฒะฐะฒ **ั„ั–ะปัŒั‚ั€ัƒะฒะฐั‚ะธ** ั†ั– ะดะฐะฝั– ะทะฐ ะดะพะฟะพะผะพะณะพัŽ response_model. ะขะพะฑั‚ะพ ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ั„ัƒะฝะบั†ั–ั ะฟะพะฒะตั€ั‚ะฐั” ะฑั–ะปัŒัˆะต ั–ะฝั„ะพั€ะผะฐั†ั–ั—, ัƒ ะฒั–ะดะฟะพะฒั–ะดั– ะฑัƒะดัƒั‚ัŒ ะปะธัˆะต ั‚ั– ะฟะพะปั, ัะบั– ะฒะบะฐะทะฐะฝั– ัƒ response_model. +ะœะธ ั…ะพั‡ะตะผะพ, ั‰ะพะฑ FastAPI ะฟั€ะพะดะพะฒะถัƒะฒะฐะฒ **ั„ั–ะปัŒั‚ั€ัƒะฒะฐั‚ะธ** ะดะฐะฝั–, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะผะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั–. ะขะพะฑั‚ะพ ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ั„ัƒะฝะบั†ั–ั ะฟะพะฒะตั€ั‚ะฐั” ะฑั–ะปัŒัˆะต ะดะฐะฝะธั…, ะฒั–ะดะฟะพะฒั–ะดัŒ ะผั–ัั‚ะธั‚ะธะผะต ะปะธัˆะต ะฟะพะปั, ะพะณะพะปะพัˆะตะฝั– ะฒ ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั–. -ะฃ ะฟะพะฟะตั€ะตะดะฝัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั–, ะพัะบั–ะปัŒะบะธ ะบะปะฐัะธ ะฑัƒะปะธ ั€ั–ะทะฝั–, ะฝะฐะผ ะดะพะฒะตะปะพัั ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `response_model`. ะะปะต ั†ะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะผะธ ะฝะต ะพั‚ั€ะธะผัƒั”ะผะพ ะฟั–ะดั‚ั€ะธะผะบะธ ะท ะฑะพะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ ะบะพะดัƒ ั‚ะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ ะฟะตั€ะตะฒั–ั€ะบะธ ั‚ะธะฟั–ะฒ ั‰ะพะดะพ ั‚ะธะฟัƒ, ัะบะธะน ะฟะพะฒะตั€ั‚ะฐั” ั„ัƒะฝะบั†ั–ั. +ะฃ ะฟะพะฟะตั€ะตะดะฝัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั–, ะพัะบั–ะปัŒะบะธ ะบะปะฐัะธ ะฑัƒะปะธ ั€ั–ะทะฝั–, ะฝะฐะผ ะดะพะฒะตะปะพัั ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ `response_model`. ะะปะต ั†ะต ั‚ะฐะบะพะถ ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะผะธ ะฝะต ะพั‚ั€ะธะผัƒั”ะผะพ ะฟั–ะดั‚ั€ะธะผะบะธ ะฒั–ะด ั€ะตะดะฐะบั‚ะพั€ะฐ ั‚ะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ัะบั– ะฟะตั€ะตะฒั–ั€ััŽั‚ัŒ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั„ัƒะฝะบั†ั–ั—. -ะŸั€ะพั‚ะต ะฒ ะฑั–ะปัŒัˆะพัั‚ั– ะฒะธะฟะฐะดะบั–ะฒ, ะบะพะปะธ ะฝะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะทั€ะพะฑะธั‚ะธ ั‰ะพััŒ ะฟะพะดั–ะฑะฝะต, ะผะธ ะฟั€ะพัั‚ะพ ั…ะพั‡ะตะผะพ, ั‰ะพะฑ ะผะพะดะตะปัŒ **ะฒั–ะดั„ั–ะปัŒั‚ั€ัƒะฒะฐะปะฐ ะฐะฑะพ ะฟั€ะธะฑั€ะฐะปะฐ** ั‡ะฐัั‚ะธะฝัƒ ะดะฐะฝะธั…, ัะบ ัƒ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั–. +ะŸั€ะพั‚ะต ะฒ ะฑั–ะปัŒัˆะพัั‚ั– ะฒะธะฟะฐะดะบั–ะฒ, ะบะพะปะธ ะฝะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะทั€ะพะฑะธั‚ะธ ั‰ะพััŒ ะฟะพะดั–ะฑะฝะต, ะผะธ ะฟั€ะพัั‚ะพ ั…ะพั‡ะตะผะพ, ั‰ะพะฑ ะผะพะดะตะปัŒ **ะฒั–ะดั„ั–ะปัŒั‚ั€ัƒะฒะฐะปะฐ/ะฟั€ะธะฑั€ะฐะปะฐ** ั‡ะฐัั‚ะธะฝัƒ ะดะฐะฝะธั…, ัะบ ัƒ ั†ัŒะพะผัƒ ะฟั€ะธะบะปะฐะดั–. -ะฃ ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบะฐั… ะผะธ ะผะพะถะตะผะพ ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ะบะปะฐัะธ ั‚ะฐ ัะฟะฐะดะบัƒะฒะฐะฝะฝั, ั‰ะพะฑ ัะบะพั€ะธัั‚ะฐั‚ะธัั **ะฐะฝะพั‚ะฐั†ั–ัะผะธ ั‚ะธะฟั–ะฒ** ั„ัƒะฝะบั†ั–ะน โ€” ั†ะต ะดะฐั” ะบั€ะฐั‰ัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ ะท ะฑะพะบัƒ ั€ะตะดะฐะบั‚ะพั€ะฐ ั‚ะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ ั‚ะธะฟัƒ mypy, ั– ะฟั€ะธ ั†ัŒะพะผัƒ FastAPI ะฟั€ะพะดะพะฒะถัƒั” ะฒะธะบะพะฝัƒะฒะฐั‚ะธ **ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ะดะฐะฝะธั…** ัƒ ะฒั–ะดะฟะพะฒั–ะดั–. +ะ† ะฒ ั‚ะฐะบะธั… ะฒะธะฟะฐะดะบะฐั… ะผะธ ะผะพะถะตะผะพ ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ะบะปะฐัะธ ั‚ะฐ ัะฟะฐะดะบัƒะฒะฐะฝะฝั, ั‰ะพะฑ ัะบะพั€ะธัั‚ะฐั‚ะธัั **ะฐะฝะพั‚ะฐั†ั–ัะผะธ ั‚ะธะฟั–ะฒ** ั„ัƒะฝะบั†ั–ะน ั– ะพั‚ั€ะธะผะฐั‚ะธ ะบั€ะฐั‰ัƒ ะฟั–ะดั‚ั€ะธะผะบัƒ ะฒ ั€ะตะดะฐะบั‚ะพั€ั– ั‚ะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐั…, ั– ะฟั€ะธ ั†ัŒะพะผัƒ ะทะฑะตั€ะตะณั‚ะธ **ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ะดะฐะฝะธั…** ัƒ FastAPI. {* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *} -ะ—ะฐะฒะดัะบะธ ั†ัŒะพะผัƒ ะผะธ ะพั‚ั€ะธะผัƒั”ะผะพ ะฟั–ะดั‚ั€ะธะผะบัƒ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ โ€” ะฒั–ะด ั€ะตะดะฐะบั‚ะพั€ั–ะฒ ั– mypy, ะพัะบั–ะปัŒะบะธ ั†ะตะน ะบะพะด ั” ะบะพั€ะตะบั‚ะฝะธะผ ะท ั‚ะพั‡ะบะธ ะทะพั€ัƒ ั‚ะธะฟั–ะฒ, โ€” ะฐะปะต ะผะธ ั‚ะฐะบะพะถ ะพั‚ั€ะธะผัƒั”ะผะพ ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ะดะฐะฝะธั… ะฒั–ะด FastAPI. +ะ—ะฐะฒะดัะบะธ ั†ัŒะพะผัƒ ะผะธ ะพั‚ั€ะธะผัƒั”ะผะพ ะฟั–ะดั‚ั€ะธะผะบัƒ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ โ€” ะฒั–ะด ั€ะตะดะฐะบั‚ะพั€ั–ะฒ ั– mypy, ะฐะดะถะต ั†ะตะน ะบะพะด ะบะพั€ะตะบั‚ะฝะธะน ะท ั‚ะพั‡ะบะธ ะทะพั€ัƒ ั‚ะธะฟั–ะฒ, โ€” ะฐะปะต ะผะธ ั‚ะฐะบะพะถ ะพั‚ั€ะธะผัƒั”ะผะพ ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ะดะฐะฝะธั… ะฒั–ะด FastAPI. ะฏะบ ั†ะต ะฟั€ะฐั†ัŽั”? ะ”ะฐะฒะฐะนั‚ะต ั€ะพะทะฑะตั€ะตะผะพัั. ๐Ÿค“ -### ะขะธะฟะธ ั‚ะฐ ะฟั–ะดั‚ั€ะธะผะบะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ +### ะะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ั– ะฟั–ะดั‚ั€ะธะผะบะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ { #type-annotations-and-tooling } -ะกะฟะตั€ัˆัƒ ะฟะพะดะธะฒะธะผะพััŒ, ัะบ ั†ะต ะฑะฐั‡ะฐั‚ัŒ ั€ะตะดะฐะบั‚ะพั€ะธ, mypy ั‚ะฐ ั–ะฝัˆั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ. +ะกะฟะตั€ัˆัƒ ะฟะพะดะธะฒั–ะผะพัั, ัะบ ั†ะต ะฑะฐั‡ะฐั‚ัŒ ั€ะตะดะฐะบั‚ะพั€ะธ, mypy ั‚ะฐ ั–ะฝัˆั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ. -`BaseUser` ะผะฐั” ะฑะฐะทะพะฒั– ะฟะพะปั. ะŸะพั‚ั–ะผ `UserIn` ัƒัะฟะฐะดะบะพะฒัƒั” `BaseUser` ั– ะดะพะดะฐั” ะฟะพะปะต `password`, ะพั‚ะถะต, ะฒั–ะฝ ะผะฐั‚ะธะผะต ะฒัั– ะฟะพะปั ะท ะพะฑะพั… ะผะพะดะตะปะตะน. +`BaseUser` ะผะฐั” ะฑะฐะทะพะฒั– ะฟะพะปั. ะŸะพั‚ั–ะผ `UserIn` ัƒัะฟะฐะดะบะพะฒัƒั” `BaseUser` ั– ะดะพะดะฐั” ะฟะพะปะต `password`, ะพั‚ะถะต ะฒั–ะฝ ะฒะบะปัŽั‡ะฐั‚ะธะผะต ะฒัั– ะฟะพะปั ะท ะพะฑะพั… ะผะพะดะตะปะตะน. -ะœะธ ะทะฐะทะฝะฐั‡ะฐั”ะผะพ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั„ัƒะฝะบั†ั–ั— ัะบ `BaseUser`, ะฐะปะต ั„ะฐะบั‚ะธั‡ะฝะพ ะฟะพะฒะตั€ั‚ะฐั”ะผะพ ะตะบะทะตะผะฟะปัั€ `UserIn`. +ะœะธ ะฐะฝะพั‚ัƒั”ะผะพ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั„ัƒะฝะบั†ั–ั— ัะบ `BaseUser`, ะฐะปะต ั„ะฐะบั‚ะธั‡ะฝะพ ะฟะพะฒะตั€ั‚ะฐั”ะผะพ ะตะบะทะตะผะฟะปัั€ `UserIn`. -ะ ะตะดะฐะบั‚ะพั€, mypy ั‚ะฐ ั–ะฝัˆั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะฝะต ัะบะฐั€ะถะธั‚ะธะผัƒั‚ัŒัั ะฝะฐ ั†ะต, ั‚ะพะผัƒ ั‰ะพ ะท ั‚ะพั‡ะบะธ ะทะพั€ัƒ ั‚ะธะฟั–ะทะฐั†ั–ั— `UserIn` ั” ะฟั–ะดะบะปะฐัะพะผ `BaseUser`, ะฐ ั†ะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒั–ะฝ ั” `ะฒะฐะปั–ะดะฝะธะผ` ั‚ะธะฟะพะผ, ะบะพะปะธ ะพั‡ั–ะบัƒั”ั‚ัŒัั ะฑัƒะดัŒ-ั‰ะพ, ั‰ะพ ั” `BaseUser`. +ะ ะตะดะฐะบั‚ะพั€, mypy ั‚ะฐ ั–ะฝัˆั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะฝะต ัะบะฐั€ะถะธั‚ะธะผัƒั‚ัŒัั ะฝะฐ ั†ะต, ั‚ะพะผัƒ ั‰ะพ ะท ั‚ะพั‡ะบะธ ะทะพั€ัƒ ั‚ะธะฟั–ะทะฐั†ั–ั— `UserIn` ั” ะฟั–ะดะบะปะฐัะพะผ `BaseUser`, ะฐ ั†ะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒั–ะฝ ั” *ะฒะฐะปั–ะดะฝะธะผ* ั‚ะธะฟะพะผ, ะบะพะปะธ ะพั‡ั–ะบัƒั”ั‚ัŒัั ะฑัƒะดัŒ-ั‰ะพ, ั‰ะพ ั” `BaseUser`. -### ะคั–ะปัŒั‚ั€ะฐั†ั–ั ะดะฐะฝะธั… ัƒ FastAPI +### ะคั–ะปัŒั‚ั€ะฐั†ั–ั ะดะฐะฝะธั… ัƒ FastAPI { #fastapi-data-filtering } -ะขะตะฟะตั€ ะดะปั FastAPI ะฒั–ะฝ ะฑะฐั‡ะธั‚ัŒ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั– ะฟะตั€ะตะบะพะฝัƒั”ั‚ัŒัั, ั‰ะพ ั‚ะต, ั‰ะพ ะ’ะธ ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต, ะผั–ัั‚ะธั‚ัŒ **ั‚ั–ะปัŒะบะธ** ะฟะพะปั, ัะบั– ะพะณะพะปะพัˆะตะฝั– ัƒ ั†ัŒะพะผัƒ ั‚ะธะฟั–. +ะขะตะฟะตั€ ะดะปั FastAPI ะฒั–ะฝ ะฟะพะฑะฐั‡ะธั‚ัŒ ั‚ะธะฟ ะฟะพะฒะตั€ะฝะตะฝะฝั ั– ะฟะตั€ะตะบะพะฝะฐั”ั‚ัŒัั, ั‰ะพ ั‚ะต, ั‰ะพ ะฒะธ ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต, ะผั–ัั‚ะธั‚ัŒ **ะปะธัˆะต** ะฟะพะปั, ัะบั– ะพะณะพะปะพัˆะตะฝั– ัƒ ั†ัŒะพะผัƒ ั‚ะธะฟั–. -FastAPI ะฒะธะบะพะฝัƒั” ะบั–ะปัŒะบะฐ ะฒะฝัƒั‚ั€ั–ัˆะฝั–ั… ะพะฟะตั€ะฐั†ั–ะน ะท Pydantic, ั‰ะพะฑ ะณะฐั€ะฐะฝั‚ัƒะฒะฐั‚ะธ, ั‰ะพ ะฟั€ะฐะฒะธะปะฐ ะฝะฐัะปั–ะดัƒะฒะฐะฝะฝั ะบะปะฐัั–ะฒ ะฝะต ะทะฐัั‚ะพัะพะฒัƒัŽั‚ัŒัั ะดะปั ั„ั–ะปัŒั‚ั€ะฐั†ั–ั— ะฟะพะฒะตั€ะฝะตะฝะธั… ะดะฐะฝะธั…, ั–ะฝะฐะบัˆะต ะ’ะธ ะผะพะณะปะธ ะฑ ะฟะพะฒะตั€ะฝัƒั‚ะธ ะทะฝะฐั‡ะฝะพ ะฑั–ะปัŒัˆะต ะดะฐะฝะธั…, ะฝั–ะถ ะพั‡ั–ะบัƒะฒะฐะปะธ. +FastAPI ะฒะธะบะพะฝัƒั” ะบั–ะปัŒะบะฐ ะฒะฝัƒั‚ั€ั–ัˆะฝั–ั… ะพะฟะตั€ะฐั†ั–ะน ะท Pydantic, ั‰ะพะฑ ะณะฐั€ะฐะฝั‚ัƒะฒะฐั‚ะธ, ั‰ะพ ั‚ั– ัะฐะผั– ะฟั€ะฐะฒะธะปะฐ ะฝะฐัะปั–ะดัƒะฒะฐะฝะฝั ะบะปะฐัั–ะฒ ะฝะต ะทะฐัั‚ะพัะพะฒัƒัŽั‚ัŒัั ะดะปั ั„ั–ะปัŒั‚ั€ะฐั†ั–ั— ะฟะพะฒะตั€ะฝะตะฝะธั… ะดะฐะฝะธั…, ั–ะฝะฐะบัˆะต ะฒะธ ะผะพะณะปะธ ะฑ ะทั€ะตัˆั‚ะพัŽ ะฟะพะฒะตั€ั‚ะฐั‚ะธ ะทะฝะฐั‡ะฝะพ ะฑั–ะปัŒัˆะต ะดะฐะฝะธั…, ะฝั–ะถ ะพั‡ั–ะบัƒะฒะฐะปะธ. -ะขะฐะบะธะผ ั‡ะธะฝะพะผ, ะ’ะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะฝะฐะนะบั€ะฐั‰ะต ะท ะดะฒะพั… ัะฒั–ั‚ั–ะฒ: ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ **ะท ะฟั–ะดั‚ั€ะธะผะบะพัŽ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ** ั– **ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ะดะฐะฝะธั…**. +ะขะฐะบะธะผ ั‡ะธะฝะพะผ ะฒะธ ะผะพะถะตั‚ะต ะพั‚ั€ะธะผะฐั‚ะธ ะฝะฐะนะบั€ะฐั‰ะต ะท ะดะฒะพั… ัะฒั–ั‚ั–ะฒ: ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ั–ะท **ะฟั–ะดั‚ั€ะธะผะบะพัŽ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ** ั– **ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ะดะฐะฝะธั…**. -## ะŸะพะดะธะฒะธั‚ะธััŒ ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +## ะŸะพะดะธะฒะธั‚ะธัั ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— { #see-it-in-the-docs } -ะšะพะปะธ ะ’ะธ ะดะธะฒะธั‚ะตััŒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ะ’ะธ ะผะพะถะตั‚ะต ะฟะพะฑะฐั‡ะธั‚ะธ, ั‰ะพ ะฒั…ั–ะดะฝะฐ ะผะพะดะตะปัŒ ั– ะฒะธั…ั–ะดะฝะฐ ะผะพะดะตะปัŒ ะผะฐัŽั‚ัŒ ะฒะปะฐัะฝัƒ JSON-ัั…ะตะผัƒ: +ะšะพะปะธ ะฒะธ ะดะธะฒะธั‚ะตัั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ะฒะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะฒั–ั€ะธั‚ะธ, ั‰ะพ ะฒั…ั–ะดะฝะฐ ะผะพะดะตะปัŒ ั– ะฒะธั…ั–ะดะฝะฐ ะผะพะดะตะปัŒ ะผะฐั‚ะธะผัƒั‚ัŒ ะฒะปะฐัะฝัƒ JSON Schema: <img src="/img/tutorial/response-model/image01.png"> -ะ† ะพะฑะธะดะฒั– ะผะพะดะตะปั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒัั ะดะปั ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพั— API-ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—: +ะ† ะพะฑะธะดะฒั– ะผะพะดะตะปั– ะฑัƒะดัƒั‚ัŒ ะฒะธะบะพั€ะธัั‚ะฐะฝั– ะดะปั ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพั— ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— API: <img src="/img/tutorial/response-model/image02.png"> -## ะ†ะฝัˆั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ะฟะพะฒะตั€ะฝะตะฝะฝั +## ะ†ะฝัˆั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ะฟะพะฒะตั€ะฝะตะฝะฝั { #other-return-type-annotations } -ะ†ัะฝัƒัŽั‚ัŒ ะฒะธะฟะฐะดะบะธ, ะบะพะปะธ ะ’ะธ ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต ั‰ะพััŒ, ั‰ะพ ะฝะต ั” ะดะพะฟัƒัั‚ะธะผะธะผ ะฟะพะปะตะผ Pydantic, ะฐะปะต ะฐะฝะพั‚ัƒั”ั‚ะต ั†ะต ัƒ ั„ัƒะฝะบั†ั–ั— ะปะธัˆะต ะดะปั ั‚ะพะณะพ, ั‰ะพะฑ ะพั‚ั€ะธะผะฐั‚ะธ ะฟั–ะดั‚ั€ะธะผะบัƒ ะฒั–ะด ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ (ั€ะตะดะฐะบั‚ะพั€ะฐ, mypy ั‚ะพั‰ะพ). +ะœะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะฒะธะฟะฐะดะบะธ, ะบะพะปะธ ะฒะธ ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต ั‰ะพััŒ, ั‰ะพ ะฝะต ั” ะฒะฐะปั–ะดะฝะธะผ ะฟะพะปะตะผ Pydantic, ั– ะฐะฝะพั‚ัƒั”ั‚ะต ั†ะต ัƒ ั„ัƒะฝะบั†ั–ั— ะปะธัˆะต ะดะปั ั‚ะพะณะพ, ั‰ะพะฑ ะพั‚ั€ะธะผะฐั‚ะธ ะฟั–ะดั‚ั€ะธะผะบัƒ ะฒั–ะด ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ (ั€ะตะดะฐะบั‚ะพั€ะฐ, mypy ั‚ะพั‰ะพ). -### ะŸะพะฒะตั€ะฝะตะฝะฝั Response ะฝะฐะฟั€ัะผัƒ +### ะŸะพะฒะตั€ะฝัƒั‚ะธ Response ะฝะฐะฟั€ัะผัƒ { #return-a-response-directly } ะะฐะนะฟะพัˆะธั€ะตะฝั–ัˆะธะผ ะฒะธะฟะฐะดะบะพะผ ะฑัƒะดะต [ะฟะพะฒะตั€ะฝะตะฝะฝั Response ะฝะฐะฟั€ัะผัƒ, ัะบ ะฟะพััะฝัŽั”ั‚ัŒัั ะฟั–ะทะฝั–ัˆะต ัƒ ั€ะพะทัˆะธั€ะตะฝั–ะน ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} ะฆะตะน ะฟั€ะพัั‚ะธะน ะฒะธะฟะฐะดะพะบ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะพะฑั€ะพะฑะปัั”ั‚ัŒัั FastAPI, ั‚ะพะผัƒ ั‰ะพ ะฐะฝะพั‚ะฐั†ั–ั ั‚ะธะฟัƒ ะฟะพะฒะตั€ะฝะตะฝะฝั โ€” ั†ะต ะบะปะฐั (ะฐะฑะพ ะฟั–ะดะบะปะฐั) `Response`. ะ† ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ั‚ะฐะบะพะถ ะฑัƒะดัƒั‚ัŒ ะทะฐะดะพะฒะพะปะตะฝั–, ะฑะพ ั– `RedirectResponse`, ั– `JSONResponse` ั” ะฟั–ะดะบะปะฐัะฐะผะธ `Response`, ะพั‚ะถะต ะฐะฝะพั‚ะฐั†ั–ั ั‚ะธะฟัƒ ะบะพั€ะตะบั‚ะฝะฐ. -### ะะฝะพั‚ะฐั†ั–ั ะฟั–ะดะบะปะฐััƒ Response +### ะะฝะพั‚ัƒะฒะฐั‚ะธ ะฟั–ะดะบะปะฐั Response { #annotate-a-response-subclass } -ะขะฐะบะพะถ ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟั–ะดะบะปะฐั `Response` ัƒ ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟัƒ: +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ะฟั–ะดะบะปะฐั `Response` ะฒ ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟัƒ: -{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} ะฆะต ั‚ะตะถ ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต, ะฑะพ `RedirectResponse` โ€” ะฟั–ะดะบะปะฐั `Response`, ั– FastAPI ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะพะฑั€ะพะฑะธั‚ัŒ ั†ะตะน ะฟั€ะพัั‚ะธะน ะฒะธะฟะฐะดะพะบ. -### ะะตะบะพั€ะตะบั‚ะฝั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟัƒ ะฟะพะฒะตั€ะฝะตะฝะฝั +### ะะตะบะพั€ะตะบั‚ะฝั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟัƒ ะฟะพะฒะตั€ะฝะตะฝะฝั { #invalid-return-type-annotations } -ะะปะต ะบะพะปะธ ะ’ะธ ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต ัะบะธะนััŒ ั–ะฝัˆะธะน ะดะพะฒั–ะปัŒะฝะธะน ะพะฑโ€™ั”ะบั‚, ั‰ะพ ะฝะต ั” ะฒะฐะปั–ะดะฝะธะผ ั‚ะธะฟะพะผ Pydantic (ะฝะฐะฟั€ะธะบะปะฐะด, ะพะฑโ€™ั”ะบั‚ ะฑะฐะทะธ ะดะฐะฝะธั…), ั– ะฐะฝะพั‚ัƒั”ั‚ะต ะนะพะณะพ ั‚ะฐะบ ัƒ ั„ัƒะฝะบั†ั–ั—, FastAPI ัะฟั€ะพะฑัƒั” ัั‚ะฒะพั€ะธั‚ะธ Pydantic ะผะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั– ะฝะฐ ะพัะฝะพะฒั– ั†ั–ั”ั— ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟัƒ, ั– ั†ะต ะทะฐะฒะตั€ัˆะธั‚ัŒัั ะฟะพะผะธะปะบะพัŽ. +ะะปะต ะบะพะปะธ ะฒะธ ะฟะพะฒะตั€ั‚ะฐั”ั‚ะต ัะบะธะนััŒ ั–ะฝัˆะธะน ะดะพะฒั–ะปัŒะฝะธะน ะพะฑโ€™ั”ะบั‚, ั‰ะพ ะฝะต ั” ะฒะฐะปั–ะดะฝะธะผ ั‚ะธะฟะพะผ Pydantic (ะฝะฐะฟั€ะธะบะปะฐะด, ะพะฑโ€™ั”ะบั‚ ะฑะฐะทะธ ะดะฐะฝะธั…), ั– ะฐะฝะพั‚ัƒั”ั‚ะต ะนะพะณะพ ั‚ะฐะบ ัƒ ั„ัƒะฝะบั†ั–ั—, FastAPI ัะฟั€ะพะฑัƒั” ัั‚ะฒะพั€ะธั‚ะธ ะผะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั– Pydantic ะฝะฐ ะพัะฝะพะฒั– ั†ั–ั”ั— ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟัƒ ั– ั†ะต ะทะฐะฒะตั€ัˆะธั‚ัŒัั ะฟะพะผะธะปะบะพัŽ. -ะขะต ัะฐะผะต ัั‚ะฐะฝะตั‚ัŒัั, ัะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต <abbr title="ะžะฑ'ั”ะดะฝะฐะฝะฝั (union) ะบั–ะปัŒะบะพั… ั‚ะธะฟั–ะฒ ะพะทะฝะฐั‡ะฐั”: ยซะฑัƒะดัŒ-ัะบะธะน ะท ั†ะธั… ั‚ะธะฟั–ะฒยป.">union</abbr> ะผั–ะถ ั€ั–ะทะฝะธะผะธ ั‚ะธะฟะฐะผะธ, ะดะต ะพะดะธะฝ ะฐะฑะพ ะฑั–ะปัŒัˆะต ะฝะต ั” ะฒะฐะปั–ะดะฝะธะผะธ ั‚ะธะฟะฐะผะธ Pydantic, ะฝะฐะฟั€ะธะบะปะฐะด, ั†ะต ัะฟั€ะธั‡ะธะฝะธั‚ัŒ ะฟะพะผะธะปะบัƒ ๐Ÿ’ฅ: +ะขะต ัะฐะผะต ัั‚ะฐะฝะตั‚ัŒัั, ัะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะฐั”ั‚ะต <abbr title='ะžะฑโ€™ั”ะดะฝะฐะฝะฝั (union) ะผั–ะถ ะบั–ะปัŒะบะพะผะฐ ั‚ะธะฟะฐะผะธ ะพะทะฝะฐั‡ะฐั” ยซะฑัƒะดัŒ-ัะบะธะน ั–ะท ั†ะธั… ั‚ะธะฟั–ะฒยป.'>union</abbr> ะผั–ะถ ั€ั–ะทะฝะธะผะธ ั‚ะธะฟะฐะผะธ, ะดะต ะพะดะธะฝ ะฐะฑะพ ะฑั–ะปัŒัˆะต ะฝะต ั” ะฒะฐะปั–ะดะฝะธะผะธ ั‚ะธะฟะฐะผะธ Pydantic, ะฝะฐะฟั€ะธะบะปะฐะด, ั†ะต ะทะฐะฒะตั€ัˆะธั‚ัŒัั ะฟะพะผะธะปะบะพัŽ ๐Ÿ’ฅ: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} -...ั†ะต ะฝะต ะฟั€ะฐั†ัŽั”, ั‚ะพะผัƒ ั‰ะพ ั‚ะธะฟ ะฐะฝะพั‚ะฐั†ั–ั— ะฝะต ั” ั‚ะธะฟะพะผ Pydantic ั– ะฝะต ั” ะฟั€ะพัั‚ะพ ะบะปะฐัะพะผ `Response` ะฐะฑะพ ะนะพะณะพ ะฟั–ะดะบะปะฐัะพะผ, ะฐ ั” ะพะฑโ€™ั”ะดะฝะฐะฝะฝัะผ (union) โ€” ะฐะฑะพ `Response`, ะฐะฑะพ `dict`. +...ั†ะต ะฝะต ะฟั€ะฐั†ัŽั”, ั‚ะพะผัƒ ั‰ะพ ะฐะฝะพั‚ะฐั†ั–ั ั‚ะธะฟัƒ ะฝะต ั” ั‚ะธะฟะพะผ Pydantic ั– ะฝะต ั” ะฟั€ะพัั‚ะพ ะพะดะฝะธะผ ะบะปะฐัะพะผ `Response` ะฐะฑะพ ะนะพะณะพ ะฟั–ะดะบะปะฐัะพะผ, ั†ะต union (ะฑัƒะดัŒ-ัะบะธะน ั–ะท ะดะฒะพั…) ะผั–ะถ `Response` ั– `dict`. -### ะ’ั–ะดะบะปัŽั‡ะตะฝะฝั ะœะพะดะตะปั– ะ’ั–ะดะฟะพะฒั–ะดั– +### ะ’ะธะผะบะฝัƒั‚ะธ ะผะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั– { #disable-response-model } -ะŸั€ะพะดะพะฒะถัƒัŽั‡ะธ ะฟั€ะธะบะปะฐะด ะฒะธั‰ะต, ะผะพะถะปะธะฒะพ, ะ’ะธ ะฝะต ั…ะพั‡ะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั…, ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ั‚ะพั‰ะพ, ัะบั– FastAPI ะฒะธะบะพะฝัƒั” ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. +ะŸั€ะพะดะพะฒะถัƒัŽั‡ะธ ะฟั€ะธะบะปะฐะด ะฒะธั‰ะต, ะผะพะถะปะธะฒะพ, ะฒะธ ะฝะต ั…ะพั‡ะตั‚ะต ะผะฐั‚ะธ ัั‚ะฐะฝะดะฐั€ั‚ะฝัƒ ะฒะฐะปั–ะดะฐั†ั–ัŽ ะดะฐะฝะธั…, ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ั‚ะพั‰ะพ, ัะบั– ะฒะธะบะพะฝัƒั” FastAPI. -ะะปะต ะฒะธ ะฒัะต ะพะดะฝะพ ะผะพะถะตั‚ะต ะทะฐะปะธัˆะธั‚ะธ ะฐะฝะพั‚ะฐั†ั–ัŽ ั‚ะธะฟัƒ ัƒ ั„ัƒะฝะบั†ั–ั—, ั‰ะพะฑ ะทะฑะตั€ะตะณั‚ะธ ะฟั–ะดั‚ั€ะธะผะบัƒ ะท ะฑะพะบัƒ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ั‚ะฐะบะธั… ัะบ ั€ะตะดะฐะบั‚ะพั€ะธ ะบะพะดัƒ ะฐะฑะพ ัั‚ะฐั‚ะธั‡ะฝั– ะฟะตั€ะตะฒั–ั€ะบะธ ั‚ะธะฟั–ะฒ (ะฝะฐะฟั€ะธะบะปะฐะด, mypy). +ะะปะต ะฒะธ ะผะพะถะตั‚ะต ะฒัะต ะพะดะฝะพ ั…ะพั‚ั–ั‚ะธ ะทะฐะปะธัˆะธั‚ะธ ะฐะฝะพั‚ะฐั†ั–ัŽ ั‚ะธะฟัƒ ะฟะพะฒะตั€ะฝะตะฝะฝั ัƒ ั„ัƒะฝะบั†ั–ั—, ั‰ะพะฑ ะพั‚ั€ะธะผะฐั‚ะธ ะฟั–ะดั‚ั€ะธะผะบัƒ ะฒั–ะด ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ัะบ-ะพั‚ ั€ะตะดะฐะบั‚ะพั€ะธ ั‚ะฐ ะฟะตั€ะตะฒั–ั€ะบะธ ั‚ะธะฟั–ะฒ (ะฝะฐะฟั€ะธะบะปะฐะด, mypy). ะฃ ั‚ะฐะบะพะผัƒ ะฒะธะฟะฐะดะบัƒ ะฒะธ ะผะพะถะตั‚ะต ะฒะธะผะบะฝัƒั‚ะธ ะณะตะฝะตั€ะฐั†ั–ัŽ ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั–, ะฒัั‚ะฐะฝะพะฒะธะฒัˆะธ `response_model=None`: {* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *} -ะฆะต ะทะผัƒัะธั‚ัŒ FastAPI ะฟั€ะพะฟัƒัั‚ะธั‚ะธ ะณะตะฝะตั€ะฐั†ั–ัŽ ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั–, ั– ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ ะ’ะธ ะทะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฑัƒะดัŒ-ัะบั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ะฟะพะฒะตั€ะฝะตะฝะฝั ะฑะตะท ะฒะฟะปะธะฒัƒ ะฝะฐ ะฒะฐัˆัƒ FastAPI ะฐะฟะปั–ะบะฐั†ั–ัŽ. ๐Ÿค“ +ะฆะต ะทะผัƒัะธั‚ัŒ FastAPI ะฟั€ะพะฟัƒัั‚ะธั‚ะธ ะณะตะฝะตั€ะฐั†ั–ัŽ ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั–, ั– ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ ะฒะธ ะทะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฑัƒะดัŒ-ัะบั– ะฟะพั‚ั€ั–ะฑะฝั– ะฐะฝะพั‚ะฐั†ั–ั— ั‚ะธะฟั–ะฒ ะฟะพะฒะตั€ะฝะตะฝะฝั ะฑะตะท ะฒะฟะปะธะฒัƒ ะฝะฐ ะฒะฐัˆ FastAPI ะทะฐัั‚ะพััƒะฝะพะบ. ๐Ÿค“ -## ะŸะฐั€ะฐะผะตั‚ั€ะธ ะบะพะดัƒะฒะฐะฝะฝั ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั– +## ะŸะฐั€ะฐะผะตั‚ั€ะธ ะบะพะดัƒะฒะฐะฝะฝั ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั– { #response-model-encoding-parameters } ะ’ะฐัˆะฐ ะผะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั– ะผะพะถะต ะผะฐั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ะฝะฐะฟั€ะธะบะปะฐะด: @@ -230,19 +229,19 @@ FastAPI ะฒะธะบะพะฝัƒั” ะบั–ะปัŒะบะฐ ะฒะฝัƒั‚ั€ั–ัˆะฝั–ั… ะพะฟะตั€ะฐั†ั–ะน ะท Pyd * `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_py310.py hl[22] *} -ั– ั†ั– ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฝะต ะฑัƒะดัƒั‚ัŒ ะฒะบะปัŽั‡ะตะฝั– ัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ, ั‚ั–ะปัŒะบะธ ั„ะฐะบั‚ะธั‡ะฝะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝั– ะทะฝะฐั‡ะตะฝะฝั. +ั– ั†ั– ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฝะต ะฑัƒะดัƒั‚ัŒ ะฒะบะปัŽั‡ะตะฝั– ัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ, ะปะธัˆะต ะทะฝะฐั‡ะตะฝะฝั, ัะบั– ั„ะฐะบั‚ะธั‡ะฝะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝั–. -ะžั‚ะถะต, ัะบั‰ะพ ะ’ะธ ะฝะฐะดั–ัˆะปะตั‚ะต ะทะฐะฟะธั‚ ะดะพ ั†ัŒะพะณะพ ะพะฟะตั€ะฐั‚ะพั€ะฐ ัˆะปัั…ัƒ ะดะปั ะตะปะตะผะตะฝั‚ะฐ ะท item_id `foo`, ะฒั–ะดะฟะพะฒั–ะดัŒ (ะฑะตะท ะฒะบะปัŽั‡ะตะฝะฝั ะทะฝะฐั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ) ะฑัƒะดะต: +ะžั‚ะถะต, ัะบั‰ะพ ะฒะธ ะฝะฐะดั–ัˆะปะตั‚ะต ะทะฐะฟะธั‚ ะดะพ ั†ั–ั”ั— *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะดะปั ะตะปะตะผะตะฝั‚ะฐ ะท ID `foo`, ะฒั–ะดะฟะพะฒั–ะดัŒ (ะฑะตะท ะฒะบะปัŽั‡ะตะฝะฝั ะทะฝะฐั‡ะตะฝัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ) ะฑัƒะดะต: ```JSON { @@ -253,32 +252,18 @@ FastAPI ะฒะธะบะพะฝัƒั” ะบั–ะปัŒะบะฐ ะฒะฝัƒั‚ั€ั–ัˆะฝั–ั… ะพะฟะตั€ะฐั†ั–ะน ะท Pyd /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะฃ Pydantic ะฒะตั€ัั–ั— 1 ะผะตั‚ะพะด ะฝะฐะทะธะฒะฐะฒัั `.dict()`, ะฒั–ะฝ ะฑัƒะฒ ะทะฐัั‚ะฐั€ั–ะปะธะน (ะฐะปะต ั‰ะต ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั) ัƒ Pydantic ะฒะตั€ัั–ั— 2 ั– ะฟะตั€ะตะนะผะตะฝะพะฒะฐะฝะธะน ัƒ `.model_dump()`. - -ะŸั€ะธะบะปะฐะดะธ ั‚ัƒั‚ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ `.dict()` ะดะปั ััƒะผั–ัะฝะพัั‚ั– ะท Pydantic v1, ะฐะปะต ะ’ะฐะผ ัะปั–ะด ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `.model_dump()`, ัะบั‰ะพ ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Pydantic v2. - -/// - -/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั - -FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” `.dict()` ะผะพะดะตะปั– Pydantic ะท <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">ะฟะฐั€ะฐะผะตั‚ั€ะพะผ `exclude_unset`</a>, ั‰ะพะฑ ะดะพััะณั‚ะธ ั†ัŒะพะณะพ. - -/// - -/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั - ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ: * `response_model_exclude_defaults=True` * `response_model_exclude_none=True` -ัะบ ะพะฟะธัะฐะฝะพ ะฒ <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Pydantic</a> for `exclude_defaults` ั‚ะฐ `exclude_none`. +ัะบ ะพะฟะธัะฐะฝะพ ะฒ <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Pydantic</a> ะดะปั `exclude_defaults` ั‚ะฐ `exclude_none`. /// -#### ะ”ะฐะฝั– ะทั– ะทะฝะฐั‡ะตะฝะฝัะผะธ ะดะปั ะฟะพะปั–ะฒ ั–ะท ั‚ะธะฟะพะฒะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ +#### ะ”ะฐะฝั– ะทั– ะทะฝะฐั‡ะตะฝะฝัะผะธ ะดะปั ะฟะพะปั–ะฒ ั–ะท ั‚ะธะฟะพะฒะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ { #data-with-values-for-fields-with-defaults } -ะะปะต ัะบั‰ะพ ะ’ะฐัˆั– ะดะฐะฝั– ะผะฐัŽั‚ัŒ ะทะฝะฐั‡ะตะฝะฝั ะดะปั ะฟะพะปั–ะฒ ะผะพะดะตะปั– ะท ั‚ะธะฟะพะฒะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ, ัะบ ัƒ ะตะปะตะผะตะฝั‚ะฐ ะท item_id `bar`: +ะะปะต ัะบั‰ะพ ะฒะฐัˆั– ะดะฐะฝั– ะผะฐัŽั‚ัŒ ะทะฝะฐั‡ะตะฝะฝั ะดะปั ะฟะพะปั–ะฒ ะผะพะดะตะปั– ะท ั‚ะธะฟะพะฒะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ, ัะบ ัƒ ะตะปะตะผะตะฝั‚ะฐ ะท ID `bar`: ```Python hl_lines="3 5" { @@ -288,11 +273,12 @@ FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” `.dict()` ะผะพะดะตะปั– Pydantic ะท <a href="htt "tax": 20.2 } ``` + ะฒะพะฝะธ ะฑัƒะดัƒั‚ัŒ ะฒะบะปัŽั‡ะตะฝั– ัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ. -#### ะ”ะฐะฝั– ะท ั‚ะธะผะธ ัะฐะผะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ, ั‰ะพ ะน ั‚ะธะฟะพะฒั– +#### ะ”ะฐะฝั– ะท ั‚ะธะผะธ ัะฐะผะธะผะธ ะทะฝะฐั‡ะตะฝะฝัะผะธ, ั‰ะพ ะน ั‚ะธะฟะพะฒั– { #data-with-the-same-values-as-the-defaults } -ะฏะบั‰ะพ ะดะฐะฝั– ะผะฐัŽั‚ัŒ ั‚ั– ัะฐะผั– ะทะฝะฐั‡ะตะฝะฝั, ั‰ะพ ะน ั‚ะธะฟะพะฒั–, ัะบ ัƒ ะตะปะตะผะตะฝั‚ะฐ ะท item_id `baz`: +ะฏะบั‰ะพ ะดะฐะฝั– ะผะฐัŽั‚ัŒ ั‚ั– ัะฐะผั– ะทะฝะฐั‡ะตะฝะฝั, ั‰ะพ ะน ั‚ะธะฟะพะฒั–, ัะบ ัƒ ะตะปะตะผะตะฝั‚ะฐ ะท ID `baz`: ```Python hl_lines="3 5-6" { @@ -304,7 +290,7 @@ FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” `.dict()` ะผะพะดะตะปั– Pydantic ะท <a href="htt } ``` -FastAPI ะดะพัั‚ะฐั‚ะฝัŒะพ ั€ะพะทัƒะผะฝะธะน (ะฝะฐัะฟั€ะฐะฒะดั–, Pydantic ะดะพัั‚ะฐั‚ะฝัŒะพ ั€ะพะทัƒะผะฝะธะน), ั‰ะพะฑ ะทั€ะพะทัƒะผั–ั‚ะธ, ั‰ะพ, ั…ะพั‡ะฐ `description`, `tax` ั– `tags` ะผะฐัŽั‚ัŒ ั‚ั– ัะฐะผั– ะทะฝะฐั‡ะตะฝะฝั, ั‰ะพ ะน ั‚ะธะฟะพะฒั–, ะฒะพะฝะธ ะฑัƒะปะธ ะฒัั‚ะฐะฝะพะฒะปะตะฝั– ัะฒะฝะพ (ะฐ ะฝะต ะฒะทัั‚ั– ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ). +FastAPI ะดะพัั‚ะฐั‚ะฝัŒะพ ั€ะพะทัƒะผะฝะธะน (ะฝะฐัะฟั€ะฐะฒะดั–, Pydantic ะดะพัั‚ะฐั‚ะฝัŒะพ ั€ะพะทัƒะผะฝะธะน), ั‰ะพะฑ ะทั€ะพะทัƒะผั–ั‚ะธ, ั‰ะพ, ั…ะพั‡ะฐ `description`, `tax` ั– `tags` ะผะฐัŽั‚ัŒ ั‚ั– ัะฐะผั– ะทะฝะฐั‡ะตะฝะฝั, ั‰ะพ ะน ั‚ะธะฟะพะฒั–, ั—ั… ะฑัƒะปะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ ัะฒะฝะพ (ะฐ ะฝะต ะฒะทัั‚ะพ ัะบ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ). ะžั‚ะถะต, ะฒะพะฝะธ ะฑัƒะดัƒั‚ัŒ ะฒะบะปัŽั‡ะตะฝั– ัƒ JSON-ะฒั–ะดะฟะพะฒั–ะดัŒ. @@ -312,24 +298,23 @@ FastAPI ะดะพัั‚ะฐั‚ะฝัŒะพ ั€ะพะทัƒะผะฝะธะน (ะฝะฐัะฟั€ะฐะฒะดั–, Pydantic ะดะพั ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ ั‚ะธะฟะพะฒั– ะทะฝะฐั‡ะตะฝะฝั ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะฑัƒะดัŒ-ัะบะธะผะธ, ะฝะต ะปะธัˆะต `None`. -ะฆะต ะผะพะถะต ะฑัƒั‚ะธ list (`[]`), `float` 10.5 ั‚ะพั‰ะพ. +ะฆะต ะผะพะถะต ะฑัƒั‚ะธ list (`[]`), `float` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `10.5` ั‚ะพั‰ะพ. /// -### `response_model_include` ั‚ะฐ `response_model_exclude` +### `response_model_include` ั‚ะฐ `response_model_exclude` { #response-model-include-and-response-model-exclude } ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ *ะดะตะบะพั€ะฐั‚ะพั€ะฐ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* `response_model_include` ั‚ะฐ `response_model_exclude`. -ะ’ะพะฝะธ ะฟั€ะธะนะผะฐัŽั‚ัŒ `set` (ะผะฝะพะถะธะฝัƒ) ั€ัะดะบั–ะฒ (`str`) ะท ั–ะผะตะฝะฐะผะธ ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ, ัะบั– ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะบะปัŽั‡ะธั‚ะธ (ะฟั€ะพะฟัƒัะบะฐัŽั‡ะธ ั–ะฝัˆั–) ะฐะฑะพ ะฒะธะบะปัŽั‡ะธั‚ะธ (ะฒะบะปัŽั‡ะฐัŽั‡ะธ ั–ะฝัˆั–). +ะ’ะพะฝะธ ะฟั€ะธะนะผะฐัŽั‚ัŒ `set` ะทั– `str` ะท ั–ะผะตะฝะฐะผะธ ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ, ัะบั– ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะบะปัŽั‡ะธั‚ะธ (ะฟั€ะพะฟัƒัะบะฐัŽั‡ะธ ั€ะตัˆั‚ัƒ) ะฐะฑะพ ะฒะธะบะปัŽั‡ะธั‚ะธ (ะฒะบะปัŽั‡ะฐัŽั‡ะธ ั€ะตัˆั‚ัƒ). -ะฆะต ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัะบ ัˆะฒะธะดะบะธะน ัะฟะพัั–ะฑ, ัะบั‰ะพ ัƒ ะ’ะฐั ั” ะปะธัˆะต ะพะดะฝะฐ ะผะพะดะตะปัŒ Pydantic ั– ะ’ะธ ั…ะพั‡ะตั‚ะต ะฒะธะดะฐะปะธั‚ะธ ะดะตัะบั– ะดะฐะฝั– ะท ะฒะธะฒะพะดัƒ. +ะฆะต ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ัะบ ัˆะฒะธะดะบะธะน ัะฟะพัั–ะฑ, ัะบั‰ะพ ัƒ ะฒะฐั ั” ะปะธัˆะต ะพะดะฝะฐ ะผะพะดะตะปัŒ Pydantic ั– ะฒะธ ั…ะพั‡ะตั‚ะต ะฒะธะดะฐะปะธั‚ะธ ะดะตัะบั– ะดะฐะฝั– ะท ะฒะธะฒะพะดัƒ. /// tip | ะŸะพั€ะฐะดะฐ -ะะปะต ะฒัะต ะถ ั€ะตะบะพะผะตะฝะดัƒั”ั‚ัŒัั ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะฟะธัะฐะฝั– ะฒะธั‰ะต ะฟั–ะดั…ะพะดะธ, ั–ะท ะทะฐัั‚ะพััƒะฒะฐะฝะฝัะผ ะบั–ะปัŒะบะพั… ะบะปะฐัั–ะฒ, ะทะฐะผั–ัั‚ัŒ ั†ะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ. +ะะปะต ะฒัะต ะถ ั€ะตะบะพะผะตะฝะดัƒั”ั‚ัŒัั ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะพะฟะธัะฐะฝั– ะฒะธั‰ะต ะฟั–ะดั…ะพะดะธ, ะทะฐัั‚ะพัะพะฒัƒัŽั‡ะธ ะบั–ะปัŒะบะฐ ะบะปะฐัั–ะฒ, ะทะฐะผั–ัั‚ัŒ ั†ะธั… ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ. - -ะฆะต ั‚ะพะผัƒ, ั‰ะพ JSON Schema, ัะบะธะน ะณะตะฝะตั€ัƒั”ั‚ัŒัั ัƒ ะฒะฐัˆะพะผัƒ OpenAPI ะดะพะดะฐั‚ะบัƒ (ั– ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—), ะฒัะต ะพะดะฝะพ ะฑัƒะดะต ะฒั–ะดะฟะพะฒั–ะดะฐั‚ะธ ะฟะพะฒะฝั–ะน ะผะพะดะตะปั–, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `response_model_include` ะฐะฑะพ `response_model_exclude` ะดะปั ะฒะธะบะปัŽั‡ะตะฝะฝั ะดะตัะบะธั… ะฐั‚ั€ะธะฑัƒั‚ั–ะฒ. +ะฆะต ั‚ะพะผัƒ, ั‰ะพ JSON Schema, ัะบะธะน ะณะตะฝะตั€ัƒั”ั‚ัŒัั ะฒ OpenAPI ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ (ั– ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—), ะฒัะต ะพะดะฝะพ ะฑัƒะดะต ะฒั–ะดะฟะพะฒั–ะดะฐั‚ะธ ะฟะพะฒะฝั–ะน ะผะพะดะตะปั–, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `response_model_include` ะฐะฑะพ `response_model_exclude`, ั‰ะพะฑ ะฟั€ะพะฟัƒัั‚ะธั‚ะธ ะดะตัะบั– ะฐั‚ั€ะธะฑัƒั‚ะธ. ะฆะต ั‚ะฐะบะพะถ ัั‚ะพััƒั”ั‚ัŒัั `response_model_by_alias`, ัะบะธะน ะฟั€ะฐั†ัŽั” ะฟะพะดั–ะฑะฝะธะผ ั‡ะธะฝะพะผ. @@ -345,14 +330,14 @@ FastAPI ะดะพัั‚ะฐั‚ะฝัŒะพ ั€ะพะทัƒะผะฝะธะน (ะฝะฐัะฟั€ะฐะฒะดั–, Pydantic ะดะพั /// -#### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `list` ะทะฐะผั–ัั‚ัŒ `set` +#### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `list` ะทะฐะผั–ัั‚ัŒ `set` { #using-lists-instead-of-sets } -ะฏะบั‰ะพ ะ’ะธ ะทะฐะฑัƒะดะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `set` ั– ะฝะฐั‚ะพะผั–ัั‚ัŒ ะทะฐัั‚ะพััƒั”ั‚ะต `list` ะฐะฑะพ `tuple`, FastAPI ะฒัะต ะพะดะฝะพ ะฟะตั€ะตั‚ะฒะพั€ะธั‚ัŒ ั†ะต ะฝะฐ `set`, ั– ะฒัะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ะฟั€ะฐะฒะธะปัŒะฝะพ: +ะฏะบั‰ะพ ะฒะธ ะทะฐะฑัƒะดะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `set` ั– ะฝะฐั‚ะพะผั–ัั‚ัŒ ะทะฐัั‚ะพััƒั”ั‚ะต `list` ะฐะฑะพ `tuple`, FastAPI ะฒัะต ะพะดะฝะพ ะฟะตั€ะตั‚ะฒะพั€ะธั‚ัŒ ั†ะต ะฝะฐ `set`, ั– ะฒัะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ะฟั€ะฐะฒะธะปัŒะฝะพ: {* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *} -## ะŸั–ะดััƒะผะพะบ +## ะŸั–ะดััƒะผะพะบ { #recap } -ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ `response_model` *ะดะตะบะพั€ะฐั‚ะพั€ะฐ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ั‰ะพะฑ ะฒะธะทะฝะฐั‡ะฐั‚ะธ ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั–, ะพัะพะฑะปะธะฒะพ ั‰ะพะฑ ะณะฐั€ะฐะฝั‚ัƒะฒะฐั‚ะธ ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ะฟั€ะธะฒะฐั‚ะฝะธั… ะดะฐะฝะธั…. +ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ `response_model` *ะดะตะบะพั€ะฐั‚ะพั€ะฐ ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ั‰ะพะฑ ะฒะธะทะฝะฐั‡ะฐั‚ะธ ะผะพะดะตะปั– ะฒั–ะดะฟะพะฒั–ะดั– ั– ะพัะพะฑะปะธะฒะพ ั‰ะพะฑ ะณะฐั€ะฐะฝั‚ัƒะฒะฐั‚ะธ ั„ั–ะปัŒั‚ั€ะฐั†ั–ัŽ ะฟั€ะธะฒะฐั‚ะฝะธั… ะดะฐะฝะธั…. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต `response_model_exclude_unset`, ั‰ะพะฑ ะฟะพะฒะตั€ั‚ะฐั‚ะธ ะปะธัˆะต ัะฒะฝะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝั– ะทะฝะฐั‡ะตะฝะฝั. diff --git a/docs/uk/docs/tutorial/response-status-code.md b/docs/uk/docs/tutorial/response-status-code.md index 1ed69d6f25..5a08ee46b5 100644 --- a/docs/uk/docs/tutorial/response-status-code.md +++ b/docs/uk/docs/tutorial/response-status-code.md @@ -1,6 +1,6 @@ -# ะกั‚ะฐั‚ัƒั ะบะพะดะธ ะ’ั–ะดะฟะพะฒั–ะดะตะน +# ะšะพะด ัั‚ะฐั‚ัƒััƒ ะฒั–ะดะฟะพะฒั–ะดั– { #response-status-code } -ะขะฐะบ ัะฐะผะพ ัะบ ะ’ะธ ะผะพะถะตั‚ะต ะฒะบะฐะทะฐั‚ะธ ะผะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั–, ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ HTTP ะบะพะด ัั‚ะฐั‚ัƒััƒ ะดะปั ะฒั–ะดะฟะพะฒั–ะดั– ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `status_code` ะฒ ะฑัƒะดัŒ-ัะบั–ะน ะท *ะพะฟะตั€ะฐั†ั–ะน ัˆะปัั…ัƒ*: +ะขะฐะบ ัะฐะผะพ, ัะบ ะฒะธ ะผะพะถะตั‚ะต ะฒะบะฐะทะฐั‚ะธ ะผะพะดะตะปัŒ ะฒั–ะดะฟะพะฒั–ะดั–, ะฒะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ HTTP ะบะพะด ัั‚ะฐั‚ัƒััƒ, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะดะปั ะฒั–ะดะฟะพะฒั–ะดั–, ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `status_code` ะฒ ะฑัƒะดัŒ-ัะบั–ะน ะท *ะพะฟะตั€ะฐั†ั–ะน ัˆะปัั…ัƒ*: * `@app.get()` * `@app.post()` @@ -8,82 +8,83 @@ * `@app.delete()` * ั‚ะพั‰ะพ. -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} -/// note | ะะพั‚ะฐั‚ะบะฐ +/// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ `status_code` ั” ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ะผะตั‚ะพะดัƒ "ะดะตะบะพั€ะฐั‚ะพั€ะฐ" (`get`, `post` ั– ั‚.ะด.), ะฐ ะฝะต ะ’ะฐัˆะพั— *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ัะบ ัƒัั– ั–ะฝัˆั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะฐ ั‚ั–ะปะพ ะทะฐะฟะธั‚ัƒ. +ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ `status_code` ั” ะฟะฐั€ะฐะผะตั‚ั€ะพะผ ะผะตั‚ะพะดัƒ ยซะดะตะบะพั€ะฐั‚ะพั€ะฐยป (`get`, `post`, ั‚ะพั‰ะพ). ะะต ะฒะฐัˆะพั— *ั„ัƒะฝะบั†ั–ั— ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*, ัะบ ัƒัั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ั‚ะฐ ั‚ั–ะปะพ. /// -ะŸะฐั€ะฐะผะตั‚ั€ `status_code` ะฟั€ะธะนะผะฐั” ั‡ะธัะปะพ, ัะบะต ะฒั–ะดะฟะพะฒั–ะดะฐั” HTTP ะบะพะดัƒ ัั‚ะฐั‚ัƒััƒ. +ะŸะฐั€ะฐะผะตั‚ั€ `status_code` ะฟั€ะธะนะผะฐั” ั‡ะธัะปะพ ะท HTTP ะบะพะดะพะผ ัั‚ะฐั‚ัƒััƒ. /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -`status_code` ั‚ะฐะบะพะถ ะผะพะถะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะทะฝะฐั‡ะตะฝะฝั ะท `IntEnum`, ะฝะฐะฟั€ะธะบะปะฐะด, ะท Python <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a>. + +`status_code` ั‚ะฐะบะพะถ ะผะพะถะต, ัะบ ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒัƒ, ะฟั€ะธะนะผะฐั‚ะธ `IntEnum`, ะฝะฐะฟั€ะธะบะปะฐะด, Python <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a>. /// ะ’ั–ะฝ ะฑัƒะดะต: -* ะŸะพะฒะตั€ั‚ะฐั‚ะธ ะฒะบะฐะทะฐะฝะธะน ะบะพะด ัั‚ะฐั‚ัƒััƒ ัƒ ะฒั–ะดะฟะพะฒั–ะดั–. -* ะ”ะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธ ะนะพะณะพ ัะบ ั‚ะฐะบะธะน ัƒ ัั…ะตะผั– OpenAPI (ั–, ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ, ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั– ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ): +* ะŸะพะฒะตั€ั‚ะฐั‚ะธ ั†ะตะน ะบะพะด ัั‚ะฐั‚ัƒััƒ ัƒ ะฒั–ะดะฟะพะฒั–ะดั–. +* ะ”ะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธ ะนะพะณะพ ัะบ ั‚ะฐะบะธะน ัƒ ัั…ะตะผั– OpenAPI (ั–, ั‚ะฐะบะธะผ ั‡ะธะฝะพะผ, ะฒ ั–ะฝั‚ะตั€ั„ะตะนัะฐั… ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ): <img src="/img/tutorial/response-status-code/image01.png"> -/// note | ะะพั‚ะฐั‚ะบะฐ +/// note | ะŸั€ะธะผั–ั‚ะบะฐ ะ”ะตัะบั– ะบะพะดะธ ะฒั–ะดะฟะพะฒั–ะดั– (ะดะธะฒ. ะฝะฐัั‚ัƒะฟะฝะธะน ั€ะพะทะดั–ะป) ะฒะบะฐะทัƒัŽั‚ัŒ, ั‰ะพ ะฒั–ะดะฟะพะฒั–ะดัŒ ะฝะต ะผะฐั” ั‚ั–ะปะฐ. -FastAPI ะทะฝะฐั” ะฟั€ะพ ั†ะต ั– ัั‚ะฒะพั€ะธั‚ัŒ OpenAPI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ัะบะฐ ะฒะบะฐะทัƒั”, ั‰ะพ ั‚ั–ะปะฐ ะฒั–ะดะฟะพะฒั–ะดั– ะฝะตะผะฐั”. +FastAPI ะทะฝะฐั” ะฟั€ะพ ั†ะต ั– ัั‚ะฒะพั€ะธั‚ัŒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ OpenAPI, ัะบะฐ ะฒะบะฐะทัƒั”, ั‰ะพ ั‚ั–ะปะฐ ะฒั–ะดะฟะพะฒั–ะดั– ะฝะตะผะฐั”. /// -## ะŸั€ะพ HTTP ัั‚ะฐั‚ัƒั ะบะพะดะธ +## ะŸั€ะพ HTTP ะบะพะดะธ ัั‚ะฐั‚ัƒััƒ { #about-http-status-codes } -/// note | ะะพั‚ะฐั‚ะบะฐ +/// note | ะŸั€ะธะผั–ั‚ะบะฐ -ะฏะบั‰ะพ ะ’ะธ ะฒะถะต ะทะฝะฐั”ั‚ะต, ั‰ะพ ั‚ะฐะบะต HTTP ะบะพะดะธ ัั‚ะฐั‚ัƒััƒ, ะฟะตั€ะตั…ะพะดัŒั‚ะต ะดะพ ะฝะฐัั‚ัƒะฟะฝะพะณะพ ั€ะพะทะดั–ะปัƒ. +ะฏะบั‰ะพ ะฒะธ ะฒะถะต ะทะฝะฐั”ั‚ะต, ั‰ะพ ั‚ะฐะบะต HTTP ะบะพะดะธ ัั‚ะฐั‚ัƒััƒ, ะฟะตั€ะตะนะดั–ั‚ัŒ ะดะพ ะฝะฐัั‚ัƒะฟะฝะพะณะพ ั€ะพะทะดั–ะปัƒ. /// -ะ’ HTTP ะ’ะธ ะฝะฐะดัะธะปะฐั”ั‚ะต ั‡ะธัะปะพะฒะธะน ะบะพะด ัั‚ะฐั‚ัƒััƒ ะท 3 ั†ะธั„ั€ ัะบ ั‡ะฐัั‚ะธะฝัƒ ะฒั–ะดะฟะพะฒั–ะดั–. +ะ’ HTTP ะฒะธ ะฝะฐะดัะธะปะฐั”ั‚ะต ั‡ะธัะปะพะฒะธะน ะบะพะด ัั‚ะฐั‚ัƒััƒ ะท 3 ั†ะธั„ั€ ัะบ ั‡ะฐัั‚ะธะฝัƒ ะฒั–ะดะฟะพะฒั–ะดั–. -ะฆั– ะบะพะดะธ ัั‚ะฐั‚ัƒััƒ ะผะฐัŽั‚ัŒ ะฟะพะฒโ€™ัะทะฐะฝัƒ ะฝะฐะทะฒัƒ ะดะปั ั—ั… ั€ะพะทะฟั–ะทะฝะฐะฒะฐะฝะฝั, ะฐะปะต ะฝะฐะนะฒะฐะถะปะธะฒั–ัˆะพัŽ ั‡ะฐัั‚ะธะฝะพัŽ ั” ัะฐะผะต ั‡ะธัะปะพ. +ะฆั– ะบะพะดะธ ัั‚ะฐั‚ัƒััƒ ะผะฐัŽั‚ัŒ ะฟะพะฒโ€™ัะทะฐะฝัƒ ะฝะฐะทะฒัƒ ะดะปั ั—ั… ั€ะพะทะฟั–ะทะฝะฐะฒะฐะฝะฝั, ะฐะปะต ะฒะฐะถะปะธะฒะพัŽ ั‡ะฐัั‚ะธะฝะพัŽ ั” ั‡ะธัะปะพ. ะšะพั€ะพั‚ะบะพ: -* **`100 - 199`** "ะ†ะฝั„ะพั€ะผะฐั†ั–ะนะฝั–" ะฒั–ะดะฟะพะฒั–ะดั–. ะ’ะธ ั€ั–ะดะบะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ั—ั… ะฝะฐะฟั€ัะผัƒ. ะ’ั–ะดะฟะพะฒั–ะดั– ะท ั‚ะฐะบะธะผะธ ะบะพะดะฐะผะธ ะฝะต ะผะพะถัƒั‚ัŒ ะผะฐั‚ะธ ั‚ั–ะปะฐ. -* **`200 - 299`** "ะฃัะฟั–ัˆะฝั–" ะฒั–ะดะฟะพะฒั–ะดั–. ะฆะต ั‚ั–, ัะบั– ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะตั‚ะต ะฝะฐะนั‡ะฐัั‚ั–ัˆะต. - * `200` - ะบะพะด ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ัะบะธะน ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒัะต ะฟั€ะพะนัˆะปะพ "OK". - * ะ†ะฝัˆะธะน ะฟั€ะธะบะปะฐะด โ€“ `201`, "Created" (ัั‚ะฒะพั€ะตะฝะพ). ะ™ะพะณะพ ะทะฐะทะฒะธั‡ะฐะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ ะฟั–ัะปั ัั‚ะฒะพั€ะตะฝะฝั ะฝะพะฒะพะณะพ ะทะฐะฟะธััƒ ะฒ ะฑะฐะทั– ะดะฐะฝะธั…. - * ะžัะพะฑะปะธะฒะธะน ะฒะธะฟะฐะดะพะบ โ€“ `204`, "No Content" (ะฝะตะผะฐั” ะฒะผั–ัั‚ัƒ). ะฆั ะฒั–ะดะฟะพะฒั–ะดัŒ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั, ะบะพะปะธ ะฝะตะผะฐั” ะดะฐะฝะธั… ะดะปั ะฟะพะฒะตั€ะฝะตะฝะฝั ะบะปั–ั”ะฝั‚ัƒ, ั‚ะพะผัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ ะฝะต ะฟะพะฒะธะฝะฝะฐ ะผะฐั‚ะธ ั‚ั–ะปะฐ. -* **`300 - 399`** "ะŸะตั€ะตะฝะฐะฟั€ะฐะฒะปะตะฝะฝั". ะ’ั–ะดะฟะพะฒั–ะดั– ะท ั†ะธะผะธ ะบะพะดะฐะผะธ ะผะพะถัƒั‚ัŒ ะผะฐั‚ะธ ะฐะฑะพ ะฝะต ะผะฐั‚ะธ ั‚ั–ะปะฐ, ะทะฐ ะฒะธะฝัั‚ะบะพะผ `304`, "Not Modified" (ะฝะต ะทะผั–ะฝะตะฝะพ), ัะบะฐ ะฝะต ะฟะพะฒะธะฝะฝะฐ ะผะฐั‚ะธ ั‚ั–ะปะฐ. -* **`400 - 499`** "ะŸะพะผะธะปะบะฐ ะบะปั–ั”ะฝั‚ะฐ". ะฆะต ะดั€ัƒะณะธะน ั‚ะธะฟ, ัะบะธะน ะ’ะธ, ะนะผะพะฒั–ั€ะฝะพ, ะฑัƒะดะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฝะฐะนั‡ะฐัั‚ั–ัˆะต. - * ะŸั€ะธะบะปะฐะด `404`, "Not Found" (ะฝะต ะทะฝะฐะนะดะตะฝะพ). - * ะ”ะปั ะทะฐะณะฐะปัŒะฝะธั… ะฟะพะผะธะปะพะบ ะบะปั–ั”ะฝั‚ะฐ ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `400`. -* `500 - 599` "ะŸะพะผะธะปะบะธ ัะตั€ะฒะตั€ะฐ". ะ’ะธ ะผะฐะนะถะต ะฝั–ะบะพะปะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ั—ั… ะฝะฐะฟั€ัะผัƒ. ะฏะบั‰ะพ ะฒ ะบะพะดั– ะ’ะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ ะฐะฑะพ ะฝะฐ ัะตั€ะฒะตั€ั– ั‰ะพััŒ ะฟั–ัˆะปะพ ะฝะต ั‚ะฐะบ, ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฑัƒะดะต ะฟะพะฒะตั€ะฝะตะฝะพ ะพะดะธะฝ ั–ะท ั†ะธั… ะบะพะดั–ะฒ ัั‚ะฐั‚ัƒััƒ. +* `100 - 199` โ€” ะดะปั ยซInformationยป. ะ’ะธ ั€ั–ะดะบะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ั—ั… ะฝะฐะฟั€ัะผัƒ. ะ’ั–ะดะฟะพะฒั–ะดั– ะท ั‚ะฐะบะธะผะธ ะบะพะดะฐะผะธ ัั‚ะฐั‚ัƒััƒ ะฝะต ะผะพะถัƒั‚ัŒ ะผะฐั‚ะธ ั‚ั–ะปะฐ. +* **`200 - 299`** โ€” ะดะปั ยซSuccessfulยป ะฒั–ะดะฟะพะฒั–ะดะตะน. ะฆะต ั‚ั–, ัะบั– ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธะผะตั‚ะต ะฝะฐะนั‡ะฐัั‚ั–ัˆะต. + * `200` โ€” ะบะพะด ัั‚ะฐั‚ัƒััƒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ัะบะธะน ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒัะต ะฑัƒะปะพ ยซOKยป. + * ะ†ะฝัˆะธะน ะฟั€ะธะบะปะฐะด โ€” `201`, ยซCreatedยป. ะ™ะพะณะพ ะทะฐะทะฒะธั‡ะฐะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ ะฟั–ัะปั ัั‚ะฒะพั€ะตะฝะฝั ะฝะพะฒะพะณะพ ะทะฐะฟะธััƒ ะฒ ะฑะฐะทั– ะดะฐะฝะธั…. + * ะžัะพะฑะปะธะฒะธะน ะฒะธะฟะฐะดะพะบ โ€” `204`, ยซNo Contentยป. ะฆัŽ ะฒั–ะดะฟะพะฒั–ะดัŒ ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ, ะบะพะปะธ ะฝะตะผะฐั” ะฒะผั–ัั‚ัƒ ะดะปั ะฟะพะฒะตั€ะฝะตะฝะฝั ะบะปั–ั”ะฝั‚ัƒ, ั– ั‚ะพะผัƒ ะฒั–ะดะฟะพะฒั–ะดัŒ ะฝะต ะฟะพะฒะธะฝะฝะฐ ะผะฐั‚ะธ ั‚ั–ะปะฐ. +* **`300 - 399`** โ€” ะดะปั ยซRedirectionยป. ะ’ั–ะดะฟะพะฒั–ะดั– ะท ั†ะธะผะธ ะบะพะดะฐะผะธ ัั‚ะฐั‚ัƒััƒ ะผะพะถัƒั‚ัŒ ะผะฐั‚ะธ ะฐะฑะพ ะฝะต ะผะฐั‚ะธ ั‚ั–ะปะฐ, ะทะฐ ะฒะธะฝัั‚ะบะพะผ `304`, ยซNot Modifiedยป, ัะบะฐ ะฝะต ะฟะพะฒะธะฝะฝะฐ ะผะฐั‚ะธ ั‚ั–ะปะฐ. +* **`400 - 499`** โ€” ะดะปั ะฒั–ะดะฟะพะฒั–ะดะตะน ยซClient errorยป. ะฆะต ะดั€ัƒะณะธะน ั‚ะธะฟ, ัะบะธะน ะฒะธ, ะนะผะพะฒั–ั€ะฝะพ, ะฑัƒะดะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฝะฐะนั‡ะฐัั‚ั–ัˆะต. + * ะŸั€ะธะบะปะฐะด โ€” `404`, ะดะปั ะฒั–ะดะฟะพะฒั–ะดั– ยซNot Foundยป. + * ะ”ะปั ะทะฐะณะฐะปัŒะฝะธั… ะฟะพะผะธะปะพะบ ะท ะฑะพะบัƒ ะบะปั–ั”ะฝั‚ะฐ ะฒะธ ะผะพะถะตั‚ะต ะฟั€ะพัั‚ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `400`. +* `500 - 599` โ€” ะดะปั ะฟะพะผะธะปะพะบ ัะตั€ะฒะตั€ะฐ. ะ’ะธ ะผะฐะนะถะต ะฝั–ะบะพะปะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ั—ั… ะฝะฐะฟั€ัะผัƒ. ะšะพะปะธ ั‰ะพััŒ ะฟั–ะดะต ะฝะต ั‚ะฐะบ ัƒ ัะบั–ะนััŒ ั‡ะฐัั‚ะธะฝั– ะบะพะดัƒ ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ ะฐะฑะพ ะฝะฐ ัะตั€ะฒะตั€ั–, ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฑัƒะดะต ะฟะพะฒะตั€ะฝะตะฝะพ ะพะดะธะฝ ั–ะท ั†ะธั… ะบะพะดั–ะฒ ัั‚ะฐั‚ัƒััƒ. /// tip | ะŸะพั€ะฐะดะฐ -ะฉะพะฑ ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ะบะพะถะตะฝ ะบะพะด ัั‚ะฐั‚ัƒััƒ ั– ะฟั€ะธะทะฝะฐั‡ะตะฝะฝั ะบะพะถะฝะพะณะพ ะท ะฝะธั…, ะฟะตั€ะตะณะปัะฝัŒั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> ะฟั€ะพ HTTP ะบะพะดะธ ัั‚ะฐั‚ัƒััƒ</a>. +ะฉะพะฑ ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ะบะพะถะตะฝ ะบะพะด ัั‚ะฐั‚ัƒััƒ ั– ะดะปั ั‡ะพะณะพ ะฟั€ะธะทะฝะฐั‡ะตะฝะธะน ะบะพะถะตะฝ ั–ะท ะฝะธั…, ะฟะตั€ะตะณะปัะฝัŒั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> ะฟั€ะพ HTTP ะบะพะดะธ ัั‚ะฐั‚ัƒััƒ</a>. /// -## ะ›ะตะณะบะธะน ัะฟะพัั–ะฑ ะทะฐะฟะฐะผ'ัั‚ะฐั‚ะธ ะฝะฐะทะฒะธ +## ะกะบะพั€ะพั‡ะตะฝะฝั, ั‰ะพะฑ ะทะฐะฟะฐะผโ€™ัั‚ะฐั‚ะธ ะฝะฐะทะฒะธ { #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` - ั†ะต ะบะพะด ัั‚ะฐั‚ัƒััƒ ะดะปั "Created" (ัั‚ะฒะพั€ะตะฝะพ). +`201` โ€” ั†ะต ะบะพะด ัั‚ะฐั‚ัƒััƒ ะดะปั ยซCreatedยป. -ะะปะต ะ’ะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะทะฐะฟะฐะผ'ัั‚ะพะฒัƒะฒะฐั‚ะธ, ั‰ะพ ะพะทะฝะฐั‡ะฐั” ะบะพะถะตะฝ ั–ะท ั†ะธั… ะบะพะดั–ะฒ. +ะะปะต ะฒะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะทะฐะฟะฐะผ'ัั‚ะพะฒัƒะฒะฐั‚ะธ, ั‰ะพ ะพะทะฝะฐั‡ะฐั” ะบะพะถะตะฝ ั–ะท ั†ะธั… ะบะพะดั–ะฒ. -ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะทั€ัƒั‡ะฝั– ะทะผั–ะฝะฝั– ะท `fastapi.status` +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะทั€ัƒั‡ะฝั– ะทะผั–ะฝะฝั– ะท `fastapi.status`. -{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} -ะฆั– ะทะผั–ะฝะฝั– ะฟั€ะพัั‚ะพ ะดะปั ะทั€ัƒั‡ะฝะพัั‚ั–. ะ’ะพะฝะธ ะผั–ัั‚ัั‚ัŒ ั‚ั– ะถ ัะฐะผั– ั‡ะธัะปะฐ, ะฐะปะต ะ’ะธ ะผะพะถะตั‚ะต ัะบะพั€ะธัั‚ะฐั‚ะธัั ะฐะฒั‚ะพะทะฐะฟะพะฒะฝะตะฝะฝัะผ ะฒ ั€ะตะดะฐะบั‚ะพั€ั–: +ะ’ะพะฝะธ โ€” ะฟั€ะพัั‚ะพ ะดะปั ะทั€ัƒั‡ะฝะพัั‚ั–, ะผั–ัั‚ัั‚ัŒ ั‚ะต ัะฐะผะต ั‡ะธัะปะพ, ะฐะปะต ั‚ะฐะบ ะฒะธ ะผะพะถะตั‚ะต ัะบะพั€ะธัั‚ะฐั‚ะธัั ะฐะฒั‚ะพะทะฐะฟะพะฒะฝะตะฝะฝัะผ ั€ะตะดะฐะบั‚ะพั€ะฐ, ั‰ะพะฑ ะทะฝะฐะนั‚ะธ ั—ั…: <img src="/img/tutorial/response-status-code/image02.png"> @@ -91,10 +92,10 @@ FastAPI ะทะฝะฐั” ะฟั€ะพ ั†ะต ั– ัั‚ะฒะพั€ะธั‚ัŒ OpenAPI ะดะพะบัƒะผะตะฝั‚ะฐั†ั– ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `from starlette import status`. -**FastAPI** ะฝะฐะดะฐั” ั‚ั– ะถ ัะฐะผั– ะทะผั–ะฝะฝั– `starlette.status` ัะบ `fastapi.status`, ะฟั€ะพัั‚ะพ ะดะปั ะทั€ัƒั‡ะฝะพัั‚ั– ั€ะพะทั€ะพะฑะฝะธะบะฐ. ะžะดะฝะฐะบ ะฒะพะฝะธ ะฟะพั…ะพะดัั‚ัŒ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะทั– Starlette. +**FastAPI** ะฝะฐะดะฐั” ั‚ะพะน ัะฐะผะธะน `starlette.status` ัะบ `fastapi.status` ะฟั€ะพัั‚ะพ ัะบ ะทั€ัƒั‡ะฝั–ัั‚ัŒ ะดะปั ะฒะฐั, ั€ะพะทั€ะพะฑะฝะธะบะฐ. ะะปะต ะฒั–ะฝ ะฟะพั…ะพะดะธั‚ัŒ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะทั– Starlette. /// -## ะ—ะผั–ะฝะฐ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ +## ะ—ะผั–ะฝะฐ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ { #changing-the-default } -ะ”ะฐะปั–, ัƒ ะŸะพัั–ะฑะฝะธะบัƒ ะดะปั ะดะพัะฒั–ะดั‡ะตะฝะธั… ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ{.internal-link target=_blank}, ะ’ะธ ะดั–ะทะฝะฐั”ั‚ะตััŒ, ัะบ ะฟะพะฒะตั€ะฝัƒั‚ะธ ั–ะฝัˆะธะน ะบะพะด ัั‚ะฐั‚ัƒััƒ, ะฝั–ะถ ั‚ะพะน, ัะบะธะน ะ’ะธ ะพะณะพะปะพัะธะปะธ ั‚ัƒั‚. +ะŸั–ะทะฝั–ัˆะต, ัƒ [ะŸะพัั–ะฑะฝะธะบัƒ ะดะปั ะดะพัะฒั–ะดั‡ะตะฝะธั… ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ](../advanced/response-change-status-code.md){.internal-link target=_blank}, ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต, ัะบ ะฟะพะฒะตั€ั‚ะฐั‚ะธ ั–ะฝัˆะธะน ะบะพะด ัั‚ะฐั‚ัƒััƒ, ะฝั–ะถ ะทะฝะฐั‡ะตะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ, ัะบะต ะฒะธ ะพะณะพะปะพัˆัƒั”ั‚ะต ั‚ัƒั‚. diff --git a/docs/uk/docs/tutorial/schema-extra-example.md b/docs/uk/docs/tutorial/schema-extra-example.md index 853fd5e659..54608c2ab1 100644 --- a/docs/uk/docs/tutorial/schema-extra-example.md +++ b/docs/uk/docs/tutorial/schema-extra-example.md @@ -1,44 +1,22 @@ -# ะ”ะตะบะปะฐั€ัƒะฒะฐะฝะฝั ะฟั€ะธะบะปะฐะดั–ะฒ ะฒั…ั–ะดะฝะธั… ะดะฐะฝะธั… +# ะ”ะตะบะปะฐั€ัƒะฒะฐะฝะฝั ะฟั€ะธะบะปะฐะดั–ะฒ ะฒั…ั–ะดะฝะธั… ะดะฐะฝะธั… { #declare-request-example-data } ะ’ะธ ะผะพะถะตั‚ะต ะทะฐะดะฐั‚ะธ ะฟั€ะธะบะปะฐะดะธ ะดะฐะฝะธั…, ัะบั– ะ’ะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ ะผะพะถะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ. ะžััŒ ะบั–ะปัŒะบะฐ ัะฟะพัะพะฑั–ะฒ, ัะบ ั†ะต ะทั€ะพะฑะธั‚ะธ. -## ะ”ะพะดะฐั‚ะบะพะฒั– ะดะฐะฝั– JSON-ัั…ะตะผะธ ะฒ ะผะพะดะตะปัั… Pydantic +## ะ”ะพะดะฐั‚ะบะพะฒั– ะดะฐะฝั– JSON-ัั…ะตะผะธ ะฒ ะผะพะดะตะปัั… Pydantic { #extra-json-schema-data-in-pydantic-models } ะ’ะธ ะผะพะถะตั‚ะต ะทะฐะดะฐั‚ะธ `examples` ะดะปั ะผะพะดะตะปั– Pydantic, ัะบั– ะฑัƒะดะต ะดะพะดะฐะฝะพ ะดะพ ะทะณะตะฝะตั€ะพะฒะฐะฝะพั— JSON-ัั…ะตะผะธ. -//// tab | Pydantic v2 - {* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} -//// +ะฆั ะดะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั ะฑัƒะดะต ะดะพะดะฐะฝะฐ ัะบ ั” ะดะพ **JSON-ัั…ะตะผะธ** ะดะปั ั†ั–ั”ั— ะผะพะดะตะปั–, ั– ะฒะพะฝะฐ ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะฐะฝะฐ ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะดะพ API. -//// tab | Pydantic v1 - -{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *} - -//// - -ะฆั ะดะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั ะฑัƒะดะต ะดะพะดะฐะฝะฐ ัะบ ั” ะดะพ **JSON-ัั…ะตะผะธ**, ั– ะฒะพะฝะฐ ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธัั ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะดะพ API. - -//// tab | Pydantic v2 - -ะฃ ะฒะตั€ัั–ั— Pydantic 2 ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฐั‚ั€ะธะฑัƒั‚ `model_config`, ัะบะธะน ะฟั€ะธะนะผะฐั” `dict`, ัะบ ะพะฟะธัะฐะฝะพ ะฒ <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Pydantic: ะšะพะฝั„ั–ะณัƒั€ะฐั†ั–ั</a>. +ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ะฐั‚ั€ะธะฑัƒั‚ `model_config`, ัะบะธะน ะฟั€ะธะนะผะฐั” `dict`, ัะบ ะพะฟะธัะฐะฝะพ ะฒ <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Pydantic: Configuration</a>. ะ’ะธ ะผะพะถะตั‚ะต ะฒัั‚ะฐะฝะพะฒะธั‚ะธ `"json_schema_extra"` ัะบ `dict`, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ ะฑัƒะดัŒ-ัะบั– ะดะพะดะฐั‚ะบะพะฒั– ะดะฐะฝั–, ัะบั– ะ’ะธ ั…ะพั‡ะตั‚ะต ะฒั–ะดะพะฑั€ะฐะทะธั‚ะธ ัƒ ะทะณะตะฝะตั€ะพะฒะฐะฝั–ะน JSON-ัั…ะตะผั–, ะฒะบะปัŽั‡ะฐัŽั‡ะธ `examples`. -//// - -//// tab | Pydantic v1 - -ะฃ ะฒะตั€ัั–ั— Pydantic 1 ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฒะฝัƒั‚ั€ั–ัˆะฝั–ะน ะบะปะฐั `Config` ั– ะฟะฐั€ะฐะผะตั‚ั€ `schema_extra`, ัะบ ะพะฟะธัะฐะฝะพ ะฒ <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Pydantic: ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ัั…ะตะผะธ</a>. - -ะ’ะธ ะผะพะถะตั‚ะต ะทะฐะดะฐั‚ะธ `schema_extra` ัะบ `dict`, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ ะฑัƒะดัŒ-ัะบั– ะดะพะดะฐั‚ะบะพะฒั– ะดะฐะฝั–, ัะบั– ะ’ะธ ั…ะพั‡ะตั‚ะต ะฑะฐั‡ะธั‚ะธ ัƒ ะทะณะตะฝะตั€ะพะฒะฐะฝั–ะน JSON-ัั…ะตะผั–, ะฒะบะปัŽั‡ะฐัŽั‡ะธ `examples`. - -//// - -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ั‚ัƒ ะถ ั‚ะตั…ะฝั–ะบัƒ, ั‰ะพะฑ ั€ะพะทัˆะธั€ะธั‚ะธ JSON-ัั…ะตะผัƒ ั– ะดะพะดะฐั‚ะธ ะฒะปะฐัะฝัƒ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ. @@ -50,19 +28,19 @@ OpenAPI 3.1.0 (ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท FastAPI 0.99.0) ะดะพะดะฐะฒ ะฟั–ะดั‚ั€ะธะผะบัƒ `examples`, ั‰ะพ ั” ั‡ะฐัั‚ะธะฝะพัŽ ัั‚ะฐะฝะดะฐั€ั‚ัƒ **JSON-ัั…ะตะผะธ**. -ะ”ะพ ั†ัŒะพะณะพ ะฟั–ะดั‚ั€ะธะผัƒะฒะฐะฒัั ะปะธัˆะต ะบะปัŽั‡ `example` ะท ะพะดะฝะธะผ ะฟั€ะธะบะปะฐะดะพะผ. ะ’ั–ะฝ ะฒัะต ั‰ะต ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั ะฒ OpenAPI 3.1.0, ะฐะปะต ั” ะทะฐัั‚ะฐั€ั–ะปะธะผ ั– ะฝะต ะฒั…ะพะดะธั‚ัŒ ะดะพ ัั‚ะฐะฝะดะฐั€ั‚ัƒ JSON Schema. ะขะพะผัƒ ั€ะตะบะพะผะตะฝะดัƒั”ั‚ัŒัั ะฟะตั€ะตะนั‚ะธ ะท `example` ะฝะฐ `examples`. ๐Ÿค“ +ะ”ะพ ั†ัŒะพะณะพ ะฟั–ะดั‚ั€ะธะผัƒะฒะฐะฒัั ะปะธัˆะต ะบะปัŽั‡ `example` ะท ะพะดะฝะธะผ ะฟั€ะธะบะปะฐะดะพะผ. ะ’ั–ะฝ ะฒัะต ั‰ะต ะฟั–ะดั‚ั€ะธะผัƒั”ั‚ัŒัั ะฒ OpenAPI 3.1.0, ะฐะปะต ั” ะทะฐัั‚ะฐั€ั–ะปะธะผ ั– ะฝะต ะฒั…ะพะดะธั‚ัŒ ะดะพ ัั‚ะฐะฝะดะฐั€ั‚ัƒ JSON Schema. ะขะพะผัƒ ั€ะตะบะพะผะตะฝะดัƒั”ั‚ัŒัั ะฟะตั€ะตะนั‚ะธ ะท `example` ะฝะฐ `examples`. ๐Ÿค“ ะ‘ั–ะปัŒัˆะต ะฟั€ะพ ั†ะต ะผะพะถะฝะฐ ะฟั€ะพั‡ะธั‚ะฐั‚ะธ ะฒ ะบั–ะฝั†ั– ั†ั–ั”ั— ัั‚ะพั€ั–ะฝะบะธ. /// -## ะ”ะพะดะฐั‚ะบะพะฒั– ะฐั€ะณัƒะผะตะฝั‚ะธ `Field` +## ะ”ะพะดะฐั‚ะบะพะฒั– ะฐั€ะณัƒะผะตะฝั‚ะธ `Field` { #field-additional-arguments } ะšะพะปะธ ะฒะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต `Field()` ัƒ ะผะพะดะตะปัั… Pydantic, ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะบะฐะทะฐั‚ะธ ะดะพะดะฐั‚ะบะพะฒั– `examples`: {* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} -## `examples` ัƒ JSON-ัั…ะตะผั– โ€” OpenAPI +## `examples` ัƒ JSON-ัั…ะตะผั– โ€” OpenAPI { #examples-in-json-schema-openapi } ะŸั€ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั– ะฑัƒะดัŒ-ะบะพะณะพ ะท ะฝะฐัั‚ัƒะฟะฝะพะณะพ: @@ -76,41 +54,41 @@ OpenAPI 3.1.0 (ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท F ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะทะฐะดะฐั‚ะธ ะฝะฐะฑั–ั€ `examples` ะท ะดะพะดะฐั‚ะบะพะฒะพัŽ ั–ะฝั„ะพั€ะผะฐั†ั–ั”ัŽ, ัะบะฐ ะฑัƒะดะต ะดะพะดะฐะฝะฐ ะดะพ ั—ั…ะฝั–ั… **JSON-ัั…ะตะผ** ัƒ **OpenAPI**. -### `Body` ะท `examples` +### `Body` ะท `examples` { #body-with-examples } ะขัƒั‚ ะผะธ ะฟะตั€ะตะดะฐั”ะผะพ `examples`, ัะบั– ะผั–ัั‚ัั‚ัŒ ะพะดะธะฝ ะฟั€ะธะบะปะฐะด ะพั‡ั–ะบัƒะฒะฐะฝะธั… ะดะฐะฝะธั… ัƒ `Body()`: {* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} -### ะŸั€ะธะบะปะฐะด ัƒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +### ะŸั€ะธะบะปะฐะด ัƒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— { #example-in-the-docs-ui } -ะ—ะฐ ะดะพะฟะพะผะพะณะพัŽ ะฑัƒะดัŒ-ัะบะพะณะพ ะท ะฝะฐะฒะตะดะตะฝะธั… ะฒะธั‰ะต ะผะตั‚ะพะดั–ะฒ ั†ะต ะฒะธะณะปัะดะฐั‚ะธะผะต ั‚ะฐะบ ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— ะทะฐ `/docs`: +ะ—ะฐ ะดะพะฟะพะผะพะณะพัŽ ะฑัƒะดัŒ-ัะบะพะณะพ ะท ะฝะฐะฒะตะดะตะฝะธั… ะฒะธั‰ะต ะผะตั‚ะพะดั–ะฒ ั†ะต ะฒะธะณะปัะดะฐั‚ะธะผะต ั‚ะฐะบ ัƒ `/docs`: <img src="/img/tutorial/body-fields/image01.png"> -### `Body` ะท ะบั–ะปัŒะบะพะผะฐ `examples` +### `Body` ะท ะบั–ะปัŒะบะพะผะฐ `examples` { #body-with-multiple-examples } ะ—ะฒะธั‡ะฐะนะฝะพ, ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ ะบั–ะปัŒะบะฐ `examples`: {* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} -ะšะพะปะธ ะ’ะธ ั†ะต ั€ะพะฑะธั‚ะต, ะฟั€ะธะบะปะฐะดะธ ะฑัƒะดัƒั‚ัŒ ั‡ะฐัั‚ะธะฝะพัŽ ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพั— **JSON-ัั…ะตะผะธ** ะดะปั ั†ะธั… ะดะฐะฝะธั…. +ะšะพะปะธ ะ’ะธ ั†ะต ั€ะพะฑะธั‚ะต, ะฟั€ะธะบะปะฐะดะธ ะฑัƒะดัƒั‚ัŒ ั‡ะฐัั‚ะธะฝะพัŽ ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพั— **JSON-ัั…ะตะผะธ** ะดะปั ั†ะธั… ะดะฐะฝะธั… ั‚ั–ะปะฐ. -ะ’ั‚ั–ะผ, ะฝะฐ ะผะพะผะตะฝั‚ ะฝะฐะฟะธัะฐะฝะฝั ั†ัŒะพะณะพ (<abbr title="2023-08-26">26 ัะตั€ะฟะฝั 2023</abbr>), Swagger UI โ€” ั–ะฝัั‚ั€ัƒะผะตะฝั‚, ัะบะธะน ะฒั–ะดะฟะพะฒั–ะดะฐั” ะทะฐ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— โ€” ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ะฟะพะบะฐะท ะบั–ะปัŒะบะพั… ะฟั€ะธะบะปะฐะดั–ะฒ ัƒ **JSON-ัั…ะตะผะธ**. ะะปะต ะฝะธะถั‡ะต ะผะพะถะฝะฐ ะฟั€ะพั‡ะธั‚ะฐั‚ะธ ะฟั€ะพ ะพะฑั…ั–ะดะฝะธะน ัˆะปัั…. +ะ’ั‚ั–ะผ, ะฝะฐ ะผะพะผะตะฝั‚ ะฝะฐะฟะธัะฐะฝะฝั ั†ัŒะพะณะพ (<abbr title="2023-08-26">ั‡ะฐั ะฝะฐะฟะธัะฐะฝะฝั ั†ัŒะพะณะพ</abbr>), Swagger UI โ€” ั–ะฝัั‚ั€ัƒะผะตะฝั‚, ัะบะธะน ะฒั–ะดะฟะพะฒั–ะดะฐั” ะทะฐ ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— โ€” ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ะฟะพะบะฐะท ะบั–ะปัŒะบะพั… ะฟั€ะธะบะปะฐะดั–ะฒ ะดะปั ะดะฐะฝะธั… ัƒ **JSON-ัั…ะตะผั–**. ะะปะต ะฝะธะถั‡ะต ะผะพะถะฝะฐ ะฟั€ะพั‡ะธั‚ะฐั‚ะธ ะฟั€ะพ ะพะฑั…ั–ะดะฝะธะน ัˆะปัั…. -### ะกะฟะตั†ะธั„ั–ั‡ะฝั– ะดะปั OpenAPI `examples` +### ะกะฟะตั†ะธั„ั–ั‡ะฝั– ะดะปั OpenAPI `examples` { #openapi-specific-examples } -ะฉะต ะดะพ ั‚ะพะณะพ, ัะบ **JSON-ัั…ะตะผะฐ** ะฟะพั‡ะฐะปะฐ ะฟั–ะดั‚ั€ะธะผัƒะฒะฐั‚ะธ `examples`, OpenAPI ะฒะถะต ะผะฐะปะฐ ะฟั–ะดั‚ั€ะธะผะบัƒ ะฟะพะปั ะท ั‚ะฐะบะพัŽ ะถ ะฝะฐะทะฒะพัŽ โ€” `examples`. +ะฉะต ะดะพ ั‚ะพะณะพ, ัะบ **JSON-ัั…ะตะผะฐ** ะฟะพั‡ะฐะปะฐ ะฟั–ะดั‚ั€ะธะผัƒะฒะฐั‚ะธ `examples`, OpenAPI ะฒะถะต ะผะฐะปะฐ ะฟั–ะดั‚ั€ะธะผะบัƒ ั–ะฝัˆะพะณะพ ะฟะพะปั, ัะบะต ั‚ะฐะบะพะถ ะฝะฐะทะธะฒะฐั”ั‚ัŒัั `examples`. -ะฆะต **ัะฟะตั†ะธั„ั–ั‡ะฝะต ะดะปั OpenAPI** ะฟะพะปะต `examples` ั€ะพะทะผั–ั‰ัƒั”ั‚ัŒัั ะฒ ั–ะฝัˆั–ะน ั‡ะฐัั‚ะธะฝั– ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OpenAPI โ€” ัƒ **ะดะตั‚ะฐะปัั… ะบะพะถะฝะพั— *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ***, ะฐ ะฝะต ะฒัะตั€ะตะดะธะฝั– ัะฐะผะพั— JSON-ัั…ะตะผะธ. +ะฆะต **ัะฟะตั†ะธั„ั–ั‡ะฝะต ะดะปั OpenAPI** ะฟะพะปะต `examples` ั€ะพะทะผั–ั‰ัƒั”ั‚ัŒัั ะฒ ั–ะฝัˆะพะผัƒ ั€ะพะทะดั–ะปั– ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OpenAPI. ะ’ะพะฝะพ ั€ะพะทะผั–ั‰ัƒั”ั‚ัŒัั ะฒ **ะดะตั‚ะฐะปัั… ะบะพะถะฝะพั— *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ***, ะฐ ะฝะต ะฒัะตั€ะตะดะธะฝั– ะบะพะถะฝะพั— JSON-ัั…ะตะผะธ. -Swagger UI ะฒะถะต ะดะฐะฒะฝะพ ะฟั–ะดั‚ั€ะธะผัƒั” ั†ะต ะฟะพะปะต `examples`. ะขะพะผัƒ ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะนะพะณะพ, ั‰ะพะฑ **ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธ** ะบั–ะปัŒะบะฐ **ะฟั€ะธะบะปะฐะดั–ะฒ ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—**. +ะ† Swagger UI ะฒะถะต ะดะฐะฒะฝะพ ะฟั–ะดั‚ั€ะธะผัƒั” ั†ะต ะฟะพะปะต `examples`. ะขะพะผัƒ ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะนะพะณะพ, ั‰ะพะฑ **ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธ** ั€ั–ะทะฝั– **ะฟั€ะธะบะปะฐะดะธ ะฒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—**. -ะฆะต ะฟะพะปะต `examples` ัƒ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OpenAPI โ€” ั†ะต `dict` (ัะปะพะฒะฝะธะบ) ะท **ะบั–ะปัŒะบะพะผะฐ ะฟั€ะธะบะปะฐะดะฐะผะธ** (ะฐ ะฝะต ัะฟะธัะพะบ `list`), ะบะพะถะตะฝ ั–ะท ัะบะธั… ะผะพะถะต ะผั–ัั‚ะธั‚ะธ ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ, ั‰ะพ ะฑัƒะดะต ะดะพะดะฐะฝะฐ ะดะพ **OpenAPI**. +ะคะพั€ะผะฐ ั†ัŒะพะณะพ ัะฟะตั†ะธั„ั–ั‡ะฝะพะณะพ ะดะปั OpenAPI ะฟะพะปั `examples` โ€” ั†ะต `dict` ะท **ะบั–ะปัŒะบะพะผะฐ ะฟั€ะธะบะปะฐะดะฐะผะธ** (ะฐ ะฝะต `list`), ะบะพะถะตะฝ ั–ะท ัะบะธั… ะผะฐั” ะดะพะดะฐั‚ะบะพะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ, ัะบะฐ ั‚ะฐะบะพะถ ะฑัƒะดะต ะดะพะดะฐะฝะฐ ะดะพ **OpenAPI**. -ะ’ะพะฝะพ ะฝะต ะฒะบะปัŽั‡ะฐั”ั‚ัŒัั ะดะพ JSON Schema ะบะพะถะฝะพะณะพ ะฟะฐั€ะฐะผะตั‚ั€ะฐ, ะฐ ั€ะพะทะผั–ั‰ัƒั”ั‚ัŒัั ะทะพะฒะฝั–, ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*. +ะ’ะพะฝะพ ะฝะต ะฒะบะปัŽั‡ะฐั”ั‚ัŒัั ะฒัะตั€ะตะดะธะฝัƒ ะบะพะถะฝะพั— JSON-ัั…ะตะผะธ, ั‰ะพ ะผั–ัั‚ะธั‚ัŒัั ะฒ OpenAPI, ะฒะพะฝะพ ั€ะพะทะผั–ั‰ัƒั”ั‚ัŒัั ะทะพะฒะฝั–, ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒ *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ*. -### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ะฐ `openapi_examples` +### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฟะฐั€ะฐะผะตั‚ั€ะฐ `openapi_examples` { #using-the-openapi-examples-parameter } ะ’ะธ ะผะพะถะตั‚ะต ะพะณะพะปะพัะธั‚ะธ ัะฟะตั†ะธั„ั–ั‡ะฝั– ะดะปั OpenAPI `examples` ัƒ FastAPI ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `openapi_examples` ะดะปั: @@ -122,30 +100,32 @@ Swagger UI ะฒะถะต ะดะฐะฒะฝะพ ะฟั–ะดั‚ั€ะธะผัƒั” ั†ะต ะฟะพะปะต `examples`. ะขะพะผ * `Form()` * `File()` -ะšะปัŽั‡ั– ัะปะพะฒะฝะธะบะฐ (`dict`) ั–ะดะตะฝั‚ะธั„ั–ะบัƒัŽั‚ัŒ ะบะพะถะตะฝ ะฟั€ะธะบะปะฐะด, ะฐ ะบะพะถะฝะต ะทะฝะฐั‡ะตะฝะฝั `dict` โ€” ะบะพะถะตะฝ ัะฟะตั†ะธั„ั–ั‡ะฝะธะน ัะปะพะฒะฝะธะบ `dict` ะฒ `examples` ะผะพะถะต ะผั–ัั‚ะธั‚ะธ: +ะšะปัŽั‡ั– `dict` ั–ะดะตะฝั‚ะธั„ั–ะบัƒัŽั‚ัŒ ะบะพะถะตะฝ ะฟั€ะธะบะปะฐะด, ะฐ ะบะพะถะฝะต ะทะฝะฐั‡ะตะฝะฝั โ€” ั†ะต ั–ะฝัˆะธะน `dict`. + +ะšะพะถะตะฝ ัะฟะตั†ะธั„ั–ั‡ะฝะธะน `dict` ะฟั€ะธะบะปะฐะดัƒ ะฒ `examples` ะผะพะถะต ะผั–ัั‚ะธั‚ะธ: * `summary`: ะบะพั€ะพั‚ะบะธะน ะพะฟะธั ะฟั€ะธะบะปะฐะดัƒ. -* `description`: ั€ะพะทะณะพั€ะฝัƒั‚ะธะน ะพะฟะธั (ะผะพะถะต ะผั–ัั‚ะธั‚ะธ Markdown). -* `value`: ัะฐะผ ะฟั€ะธะบะปะฐะด, ะฝะฐะฟั€ะธะบะปะฐะด, ัะปะพะฒะฝะธะบ (`dict`). -* `externalValue`: ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฐ `value`, URL-ะฐะดั€ะตัะฐ, ั‰ะพ ะฒะบะฐะทัƒั” ะฝะฐ ะฟั€ะธะบะปะฐะด. ะŸั€ะพั‚ะต ั†ั ะพะฟั†ั–ั ะผะพะถะต ะฝะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐั‚ะธัั ะฑั–ะปัŒัˆั–ัั‚ัŽ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ะฝะฐ ะฒั–ะดะผั–ะฝัƒ ะฒั–ะด `value`. +* `description`: ั€ะพะทะณะพั€ะฝัƒั‚ะธะน ะพะฟะธั, ัะบะธะน ะผะพะถะต ะผั–ัั‚ะธั‚ะธ Markdown. +* `value`: ั†ะต ัะฐะผ ะฟั€ะธะบะปะฐะด, ัะบะธะน ะฑัƒะดะต ะฟะพะบะฐะทะฐะฝะพ, ะฝะฐะฟั€ะธะบะปะฐะด `dict`. +* `externalValue`: ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฐ `value`, URL-ะฐะดั€ะตัะฐ, ั‰ะพ ะฒะบะฐะทัƒั” ะฝะฐ ะฟั€ะธะบะปะฐะด. ะŸั€ะพั‚ะต ั†ะต ะผะพะถะต ะฝะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐั‚ะธัั ั‚ะฐะบะพัŽ ะบั–ะปัŒะบั–ัั‚ัŽ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ัะบ `value`. ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฒะธะณะปัะดะฐั” ั‚ะฐะบ: {* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} -### ะŸั€ะธะบะปะฐะดะธ OpenAPI ัƒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— +### ะŸั€ะธะบะปะฐะดะธ OpenAPI ะฒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— { #openapi-examples-in-the-docs-ui } -ะ— ะฟะฐั€ะฐะผะตั‚ั€ะพะผ `openapi_examples`, ะดะพะดะฐะฝะธะผ ะดะพ `Body()`, ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั `/docs` ะฒะธะณะปัะดะฐั‚ะธะผะต ั‚ะฐะบ: +ะ— `openapi_examples`, ะดะพะดะฐะฝะธะผ ะดะพ `Body()`, `/docs` ะฒะธะณะปัะดะฐั‚ะธะผะต ั‚ะฐะบ: <img src="/img/tutorial/body-fields/image02.png"> -## ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– +## ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– { #technical-details } -/// tip | ะŸั–ะดะบะฐะทะบะฐ +/// tip | ะŸะพั€ะฐะดะฐ -ะฏะบั‰ะพ ะ’ะธ ะฒะถะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต **FastAPI** ะฒะตั€ัั–ั— **0.99.0 ะฐะฑะพ ะฒะธั‰ะต**, ะ’ะธ ะผะพะถะตั‚ะต **ะฟั€ะพะฟัƒัั‚ะธั‚ะธ** ั†ะตะน ั€ะพะทะดั–ะป. +ะฏะบั‰ะพ ะ’ะธ ะฒะถะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต **FastAPI** ะฒะตั€ัั–ั— **0.99.0 ะฐะฑะพ ะฒะธั‰ะต**, ะ’ะธ, ะนะผะพะฒั–ั€ะฝะพ, ะผะพะถะตั‚ะต **ะฟั€ะพะฟัƒัั‚ะธั‚ะธ** ั†ั– ั‚ะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั–. -ะ’ั–ะฝ ะฑั–ะปัŒัˆ ะฐะบั‚ัƒะฐะปัŒะฝะธะน ะดะปั ัั‚ะฐั€ะธั… ะฒะตั€ัั–ะน, ะดะพ ะฟะพัะฒะธ OpenAPI 3.1.0. +ะ’ะพะฝะธ ะฑั–ะปัŒัˆ ะฐะบั‚ัƒะฐะปัŒะฝั– ะดะปั ัั‚ะฐั€ะธั… ะฒะตั€ัั–ะน, ะดะพ ะฟะพัะฒะธ OpenAPI 3.1.0. ะœะพะถะฝะฐ ะฒะฒะฐะถะฐั‚ะธ ั†ะต ะบะพั€ะพั‚ะบะธะผ **ั–ัั‚ะพั€ะธั‡ะฝะธะผ ะตะบัะบัƒั€ัะพะผ** ัƒ OpenAPI ั‚ะฐ JSON Schema. ๐Ÿค“ @@ -155,68 +135,68 @@ Swagger UI ะฒะถะต ะดะฐะฒะฝะพ ะฟั–ะดั‚ั€ะธะผัƒั” ั†ะต ะฟะพะปะต `examples`. ะขะพะผ ะฆะต ะดัƒะถะต ั‚ะตั…ะฝั–ั‡ะฝะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ัั‚ะฐะฝะดะฐั€ั‚ะธ **JSON Schema** ั– **OpenAPI**. -ะฏะบั‰ะพ ะฒะธั‰ะตะทะณะฐะดะฐะฝั– ั–ะดะตั— ะฒะถะต ะฟั€ะฐั†ัŽัŽั‚ัŒ ัƒ ะ’ะฐั โ€” ะผะพะถะตั‚ะต ะฝะต ะทะฐะณะปะธะฑะปัŽะฒะฐั‚ะธัั ะฒ ั†ั– ะดะตั‚ะฐะปั–. +ะฏะบั‰ะพ ะฒะธั‰ะตะทะณะฐะดะฐะฝั– ั–ะดะตั— ะฒะถะต ะฟั€ะฐั†ัŽัŽั‚ัŒ ัƒ ะ’ะฐั, ั†ัŒะพะณะพ ะผะพะถะต ะฑัƒั‚ะธ ะดะพัั‚ะฐั‚ะฝัŒะพ, ั– ะ’ะฐะผ, ะนะผะพะฒั–ั€ะฝะพ, ะฝะต ะฟะพั‚ั€ั–ะฑะฝั– ั†ั– ะดะตั‚ะฐะปั– โ€” ะผะพะถะตั‚ะต ะฟั€ะพะฟัƒัั‚ะธั‚ะธ. /// -ะ”ะพ OpenAPI 3.1.0 ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะปะฐ ัั‚ะฐั€ัƒ ั‚ะฐ ะผะพะดะธั„ั–ะบะพะฒะฐะฝัƒ ะฒะตั€ัั–ัŽ **JSON Schema**. +ะ”ะพ OpenAPI 3.1.0 OpenAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะฒ ัั‚ะฐั€ัƒ ั‚ะฐ ะผะพะดะธั„ั–ะบะพะฒะฐะฝัƒ ะฒะตั€ัั–ัŽ **JSON Schema**. -ะžัะบั–ะปัŒะบะธ JSON Schema ั€ะฐะฝั–ัˆะต ะฝะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐะปะฐ `examples`, OpenAPI ะดะพะดะฐะปะฐ ะฒะปะฐัะฝะต ะฟะพะปะต `examples`. +JSON Schema ะฝะต ะผะฐะปะฐ `examples`, ั‚ะพะถ OpenAPI ะดะพะดะฐะปะฐ ะฒะปะฐัะฝะต ะฟะพะปะต `example` ะดะพ ัะฒะพั”ั— ะผะพะดะธั„ั–ะบะพะฒะฐะฝะพั— ะฒะตั€ัั–ั—. -OpenAPI ั‚ะฐะบะพะถ ะดะพะดะฐะปะฐ `example` ั– `examples` ะดะพ ั–ะฝัˆะธั… ั‡ะฐัั‚ะธะฝ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั—: +OpenAPI ั‚ะฐะบะพะถ ะดะพะดะฐะปะฐ ะฟะพะปั `example` ั– `examples` ะดะพ ั–ะฝัˆะธั… ั‡ะฐัั‚ะธะฝ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั—: -* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`Parameter Object` (ะฒ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั—)</a> ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั FastAPI ะดะปั: +* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`Parameter Object` (ะฒ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั—)</a>, ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะฒัั ัƒั‚ะธะปั–ั‚ะฐะผะธ FastAPI: * `Path()` * `Query()` * `Header()` * `Cookie()` -* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">`Request Body Object`, ะฒ ะฟะพะปั– `content`, ะฒ `Media Type Object` (ะฒ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั—)</a> ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั FastAPI ะดะปั: +* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">`Request Body Object`, ัƒ ะฟะพะปั– `content`, ัƒ `Media Type Object` (ะฒ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั—)</a>, ัะบะธะน ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะฒัั ัƒั‚ะธะปั–ั‚ะฐะผะธ FastAPI: * `Body()` * `File()` * `Form()` /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะฆะตะน ัั‚ะฐั€ะธะน ะฟะฐั€ะฐะผะตั‚ั€ `examples`, ัะฟะตั†ะธั„ั–ั‡ะฝะธะน ะดะปั OpenAPI, ั‚ะตะฟะตั€ ะฝะฐะทะธะฒะฐั”ั‚ัŒัั `openapi_examples`, ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท FastAPI ะฒะตั€ัั–ั— `0.103.0`. +ะฆะตะน ัั‚ะฐั€ะธะน ัะฟะตั†ะธั„ั–ั‡ะฝะธะน ะดะปั OpenAPI ะฟะฐั€ะฐะผะตั‚ั€ `examples` ั‚ะตะฟะตั€ ะฝะฐะทะธะฒะฐั”ั‚ัŒัั `openapi_examples`, ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท FastAPI `0.103.0`. /// -### ะŸะพะปะต `examples` ัƒ JSON Schema +### ะŸะพะปะต `examples` ัƒ JSON Schema { #json-schemas-examples-field } ะŸั–ะทะฝั–ัˆะต JSON Schema ะดะพะดะฐะปะฐ ะฟะพะปะต <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a> ัƒ ะฝะพะฒัƒ ะฒะตั€ัั–ัŽ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั—. -ะ† ะฒะถะต OpenAPI 3.1.0 ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ ั†ั–ะน ะฝะพะฒั–ะน ะฒะตั€ัั–ั— (JSON Schema 2020-12), ัะบะฐ ะฒะบะปัŽั‡ะฐั” ะฟะพะปะต `examples`. +ะ ะฟะพั‚ั–ะผ ะฝะพะฒะธะน OpenAPI 3.1.0 ะฑะฐะทัƒะฒะฐะฒัั ะฝะฐ ะฝะฐะนะฝะพะฒั–ัˆั–ะน ะฒะตั€ัั–ั— (JSON Schema 2020-12), ัะบะฐ ะฒะบะปัŽั‡ะฐะปะฐ ั†ะต ะฝะพะฒะต ะฟะพะปะต `examples`. -ะขะตะฟะตั€ ั†ะต ะฟะพะปะต `examples` ั” ะฟั€ั–ะพั€ะธั‚ะตั‚ะฝะธะผ ั– ะทะฐะผั–ะฝัŽั” ัั‚ะฐั€ะต (ั– ะบะฐัั‚ะพะผะฝะต) ะฟะพะปะต `example`, ัะบะต ัั‚ะฐะปะพ ะทะฐัั‚ะฐั€ั–ะปะธะผ. +ะ† ั‚ะตะฟะตั€ ั†ะต ะฝะพะฒะต ะฟะพะปะต `examples` ะผะฐั” ะฒะธั‰ะธะน ะฟั€ั–ะพั€ะธั‚ะตั‚ ะทะฐ ัั‚ะฐั€ะต ะพะดะธะฝะพั‡ะฝะต (ั– ะบะฐัั‚ะพะผะฝะต) ะฟะพะปะต `example`, ัะบะต ั‚ะตะฟะตั€ ั” ะทะฐัั‚ะฐั€ั–ะปะธะผ. -ะะพะฒะต ะฟะพะปะต `examples` ัƒ JSON Schema โ€” ั†ะต **ะฟั€ะพัั‚ะพ ัะฟะธัะพะบ (`list`)** ะฟั€ะธะบะปะฐะดั–ะฒ, ะฑะตะท ะดะพะดะฐั‚ะบะพะฒะธั… ะผะตั‚ะฐะดะฐะฝะธั… (ะฝะฐ ะฒั–ะดะผั–ะฝัƒ ะฒั–ะด OpenAPI). +ะฆะต ะฝะพะฒะต ะฟะพะปะต `examples` ัƒ JSON Schema โ€” ั†ะต **ะฟั€ะพัั‚ะพ `list`** ะฟั€ะธะบะปะฐะดั–ะฒ, ะฐ ะฝะต `dict` ะท ะดะพะดะฐั‚ะบะพะฒะธะผะธ ะผะตั‚ะฐะดะฐะฝะธะผะธ, ัะบ ะฒ ั–ะฝัˆะธั… ะผั–ัั†ัั… OpenAPI (ะพะฟะธัะฐะฝะธั… ะฒะธั‰ะต). /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั -ะะฐะฒั–ั‚ัŒ ะฟั–ัะปั ั‚ะพะณะพ, ัะบ ะท'ัะฒะธะฒัั OpenAPI 3.1.0, ัะบะธะน ะฟั–ะดั‚ั€ะธะผัƒะฒะฐะฒ examples ัƒ JSON Schema, ั–ะฝัั‚ั€ัƒะผะตะฝั‚ Swagger UI ั‰ะต ะดะตัะบะธะน ั‡ะฐั ะฝะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐะฒ ั†ัŽ ะฒะตั€ัั–ัŽ (ะฟั–ะดั‚ั€ะธะผะบะฐ ะทโ€™ัะฒะธะปะฐััŒ ะท ะฒะตั€ัั–ั— 5.0.0 ๐ŸŽ‰). +ะะฐะฒั–ั‚ัŒ ะฟั–ัะปั ั€ะตะปั–ะทัƒ OpenAPI 3.1.0 ะท ั†ั–ั”ัŽ ะฝะพะฒะพัŽ ะฟั€ะพัั‚ั–ัˆะพัŽ ั–ะฝั‚ะตะณั€ะฐั†ั–ั”ัŽ ะท JSON Schema, ะฟั€ะพั‚ัะณะพะผ ะฟะตะฒะฝะพะณะพ ั‡ะฐััƒ Swagger UI, ั–ะฝัั‚ั€ัƒะผะตะฝั‚, ัะบะธะน ะฝะฐะดะฐั” ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ะฝะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐะฒ OpenAPI 3.1.0 (ั‚ะตะฟะตั€ ะฟั–ะดั‚ั€ะธะผัƒั”, ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท ะฒะตั€ัั–ั— 5.0.0 ๐ŸŽ‰). ะงะตั€ะตะท ั†ะต ะฒะตั€ัั–ั— FastAPI ะดะพ 0.99.0 ะฒัะต ั‰ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะปะธ ะฒะตั€ัั–ั— OpenAPI ะฝะธะถั‡ั– ะทะฐ 3.1.0. /// -### `Examples` ะฒ Pydantic ั– FastAPI +### `examples` ัƒ Pydantic ั– FastAPI { #pydantic-and-fastapi-examples } -ะšะพะปะธ ะ’ะธ ะดะพะดะฐั”ั‚ะต `examples` ัƒ ะผะพะดะตะปัŒ Pydantic ั‡ะตั€ะตะท `schema_extra` ะฐะฑะพ `Field(examples=["something"])`, ั†ั– ะฟั€ะธะบะปะฐะดะธ ะดะพะดะฐัŽั‚ัŒัั ะดะพ **JSON Schema** ั†ั–ั”ั— ะผะพะดะตะปั–. +ะšะพะปะธ ะ’ะธ ะดะพะดะฐั”ั‚ะต `examples` ัƒ ะผะพะดะตะปัŒ Pydantic ั‡ะตั€ะตะท `schema_extra` ะฐะฑะพ `Field(examples=["something"])`, ั†ะตะน ะฟั€ะธะบะปะฐะด ะดะพะดะฐั”ั‚ัŒัั ะดะพ **JSON Schema** ะดะปั ั†ั–ั”ั— ะผะพะดะตะปั– Pydantic. -ะ† ั†ั **JSON Schema** Pydantic-ะผะพะดะตะปั– ะฒะบะปัŽั‡ะฐั”ั‚ัŒัั ะดะพ **OpenAPI** ะ’ะฐัˆะพะณะพ API, ะฐ ะฟะพั‚ั–ะผ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— (docs UI). +ะ† ั†ั **JSON Schema** Pydantic-ะผะพะดะตะปั– ะฒะบะปัŽั‡ะฐั”ั‚ัŒัั ะดะพ **OpenAPI** ะ’ะฐัˆะพะณะพ API, ะฐ ะฟะพั‚ั–ะผ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. -ะฃ ะฒะตั€ัั–ัั… FastAPI ะดะพ 0.99.0 (ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท 0.99.0 ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฝะพะฒั–ัˆะธะน OpenAPI 3.1.0), ะบะพะปะธ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะปะธ `example` ะฐะฑะพ `examples` ะท ั–ะฝัˆะธะผะธ ัƒั‚ะธะปั–ั‚ะฐะผะธ (`Query()`, `Body()` ั‚ะพั‰ะพ), ั†ั– ะฟั€ะธะบะปะฐะดะธ ะฝะต ะดะพะดะฐะฒะฐะปะธัั ะดะพ JSON Schema, ัะบะธะน ะพะฟะธััƒั” ั†ั– ะดะฐะฝั– (ะฝะฐะฒั–ั‚ัŒ ะฝะต ะดะพ ะฒะปะฐัะฝะพั— ะฒะตั€ัั–ั— JSON Schema ัƒ OpenAPI). ะะฐั‚ะพะผั–ัั‚ัŒ ะฒะพะฝะธ ะดะพะดะฐะฒะฐะปะธัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะดะพ ะพะฟะธััƒ *ะพะฑั€ะพะฑะฝะธะบะฐ ัˆะปัั…ัƒ* *(path operation)* ะฒ OpenAPI (ั‚ะพะฑั‚ะพ ะฟะพะทะฐ ะผะตะถะฐะผะธ ั‡ะฐัั‚ะธะฝ, ัะบั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ JSON Schema). +ะฃ ะฒะตั€ัั–ัั… FastAPI ะดะพ 0.99.0 (0.99.0 ั– ะฒะธั‰ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ ะฝะพะฒั–ัˆะธะน OpenAPI 3.1.0), ะบะพะปะธ ะ’ะธ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะปะธ `example` ะฐะฑะพ `examples` ะท ะฑัƒะดัŒ-ัะบะธะผะธ ั–ะฝัˆะธะผะธ ัƒั‚ะธะปั–ั‚ะฐะผะธ (`Query()`, `Body()` ั‚ะพั‰ะพ), ั†ั– ะฟั€ะธะบะปะฐะดะธ ะฝะต ะดะพะดะฐะฒะฐะปะธัั ะดะพ JSON Schema, ั‰ะพ ะพะฟะธััƒั” ั†ั– ะดะฐะฝั– (ะฝะฐะฒั–ั‚ัŒ ะฝะต ะดะพ ะฒะปะฐัะฝะพั— ะฒะตั€ัั–ั— JSON Schema ะฒ OpenAPI), ะฝะฐั‚ะพะผั–ัั‚ัŒ ะฒะพะฝะธ ะดะพะดะฐะฒะฐะปะธัั ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะดะพ ะดะตะบะปะฐั€ะฐั†ั–ั— *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะฒ OpenAPI (ะฟะพะทะฐ ะผะตะถะฐะผะธ ั‡ะฐัั‚ะธะฝ OpenAPI, ัะบั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ JSON Schema). -ะะปะต ั‚ะตะฟะตั€, ะบะพะปะธ FastAPI 0.99.0 ั– ะฒะธั‰ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ OpenAPI 3.1.0, ะฐ ั‚ะพะน โ€” JSON Schema 2020-12, ั€ะฐะทะพะผ ั–ะท Swagger UI 5.0.0 ั– ะฒะธั‰ะต โ€” ะฒัะต ัั‚ะฐะปะพ ะฑั–ะปัŒัˆ ัƒะทะณะพะดะถะตะฝะธะผ, ั– examples ั‚ะตะฟะตั€ ะฒะบะปัŽั‡ะฐัŽั‚ัŒัั ะดะพ 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 ั‚ะฐ ัะฟะตั†ะธั„ั–ั‡ะฝั– ะดะปั OpenAPI `examples` { #swagger-ui-and-openapi-specific-examples } -ะ ะฐะฝั–ัˆะต (ัั‚ะฐะฝะพะผ ะฝะฐ 26 ัะตั€ะฟะฝั 2023 ั€ะพะบัƒ) Swagger UI ะฝะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐะฒ ะบั–ะปัŒะบะฐ ะฟั€ะธะบะปะฐะดั–ะฒ ัƒ JSON Schema, ั‚ะพะผัƒ ะบะพั€ะธัั‚ัƒะฒะฐั‡ั– ะฝะต ะผะฐะปะธ ะผะพะถะปะธะฒะพัั‚ั– ะฟะพะบะฐะทะฐั‚ะธ ะดะตะบั–ะปัŒะบะฐ ะฟั€ะธะบะปะฐะดั–ะฒ ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. +ะžัะบั–ะปัŒะบะธ Swagger UI ะฝะต ะฟั–ะดั‚ั€ะธะผัƒะฒะฐะฒ ะบั–ะปัŒะบะฐ ะฟั€ะธะบะปะฐะดั–ะฒ JSON Schema (ัั‚ะฐะฝะพะผ ะฝะฐ 2023-08-26), ะบะพั€ะธัั‚ัƒะฒะฐั‡ั– ะฝะต ะผะฐะปะธ ะผะพะถะปะธะฒะพัั‚ั– ะฟะพะบะฐะทะฐั‚ะธ ะบั–ะปัŒะบะฐ ะฟั€ะธะบะปะฐะดั–ะฒ ัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. -ะฉะพะฑ ะฒะธั€ั–ัˆะธั‚ะธ ั†ะต, FastAPI ะฟะพั‡ะธะฝะฐัŽั‡ะธ ะท ะฒะตั€ัั–ั— 0.103.0 **ะดะพะดะฐะฒ ะฟั–ะดั‚ั€ะธะผะบัƒ** ัั‚ะฐั€ะพะณะพ **OpenAPI-ัะฟะตั†ะธั„ั–ั‡ะฝะพะณะพ** ะฟะพะปั `examples` ั‡ะตั€ะตะท ะฝะพะฒะธะน ะฟะฐั€ะฐะผะตั‚ั€ `openapi_examples`. ๐Ÿค“ +ะฉะพะฑ ะฒะธั€ั–ัˆะธั‚ะธ ั†ะต, FastAPI `0.103.0` **ะดะพะดะฐะฒ ะฟั–ะดั‚ั€ะธะผะบัƒ** ะพะณะพะปะพัˆะตะฝะฝั ั‚ะพะณะพ ัะฐะผะพะณะพ ัั‚ะฐั€ะพะณะพ **OpenAPI-ัะฟะตั†ะธั„ั–ั‡ะฝะพะณะพ** ะฟะพะปั `examples` ั‡ะตั€ะตะท ะฝะพะฒะธะน ะฟะฐั€ะฐะผะตั‚ั€ `openapi_examples`. ๐Ÿค“ -### ะŸั–ะดััƒะผะพะบ +### ะŸั–ะดััƒะผะพะบ { #summary } -ะ ะฐะฝั–ัˆะต ั ะบะฐะทะฐะฒ, ั‰ะพ ะฝะต ะปัŽะฑะปัŽ ั–ัั‚ะพั€ั–ัŽ... ะฐ ั‚ะตะฟะตั€ ะพััŒ ั โ€” ั€ะพะทะฟะพะฒั–ะดะฐัŽ "ั‚ะตั…ะฝั–ั‡ะฝั– ั–ัั‚ะพั€ะธั‡ะฝั–" ะปะตะบั†ั–ั—. ๐Ÿ˜… +ะ ะฐะฝั–ัˆะต ั ะบะฐะทะฐะฒ, ั‰ะพ ะฝะต ะดัƒะถะต ะปัŽะฑะปัŽ ั–ัั‚ะพั€ั–ัŽ... ะฐ ั‚ะตะฟะตั€ ะฟะพะดะธะฒั–ั‚ัŒัั ะฝะฐ ะผะตะฝะต โ€” ั‡ะธั‚ะฐัŽ ยซั‚ะตั…ะฝั–ั‡ะฝั– ั–ัั‚ะพั€ะธั‡ะฝั–ยป ะปะตะบั†ั–ั—. ๐Ÿ˜… ะšะพั€ะพั‚ะบะพ: **ะพะฝะพะฒั–ั‚ัŒัั ะดะพ FastAPI 0.99.0 ะฐะฑะพ ะฒะธั‰ะต** โ€” ั– ะฒัะต ัั‚ะฐะฝะต ะทะฝะฐั‡ะฝะพ **ะฟั€ะพัั‚ั–ัˆะธะผ, ัƒะทะณะพะดะถะตะฝะธะผ ั‚ะฐ ั–ะฝั‚ัƒั—ั‚ะธะฒะฝะพ ะทั€ะพะทัƒะผั–ะปะธะผ**, ั– ะ’ะฐะผ ะฝะต ะดะพะฒะตะดะตั‚ัŒัั ะทะฝะฐั‚ะธ ะฒัั– ั†ั– ั–ัั‚ะพั€ะธั‡ะฝั– ะดะตั‚ะฐะปั–. ๐Ÿ˜Ž diff --git a/docs/uk/docs/tutorial/security/index.md b/docs/uk/docs/tutorial/security/index.md index c3d94be8d9..f1fb25178d 100644 --- a/docs/uk/docs/tutorial/security/index.md +++ b/docs/uk/docs/tutorial/security/index.md @@ -1,70 +1,72 @@ -# ะ‘ะตะทะฟะตะบะฐ +# ะ‘ะตะทะฟะตะบะฐ { #security } ะ†ัะฝัƒั” ะฑะฐะณะฐั‚ะพ ัะฟะพัะพะฑั–ะฒ ั€ะตะฐะปั–ะทัƒะฒะฐั‚ะธ ะฑะตะทะฟะตะบัƒ, ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ัŽ ั‚ะฐ ะฐะฒั‚ะพั€ะธะทะฐั†ั–ัŽ. -ะฆะต ะทะฐะทะฒะธั‡ะฐะน ัะบะปะฐะดะฝะฐ ั– "ะฝะตะฟั€ะพัั‚ะฐ" ั‚ะตะผะฐ. +ะ† ะทะฐะทะฒะธั‡ะฐะน ั†ะต ัะบะปะฐะดะฝะฐ ั– ยซะฝะตะฟั€ะพัั‚ะฐยป ั‚ะตะผะฐ. -ะฃ ะฑะฐะณะฐั‚ัŒะพั… ั„ั€ะตะนะผะฒะพั€ะบะฐั… ั– ัะธัั‚ะตะผะฐั… ะทะฐะฑะตะทะฟะตั‡ะตะฝะฝั ะฑะตะทะฟะตะบะธ ั‚ะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ะทะฐะนะผะฐั” ะฒะตะปะธั‡ะตะทะฝัƒ ั‡ะฐัั‚ะธะฝัƒ ะทัƒัะธะปัŒ ั– ะบะพะดัƒ (ั–ะฝะพะดั– โ€” ะฟะพะฝะฐะด 50% ะฒััŒะพะณะพ ะฝะฐะฟะธัะฐะฝะพะณะพ ะบะพะดัƒ). +ะฃ ะฑะฐะณะฐั‚ัŒะพั… ั„ั€ะตะนะผะฒะพั€ะบะฐั… ั– ัะธัั‚ะตะผะฐั… ะปะธัˆะต ะพะฑั€ะพะฑะบะฐ ะฑะตะทะฟะตะบะธ ั‚ะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ะฟะพั‚ั€ะตะฑัƒั” ะฒะตะปะธะบะธั… ะทัƒัะธะปัŒ ั– ะบะพะดัƒ (ัƒ ะฑะฐะณะฐั‚ัŒะพั… ะฒะธะฟะฐะดะบะฐั… ั†ะต ะผะพะถะต ะฑัƒั‚ะธ 50% ะฐะฑะพ ะฑั–ะปัŒัˆะต ะฒั–ะด ัƒััŒะพะณะพ ะฝะฐะฟะธัะฐะฝะพะณะพ ะบะพะดัƒ). -**FastAPI** ะฝะฐะดะฐั” ะบั–ะปัŒะบะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ัะบั– ะดะพะฟะพะผะพะถัƒั‚ัŒ ะ’ะฐะผ ะฒะฟะพั€ะฐั‚ะธัั ะท **ะฑะตะทะฟะตะบะพัŽ** ะปะตะณะบะพ, ัˆะฒะธะดะบะพ, ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะผ ัะฟะพัะพะฑะพะผ, ะฑะตะท ะฝะตะพะฑั…ั–ะดะฝะพัั‚ั– ะฒะธะฒั‡ะฐั‚ะธ ะฒัั– ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— ะฑะตะทะฟะตะบะธ. +**FastAPI** ะฝะฐะดะฐั” ะบั–ะปัŒะบะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ัะบั– ะดะพะฟะพะผะพะถัƒั‚ัŒ ะฒะฐะผ ะฟั€ะฐั†ัŽะฒะฐั‚ะธ ะท **ะฑะตะทะฟะตะบะพัŽ** ะปะตะณะบะพ, ัˆะฒะธะดะบะพ, ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะผ ัะฟะพัะพะฑะพะผ, ะฑะตะท ะฝะตะพะฑั…ั–ะดะฝะพัั‚ั– ะฒะธะฒั‡ะฐั‚ะธ ะฒัั– ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— ะฑะตะทะฟะตะบะธ. -ะะปะต ัะฟะพั‡ะฐั‚ะบัƒ โ€” ะบั–ะปัŒะบะฐ ะบะพั€ะพั‚ะบะธั… ะฟะพะฝัั‚ัŒ. +ะะปะต ัะฟะพั‡ะฐั‚ะบัƒ ั€ะพะทะณะปัะฝัŒะผะพ ะบั–ะปัŒะบะฐ ะฝะตะฒะตะปะธะบะธั… ะฟะพะฝัั‚ัŒ. -## ะŸะพัะฟั–ัˆะฐั”ั‚ะต? +## ะŸะพัะฟั–ัˆะฐั”ั‚ะต? { #in-a-hurry } -ะฏะบั‰ะพ ะ’ะฐะผ ะฝะต ั†ั–ะบะฐะฒั– ะฒัั– ั†ั– ั‚ะตั€ะผั–ะฝะธ ะน ะฟั€ะพัั‚ะพ ะฟะพั‚ั€ั–ะฑะฝะพ *ัˆะฒะธะดะบะพ* ะดะพะดะฐั‚ะธ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ัŽ ะทะฐ ะปะพะณั–ะฝะพะผ ั– ะฟะฐั€ะพะปะตะผ โ€” ะฟะตั€ะตั…ะพะดัŒั‚ะต ะดะพ ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปั–ะฒ. +ะฏะบั‰ะพ ะฒะฐะผ ะฝะต ั†ั–ะบะฐะฒั– ะฒัั– ั†ั– ั‚ะตั€ะผั–ะฝะธ ะน ะฒะฐะผ ะฟั€ะพัั‚ะพ ะฟะพั‚ั€ั–ะฑะฝะพ ะดะพะดะฐั‚ะธ ะฑะตะทะฟะตะบัƒ ะท ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั”ัŽ ะฝะฐ ะพัะฝะพะฒั– ั–ะผะตะฝั– ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ั‚ะฐ ะฟะฐั€ะพะปั *ะฟั€ัะผะพ ะทะฐั€ะฐะท*, ะฟะตั€ะตั…ะพะดัŒั‚ะต ะดะพ ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปั–ะฒ. -## OAuth2 +## OAuth2 { #oauth2 } -OAuth2 โ€” ั†ะต ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั, ั‰ะพ ะพะฟะธััƒั” ะบั–ะปัŒะบะฐ ัะฟะพัะพะฑั–ะฒ ะพะฑั€ะพะฑะบะธ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ั‚ะฐ ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั—. +OAuth2 โ€” ั†ะต ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั, ั‰ะพ ะฒะธะทะฝะฐั‡ะฐั” ะบั–ะปัŒะบะฐ ัะฟะพัะพะฑั–ะฒ ะพะฑั€ะพะฑะบะธ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ั‚ะฐ ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั—. -ะฆะต ะดะพัะธั‚ัŒ ะพะฑ'ั”ะผะฝะฐ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั, ัะบะฐ ะพั…ะพะฟะปัŽั” ัะบะปะฐะดะฝั– ะฒะธะฟะฐะดะบะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั. +ะฆะต ะดะพัะธั‚ัŒ ะพะฑ'ั”ะผะฝะฐ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั, ัะบะฐ ะพั…ะพะฟะปัŽั” ะบั–ะปัŒะบะฐ ัะบะปะฐะดะฝะธั… ะฒะธะฟะฐะดะบั–ะฒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั. -ะ’ะพะฝะฐ ะฒะบะปัŽั‡ะฐั” ัะฟะพัะพะฑะธ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ั‡ะตั€ะตะท "ั‚ั€ะตั‚ัŽ ัั‚ะพั€ะพะฝัƒ". +ะ’ะพะฝะฐ ะฒะบะปัŽั‡ะฐั” ัะฟะพัะพะฑะธ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ั‡ะตั€ะตะท ยซั‚ั€ะตั‚ัŽ ัั‚ะพั€ะพะฝัƒยป. -ะกะฐะผะต ั†ะต ะปะตะถะธั‚ัŒ ะฒ ะพัะฝะพะฒั– "ะฒั…ะพะดัƒ ั‡ะตั€ะตะท Google, Facebook, X (Twitter), GitHub" ั‚ะพั‰ะพ. +ะกะฐะผะต ั†ะต ะปะตะถะธั‚ัŒ ะฒ ะพัะฝะพะฒั– ะฒัั–ั… ัะธัั‚ะตะผ ั–ะท ยซัƒะฒั–ะนั‚ะธ ั‡ะตั€ะตะท Facebook, Google, X (Twitter), GitHubยป. -### OAuth 1 +### 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 } OpenID Connect โ€” ั‰ะต ะพะดะฝะฐ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั, ะฟะพะฑัƒะดะพะฒะฐะฝะฐ ะฝะฐ ะพัะฝะพะฒั– **OAuth2**. -ะ’ะพะฝะฐ ั€ะพะทัˆะธั€ัŽั” OAuth2, ัƒั‚ะพั‡ะฝัŽัŽั‡ะธ ะดะตัะบั– ะฝะตะพะดะฝะพะทะฝะฐั‡ะฝะพัั‚ั– ะดะปั ะดะพััะณะฝะตะฝะฝั ะบั€ะฐั‰ะพั— ััƒะผั–ัะฝะพัั‚ั–. +ะ’ะพะฝะฐ ะฟั€ะพัั‚ะพ ั€ะพะทัˆะธั€ัŽั” OAuth2, ัƒั‚ะพั‡ะฝัŽัŽั‡ะธ ะดะตัะบั– ะฒั–ะดะฝะพัะฝะพ ะฝะตะพะดะฝะพะทะฝะฐั‡ะฝั– ั€ะตั‡ั– ะฒ OAuth2, ั‰ะพะฑ ะทั€ะพะฑะธั‚ะธ ะนะพะณะพ ะฑั–ะปัŒัˆ ััƒะผั–ัะฝะธะผ. -ะะฐะฟั€ะธะบะปะฐะด, ะฒั…ั–ะด ั‡ะตั€ะตะท Google ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” OpenID Connect (ัะบะธะน ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ OAuth2). +ะะฐะฟั€ะธะบะปะฐะด, ะฒั…ั–ะด ั‡ะตั€ะตะท Google ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” OpenID Connect (ัะบะธะน ะฟั–ะด ะบะฐะฟะพั‚ะพะผ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” OAuth2). -ะะปะต ะฒั…ั–ะด ั‡ะตั€ะตะท Facebook โ€” ะฝั–. ะ’ั–ะฝ ะผะฐั” ะฒะปะฐัะฝัƒ ั€ะตะฐะปั–ะทะฐั†ั–ัŽ ะฝะฐ ะฑะฐะทั– OAuth2. +ะะปะต ะฒั…ั–ะด ั‡ะตั€ะตะท Facebook ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” OpenID Connect. ะ’ั–ะฝ ะผะฐั” ะฒะปะฐัะฝะธะน ั€ั–ะทะฝะพะฒะธะด OAuth2. -### OpenID (ะฝะต "OpenID Connect") +### OpenID (ะฝะต ยซOpenID Connectยป) { #openid-not-openid-connect } -ะ†ัะฝัƒะฒะฐะปะฐ ั‚ะฐะบะพะถ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั "OpenID", ัะบะฐ ะฝะฐะผะฐะณะฐะปะฐัั ั€ะพะทะฒสผัะทะฐั‚ะธ ั‚ั– ัะฐะผั– ะทะฐะดะฐั‡ั–, ั‰ะพ ะน **OpenID Connect**, ะฐะปะต ะฝะต ะฑะฐะทัƒะฒะฐะปะฐััŒ ะฝะฐ OAuth2. +ะ†ัะฝัƒะฒะฐะปะฐ ั‚ะฐะบะพะถ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั ยซOpenIDยป. ะ’ะพะฝะฐ ะฝะฐะผะฐะณะฐะปะฐัั ั€ะพะทะฒสผัะทะฐั‚ะธ ั‚ะต ัะฐะผะต, ั‰ะพ ะน **OpenID Connect**, ะฐะปะต ะฝะต ะฑะฐะทัƒะฒะฐะปะฐััŒ ะฝะฐ OAuth2. -ะฆะต ะฑัƒะปะฐ ะทะพะฒัั–ะผ ั–ะฝัˆะฐ ัะธัั‚ะตะผะฐ, ั– ััŒะพะณะพะดะฝั– ะฒะพะฝะฐ ะผะฐะนะถะต ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั. +ะขะพะถ ั†ะต ะฑัƒะปะฐ ะฟะพะฒะฝั–ัั‚ัŽ ะดะพะดะฐั‚ะบะพะฒะฐ ัะธัั‚ะตะผะฐ. -## OpenAPI +ะ—ะฐั€ะฐะท ะฒะพะฝะฐ ะฝะต ะดัƒะถะต ะฟะพะฟัƒะปัั€ะฝะฐ ะฐะฑะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั. -OpenAPI (ั€ะฐะฝั–ัˆะต Swagger) โ€” ั†ะต ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั ะดะปั ะฟะพะฑัƒะดะพะฒะธ API (ั‚ะตะฟะตั€ ะฟั–ะด ะตะณั–ะดะพัŽ Linux Foundation). +## OpenAPI { #openapi } + +OpenAPI (ั€ะฐะฝั–ัˆะต ะฒั–ะดะพะผะธะน ัะบ Swagger) โ€” ั†ะต ะฒั–ะดะบั€ะธั‚ะฐ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั ะดะปั ะฟะพะฑัƒะดะพะฒะธ API (ั‚ะตะฟะตั€ ั‡ะฐัั‚ะธะฝะฐ Linux Foundation). **FastAPI** ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ **OpenAPI**. -ะ—ะฐะฒะดัะบะธ ั†ัŒะพะผัƒ ะ’ะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ะณะตะฝะตั€ะฐั†ั–ัŽ ะบะพะดัƒ ั‚ะฐ ะฑะฐะณะฐั‚ะพ ั–ะฝัˆะพะณะพ. +ะกะฐะผะต ั†ะต ั€ะพะฑะธั‚ัŒ ะผะพะถะปะธะฒะธะผะธ ะบั–ะปัŒะบะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะธั… ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะธั… ั–ะฝั‚ะตั€ั„ะตะนัั–ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—, ะณะตะฝะตั€ะฐั†ั–ัŽ ะบะพะดัƒ ั‚ะพั‰ะพ. -OpenAPI ะดะพะทะฒะพะปัั” ะพะฟะธััƒะฒะฐั‚ะธ ั€ั–ะทะฝั– "ัั…ะตะผะธ" ะฑะตะทะฟะตะบะธ. +OpenAPI ะผะฐั” ัะฟะพัั–ะฑ ะฒะธะทะฝะฐั‡ะฐั‚ะธ ั€ั–ะทะฝั– ยซัั…ะตะผะธยป ะฑะตะทะฟะตะบะธ. -ะ’ะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั—ั…, ะ’ะธ ะผะพะถะตั‚ะต ัะบะพั€ะธัั‚ะฐั‚ะธัั ะฒัั–ะผะฐ ั†ะธะผะธ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ, ั‰ะพ ะฑะฐะทัƒัŽั‚ัŒัั ะฝะฐ ัั‚ะฐะฝะดะฐั€ั‚ะฐั…, ะทะพะบั€ะตะผะฐ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะธะผะธ ัะธัั‚ะตะผะฐะผะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. +ะ’ะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั—ั…, ะฒะธ ะผะพะถะตั‚ะต ัะบะพั€ะธัั‚ะฐั‚ะธัั ะฒัั–ะผะฐ ั†ะธะผะธ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ, ั‰ะพ ะฑะฐะทัƒัŽั‚ัŒัั ะฝะฐ ัั‚ะฐะฝะดะฐั€ั‚ะฐั…, ะทะพะบั€ะตะผะฐ ั†ะธะผะธ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะธะผะธ ัะธัั‚ะตะผะฐะผะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. OpenAPI ะฒะธะทะฝะฐั‡ะฐั” ั‚ะฐะบั– ัั…ะตะผะธ ะฑะตะทะฟะตะบะธ: @@ -72,33 +74,33 @@ OpenAPI ะฒะธะทะฝะฐั‡ะฐั” ั‚ะฐะบั– ัั…ะตะผะธ ะฑะตะทะฟะตะบะธ: * ะŸะฐั€ะฐะผะตั‚ั€ ะทะฐะฟะธั‚ัƒ. * ะ—ะฐะณะพะปะพะฒะพะบ. * Cookie. -* `http`: ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ะผะตั‚ะพะดะธ HTTP-ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—, ะฒะบะปัŽั‡ะฐัŽั‡ะธ: - * `bearer`: ะทะฐะณะพะปะพะฒะพะบ `Authorization` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `Bearer` ั‚ะฐ ั‚ะพะบะตะฝะพะผ. ะฆะต ัƒัะฟะฐะดะบะพะฒะฐะฝะพ ะท OAuth2. - * HTTP Basic ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั - * HTTP Digest, ั‚ะพั‰ะพ. +* `http`: ัั‚ะฐะฝะดะฐั€ั‚ะฝั– ัะธัั‚ะตะผะธ HTTP-ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—, ะฒะบะปัŽั‡ะฐัŽั‡ะธ: + * `bearer`: ะทะฐะณะพะปะพะฒะพะบ `Authorization` ะทั– ะทะฝะฐั‡ะตะฝะฝัะผ `Bearer ` ั‚ะฐ ั‚ะพะบะตะฝะพะผ. ะฆะต ัƒัะฟะฐะดะบะพะฒะฐะฝะพ ะท OAuth2. + * HTTP Basic ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ัŽ. + * HTTP Digest ั‚ะพั‰ะพ. * `oauth2`: ัƒัั– ัะฟะพัะพะฑะธ ะพะฑั€ะพะฑะบะธ ะฑะตะทะฟะตะบะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ OAuth2 (ั‚ะฐะบ ะทะฒะฐะฝั– ยซะฟะพั‚ะพะบะธยป). - * ะ”ะตัะบั– ะท ั†ะธั… ะฟะพั‚ะพะบั–ะฒ ะฟั–ะดั…ะพะดัั‚ัŒ ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ะฒะปะฐัะฝะพะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— OAuth 2.0 (ะฝะฐะฟั€ะธะบะปะฐะด, Google, Facebook, X (Twitter), GitHub ั‚ะพั‰ะพ): - * `implicit`โ€” ะฝะตัะฒะฝะธะน - * `clientCredentials`โ€” ะพะฑะปั–ะบะพะฒั– ะดะฐะฝั– ะบะปั–ั”ะฝั‚ะฐ - * `authorizationCode` โ€” ะบะพะด ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั— - * ะะปะต ั” ะพะดะธะฝ ะพะบั€ะตะผะธะน ยซะฟะพั‚ั–ะบยป, ัะบะธะน ั–ะดะตะฐะปัŒะฝะพ ะฟั–ะดั…ะพะดะธั‚ัŒ ะดะปั ั€ะตะฐะปั–ะทะฐั†ั–ั— ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ะฒัะตั€ะตะดะธะฝั– ะพะดะฝะพะณะพ ะดะพะดะฐั‚ะบัƒ: - * `password`: ัƒ ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปะฐั… ะฑัƒะดะต ะฟั€ะธะบะปะฐะด ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ั†ัŒะพะณะพ ะฟะพั‚ะพะบัƒ. -* `openIdConnect`: ะดะพะทะฒะพะปัั” ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธัะฒะปัั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— OAuth2. - * ะฆะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฒะธัะฒะปะตะฝะฝั ะฒะธะทะฝะฐั‡ะฐั”ั‚ัŒัั ัƒ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OpenID Connect. + * ะ”ะตะบั–ะปัŒะบะฐ ะท ั†ะธั… ะฟะพั‚ะพะบั–ะฒ ะฟั–ะดั…ะพะดัั‚ัŒ ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ะฟั€ะพะฒะฐะนะดะตั€ะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— OAuth 2.0 (ะฝะฐะฟั€ะธะบะปะฐะด, Google, Facebook, X (Twitter), GitHub ั‚ะพั‰ะพ): + * `implicit` + * `clientCredentials` + * `authorizationCode` + * ะะปะต ั” ะพะดะธะฝ ะพะบั€ะตะผะธะน ยซะฟะพั‚ั–ะบยป, ัะบะธะน ะผะพะถะฝะฐ ั–ะดะตะฐะปัŒะฝะพ ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ ะดะปั ะพะฑั€ะพะฑะบะธ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ะฝะฐะฟั€ัะผัƒ ะฒ ั†ัŒะพะผัƒ ะถ ะทะฐัั‚ะพััƒะฝะบัƒ: + * `password`: ัƒ ะบั–ะปัŒะบะพั… ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปะฐั… ะฑัƒะดัƒั‚ัŒ ะฟั€ะธะบะปะฐะดะธ ั†ัŒะพะณะพ. +* `openIdConnect`: ะผะฐั” ัะฟะพัั–ะฑ ะฒะธะทะฝะฐั‡ะธั‚ะธ, ัะบ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธัะฒะปัั‚ะธ ะดะฐะฝั– ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— OAuth2. + * ะกะฐะผะต ั†ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะต ะฒะธัะฒะปะตะฝะฝั ะฒะธะทะฝะฐั‡ะตะฝะพ ัƒ ัะฟะตั†ะธั„ั–ะบะฐั†ั–ั— OpenID Connect. /// tip | ะŸะพั€ะฐะดะฐ -ะ†ะฝั‚ะตะณั€ะฐั†ั–ั ั–ะฝัˆะธั… ะฟั€ะพะฒะฐะนะดะตั€ั–ะฒ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—/ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั—, ั‚ะฐะบะธั… ัะบ Google, Facebook, X (Twitter), GitHub ั‚ะพั‰ะพ โ€” ั‚ะฐะบะพะถ ะผะพะถะปะธะฒะฐ ั– ะฒั–ะดะฝะพัะฝะพ ะฟั€ะพัั‚ะฐ. +ะ†ะฝั‚ะตะณั€ะฐั†ั–ั ั–ะฝัˆะธั… ะฟั€ะพะฒะฐะนะดะตั€ั–ะฒ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—/ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั—, ั‚ะฐะบะธั… ัะบ Google, Facebook, X (Twitter), GitHub ั‚ะพั‰ะพ, ั‚ะฐะบะพะถ ะผะพะถะปะธะฒะฐ ั– ะฒั–ะดะฝะพัะฝะพ ะฟั€ะพัั‚ะฐ. -ะะฐะนัะบะปะฐะดะฝั–ัˆะต โ€” ั†ะต ัั‚ะฒะพั€ะธั‚ะธ ะฒะปะฐัะฝะพะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—/ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั—, ัะบ Google ั‡ะธ Facebook. ะะปะต **FastAPI** ะฝะฐะดะฐั” ะ’ะฐะผ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ, ั‰ะพะฑ ะทั€ะพะฑะธั‚ะธ ั†ะต ะปะตะณะบะพ, ะฑะตั€ัƒั‡ะธ ะฝะฐ ัะตะฑะต ะฒะฐะถะบัƒ ั‡ะฐัั‚ะธะฝัƒ ั€ะพะฑะพั‚ะธ. +ะะฐะนัะบะปะฐะดะฝั–ัˆะต โ€” ั†ะต ัั‚ะฒะพั€ะธั‚ะธ ะฟั€ะพะฒะฐะนะดะตั€ะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—/ะฐะฒั‚ะพั€ะธะทะฐั†ั–ั— ะฝะฐ ะบัˆั‚ะฐะปั‚ ั‚ะฐะบะธั…, ะฐะปะต **FastAPI** ะฝะฐะดะฐั” ะฒะฐะผ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ, ั‰ะพะฑ ะทั€ะพะฑะธั‚ะธ ั†ะต ะปะตะณะบะพ, ะฒะธะบะพะฝัƒัŽั‡ะธ ะฒะฐะถะบัƒ ั‡ะฐัั‚ะธะฝัƒ ั€ะพะฑะพั‚ะธ ะทะฐ ะฒะฐั. /// -## ะ†ะฝัั‚ั€ัƒะผะตะฝั‚ะธ **FastAPI** +## ะฃั‚ะธะปั–ั‚ะธ **FastAPI** { #fastapi-utilities } -FastAPI ะฝะฐะดะฐั” ะบั–ะปัŒะบะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ ะดะปั ะบะพะถะฝะพั— ะท ะพะฟะธัะฐะฝะธั… ัั…ะตะผ ะฑะตะทะฟะตะบะธ ะฒ ะผะพะดัƒะปั– `fastapi.security`, ัะบั– ัะฟั€ะพั‰ัƒัŽั‚ัŒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ั†ะธั… ะผะตั…ะฐะฝั–ะทะผั–ะฒ ะทะฐั…ะธัั‚ัƒ. +FastAPI ะฝะฐะดะฐั” ะบั–ะปัŒะบะฐ ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ ะดะปั ะบะพะถะฝะพั— ะท ะพะฟะธัะฐะฝะธั… ัั…ะตะผ ะฑะตะทะฟะตะบะธ ะฒ ะผะพะดัƒะปั– `fastapi.security`, ัะบั– ัะฟั€ะพั‰ัƒัŽั‚ัŒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ั†ะธั… ะผะตั…ะฐะฝั–ะทะผั–ะฒ ะฑะตะทะฟะตะบะธ. -ะฃ ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปะฐั… ะ’ะธ ะฟะพะฑะฐั‡ะธั‚ะต, ัะบ ะดะพะดะฐั‚ะธ ะฑะตะทะฟะตะบัƒ ะดะพ ัะฒะพะณะพ API ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ั†ะธั… ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ **FastAPI**. +ะฃ ะฝะฐัั‚ัƒะฟะฝะธั… ั€ะพะทะดั–ะปะฐั… ะฒะธ ะฟะพะฑะฐั‡ะธั‚ะต, ัะบ ะดะพะดะฐั‚ะธ ะฑะตะทะฟะตะบัƒ ะดะพ ัะฒะพะณะพ API ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ั†ะธั… ั–ะฝัั‚ั€ัƒะผะตะฝั‚ั–ะฒ, ัะบั– ะฝะฐะดะฐั” **FastAPI**. -ะ ั‚ะฐะบะพะถ ะฟะพะฑะฐั‡ะธั‚ะต, ัะบ ะฒะพะฝะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ั–ะฝั‚ะตะณั€ัƒั”ั‚ัŒัั ะฒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ ะฒะฐัˆะพะณะพ API. +ะ ั‚ะฐะบะพะถ ะฟะพะฑะฐั‡ะธั‚ะต, ัะบ ั†ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ั–ะฝั‚ะตะณั€ัƒั”ั‚ัŒัั ะฒ ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝัƒ ัะธัั‚ะตะผัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. diff --git a/docs/uk/docs/tutorial/static-files.md b/docs/uk/docs/tutorial/static-files.md index 3427f23765..32ca1311d2 100644 --- a/docs/uk/docs/tutorial/static-files.md +++ b/docs/uk/docs/tutorial/static-files.md @@ -1,13 +1,13 @@ -# ะกั‚ะฐั‚ะธั‡ะฝั– ั„ะฐะนะปะธ +# ะกั‚ะฐั‚ะธั‡ะฝั– ั„ะฐะนะปะธ { #static-files } ะ’ะธ ะผะพะถะตั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฝะฐะดะฐะฒะฐั‚ะธ ัั‚ะฐั‚ะธั‡ะฝั– ั„ะฐะนะปะธ ะท ะบะฐั‚ะฐะปะพะณัƒ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `StaticFiles`. -## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `StaticFiles` +## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `StaticFiles` { #use-staticfiles } * ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต `StaticFiles`. -* "ะŸั–ะด'ั”ะดะฝะฐั‚ะธ" ะตะบะทะตะผะฟะปัั€ `StaticFiles()` ะท ะฒะบะฐะทะฐะฝะฝัะผ ะฝะตะพะฑั…ั–ะดะฝะพะณะพ ัˆะปัั…ัƒ. +* ยซะŸั–ะด'ั”ะดะฝะฐั‚ะธยป ะตะบะทะตะผะฟะปัั€ `StaticFiles()` ะท ะฒะบะฐะทะฐะฝะฝัะผ ะฝะตะพะฑั…ั–ะดะฝะพะณะพ ัˆะปัั…ัƒ. -{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} /// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– @@ -17,24 +17,24 @@ /// -### ะฉะพ ั‚ะฐะบะต "ะŸั–ะด'ั”ะดะฝะฐะฝะฝั" +### ะฉะพ ั‚ะฐะบะต ยซะŸั–ะด'ั”ะดะฝะฐะฝะฝัยป { #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**. -ะฃัั– ั†ั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะทะผั–ะฝะตะฝั– ะฒั–ะดะฟะพะฒั–ะดะฝะพ ะดะพ ะฟะพั‚ั€ะตะฑ ั– ะพัะพะฑะปะธะฒะพัั‚ะตะน ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ. +ะฃัั– ั†ั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ั–ะฝัˆะธะผะธ ะทะฐ "`static`", ะฝะฐะปะฐัˆั‚ัƒะนั‚ะต ั—ั… ะฒั–ะดะฟะพะฒั–ะดะฝะพ ะดะพ ะฟะพั‚ั€ะตะฑ ั– ะพัะพะฑะปะธะฒะพัั‚ะตะน ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ. -## ะ”ะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั +## ะ”ะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั { #more-info } ะ”ะตั‚ะฐะปัŒะฝั–ัˆะต ะฟั€ะพ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ั‚ะฐ ะผะพะถะปะธะฒะพัั‚ั– ะผะพะถะฝะฐ ะดั–ะทะฝะฐั‚ะธัั ะฒ <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Starlette ะฟั€ะพ ัั‚ะฐั‚ะธั‡ะฝั– ั„ะฐะนะปะธ</a>. diff --git a/docs/uk/docs/tutorial/testing.md b/docs/uk/docs/tutorial/testing.md index 1105c6b0a8..462592829f 100644 --- a/docs/uk/docs/tutorial/testing.md +++ b/docs/uk/docs/tutorial/testing.md @@ -1,17 +1,18 @@ -# ะขะตัั‚ัƒะฒะฐะฝะฝั +# ะขะตัั‚ัƒะฒะฐะฝะฝั { #testing } -ะขะตัั‚ัƒะฒะฐะฝะฝั **FastAPI** ะดะพะดะฐั‚ะบั–ะฒ ั” ะฟั€ะพัั‚ะธะผ ั‚ะฐ ะตั„ะตะบั‚ะธะฒะฝะธะผ ะทะฐะฒะดัะบะธ ะฑั–ะฑะปั–ะพั‚ะตั†ั– <a href="https://www.starlette.dev/testclient/" class="external-link" target="_blank">Starlette</a>, ัะบะฐ ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>. -ะžัะบั–ะปัŒะบะธ HTTPX ั€ะพะทั€ะพะฑะปะตะฝะธะน ะฝะฐ ะพัะฝะพะฒั– Requests, ะนะพะณะพ API ั” ั–ะฝั‚ัƒั—ั‚ะธะฒะฝะพ ะทั€ะพะทัƒะผั–ะปะธะผ ะดะปั ั‚ะธั…, ั…ั‚ะพ ะฒะถะต ะทะฝะฐะนะพะผะธะน ะท Requests. +ะ—ะฐะฒะดัะบะธ <a href="https://www.starlette.dev/testclient/" class="external-link" target="_blank">Starlette</a> ั‚ะตัั‚ัƒะฒะฐั‚ะธ ะทะฐัั‚ะพััƒะฝะบะธ **FastAPI** ะฟั€ะพัั‚ะพ ะน ะฟั€ะธั”ะผะฝะพ. -ะ— ะนะพะณะพ ะดะพะฟะพะผะพะณะพัŽ ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะท **FastAPI**. +ะ’ะพะฝะพ ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, ัะบะธะน, ัะฒะพั”ัŽ ั‡ะตั€ะณะพัŽ, ัะฟั€ะพั”ะบั‚ะพะฒะฐะฝะธะน ะฝะฐ ะพัะฝะพะฒั– Requests, ั‚ะพะถ ะฒั–ะฝ ะดัƒะถะต ะทะฝะฐะนะพะผะธะน ั‚ะฐ ั–ะฝั‚ัƒั—ั‚ะธะฒะฝะพ ะทั€ะพะทัƒะผั–ะปะธะน. -## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `TestClient` +ะ— ะนะพะณะพ ะดะพะฟะพะผะพะณะพัŽ ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะท **FastAPI**. + +## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `TestClient` { #using-testclient } /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฉะพะฑ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `TestClient`, ัะฟะพั‡ะฐั‚ะบัƒ ะฒัั‚ะฐะฝะพะฒั–ั‚ัŒ <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>. -ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะ’ะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ ัะฐะผัƒ ะฑั–ะฑะปั–ะพั‚ะตะบัƒ, ะฝะฐะฟั€ะธะบะปะฐะด: +ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะธะปะธ `httpx`, ะฝะฐะฟั€ะธะบะปะฐะด: ```console $ pip install httpx @@ -21,7 +22,7 @@ $ pip install httpx ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต `TestClient`. -ะกั‚ะฒะพั€ั–ั‚ัŒ `TestClient`, ะฟะตั€ะตะดะฐะฒัˆะธ ะนะพะผัƒ ะ’ะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ **FastAPI**. +ะกั‚ะฒะพั€ั–ั‚ัŒ `TestClient`, ะฟะตั€ะตะดะฐะฒัˆะธ ะนะพะผัƒ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ **FastAPI**. ะกั‚ะฒะพั€ัŽะนั‚ะต ั„ัƒะฝะบั†ั–ั— ะท ั–ะผะตะฝะฐะผะธ, ั‰ะพ ะฟะพั‡ะธะฝะฐัŽั‚ัŒัั ะท `test_` (ั†ะต ัั‚ะฐะฝะดะฐั€ั‚ะฝะฐ ัƒะณะพะดะฐ ะดะปั `pytest`). @@ -29,8 +30,7 @@ $ pip install httpx ะ—ะฐะฟะธััƒะนั‚ะต ะฟั€ะพัั‚ั– `assert`-ะฒะธั€ะฐะทะธ ะทั– ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะผะธ ะฒะธั€ะฐะทะฐะผะธ Python, ัะบั– ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตะฒั–ั€ะธั‚ะธ (ั†ะต ั‚ะฐะบะพะถ ัั‚ะฐะฝะดะฐั€ั‚ ะดะปั `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 | ะŸะพั€ะฐะดะฐ @@ -46,25 +46,25 @@ $ pip install httpx ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `from starlette.testclient import TestClient`. -**FastAPI** ะฝะฐะดะฐั” ั‚ะพะน ัะฐะผะธะน `starlette.testclient` ะฟั–ะด ะฝะฐะทะฒะพัŽ `fastapi.testclient` ะดะปั ะทั€ัƒั‡ะฝะพัั‚ั– ั€ะพะทั€ะพะฑะฝะธะบั–ะฒ, ะฐะปะต ะฒั–ะฝ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฟะพั…ะพะดะธั‚ัŒ ั–ะท Starlette. +**FastAPI** ะฝะฐะดะฐั” ั‚ะพะน ัะฐะผะธะน `starlette.testclient` ะฟั–ะด ะฝะฐะทะฒะพัŽ `fastapi.testclient` ะฟั€ะพัั‚ะพ ะดะปั ะทั€ัƒั‡ะฝะพัั‚ั– ะดะปั ะฒะฐั, ั€ะพะทั€ะพะฑะฝะธะบะฐ. ะะปะต ะฒั–ะฝ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฟะพั…ะพะดะธั‚ัŒ ั–ะท Starlette. /// /// tip | ะŸะพั€ะฐะดะฐ -ะฏะบั‰ะพ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะบะปะธะบะฐั‚ะธ `async`-ั„ัƒะฝะบั†ั–ั— ัƒ ะฒะฐัˆะธั… ั‚ะตัั‚ะฐั…, ะพะบั€ั–ะผ ะฒั–ะดะฟั€ะฐะฒะปะตะฝะฝั ะทะฐะฟะธั‚ั–ะฒ ะดะพ FastAPI-ะทะฐัั‚ะพััƒะฝะบัƒ (ะฝะฐะฟั€ะธะบะปะฐะด, ะฐัะธะฝั…ั€ะพะฝะฝั– ั„ัƒะฝะบั†ั–ั— ั€ะพะฑะพั‚ะธ ะท ะฑะฐะทะพัŽ ะดะฐะฝะธั…), ะฟะตั€ะตะณะปัะฝัŒั‚ะต [ะัะธะฝั…ั€ะพะฝะฝั– ั‚ะตัั‚ะธ](../advanced/async-tests.md){.internal-link target=_blank} ัƒ ั€ะพะทัˆะธั€ะตะฝะพะผัƒ ะบะตั€ั–ะฒะฝะธั†ั‚ะฒั–. +ะฏะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะฒะธะบะปะธะบะฐั‚ะธ `async`-ั„ัƒะฝะบั†ั–ั— ัƒ ะฒะฐัˆะธั… ั‚ะตัั‚ะฐั…, ะพะบั€ั–ะผ ะฒั–ะดะฟั€ะฐะฒะปะตะฝะฝั ะทะฐะฟะธั‚ั–ะฒ ะดะพ ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ FastAPI (ะฝะฐะฟั€ะธะบะปะฐะด, ะฐัะธะฝั…ั€ะพะฝะฝั– ั„ัƒะฝะบั†ั–ั— ั€ะพะฑะพั‚ะธ ะท ะฑะฐะทะพัŽ ะดะฐะฝะธั…), ะฟะตั€ะตะณะปัะฝัŒั‚ะต [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} ัƒ ั€ะพะทัˆะธั€ะตะฝะพะผัƒ ะบะตั€ั–ะฒะฝะธั†ั‚ะฒั–. /// -## ะ ะพะทะดั–ะปะตะฝะฝั ั‚ะตัั‚ั–ะฒ +## ะ ะพะทะดั–ะปะตะฝะฝั ั‚ะตัั‚ั–ะฒ { #separating-tests } -ะฃ ั€ะตะฐะปัŒะฝะพะผัƒ ะทะฐัั‚ะพััƒะฝะบัƒ ะ’ะฐัˆั– ั‚ะตัั‚ะธ, ะนะผะพะฒั–ั€ะฝะพ, ะฑัƒะดัƒั‚ัŒ ะฒ ะพะบั€ะตะผะพะผัƒ ั„ะฐะนะปั–. +ะฃ ั€ะตะฐะปัŒะฝะพะผัƒ ะทะฐัั‚ะพััƒะฝะบัƒ ะฒะฐัˆั– ั‚ะตัั‚ะธ, ะนะผะพะฒั–ั€ะฝะพ, ะฑัƒะดัƒั‚ัŒ ะฒ ะพะบั€ะตะผะพะผัƒ ั„ะฐะนะปั–. -ะขะฐะบะพะถ ะ’ะฐัˆ **FastAPI**-ะทะฐัั‚ะพััƒะฝะพะบ ะผะพะถะต ัะบะปะฐะดะฐั‚ะธัั ะท ะบั–ะปัŒะบะพั… ั„ะฐะนะปั–ะฒ ะฐะฑะพ ะผะพะดัƒะปั–ะฒ ั‚ะพั‰ะพ. +ะขะฐะบะพะถ ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ **FastAPI** ะผะพะถะต ัะบะปะฐะดะฐั‚ะธัั ะท ะบั–ะปัŒะบะพั… ั„ะฐะนะปั–ะฒ/ะผะพะดัƒะปั–ะฒ ั‚ะพั‰ะพ. -### ะคะฐะนะป ะทะฐัั‚ะพััƒะฝะบัƒ **FastAPI** +### ะคะฐะนะป ะทะฐัั‚ะพััƒะฝะบัƒ **FastAPI** { #fastapi-app-file } -ะŸั€ะธะฟัƒัั‚ะธะผะพ, ัƒ ะ’ะฐั ั” ัั‚ั€ัƒะบั‚ัƒั€ะฐ ั„ะฐะนะปั–ะฒ, ะพะฟะธัะฐะฝะฐ ะฒ ั€ะพะทะดั–ะปั– [ะ‘ั–ะปัŒัˆั– ะทะฐัั‚ะพััƒะฝะบะธ](bigger-applications.md){.internal-link target=_blank}: +ะŸั€ะธะฟัƒัั‚ะธะผะพ, ัƒ ะฒะฐั ั” ัั‚ั€ัƒะบั‚ัƒั€ะฐ ั„ะฐะนะปั–ะฒ, ะพะฟะธัะฐะฝะฐ ะฒ ั€ะพะทะดั–ะปั– [Bigger Applications](bigger-applications.md){.internal-link target=_blank}: ``` . @@ -72,14 +72,15 @@ $ pip install httpx โ”‚ย ย  โ”œโ”€โ”€ __init__.py โ”‚ย ย  โ””โ”€โ”€ main.py ``` -ะฃ ั„ะฐะนะปั– `main.py` ะทะฝะฐั…ะพะดะธั‚ัŒัั ะ’ะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ **FastAPI** : -{* ../../docs_src/app_testing/main.py *} +ะฃ ั„ะฐะนะปั– `main.py` ะทะฝะฐั…ะพะดะธั‚ัŒัั ะฒะฐัˆ ะทะฐัั‚ะพััƒะฝะพะบ **FastAPI**: -### ะคะฐะนะป ั‚ะตัั‚ัƒะฒะฐะฝะฝั -ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ ั„ะฐะนะป `test_main.py` ะท ะ’ะฐัˆะธะผะธ ั‚ะตัั‚ะฐะผะธ. ะ’ั–ะฝ ะผะพะถะต ะทะฝะฐั…ะพะดะธั‚ะธัั ะฒ ั‚ะพะผัƒ ะถ ะฟะฐะบะตั‚ั– Python (ัƒ ั‚ั–ะน ัะฐะผั–ะน ะดะธั€ะตะบั‚ะพั€ั–ั— ะท ั„ะฐะนะปะพะผ `__init__.py`): +{* ../../docs_src/app_testing/app_a_py39/main.py *} +### ะคะฐะนะป ั‚ะตัั‚ัƒะฒะฐะฝะฝั { #testing-file } + +ะ’ะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ ั„ะฐะนะป `test_main.py` ะท ะฒะฐัˆะธะผะธ ั‚ะตัั‚ะฐะผะธ. ะ’ั–ะฝ ะผะพะถะต ะทะฝะฐั…ะพะดะธั‚ะธัั ะฒ ั‚ะพะผัƒ ะถ ะฟะฐะบะตั‚ั– Python (ัƒ ั‚ั–ะน ัะฐะผั–ะน ะดะธั€ะตะบั‚ะพั€ั–ั— ะท ั„ะฐะนะปะพะผ `__init__.py`): ``` hl_lines="5" . @@ -89,18 +90,18 @@ $ pip install httpx โ”‚ย ย  โ””โ”€โ”€ test_main.py ``` -ะžัะบั–ะปัŒะบะธ ั†ะตะน ั„ะฐะนะป ะทะฝะฐั…ะพะดะธั‚ัŒัั ะฒ ั‚ะพะผัƒ ะถ ะฟะฐะบะตั‚ั–, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒั–ะดะฝะพัะฝะธะน ั–ะผะฟะพั€ั‚, ั‰ะพะฑ ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ ะพะฑ'ั”ะบั‚ `app` ั–ะท ะผะพะดัƒะปั `main` (`main.py`): +ะžัะบั–ะปัŒะบะธ ั†ะตะน ั„ะฐะนะป ะทะฝะฐั…ะพะดะธั‚ัŒัั ะฒ ั‚ะพะผัƒ ะถ ะฟะฐะบะตั‚ั–, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒั–ะดะฝะพัะฝะธะน ั–ะผะฟะพั€ั‚, ั‰ะพะฑ ั–ะผะฟะพั€ั‚ัƒะฒะฐั‚ะธ ะพะฑ'ั”ะบั‚ `app` ั–ะท ะผะพะดัƒะปั `main` (`main.py`): -{* ../../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** +### ะ ะพะทัˆะธั€ะตะฝะธะน ั„ะฐะนะป ะทะฐัั‚ะพััƒะฝะบัƒ **FastAPI** { #extended-fastapi-app-file } ะ—ะฐะปะธัˆะธะผะพ ั‚ัƒ ัะฐะผัƒ ัั‚ั€ัƒะบั‚ัƒั€ัƒ ั„ะฐะนะปั–ะฒ: @@ -112,75 +113,26 @@ $ pip install httpx โ”‚ย ย  โ””โ”€โ”€ test_main.py ``` -ะŸั€ะธะฟัƒัั‚ะธะผะพ, ั‰ะพ ั‚ะตะฟะตั€ ั„ะฐะนะป `main.py` ั–ะท ะ’ะฐัˆะธะผ **FastAPI**-ะทะฐัั‚ะพััƒะฝะบะพะผ ะผั–ัั‚ะธั‚ัŒ ะดะพะดะฐั‚ะบะพะฒั– ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ (**path operations**). +ะŸั€ะธะฟัƒัั‚ะธะผะพ, ั‰ะพ ั‚ะตะฟะตั€ ั„ะฐะนะป `main.py` ั–ะท ะฒะฐัˆะธะผ ะทะฐัั‚ะพััƒะฝะบะพะผ **FastAPI** ะผั–ัั‚ะธั‚ัŒ ั–ะฝัˆั– **ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ**. ะ’ั–ะฝ ะผะฐั” `GET`-ะพะฟะตั€ะฐั†ั–ัŽ, ัะบะฐ ะผะพะถะต ะฟะพะฒะตั€ั‚ะฐั‚ะธ ะฟะพะผะธะปะบัƒ. ะ’ั–ะฝ ะผะฐั” `POST`-ะพะฟะตั€ะฐั†ั–ัŽ, ัะบะฐ ะผะพะถะต ะฟะพะฒะตั€ั‚ะฐั‚ะธ ะบั–ะปัŒะบะฐ ะฟะพะผะธะปะพะบ. -ะžะฑะธะดะฒั– ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ ะฒะธะผะฐะณะฐัŽั‚ัŒ ะทะฐะณะพะปะพะฒะพะบ `X-Token`. +ะžะฑะธะดะฒั– *ะพะฟะตั€ะฐั†ั–ั— ัˆะปัั…ัƒ* ะฒะธะผะฐะณะฐัŽั‚ัŒ ะทะฐะณะพะปะพะฒะพะบ `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!} -``` +### ะ ะพะทัˆะธั€ะตะฝะธะน ั‚ะตัั‚ะพะฒะธะน ั„ะฐะนะป { #extended-testing-file } -//// +ะŸะพั‚ั–ะผ ะฒะธ ะผะพะถะตั‚ะต ะพะฝะพะฒะธั‚ะธ `test_main.py`, ะดะพะดะฐะฒัˆะธ ั€ะพะทัˆะธั€ะตะฝั– ั‚ะตัั‚ะธ: -//// tab | Python 3.9+ +{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *} -```Python -{!> ../../docs_src/app_testing/app_b_an_py39/main.py!} -``` -//// +ะšะพะปะธ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตะดะฐั‚ะธ ะบะปั–ั”ะฝั‚ัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะฒ ะทะฐะฟะธั‚ั–, ะฐะปะต ะฒะธ ะฝะต ะทะฝะฐั”ั‚ะต, ัะบ ั†ะต ะทั€ะพะฑะธั‚ะธ, ะฒะธ ะผะพะถะตั‚ะต ะฟะพัˆัƒะบะฐั‚ะธ (Google), ัะบ ั†ะต ะทั€ะพะฑะธั‚ะธ ะฒ `httpx`, ะฐะฑะพ ะฝะฐะฒั–ั‚ัŒ ัะบ ั†ะต ะทั€ะพะฑะธั‚ะธ ะท `requests`, ะพัะบั–ะปัŒะบะธ ะดะธะทะฐะนะฝ HTTPX ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ ะดะธะทะฐะนะฝั– Requests. -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/app_testing/app_b_an/main.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | ะŸะพั€ะฐะดะฐ - -ะ‘ะฐะถะฐะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะตั€ัั–ัŽ ะท `Annotated`, ัะบั‰ะพ ั†ะต ะผะพะถะปะธะฒะพ - -/// - -```Python -{!> ../../docs_src/app_testing/app_b_py310/main.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | ะŸะพั€ะฐะดะฐ - -ะ‘ะฐะถะฐะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะตั€ัั–ัŽ ะท `Annotated`, ัะบั‰ะพ ั†ะต ะผะพะถะปะธะฒะพ - -/// - -```Python -{!> ../../docs_src/app_testing/app_b/main.py!} -``` - -//// - -### ะ ะพะทัˆะธั€ะตะฝะธะน ั‚ะตัั‚ะพะฒะธะน ั„ะฐะนะป - -ะŸะพั‚ั–ะผ ะ’ะธ ะผะพะถะตั‚ะต ะพะฝะพะฒะธั‚ะธ `test_main.py`, ะดะพะดะฐะฒัˆะธ ั€ะพะทัˆะธั€ะตะฝั– ั‚ะตัั‚ะธ: - -{* ../../docs_src/app_testing/app_b/test_main.py *} - -ะšะพะปะธ ะ’ะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฟะตั€ะตะดะฐั‚ะธ ะบะปั–ั”ะฝั‚ัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ะฒ ะทะฐะฟะธั‚ั–, ะฐะปะต ะ’ะธ ะฝะต ะทะฝะฐั”ั‚ะต, ัะบ ั†ะต ะทั€ะพะฑะธั‚ะธ, ะ’ะธ ะผะพะถะตั‚ะต ะฟะพัˆัƒะบะฐั‚ะธ (ะฝะฐะฟั€ะธะบะปะฐะด, ัƒ Google) ัะฟะพัั–ะฑ ั€ะตะฐะปั–ะทะฐั†ั–ั— ะฒ `httpx`, ะฐะฑะพ ะฝะฐะฒั–ั‚ัŒ ัƒ `requests`, ะพัะบั–ะปัŒะบะธ HTTPX ั€ะพะทั€ะพะฑะปะตะฝะธะน ะฝะฐ ะพัะฝะพะฒั– ะดะธะทะฐะนะฝัƒ Requests. - -ะ”ะฐะปั– ะ’ะธ ะฟั€ะพัั‚ะพ ะฟะพะฒั‚ะพั€ัŽั”ั‚ะต ั†ั– ะถ ะดั–ั— ัƒ ะฒะฐัˆะธั… ั‚ะตัั‚ะฐั…. +ะ”ะฐะปั– ะฒะธ ะฟั€ะพัั‚ะพ ะฟะพะฒั‚ะพั€ัŽั”ั‚ะต ั†ั– ะถ ะดั–ั— ัƒ ะฒะฐัˆะธั… ั‚ะตัั‚ะฐั…. ะะฐะฟั€ะธะบะปะฐะด: @@ -195,15 +147,16 @@ $ pip install httpx /// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะ—ะฒะตั€ะฝั–ั‚ัŒ ัƒะฒะฐะณัƒ, ั‰ะพ `TestClient` ะพั‚ั€ะธะผัƒั” ะดะฐะฝั–, ัะบั– ะผะพะถะฝะฐ ะบะพะฝะฒะตั€ั‚ัƒะฒะฐั‚ะธ ะฒ JSON, ะฐ ะฝะต Pydantic-ะผะพะดะตะปั–. -ะฏะบั‰ะพ ัƒ ะ’ะฐั ั” Pydantic-ะผะพะดะตะปัŒ ัƒ ั‚ะตัั‚ั–, ั– ะ’ะธ ั…ะพั‡ะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ ั—ั— ะดะฐะฝั– ะฒ ะดะพะดะฐั‚ะพะบ ะฟั–ะด ั‡ะฐั ั‚ะตัั‚ัƒะฒะฐะฝะฝั, ะ’ะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `jsonable_encoder`, ะพะฟะธัะฐะฝะธะน ัƒ ั€ะพะทะดั–ะปั– [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}. + +ะฏะบั‰ะพ ัƒ ะฒะฐั ั” Pydantic-ะผะพะดะตะปัŒ ัƒ ั‚ะตัั‚ั–, ั– ะฒะธ ั…ะพั‡ะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ ั—ั— ะดะฐะฝั– ะฒ ะทะฐัั‚ะพััƒะฝะพะบ ะฟั–ะด ั‡ะฐั ั‚ะตัั‚ัƒะฒะฐะฝะฝั, ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ `jsonable_encoder`, ะพะฟะธัะฐะฝะธะน ัƒ ั€ะพะทะดั–ะปั– [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}. /// -## ะ—ะฐะฟัƒัะบ ั‚ะตัั‚ั–ะฒ +## ะ—ะฐะฟัƒัะบ { #run-it } ะŸั–ัะปั ั†ัŒะพะณะพ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะพ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ `pytest`. -ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะ’ะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต]{.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ ั– ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฝะตะพะฑั…ั–ะดะฝั– ะฟะฐะบะตั‚ะธ, ะฝะฐะฟั€ะธะบะปะฐะด: +ะŸะตั€ะตะบะพะฝะฐะนั‚ะตัั, ั‰ะพ ะฒะธ ัั‚ะฒะพั€ะธะปะธ [ะฒั–ั€ั‚ัƒะฐะปัŒะฝะต ัะตั€ะตะดะพะฒะธั‰ะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒัƒะฒะฐะปะธ ะนะพะณะพ ั– ะฒัั‚ะฐะฝะพะฒะธะปะธ ะฝะตะพะฑั…ั–ะดะฝั– ะฟะฐะบะตั‚ะธ, ะฝะฐะฟั€ะธะบะปะฐะด: <div class="termy"> @@ -215,7 +168,7 @@ $ pip install pytest </div> -`pytest` ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะฝะฐะนะดะต ั„ะฐะนะปะธ ะท ั‚ะตัั‚ะฐะผะธ, ะฒะธะบะพะฝะฐั” ั—ั… ั– ะฝะฐะดะฐัั‚ัŒ ะฒะฐะผ ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ. +ะ’ั–ะฝ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะฝะฐะนะดะต ั„ะฐะนะปะธ ั‚ะฐ ั‚ะตัั‚ะธ, ะฒะธะบะพะฝะฐั” ั—ั… ั– ะฟะพะฒั–ะดะพะผะธั‚ัŒ ะฒะฐะผ ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ. ะ—ะฐะฟัƒัั‚ั–ั‚ัŒ ั‚ะตัั‚ะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ: diff --git a/scripts/docs.py b/scripts/docs.py index 84cf01c724..a9abce56b5 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -25,6 +25,7 @@ SUPPORTED_LANGS = { "es", "pt", "ru", + "uk", } From cf8dc98aadc07d28c8f3dc0497324128e782c358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sat, 10 Jan 2026 16:15:26 -0800 Subject: [PATCH 062/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20ko=20(update-outdated)=20(#14589)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com> --- docs/en/mkdocs.yml | 2 + docs/ko/docs/about/index.md | 4 +- .../docs/advanced/additional-status-codes.md | 10 +- .../ko/docs/advanced/advanced-dependencies.md | 123 +++- docs/ko/docs/advanced/async-tests.md | 71 +- docs/ko/docs/advanced/custom-response.md | 114 ++-- docs/ko/docs/advanced/events.md | 120 ++-- docs/ko/docs/advanced/index.md | 12 +- .../advanced/response-change-status-code.md | 16 +- docs/ko/docs/advanced/response-cookies.md | 30 +- docs/ko/docs/advanced/response-directly.md | 42 +- docs/ko/docs/advanced/response-headers.md | 22 +- docs/ko/docs/advanced/sub-applications.md | 60 +- docs/ko/docs/advanced/templates.md | 45 +- docs/ko/docs/advanced/testing-dependencies.md | 18 +- docs/ko/docs/advanced/testing-events.md | 13 +- docs/ko/docs/advanced/testing-websockets.md | 8 +- .../docs/advanced/using-request-directly.md | 26 +- docs/ko/docs/advanced/websockets.md | 70 +- docs/ko/docs/advanced/wsgi.md | 14 +- docs/ko/docs/benchmarks.md | 14 +- docs/ko/docs/deployment/cloud.md | 21 +- docs/ko/docs/deployment/docker.md | 621 +++++++----------- docs/ko/docs/deployment/index.md | 24 +- docs/ko/docs/deployment/server-workers.md | 186 ++---- docs/ko/docs/deployment/versions.md | 133 ++-- docs/ko/docs/environment-variables.md | 52 +- docs/ko/docs/how-to/conditional-openapi.md | 33 +- docs/ko/docs/how-to/configure-swagger-ui.md | 24 +- docs/ko/docs/index.md | 267 +++++--- docs/ko/docs/learn/index.md | 6 +- docs/ko/docs/project-generation.md | 28 +- docs/ko/docs/python-types.md | 527 +++++++++------ docs/ko/docs/resources/index.md | 4 +- docs/ko/docs/tutorial/background-tasks.md | 88 +-- docs/ko/docs/tutorial/body-fields.md | 16 +- docs/ko/docs/tutorial/body-multiple-params.md | 130 ++-- docs/ko/docs/tutorial/body-nested-models.md | 147 ++--- docs/ko/docs/tutorial/body.md | 58 +- docs/ko/docs/tutorial/cookie-param-models.md | 24 +- docs/ko/docs/tutorial/cookie-params.md | 22 +- docs/ko/docs/tutorial/cors.md | 92 +-- docs/ko/docs/tutorial/debugging.md | 20 +- .../dependencies/classes-as-dependencies.md | 239 +++++-- ...pendencies-in-path-operation-decorators.md | 20 +- .../dependencies/dependencies-with-yield.md | 124 ++-- .../dependencies/global-dependencies.md | 17 +- docs/ko/docs/tutorial/dependencies/index.md | 70 +- docs/ko/docs/tutorial/encoder.md | 30 +- docs/ko/docs/tutorial/extra-data-types.md | 12 +- docs/ko/docs/tutorial/extra-models.md | 72 +- docs/ko/docs/tutorial/first-steps.md | 213 +++--- docs/ko/docs/tutorial/header-param-models.md | 26 +- docs/ko/docs/tutorial/header-params.md | 24 +- docs/ko/docs/tutorial/index.md | 95 +-- docs/ko/docs/tutorial/metadata.md | 78 +-- docs/ko/docs/tutorial/middleware.md | 89 ++- .../tutorial/path-operation-configuration.md | 74 ++- .../path-params-numeric-validations.md | 135 ++-- docs/ko/docs/tutorial/path-params.md | 143 ++-- docs/ko/docs/tutorial/query-param-models.md | 34 +- .../tutorial/query-params-str-validations.md | 360 +++++++--- docs/ko/docs/tutorial/query-params.md | 65 +- docs/ko/docs/tutorial/request-files.md | 81 ++- docs/ko/docs/tutorial/request-form-models.md | 18 +- .../docs/tutorial/request-forms-and-files.md | 30 +- docs/ko/docs/tutorial/request-forms.md | 17 +- docs/ko/docs/tutorial/response-model.md | 277 +++++--- docs/ko/docs/tutorial/response-status-code.md | 72 +- docs/ko/docs/tutorial/schema-extra-example.md | 104 ++- .../tutorial/security/get-current-user.md | 96 +-- docs/ko/docs/tutorial/security/oauth2-jwt.md | 182 ++--- .../docs/tutorial/security/simple-oauth2.md | 58 +- docs/ko/docs/tutorial/sql-databases.md | 155 +++-- docs/ko/docs/tutorial/static-files.md | 39 +- docs/ko/docs/tutorial/testing.md | 146 ++-- docs/ko/docs/virtual-environments.md | 400 +++++------ scripts/docs.py | 1 + 78 files changed, 3842 insertions(+), 3111 deletions(-) diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 84255f0f80..1c29546198 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: /ko/ + name: ko - ํ•œ๊ตญ์–ด - link: /pt/ name: pt - portuguรชs - link: /ru/ diff --git a/docs/ko/docs/about/index.md b/docs/ko/docs/about/index.md index ee7804d323..dc2c728740 100644 --- a/docs/ko/docs/about/index.md +++ b/docs/ko/docs/about/index.md @@ -1,3 +1,3 @@ -# ์†Œ๊ฐœ +# ์†Œ๊ฐœ { #about } -FastAPI์— ๋Œ€ํ•œ ๋””์ž์ธ, ์˜๊ฐ ๋“ฑ์— ๋Œ€ํ•ด ๐Ÿค“ +FastAPI, ๊ทธ ๋””์ž์ธ, ์˜๊ฐ ๋“ฑ์— ๋Œ€ํ•ด ๐Ÿค“ diff --git a/docs/ko/docs/advanced/additional-status-codes.md b/docs/ko/docs/advanced/additional-status-codes.md index da06cb778a..64a7eabd5e 100644 --- a/docs/ko/docs/advanced/additional-status-codes.md +++ b/docs/ko/docs/advanced/additional-status-codes.md @@ -1,16 +1,16 @@ -# ์ถ”๊ฐ€ ์ƒํƒœ ์ฝ”๋“œ +# ์ถ”๊ฐ€ ์ƒํƒœ ์ฝ”๋“œ { #additional-status-codes } ๊ธฐ๋ณธ์ ์œผ๋กœ **FastAPI**๋Š” ์‘๋‹ต์„ `JSONResponse`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, *๊ฒฝ๋กœ ์ž‘์—…(path operation)*์—์„œ ๋ฐ˜ํ™˜ํ•œ ๋‚ด์šฉ์„ ํ•ด๋‹น `JSONResponse` ์•ˆ์— ๋„ฃ์–ด ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ์ƒํƒœ ์ฝ”๋“œ ๋˜๋Š” *๊ฒฝ๋กœ ์ž‘์—…*์—์„œ ์„ค์ •ํ•œ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -## ์ถ”๊ฐ€ ์ƒํƒœ ์ฝ”๋“œ +## ์ถ”๊ฐ€ ์ƒํƒœ ์ฝ”๋“œ { #additional-status-codes_1 } ๊ธฐ๋ณธ ์ƒํƒœ ์ฝ”๋“œ์™€ ๋ณ„๋„๋กœ ์ถ”๊ฐ€ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ ค๋ฉด `JSONResponse`์™€ ๊ฐ™์ด `Response`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์ถ”๊ฐ€ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํ•ญ๋ชฉ์„ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋Š” *๊ฒฝ๋กœ ์ž‘์—…*์ด ์žˆ๊ณ  ์„ฑ๊ณต ์‹œ 200 โ€œOKโ€์˜ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์ƒˆ๋กœ์šด ํ•ญ๋ชฉ์„ ํ—ˆ์šฉํ•˜๊ธฐ๋ฅผ ์›ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•ญ๋ชฉ์ด ์ด์ „์— ์กด์žฌํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์ด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  HTTP ์ƒํƒœ ์ฝ”๋“œ 201 "Created"๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์ƒˆ๋กœ์šด ํ•ญ๋ชฉ์„ ํ—ˆ์šฉํ•˜๊ธฐ๋ฅผ ์›ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ•ญ๋ชฉ์ด ์ด์ „์— ์กด์žฌํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์ด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  HTTP ์ƒํƒœ ์ฝ”๋“œ 201 "Created"๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” `JSONResponse`๋ฅผ ๊ฐ€์ ธ์™€์„œ ์›ํ•˜๋Š” `status_code`๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ฝ˜ํ…์ธ ๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค: @@ -26,7 +26,7 @@ /// -/// note | ๊ธฐ์ˆ ์  ์„ธ๋ถ€ ์ •๋ณด +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `from starlette.responses import JSONResponse`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -34,7 +34,7 @@ /// -## OpenAPI ๋ฐ API ๋ฌธ์„œ +## OpenAPI ๋ฐ API ๋ฌธ์„œ { #openapi-and-api-docs } ์ถ”๊ฐ€ ์ƒํƒœ ์ฝ”๋“œ์™€ ์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ, FastAPI๋Š” ๋ฐ˜ํ™˜ํ•  ๋‚ด์šฉ์„ ๋ฏธ๋ฆฌ ์•Œ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— OpenAPI ์Šคํ‚ค๋งˆ(API ๋ฌธ์„œ)์— ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/advanced/advanced-dependencies.md b/docs/ko/docs/advanced/advanced-dependencies.md index 7fa043fa3a..04e557d15b 100644 --- a/docs/ko/docs/advanced/advanced-dependencies.md +++ b/docs/ko/docs/advanced/advanced-dependencies.md @@ -1,6 +1,6 @@ -# ๊ณ ๊ธ‰ ์˜์กด์„ฑ +# ๊ณ ๊ธ‰ ์˜์กด์„ฑ { #advanced-dependencies } -## ๋งค๊ฐœ๋ณ€์ˆ˜ํ™”๋œ ์˜์กด์„ฑ +## ๋งค๊ฐœ๋ณ€์ˆ˜ํ™”๋œ ์˜์กด์„ฑ { #parameterized-dependencies } ์ง€๊ธˆ๊นŒ์ง€ ๋ณธ ๋ชจ๋“  ์˜์กด์„ฑ์€ ๊ณ ์ •๋œ ํ•จ์ˆ˜ ๋˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. @@ -10,7 +10,7 @@ ์ด๋•Œ ํ•ด๋‹น ๊ณ ์ •๋œ ๋‚ด์šฉ์„ ๋งค๊ฐœ๋ณ€์ˆ˜ํ™”ํ•  ์ˆ˜ ์žˆ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. -## "ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ" ์ธ์Šคํ„ด์Šค +## "ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ" ์ธ์Šคํ„ด์Šค { #a-callable-instance } Python์—๋Š” ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ"ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. @@ -21,9 +21,9 @@ Python์—๋Š” ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ"ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• {* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} ์ด ๊ฒฝ์šฐ, **FastAPI**๋Š” ์ถ”๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ํ•˜์œ„ ์˜์กด์„ฑ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด `__call__`์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉฐ, -๋‚˜์ค‘์— *๊ฒฝ๋กœ ์—ฐ์‚ฐ ํ•จ์ˆ˜*์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๊ฐ’์„ ์ „๋‹ฌํ•  ๋•Œ ์ด๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +๋‚˜์ค‘์— *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๊ฐ’์„ ์ „๋‹ฌํ•  ๋•Œ ์ด๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -## ์ธ์Šคํ„ด์Šค ๋งค๊ฐœ๋ณ€์ˆ˜ํ™”ํ•˜๊ธฐ +## ์ธ์Šคํ„ด์Šค ๋งค๊ฐœ๋ณ€์ˆ˜ํ™”ํ•˜๊ธฐ { #parameterize-the-instance } ์ด์ œ `__init__`์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜์กด์„ฑ์„ "๋งค๊ฐœ๋ณ€์ˆ˜ํ™”"ํ•  ์ˆ˜ ์žˆ๋Š” ์ธ์Šคํ„ด์Šค์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -31,7 +31,7 @@ Python์—๋Š” ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ"ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• ์ด ๊ฒฝ์šฐ, **FastAPI**๋Š” `__init__`์— ์ „ํ˜€ ๊ด€์—ฌํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์šฐ๋ฆฌ๋Š” ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์ฝ”๋“œ์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -## ์ธ์Šคํ„ด์Šค ์ƒ์„ฑํ•˜๊ธฐ +## ์ธ์Šคํ„ด์Šค ์ƒ์„ฑํ•˜๊ธฐ { #create-an-instance } ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ด ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -39,10 +39,9 @@ Python์—๋Š” ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ"ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด `checker.fixed_content` ์†์„ฑ์— `"bar"`๋ผ๋Š” ๊ฐ’์„ ๋‹ด์•„ ์˜์กด์„ฑ์„ "๋งค๊ฐœ๋ณ€์ˆ˜ํ™”"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ์ธ์Šคํ„ด์Šค๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ +## ์ธ์Šคํ„ด์Šค๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ { #use-the-instance-as-a-dependency } -๊ทธ๋Ÿฐ ๋‹ค์Œ, `Depends(FixedContentQueryChecker)` ๋Œ€์‹  `Depends(checker)`์—์„œ ์ด `checker` ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, -ํด๋ž˜์Šค ์ž์ฒด๊ฐ€ ์•„๋‹Œ ์ธ์Šคํ„ด์Šค `checker`๊ฐ€ ์˜์กด์„ฑ์ด ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ, ํด๋ž˜์Šค ์ž์ฒด๊ฐ€ ์•„๋‹Œ ์ธ์Šคํ„ด์Šค `checker`๊ฐ€ ์˜์กด์„ฑ์ด ๋˜๋ฏ€๋กœ, `Depends(FixedContentQueryChecker)` ๋Œ€์‹  `Depends(checker)`์—์„œ ์ด `checker` ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜์กด์„ฑ์„ ํ•ด๊ฒฐํ•  ๋•Œ **FastAPI**๋Š” ์ด `checker`๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค: @@ -50,18 +49,116 @@ Python์—๋Š” ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ"ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• checker(q="somequery") ``` -...๊ทธ๋ฆฌ๊ณ  ์ด๋•Œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ’์„ *๊ฒฝ๋กœ ์—ฐ์‚ฐ ํ•จ์ˆ˜*์˜ `fixed_content_included` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค: +...๊ทธ๋ฆฌ๊ณ  ์ด๋•Œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ’์„ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ์˜์กด์„ฑ ๊ฐ’์œผ๋กœ, `fixed_content_included` ๋งค๊ฐœ๋ณ€์ˆ˜์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค: {* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} -/// tip | ์ฐธ๊ณ  +/// 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` ์ดํ›„์˜ ์ข…๋ฃŒ ์ฝ”๋“œ๋Š” ์‘๋‹ต์ด ์ „์†ก๋œ ํ›„์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + +์ž์„ธํ•œ ๋‚ด์šฉ์€ [Dependencies with `yield` - Early exit and `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`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์—์„œ ์กฐ๊ธฐ ์ข…๋ฃŒ๊ฐ€ ํ•„์š”ํ•œ ๋‹ค๋ฅธ ์‚ฌ์šฉ ์‚ฌ๋ก€๊ฐ€ ์žˆ๋‹ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์˜ ๊ตฌ์ฒด์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€์™€ `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์— ๋Œ€ํ•œ ์กฐ๊ธฐ ์ข…๋ฃŒ๊ฐ€ ์–ด๋–ค ์ ์—์„œ ์ด๋“์ด ๋˜๋Š”์ง€๋ฅผ ํฌํ•จํ•ด <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussion Question</a>์„ ์ƒ์„ฑํ•ด ์ฃผ์„ธ์š”. + +`yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์—์„œ ์กฐ๊ธฐ ์ข…๋ฃŒ์— ๋Œ€ํ•œ ์„ค๋“๋ ฅ ์žˆ๋Š” ์‚ฌ์šฉ ์‚ฌ๋ก€๊ฐ€ ์žˆ๋‹ค๋ฉด, ์กฐ๊ธฐ ์ข…๋ฃŒ๋ฅผ ์„ ํƒ์ ์œผ๋กœ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒˆ๋กœ์šด ๋ฐฉ๋ฒ•์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. + +### `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ๊ณผ `except`, ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ { #dependencies-with-yield-and-except-technical-details } + +FastAPI 0.110.0 ์ด์ „์—๋Š” `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•œ ๋‹ค์Œ ๊ทธ ์˜์กด์„ฑ์—์„œ `except`๋กœ ์˜ˆ์™ธ๋ฅผ ์žก๊ณ , ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š์œผ๋ฉด, ์˜ˆ์™ธ๊ฐ€ ์ž๋™์œผ๋กœ ์–ด๋–ค ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ ๋˜๋Š” ๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜ ํ•ธ๋“ค๋Ÿฌ๋กœ raise/forward ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ํ•ธ๋“ค๋Ÿฌ ์—†์ด ์ „๋‹ฌ๋œ ์˜ˆ์™ธ(๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜)๋กœ ์ธํ•ด ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ์ด ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•˜๊ณ , ์ผ๋ฐ˜์ ์ธ Python ์ฝ”๋“œ์˜ ๋™์ž‘๊ณผ ์ผ๊ด€๋˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด 0.110.0 ๋ฒ„์ „์—์„œ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +### ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ์™€ `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}๊ฐ€ ์ด๋ฏธ ์‹คํ–‰๋œ ๋’ค์˜€์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ์ฃผ๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ ์•ˆ์—์„œ ์˜์กด์„ฑ์ด "yield"ํ•œ ๋™์ผํ•œ ๊ฐ์ฒด๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ์„ค๊ณ„์˜€์Šต๋‹ˆ๋‹ค. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ๊ฐ€ ๋๋‚œ ๋’ค์— ์ข…๋ฃŒ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +์ด๋Š” ์‘๋‹ต์ด ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ ๋ฆฌ์†Œ์Šค๋ฅผ ์ ์œ ํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•œ ์˜๋„๋กœ FastAPI 0.106.0์—์„œ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์ถ”๊ฐ€๋กœ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ๋Š” ๋ณดํ†ต ๋ณ„๋„์˜ ๋ฆฌ์†Œ์Šค(์˜ˆ: ์ž์ฒด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ)๋ฅผ ๊ฐ€์ง€๊ณ  ๋”ฐ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•˜๋Š” ๋…๋ฆฝ์ ์ธ ๋กœ์ง ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ์ด ๋ฐฉ์‹์ด ์ฝ”๋“œ๋ฅผ ๋” ๊น”๋”ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ค„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. + +/// + +์ด ๋™์ž‘์— ์˜์กดํ•˜๋˜ ๊ฒฝ์šฐ๋ผ๋ฉด, ์ด์ œ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ๋ฅผ ์œ„ํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ ๋‚ด๋ถ€์—์„œ ์ƒ์„ฑํ•˜๊ณ , ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์˜ ๋ฆฌ์†Œ์Šค์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ ๋‚ด๋ถ€์—์„œ ์ƒˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธ์…˜์„ ์ƒ์„ฑํ•˜๊ณ , ์ด ์ƒˆ ์„ธ์…˜์„ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜จ ๊ฐ์ฒด๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•˜๋Š” ๋Œ€์‹ , ํ•ด๋‹น ๊ฐ์ฒด์˜ ID๋ฅผ ์ „๋‹ฌํ•œ ๋‹ค์Œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ๊ฐ์ฒด๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๋ฉด ๋ฉ๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/advanced/async-tests.md b/docs/ko/docs/advanced/async-tests.md index 37dfe29792..6c85936812 100644 --- a/docs/ko/docs/advanced/async-tests.md +++ b/docs/ko/docs/advanced/async-tests.md @@ -1,31 +1,26 @@ -# ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ +# ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ { #async-tests } -์ด์ „ ์žฅ์—์„œ `TestClient` ๋ฅผ ์ด์šฉํ•ด **FastAPI** ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฒ•์„ ๋ฐฐ์šฐ์…จ์„ํ…๋ฐ์š”. -์ง€๊ธˆ๊นŒ์ง€๋Š” `async` ํ‚ค์›Œ๋“œ ์‚ฌ์šฉ์—†์ด ๋™๊ธฐ ํ•จ์ˆ˜์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฒ•๋งŒ ์ตํ˜”์Šต๋‹ˆ๋‹ค. +์ œ๊ณต๋œ `TestClient`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ด๋ฏธ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€๋Š” `async` ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ๋™๊ธฐ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•๋งŒ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋น„๋™๊ธฐ๋กœ ์ฟผ๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค. -FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฑ์—”๋“œ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋กํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค. +ํ…Œ์ŠคํŠธ์—์„œ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋น„๋™๊ธฐ๋กœ ์ฟผ๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด ๋ณด์„ธ์š”. FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ธ ๋‹ค์Œ, async ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋ฐฑ์—”๋“œ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜ฌ๋ฐ”๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๊ธฐ๋กํ–ˆ๋Š”์ง€ ๊ฒ€์ฆํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๋Ÿฐ ๊ฒฝ์šฐ์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์–ด๋–ป๊ฒŒ ๋น„๋™๊ธฐ๋กœ ์ž‘์„ฑํ•˜๋Š”์ง€ ์•Œ์•„๋ด…์‹œ๋‹ค. +์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -## pytest.mark.anyio +## pytest.mark.anyio { #pytest-mark-anyio } -์•ž์—์„œ ์ž‘์„ฑํ•œ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜์—์„œ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋„ ๋น„๋™๊ธฐ ํ•จ์ˆ˜์—ฌ์•ผํ•ฉ๋‹ˆ๋‹ค. -AnyIO๋Š” ํŠน์ • ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋ฅผ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋กœ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ๋Š” ๊น”๋”ํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +ํ…Œ์ŠคํŠธ์—์„œ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ ค๋ฉด, ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋„ ๋น„๋™๊ธฐ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. AnyIO๋Š” ์ด๋ฅผ ์œ„ํ•œ ๊น”๋”ํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ œ๊ณตํ•˜๋ฉฐ, ์ผ๋ถ€ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋ฅผ ๋น„๋™๊ธฐ๋กœ ํ˜ธ์ถœํ•˜๋„๋ก ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +## HTTPX { #httpx } -## HTTPX +**FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด `async def` ๋Œ€์‹  ์ผ๋ฐ˜ `def` ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„, ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ์—ฌ์ „ํžˆ `async` ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค. -**FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด `async def` ๋Œ€์‹  `def` ํ‚ค์›Œ๋“œ๋กœ ์„ ์–ธ๋œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„, ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ์—ฌ์ „ํžˆ `๋น„๋™๊ธฐ` ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค. +`TestClient`๋Š” ํ‘œ์ค€ pytest๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ์ผ๋ฐ˜ `def` ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜ ์•ˆ์—์„œ ๋น„๋™๊ธฐ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ˜ธ์ถœํ•˜๋„๋ก ๋‚ด๋ถ€์—์„œ ๋งˆ๋ฒ• ๊ฐ™์€ ์ฒ˜๋ฆฌ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ์•ˆ์—์„œ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ทธ ๋งˆ๋ฒ•์€ ๋” ์ด์ƒ ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰ํ•˜๋ฉด, ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜ ์•ˆ์—์„œ `TestClient`๋ฅผ ๋” ์ด์ƒ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -`TestClient`๋Š” pytest ํ‘œ์ค€์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ผ๋ฐ˜์ ์ธ `def` ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜ ๋‚ด์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‚ด๋ถ€์—์„œ ๋งˆ์ˆ ์„ ๋ถ€๋ฆฝ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๋งˆ์ˆ ์€ ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋” ์ด์ƒ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰ํ•˜๋ฉด, ๋” ์ด์ƒ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ `TestClient`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. +`TestClient`๋Š” <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ, ๋‹คํ–‰ํžˆ HTTPX๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•ด API๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -`TestClient`๋Š” <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๋‹คํ–‰ํžˆ ์ด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์—ฌ API๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +## ์˜ˆ์‹œ { #example } -## ์˜ˆ์‹œ - -๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ๋ฅผ ์œ„ํ•ด [๋” ํฐ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋งŒ๋“ค๊ธฐ](../ko/tutorial/bigger-applications.md){.internal-link target=_blank} ์™€ [ํ…Œ์ŠคํŠธ](../ko/tutorial/testing.md){.internal-link target=_blank}:์—์„œ ๋‹ค๋ฃฌ ํŒŒ์ผ ๊ตฌ์กฐ์™€ ๋น„์Šทํ•œ ํ˜•ํƒœ๋ฅผ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค: +๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ๋กœ, [๋” ํฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜](../tutorial/bigger-applications.md){.internal-link target=_blank}๊ณผ [ํ…Œ์ŠคํŠธ](../tutorial/testing.md){.internal-link target=_blank}์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ๊ณผ ๋น„์Šทํ•œ ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: ``` . @@ -35,17 +30,17 @@ AnyIO๋Š” ํŠน์ • ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋ฅผ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋กœ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ๋Š” โ”‚ย ย  โ””โ”€โ”€ test_main.py ``` - `main.py`๋Š” ์•„๋ž˜์™€ ๊ฐ™์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค: +`main.py` ํŒŒ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/async_tests/main.py *} +{* ../../docs_src/async_tests/app_a_py39/main.py *} -`test_main.py` ํŒŒ์ผ์€ `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 } -์•„๋ž˜์˜ ๋ช…๋ น์–ด๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค: +๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‰์†Œ์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <div class="termy"> @@ -57,52 +52,48 @@ $ pytest </div> -## ์ž์„ธํžˆ ๋ณด๊ธฐ +## ์ž์„ธํžˆ ๋ณด๊ธฐ { #in-detail } -`@pytest.mark.anyio` ๋งˆ์ปค๋Š” pytest์—๊ฒŒ ์ด ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๊ฐ€ ๋น„๋™๊ธฐ๋กœ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•จ์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค: +`@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 | ํŒ -ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๊ฐ€ ์ด์ œ `TestClient`๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์ฒ˜๋Ÿผ ๋‹จ์ˆœํžˆ `def`๊ฐ€ ์•„๋‹ˆ๋ผ `async def`๋กœ ์ž‘์„ฑ๋œ ์ ์— ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. +`TestClient`๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์ฒ˜๋Ÿผ ๋‹จ์ˆœํžˆ `def`๊ฐ€ ์•„๋‹ˆ๋ผ, ์ด์ œ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๊ฐ€ `async def`๋ผ๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. /// -๊ทธ ๋‹ค์Œ์— `AsyncClient` ๋กœ ์•ฑ์„ ๋งŒ๋“ค๊ณ  ๋น„๋™๊ธฐ ์š”์ฒญ์„ `await` ํ‚ค์›Œ๋“œ๋กœ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ทธ ๋‹ค์Œ ์•ฑ์œผ๋กœ `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] *} -์œ„์˜ ์ฝ”๋“œ๋Š”: +์ด๋Š” ๋‹ค์Œ๊ณผ ๋™๋“ฑํ•ฉ๋‹ˆ๋‹ค: ```Python response = client.get('/') ``` -`TestClient` ์— ์š”์ฒญ์„ ๋ณด๋‚ด๋˜ ๊ฒƒ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. +`TestClient`๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋˜ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. /// tip | ํŒ -์ƒˆ๋กœ์šด `AsyncClient`๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ async/await๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. ์ด ์š”์ฒญ์€ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. +์ƒˆ `AsyncClient`์™€ ํ•จ๊ป˜ async/await๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. ์š”์ฒญ์€ ๋น„๋™๊ธฐ์ž…๋‹ˆ๋‹ค. /// /// warning | ๊ฒฝ๊ณ  -๋งŒ์•ฝ์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด Lifespan ์ด๋ฒคํŠธ์— ์˜์กด์„ฑ์„ ๊ฐ–๊ณ  ์žˆ๋‹ค๋ฉด `AsyncClient` ๊ฐ€ ์ด๋Ÿฌํ•œ ์ด๋ฒคํŠธ๋ฅผ ์‹คํ–‰์‹œํ‚ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -`AsyncClient` ๊ฐ€ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰์‹œ์ผฐ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด -`LifespanManager` from <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>.ํ™•์ธํ•ด์ฃผ์„ธ์š”. - +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด lifespan ์ด๋ฒคํŠธ์— ์˜์กดํ•œ๋‹ค๋ฉด, `AsyncClient`๋Š” ์ด๋Ÿฌํ•œ ์ด๋ฒคํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜๋„๋ก ํ•˜๋ ค๋ฉด <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>์˜ `LifespanManager`๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. /// -## ๊ทธ ์™ธ์˜ ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ํ˜ธ์ถœ +## ๊ธฐํƒ€ ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ํ˜ธ์ถœ { #other-asynchronous-function-calls } -ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๊ฐ€ ์ด์ œ ๋น„๋™๊ธฐ ํ•จ์ˆ˜์ด๋ฏ€๋กœ, FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ ์™ธ์—๋„ ๋‹ค๋ฅธ `async` ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  `await` ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๊ฐ€ ์ด์ œ ๋น„๋™๊ธฐ์ด๋ฏ€๋กœ, ํ…Œ์ŠคํŠธ์—์„œ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ ์™ธ์—๋„ ๋‹ค๋ฅธ `async` ํ•จ์ˆ˜๋ฅผ ์ฝ”๋“œ์˜ ๋‹ค๋ฅธ ๊ณณ์—์„œ ํ˜ธ์ถœํ•˜๋“ฏ์ด ๋™์ผํ•˜๊ฒŒ ํ˜ธ์ถœํ•˜๊ณ  (`await`) ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. /// tip | ํŒ -ํ…Œ์ŠคํŠธ์— ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ํ†ตํ•ฉํ•  ๋•Œ (์˜ˆ: <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB์˜ MotorClient</a>๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ) `RuntimeError: Task attached to a different loop` ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ํ•„์š”ํ•œ ๊ฐ์ฒด๋Š” ๋ฐ˜๋“œ์‹œ ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ๋‚ด์—์„œ๋งŒ ์ธ์Šคํ„ด์Šคํ™”ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์„ ์ฃผ์˜ํ•˜์„ธ์š”! -์˜ˆ๋ฅผ ๋“ค์–ด `@app.on_event("startup")` ์ฝœ๋ฐฑ ๋‚ด์—์„œ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. +ํ…Œ์ŠคํŠธ์— ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ํ†ตํ•ฉํ•  ๋•Œ(์˜ˆ: <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB์˜ MotorClient</a>๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ) `RuntimeError: Task attached to a different loop`๋ฅผ ๋งˆ์ฃผ์นœ๋‹ค๋ฉด, ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ํ•„์š”ํ•œ ๊ฐ์ฒด๋Š” async ํ•จ์ˆ˜ ์•ˆ์—์„œ๋งŒ ์ธ์Šคํ„ด์Šคํ™”ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด `@app.on_event("startup")` ์ฝœ๋ฐฑ์—์„œ ์ธ์Šคํ„ด์Šคํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// diff --git a/docs/ko/docs/advanced/custom-response.md b/docs/ko/docs/advanced/custom-response.md index 2001956fa2..55dc2a4be1 100644 --- a/docs/ko/docs/advanced/custom-response.md +++ b/docs/ko/docs/advanced/custom-response.md @@ -1,4 +1,4 @@ -# ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต - HTML, Stream, ํŒŒ์ผ, ๊ธฐํƒ€ +# ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต - HTML, Stream, ํŒŒ์ผ, ๊ธฐํƒ€ { #custom-response-html-stream-file-others } ๊ธฐ๋ณธ์ ์œผ๋กœ, **FastAPI** ์‘๋‹ต์„ `JSONResponse`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. @@ -6,11 +6,11 @@ ๊ทธ๋Ÿฌ๋‚˜ `Response` (๋˜๋Š” `JSONResponse`์™€ ๊ฐ™์€ ํ•˜์œ„ ํด๋ž˜์Šค)๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ๋ฐ์ดํ„ฐ๊ฐ€ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜๋˜์ง€ ์•Š์œผ๋ฉฐ (์‹ฌ์ง€์–ด `response_model`์„ ์„ ์–ธํ–ˆ๋”๋ผ๋„), ๋ฌธ์„œํ™”๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด, ์ƒ์„ฑ๋œ OpenAPI์˜ ์ผ๋ถ€๋กœ HTTP ํ—ค๋” `Content-Type`์— ํŠน์ • "๋ฏธ๋””์–ด ํƒ€์ž…"์„ ํฌํ•จํ•˜๋Š” ๊ฒฝ์šฐ). -ํ•˜์ง€๋งŒ *๊ฒฝ๋กœ ์ž‘์—… ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›ํ•˜๋Š” `Response`(์˜ˆ: ๋ชจ๋“  `Response` ํ•˜์œ„ ํด๋ž˜์Šค)๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›ํ•˜๋Š” `Response`(์˜ˆ: ๋ชจ๋“  `Response` ํ•˜์œ„ ํด๋ž˜์Šค)๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -*๊ฒฝ๋กœ ์ž‘์—… ํ•จ์ˆ˜*์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋‚ด์šฉ์€ ํ•ด๋‹น `Response`์•ˆ์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋‚ด์šฉ์€ ํ•ด๋‹น `Response`์•ˆ์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ๋งŒ์•ฝ ๊ทธ `Response`๊ฐ€ `JSONResponse`์™€ `UJSONResponse`์˜ ๊ฒฝ์šฐ ์ฒ˜๋Ÿผ JSON ๋ฏธ๋””์–ด ํƒ€์ž…(`application/json`)์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, *๊ฒฝ๋กœ ์ž‘์—… ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ ์„ ์–ธํ•œ Pydantic์˜ `response_model`์„ ์‚ฌ์šฉํ•ด ์ž๋™์œผ๋กœ ๋ณ€ํ™˜(๋ฐ ํ•„ํ„ฐ๋ง) ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๋งŒ์•ฝ ๊ทธ `Response`๊ฐ€ `JSONResponse`์™€ `UJSONResponse`์˜ ๊ฒฝ์šฐ ์ฒ˜๋Ÿผ JSON ๋ฏธ๋””์–ด ํƒ€์ž…(`application/json`)์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ ์„ ์–ธํ•œ Pydantic์˜ `response_model`์„ ์‚ฌ์šฉํ•ด ์ž๋™์œผ๋กœ ๋ณ€ํ™˜(๋ฐ ํ•„ํ„ฐ๋ง) ๋ฉ๋‹ˆ๋‹ค. /// note | ์ฐธ๊ณ  @@ -18,11 +18,11 @@ /// -## `ORJSONResponse` ์‚ฌ์šฉํ•˜๊ธฐ +## `ORJSONResponse` ์‚ฌ์šฉํ•˜๊ธฐ { #use-orjsonresponse } -์˜ˆ๋ฅผ ๋“ค์–ด, ์„ฑ๋Šฅ์„ ๊ทน๋Œ€ํ™”ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ, <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">orjson</a>์„ ์„ค์น˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์‘๋‹ต์„ `ORJSONResponse`๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, ์„ฑ๋Šฅ์„ ๊ทน๋Œ€ํ™”ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ, <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>์„ ์„ค์น˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์‘๋‹ต์„ `ORJSONResponse`๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” `Response` ํด๋ž˜์Šค(ํ•˜์œ„ ํด๋ž˜์Šค)๋ฅผ ์ž„ํฌํŠธํ•œ ํ›„, **๊ฒฝ๋กœ ์ž‘์—… ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ ์„ ์–ธํ•˜์„ธ์š”. +์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” `Response` ํด๋ž˜์Šค(ํ•˜์œ„ ํด๋ž˜์Šค)๋ฅผ ์ž„ํฌํŠธํ•œ ํ›„, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ ์„ ์–ธํ•˜์„ธ์š”. ๋Œ€๊ทœ๋ชจ ์‘๋‹ต์˜ ๊ฒฝ์šฐ, ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค `Response`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋น ๋ฆ…๋‹ˆ๋‹ค. @@ -30,7 +30,7 @@ ํ•˜์ง€๋งŒ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋‚ด์šฉ์ด **JSON์œผ๋กœ ์ง๋ ฌํ™” ๊ฐ€๋Šฅ**ํ•˜๋‹ค๊ณ  ํ™•์‹ ํ•˜๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น ๋‚ด์šฉ์„ ์‘๋‹ต ํด๋ž˜์Šค์— ์ง์ ‘ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, FastAPI๊ฐ€ ๋ฐ˜ํ™˜ ๋‚ด์šฉ์„ `jsonable_encoder`๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•œ ๋’ค ์‘๋‹ต ํด๋ž˜์Šค์— ์ „๋‹ฌํ•˜๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} /// info | ์ •๋ณด @@ -48,14 +48,14 @@ /// -## HTML ์‘๋‹ต +## HTML ์‘๋‹ต { #html-response } **FastAPI**์—์„œ HTML ์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋ ค๋ฉด `HTMLResponse`๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. * `HTMLResponse`๋ฅผ ์ž„ํฌํŠธ ํ•ฉ๋‹ˆ๋‹ค. -* *๊ฒฝ๋กœ ์ž‘์—… ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์˜ `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `HTMLResponse`๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. +* *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์˜ `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `HTMLResponse`๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} /// info | ์ •๋ณด @@ -67,17 +67,17 @@ /// -### `Response` ๋ฐ˜ํ™˜ํ•˜๊ธฐ +### `Response` ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #return-a-response } -[์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ](response-directly.md){.internal-link target=_blank}์—์„œ ๋ณธ ๊ฒƒ ์ฒ˜๋Ÿผ, *๊ฒฝ๋กœ ์ž‘์—…*์—์„œ ์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์žฌ์ •์˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +[์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ](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 | ๊ฒฝ๊ณ  -*๊ฒฝ๋กœ ์ž‘์—… ํ•จ์ˆ˜*์—์„œ ์ง์ ‘ ๋ฐ˜ํ™˜๋œ `Response`๋Š” OpenAPI์— ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์˜ˆ๋ฅผ๋“ค์–ด, `Content-Type`์ด ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์Œ) ์ž๋™ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ๋„ ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ์ง์ ‘ ๋ฐ˜ํ™˜๋œ `Response`๋Š” OpenAPI์— ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์˜ˆ๋ฅผ๋“ค์–ด, `Content-Type`์ด ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์Œ) ์ž๋™ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ๋„ ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. /// @@ -87,27 +87,27 @@ /// -### OpenAPI์— ๋ฌธ์„œํ™”ํ•˜๊ณ  `Response` ์žฌ์ •์˜ ํ•˜๊ธฐ +### OpenAPI์— ๋ฌธ์„œํ™”ํ•˜๊ณ  `Response` ์žฌ์ •์˜ ํ•˜๊ธฐ { #document-in-openapi-and-override-response } ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ์‘๋‹ต์„ ์žฌ์ •์˜ํ•˜๋ฉด์„œ ๋™์‹œ์— OpenAPI์—์„œ "๋ฏธ๋””์–ด ํƒ€์ž…"์„ ๋ฌธ์„œํ™”ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, `response_class` ๋งค๊ฒŒ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ `Response` ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ `response_class`๋Š” OpenAPI *๊ฒฝ๋กœ ์ž‘์—…*์„ ๋ฌธ์„œํ™”ํ•˜๋Š” ๋ฐ๋งŒ ์‚ฌ์šฉ๋˜๊ณ , ์‹ค์ œ๋กœ๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ๋ฐ˜ํ™˜ํ•œ `Response`๊ฐ€ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ `response_class`๋Š” OpenAPI *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ๋ฌธ์„œํ™”ํ•˜๋Š” ๋ฐ๋งŒ ์‚ฌ์šฉ๋˜๊ณ , ์‹ค์ œ๋กœ๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ๋ฐ˜ํ™˜ํ•œ `Response`๊ฐ€ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -### `HTMLResponse`์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ +#### `HTMLResponse`์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #return-an-htmlresponse-directly } ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} ์ด ์˜ˆ์ œ์—์„œ, `generate_html_response()` ํ•จ์ˆ˜๋Š” HTML์„ `str`๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹  ์ด๋ฏธ `Response`๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. `generate_html_response()`๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•จ์œผ๋กœ์จ, ๊ธฐ๋ณธ์ ์ธ **FastAPI** ๊ธฐ๋ณธ ๋™์ž‘์„ ์žฌ์ •์˜ ํ•˜๋Š” `Response`๋ฅผ ์ด๋ฏธ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ `response_class`์— `HTMLResponse`๋ฅผ ํ•จ๊ป˜ ์ „๋‹ฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, FastAPI๋Š” ์ด๋ฅผ OpenAPI ๋ฐ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ `text/html`๋กœ HTML์„ ๋ฌธ์„œํ™” ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ `response_class`์— `HTMLResponse`๋ฅผ ํ•จ๊ป˜ ์ „๋‹ฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, **FastAPI**๋Š” ์ด๋ฅผ OpenAPI ๋ฐ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ `text/html`๋กœ HTML์„ ๋ฌธ์„œํ™” ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <img src="/img/tutorial/custom-response/image01.png"> -## ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์‘๋‹ต๋“ค +## ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์‘๋‹ต๋“ค { #available-responses } ๋‹ค์Œ์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡๊ฐ€์ง€ ์‘๋‹ต๋“ค ์ž…๋‹ˆ๋‹ค. @@ -121,7 +121,7 @@ /// -### `Response` +### `Response` { #response } ๊ธฐ๋ณธ `Response` ํด๋ž˜์Šค๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  ์‘๋‹ต ํด๋ž˜์Šค์˜ ๋ถ€๋ชจ ํด๋ž˜์Šค ์ž…๋‹ˆ๋‹ค. @@ -134,27 +134,27 @@ * `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.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py39.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 } <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น ๋ฅธ JSON ์‘๋‹ต์„ ์ œ๊ณตํ•˜๋Š” ๋Œ€์•ˆ์ž…๋‹ˆ๋‹ค. ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋‚ด์šฉ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. @@ -164,13 +164,13 @@ FastAPI (์‹ค์ œ๋กœ๋Š” Starlette)๊ฐ€ ์ž๋™์œผ๋กœ `Content-Length` ํ—ค๋”๋ฅผ ํฌ /// -### `UJSONResponse` +### `UJSONResponse` { #ujsonresponse } <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>์„ ์‚ฌ์šฉํ•œ ๋˜ ๋‹ค๋ฅธ JSON ์‘๋‹ต ํ˜•์‹์ž…๋‹ˆ๋‹ค. /// info | ์ •๋ณด -์ด ์‘๋‹ต์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด `ujson`์„ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: 'pip install ujson`. +์ด ์‘๋‹ต์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด `ujson`์„ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: `pip install ujson`. /// @@ -180,7 +180,7 @@ FastAPI (์‹ค์ œ๋กœ๋Š” Starlette)๊ฐ€ ์ž๋™์œผ๋กœ `Content-Length` ํ—ค๋”๋ฅผ ํฌ /// -{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} /// tip | ํŒ @@ -188,22 +188,22 @@ FastAPI (์‹ค์ œ๋กœ๋Š” Starlette)๊ฐ€ ์ž๋™์œผ๋กœ `Content-Length` ํ—ค๋”๋ฅผ ํฌ /// -### `RedirectResponse` +### `RedirectResponse` { #redirectresponse } HTTP ๋ฆฌ๋””๋ ‰์…˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒํƒœ ์ฝ”๋“œ๋Š” 307(์ž„์‹œ ๋ฆฌ๋””๋ ‰์…˜)์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. `RedirectResponse`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} --- ๋˜๋Š” `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} -์ด ๊ฒฝ์šฐ, *๊ฒฝ๋กœ ์ž‘์—…* ํ•จ์ˆ˜์—์„œ URL์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ํ•จ์ˆ˜์—์„œ URL์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ์‚ฌ์šฉ๋˜๋Š” `status_code`๋Š” `RedirectResponse`์˜ ๊ธฐ๋ณธ๊ฐ’์ธ `307` ์ž…๋‹ˆ๋‹ค. @@ -211,23 +211,23 @@ HTTP ๋ฆฌ๋””๋ ‰์…˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒํƒœ ์ฝ”๋“œ๋Š” 30 `status_code` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} -### `StreamingResponse` +### `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()`์œผ๋กœ ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด)๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น ํŒŒ์ผ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ๋ฐ˜๋ณต(iterate)ํ•˜๋Š” ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> ๊ฐ์ฒด(์˜ˆ: `open()`์œผ๋กœ ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด)๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น file-like ๊ฐ์ฒด๋ฅผ ๋ฐ˜๋ณต(iterate)ํ•˜๋Š” ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์œผ๋กœ, ํŒŒ์ผ ์ „์ฒด๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋จผ์ € ์ฝ์–ด๋“ค์ผ ํ•„์š” ์—†์ด, ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๋ฅผ `StreamingResponse`์— ์ „๋‹ฌํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€, ๋น„๋””์˜ค ์ฒ˜๋ฆฌ ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} 1. ์ด๊ฒƒ์ด ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. `yield` ๋ฌธ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ "์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜"์ž…๋‹ˆ๋‹ค. 2. `with` ๋ธ”๋ก์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ, ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๊ฐ€ ์™„๋ฃŒ๋œ ํ›„ ํŒŒ์ผ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๊ฐ€ ๋‹ซํžˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์‘๋‹ต ์ „์†ก์ด ๋๋‚œ ํ›„ ๋‹ซํž™๋‹ˆ๋‹ค. @@ -235,15 +235,15 @@ HTTP ๋ฆฌ๋””๋ ‰์…˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒํƒœ ์ฝ”๋“œ๋Š” 30 ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด "์ƒ์„ฑ(generating)" ์ž‘์—…์„ ๋‚ด๋ถ€์ ์œผ๋กœ ๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€์— ์œ„์ž„ํ•˜๋Š” ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. - ์ด ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด `with` ๋ธ”๋ก ์•ˆ์—์„œ ํŒŒ์ผ์„ ์—ด ์ˆ˜ ์žˆ์–ด, ์ž‘์—…์ด ์™„๋ฃŒ๋œ ํ›„ ํŒŒ์ผ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๊ฐ€ ๋‹ซํžˆ๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + ์ด ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด `with` ๋ธ”๋ก ์•ˆ์—์„œ ํŒŒ์ผ์„ ์—ด ์ˆ˜ ์žˆ์–ด, ์ž‘์—…์ด ์™„๋ฃŒ๋œ ํ›„ ํŒŒ์ผ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๊ฐ€ ๋‹ซํžˆ๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// tip | ํŒ -์—ฌ๊ธฐ์„œ ํ‘œ์ค€ `open()`์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— `async`์™€ `await`๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฒฝ๋กœ ์ž‘์—…์€ ์ผ๋ฐ˜ `def`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์„œ ํ‘œ์ค€ `open()`์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— `async`์™€ `await`๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๋Š” ์ผ๋ฐ˜ `def`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. /// -### `FileResponse` +### `FileResponse` { #fileresponse } ํŒŒ์ผ์„ ๋น„๋™๊ธฐ๋กœ ์ŠคํŠธ๋ฆฌ๋ฐํ•˜์—ฌ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค. @@ -256,25 +256,25 @@ HTTP ๋ฆฌ๋””๋ ‰์…˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒํƒœ ์ฝ”๋“œ๋Š” 30 ํŒŒ์ผ ์‘๋‹ต์—๋Š” ์ ์ ˆํ•œ `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` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} -์ด ๊ฒฝ์šฐ, ๊ฒฝ๋กœ ์ž‘์—… ํ•จ์ˆ˜์—์„œ ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ, ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜์—์„œ ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต ํด๋ž˜์Šค +## ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต ํด๋ž˜์Šค { #custom-response-class } `Response`๋ฅผ ์ƒ์†๋ฐ›์•„ ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ํฌํ•จ๋œ `ORJSONResponse` ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์„ค์ •์œผ๋กœ <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">orjson</a>์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, ํฌํ•จ๋œ `ORJSONResponse` ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์„ค์ •์œผ๋กœ <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. ๋งŒ์•ฝ ๋“ค์—ฌ์“ฐ๊ธฐ ๋ฐ ํฌ๋งท๋œ JSON์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, `orjson.OPT_INDENT_2` ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. `CustomORJSONResponse`๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ํ•ต์‹ฌ์€ `Response.render(content)` ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋‚ด์šฉ์„ `bytes`๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค: -{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} ์ด์ œ ๋‹ค์Œ ๋Œ€์‹ : @@ -282,7 +282,7 @@ HTTP ๋ฆฌ๋””๋ ‰์…˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒํƒœ ์ฝ”๋“œ๋Š” 30 {"message": "Hello World"} ``` -์ด ์‘๋‹ต์€ ์ด๋ ‡๊ฒŒ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค: +...์ด ์‘๋‹ต์€ ์ด๋ ‡๊ฒŒ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค: ```json { @@ -292,22 +292,22 @@ HTTP ๋ฆฌ๋””๋ ‰์…˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒํƒœ ์ฝ”๋“œ๋Š” 30 ๋ฌผ๋ก  JSON ํฌ๋งทํŒ…๋ณด๋‹ค ๋” ์œ ์šฉํ•˜๊ฒŒ ํ™œ์šฉํ•  ๋ฐฉ๋ฒ•์„ ์ฐพ์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜‰ -## ๊ธฐ๋ณธ ์‘๋‹ต ํด๋ž˜์Šค +## ๊ธฐ๋ณธ ์‘๋‹ต ํด๋ž˜์Šค { #default-response-class } **FastAPI** ํด๋ž˜์Šค ๊ฐ์ฒด ๋˜๋Š” `APIRouter`๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์‘๋‹ต ํด๋ž˜์Šค๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์ •์˜ํ•˜๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” `default_response_class`์ž…๋‹ˆ๋‹ค. -์•„๋ž˜ ์˜ˆ์ œ์—์„œ **FastAPI**๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ ์ž‘์—…์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ `JSONResponse` ๋Œ€์‹  `ORJSONResponse`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +์•„๋ž˜ ์˜ˆ์ œ์—์„œ **FastAPI**๋Š” ๋ชจ๋“  *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ `JSONResponse` ๋Œ€์‹  `ORJSONResponse`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} /// tip | ํŒ -์—ฌ์ „ํžˆ ์ด์ „์ฒ˜๋Ÿผ *๊ฒฝ๋กœ ์ž‘์—…*์—์„œ `response_class`๋ฅผ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฌ์ „ํžˆ ์ด์ „์ฒ˜๋Ÿผ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ `response_class`๋ฅผ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## ์ถ”๊ฐ€ ๋ฌธ์„œํ™” +## ์ถ”๊ฐ€ ๋ฌธ์„œํ™” { #additional-documentation } OpenAPI์—์„œ `responses`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฏธ๋””์–ด ํƒ€์ž… ๋ฐ ๊ธฐํƒ€ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: [OpenAPI์—์„œ ์ถ”๊ฐ€ ์‘๋‹ต](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/ko/docs/advanced/events.md b/docs/ko/docs/advanced/events.md index 4318ada54e..35223eaf39 100644 --- a/docs/ko/docs/advanced/events.md +++ b/docs/ko/docs/advanced/events.md @@ -1,67 +1,66 @@ -# Lifespan ์ด๋ฒคํŠธ +# Lifespan ์ด๋ฒคํŠธ { #lifespan-events } -์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ **์‹œ์ž‘ ์ „**์— ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋กœ์ง(์ฝ”๋“œ)์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ด ์ฝ”๋“œ๊ฐ€ **ํ•œ ๋ฒˆ**๋งŒ ์‹คํ–‰๋˜๋ฉฐ, **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์š”์ฒญ์„ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ „**์— ์‹คํ–‰๋œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **์‹œ์ž‘**ํ•˜๊ธฐ ์ „์— ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋กœ์ง(์ฝ”๋“œ)์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ด ์ฝ”๋“œ๊ฐ€ **ํ•œ ๋ฒˆ**๋งŒ ์‹คํ–‰๋˜๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **์š”์ฒญ์„ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ „**์— ์‹คํ–‰๋œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. -๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **์ข…๋ฃŒ๋  ๋•Œ** ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋กœ์ง(์ฝ”๋“œ)์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ์ด ์ฝ”๋“œ๋Š” **ํ•œ ๋ฒˆ**๋งŒ ์‹คํ–‰๋˜๋ฉฐ, **์—ฌ๋Ÿฌ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ ํ›„**์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **์ข…๋ฃŒ**๋  ๋•Œ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋กœ์ง(์ฝ”๋“œ)์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ์ด ์ฝ”๋“œ๋Š” **ํ•œ ๋ฒˆ**๋งŒ ์‹คํ–‰๋˜๋ฉฐ, **์—ฌ๋Ÿฌ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ ํ›„**์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. -์ด ์ฝ”๋“œ๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **์š”์ฒญ์„ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์—** ์‹คํ–‰๋˜๊ณ , ์š”์ฒญ ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚œ ํ›„ **์ข…๋ฃŒ ์ง์ „์—** ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ **์ˆ˜๋ช…(Lifespan)**์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. (์ž ์‹œ ํ›„ "์ˆ˜๋ช…"์ด๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ์ค‘์š”ํ•ด์ง‘๋‹ˆ๋‹ค ๐Ÿ˜‰) +์ด ์ฝ”๋“œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์š”์ฒญ์„ ๋ฐ›๊ธฐ **์‹œ์ž‘**ํ•˜๊ธฐ ์ „์— ์‹คํ–‰๋˜๊ณ , ์š”์ฒญ ์ฒ˜๋ฆฌ๋ฅผ **๋๋‚ธ ์งํ›„**์— ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ **์ˆ˜๋ช…(lifespan)**์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค(์ž ์‹œ ํ›„ "lifespan"์ด๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ์ค‘์š”ํ•ด์ง‘๋‹ˆ๋‹ค ๐Ÿ˜‰). -์ด ๋ฐฉ๋ฒ•์€ ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” **์ž์›**์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ์š”์ฒญ ๊ฐ„์— **๊ณต์œ ๋˜๋Š”** ์ž์›์„ ์„ค์ •ํ•˜๊ณ , ๋˜๋Š” ๊ทธ ํ›„์— **์ •๋ฆฌ**ํ•˜๋Š” ๋ฐ ๋งค์šฐ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€ ๋˜๋Š” ๊ณต์œ ๋˜๋Š” ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. +์ด๋Š” ์ „์ฒด ์•ฑ์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” **์ž์›**์„ ์„ค์ •ํ•˜๊ณ , ์š”์ฒญ ๊ฐ„์— **๊ณต์œ ๋˜๋Š”** ์ž์›์„ ์„ค์ •ํ•˜๊ณ , ๊ทธ๋ฆฌ๊ณ /๋˜๋Š” ์ดํ›„์— **์ •๋ฆฌ**ํ•˜๋Š” ๋ฐ ๋งค์šฐ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€ ๋˜๋Š” ๊ณต์œ  ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. +## ์‚ฌ์šฉ ์‚ฌ๋ก€ { #use-case } -## ์‚ฌ์šฉ ์‚ฌ๋ก€ +๋จผ์ € **์‚ฌ์šฉ ์‚ฌ๋ก€** ์˜ˆ์‹œ๋กœ ์‹œ์ž‘ํ•œ ๋‹ค์Œ, ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ• ์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -๋จผ์ € **์‚ฌ์šฉ ์‚ฌ๋ก€**๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด๋ณด๊ณ , ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. +์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ **๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ**์ด ์žˆ๋‹ค๊ณ  ์ƒ์ƒํ•ด ๋ด…์‹œ๋‹ค. ๐Ÿค– -์šฐ๋ฆฌ๊ฐ€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ **๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ**์ด ์žˆ๋‹ค๊ณ  ์ƒ์ƒํ•ด ๋ด…์‹œ๋‹ค. ๐Ÿค– +๋™์ผํ•œ ๋ชจ๋ธ์ด ์š”์ฒญ ๊ฐ„์— ๊ณต์œ ๋˜๋ฏ€๋กœ, ์š”์ฒญ๋งˆ๋‹ค ๋ชจ๋ธ์ด ํ•˜๋‚˜์”ฉ ์žˆ๊ฑฐ๋‚˜ ์‚ฌ์šฉ์ž๋งˆ๋‹ค ํ•˜๋‚˜์”ฉ ์žˆ๋Š” ๋“ฑ์˜ ๋ฐฉ์‹์ด ์•„๋‹™๋‹ˆ๋‹ค. -์ด ๋ชจ๋ธ๋“ค์€ ์š”์ฒญ ๊ฐ„์— ๊ณต์œ ๋˜๋ฏ€๋กœ, ์š”์ฒญ๋งˆ๋‹ค ๋ชจ๋ธ์ด ํ•˜๋‚˜์”ฉ ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์—ฌ๋Ÿฌ ์š”์ฒญ์—์„œ ๋™์ผํ•œ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋Š” ๋ฐ **์ƒ๋‹นํ•œ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค๊ณ  ์ƒ์ƒํ•ด ๋ด…์‹œ๋‹ค**, ์™œ๋ƒํ•˜๋ฉด ๋ชจ๋ธ์ด **๋””์Šคํฌ์—์„œ ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์•ผ** ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ชจ๋“  ์š”์ฒญ๋งˆ๋‹ค ์ด๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ์‹ถ์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. -๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋Š” ๋ฐ **์ƒ๋‹นํ•œ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค๊ณ  ์ƒ์ƒํ•ด ๋ด…์‹œ๋‹ค**, ์™œ๋ƒํ•˜๋ฉด ๋ชจ๋ธ์ด **๋””์Šคํฌ์—์„œ ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์•ผ** ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ๋ชจ๋ธ์„ ๋งค๋ฒˆ ๋กœ๋“œํ•˜๊ณ  ์‹ถ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +๋ชจ๋“ˆ/ํŒŒ์ผ์˜ ์ตœ์ƒ์œ„์—์„œ ๋กœ๋“œํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ทธ๋Ÿฌ๋ฉด ๋‹จ์ˆœํ•œ ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ **๋ชจ๋ธ์„ ๋กœ๋“œ**ํ•˜๊ฒŒ ๋˜๊ณ , ํ…Œ์ŠคํŠธ๊ฐ€ ์ฝ”๋“œ์˜ ๋…๋ฆฝ์ ์ธ ๋ถ€๋ถ„์„ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ๋ชจ๋ธ์ด ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•˜๋ฏ€๋กœ **๋А๋ ค์ง‘๋‹ˆ๋‹ค**. -๋ชจ๋“ˆ/ํŒŒ์ผ์˜ ์ตœ์ƒ์œ„์—์„œ ๋ชจ๋ธ์„ ๋กœ๋“œํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ทธ๋Ÿฌ๋ฉด **๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋Š”๋ฐ** ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์—, ๋‹จ์ˆœํ•œ ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋„ ๋ชจ๋ธ์ด ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•ด์„œ **ํ…Œ์ŠคํŠธ ์†๋„๊ฐ€ ๋А๋ ค์ง‘๋‹ˆ๋‹ค**. +์ด๊ฒƒ์ด ์šฐ๋ฆฌ๊ฐ€ ํ•ด๊ฒฐํ•  ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์ „์— ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋˜, ์ฝ”๋“œ๊ฐ€ ๋กœ๋“œ๋˜๋Š” ๋™์•ˆ์ด ์•„๋‹ˆ๋ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์š”์ฒญ์„ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ง์ „์—๋งŒ ๋กœ๋“œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. -์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๊ณ  ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์ „์— ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋˜, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์š”์ฒญ์„ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ง์ „์—๋งŒ ๋กœ๋“œํ•˜๊ณ , ์ฝ”๋“œ๊ฐ€ ๋กœ๋“œ๋˜๋Š” ๋™์•ˆ์€ ๋กœ๋“œํ•˜์ง€ ์•Š๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. +## Lifespan { #lifespan } -## Lifespan +`FastAPI` ์•ฑ์˜ `lifespan` ๋งค๊ฐœ๋ณ€์ˆ˜์™€ "์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €"๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ *์‹œ์ž‘*๊ณผ *์ข…๋ฃŒ* ๋กœ์ง์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ž ์‹œ ํ›„์— ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค). -`FastAPI` ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ `lifespan` ๋งค๊ฐœ๋ณ€์ˆ˜์™€ "์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €"๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ *์‹œ์ž‘*๊ณผ *์ข…๋ฃŒ* ๋กœ์ง์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ž ์‹œ ํ›„์— ์„ค๋ช…๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.) +์˜ˆ์ œ๋กœ ์‹œ์ž‘ํ•œ ๋‹ค์Œ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์‹œ์ž‘ํ•˜๊ณ , ๊ทธ ํ›„์— ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. +`yield`๋ฅผ ์‚ฌ์šฉํ•ด ๋น„๋™๊ธฐ ํ•จ์ˆ˜ `lifespan()`์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: -์šฐ๋ฆฌ๋Š” `yield`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ํ•จ์ˆ˜ `lifespan()`์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: +{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} -{* ../../docs_src/events/tutorial003.py hl[16,19] *} +์—ฌ๊ธฐ์„œ๋Š” `yield` ์ด์ „์— (๊ฐ€์งœ) ๋ชจ๋ธ ํ•จ์ˆ˜๋ฅผ ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์ด ๋“ค์–ด ์žˆ๋Š” ๋”•์…”๋„ˆ๋ฆฌ์— ๋„ฃ์–ด ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋Š” ๋น„์šฉ์ด ํฐ *์‹œ์ž‘* ์ž‘์—…์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **์š”์ฒญ์„ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ „**, *์‹œ์ž‘* ๋™์•ˆ์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. -์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๋Š” ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋Š” ๋น„์‹ผ *์‹œ์ž‘* ์ž‘์—…์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. `yield` ์•ž์—์„œ (๊ฐ€์งœ) ๋ชจ๋ธ ํ•จ์ˆ˜๋ฅผ ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์ด ๋‹ด๊ธด ๋”•์…”๋„ˆ๋ฆฌ์— ๋„ฃ์Šต๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋Š” **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์š”์ฒญ์„ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ „**, *์‹œ์ž‘* ๋™์•ˆ์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. - -๊ทธ๋ฆฌ๊ณ  `yield` ์งํ›„์—๋Š” ๋ชจ๋ธ์„ ์–ธ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋Š” **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์š”์ฒญ ์ฒ˜๋ฆฌ ์™„๋ฃŒ ํ›„**, *์ข…๋ฃŒ* ์ง์ „์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฉ”๋ชจ๋ฆฌ๋‚˜ GPU์™€ ๊ฐ™์€ ์ž์›์„ ํ•ด์ œํ•˜๋Š” ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  `yield` ์งํ›„์—๋Š” ๋ชจ๋ธ์„ ์–ธ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **์š”์ฒญ ์ฒ˜๋ฆฌ๋ฅผ ๋งˆ์นœ ํ›„**, *์ข…๋ฃŒ* ์ง์ „์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฉ”๋ชจ๋ฆฌ๋‚˜ GPU ๊ฐ™์€ ์ž์›์„ ํ•ด์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// tip | ํŒ -`shutdown`์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ **์ข…๋ฃŒ**ํ•  ๋•Œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. +`shutdown`์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ **์ค‘์ง€**ํ•  ๋•Œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. -์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ์‹œ์ž‘ํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜, ๊ทธ๋ƒฅ ์‹คํ–‰์„ ๋ฉˆ์ถ”๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿคท +์ƒˆ ๋ฒ„์ „์„ ์‹œ์ž‘ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ๊ทธ๋ƒฅ ์‹คํ–‰ํ•˜๋Š” ๊ฒŒ ์ง€๊ฒจ์›Œ์กŒ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿคท /// -### Lifespan ํ•จ์ˆ˜ +### Lifespan ํ•จ์ˆ˜ { #lifespan-function } -๋จผ์ € ์ฃผ๋ชฉํ•  ์ ์€, `yield`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ํ•จ์ˆ˜(async function)๋ฅผ ์ •์˜ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” `yield`๋ฅผ ์‚ฌ์šฉํ•œ ์˜์กด์„ฑ๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. +๋จผ์ € ์ฃผ๋ชฉํ•  ์ ์€ `yield`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/events/tutorial003.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} ํ•จ์ˆ˜์˜ ์ฒซ ๋ฒˆ์งธ ๋ถ€๋ถ„, ์ฆ‰ `yield` ์ด์ „์˜ ์ฝ”๋“œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋˜๊ธฐ **์ „์—** ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  `yield` ์ดํ›„์˜ ๋ถ€๋ถ„์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์™„๋ฃŒ๋œ ํ›„ **๋‚˜์ค‘์—** ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  `yield` ์ดํ›„์˜ ๋ถ€๋ถ„์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ข…๋ฃŒ๋œ **ํ›„์—** ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. -### ๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ € +### ๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ € { #async-context-manager } -ํ•จ์ˆ˜๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด, `@asynccontextmanager`๋กœ ์žฅ์‹๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +ํ™•์ธํ•ด ๋ณด๋ฉด, ํ•จ์ˆ˜๋Š” `@asynccontextmanager`๋กœ ๋ฐ์ฝ”๋ ˆ์ด์…˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๊ฒƒ์€ ํ•จ์ˆ˜๋ฅผ "**๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €**"๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ๊ฒƒ์œผ๋กœ ๋ณ€ํ™˜์‹œํ‚ต๋‹ˆ๋‹ค. +์ด๋Š” ํ•จ์ˆ˜๋ฅผ "**๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €**"๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ๊ฒƒ์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/events/tutorial003.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} ํŒŒ์ด์ฌ์—์„œ **์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €**๋Š” `with` ๋ฌธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, `open()`์€ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -69,97 +68,98 @@ with open("file.txt") as file: file.read() ``` -์ตœ๊ทผ ๋ฒ„์ „์˜ ํŒŒ์ด์ฌ์—์„œ๋Š” **๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €**๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ `async with`์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: + +์ตœ๊ทผ ๋ฒ„์ „์˜ ํŒŒ์ด์ฌ์—๋Š” **๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €**๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ `async with`์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: ```Python async with lifespan(app): await do_stuff() ``` -์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋‚˜ ์œ„์™€ ๊ฐ™์€ ๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ๋งŒ๋“ค๋ฉด, `with` ๋ธ”๋ก์— ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— `yield` ์ด์ „์˜ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ณ , `with` ๋ธ”๋ก์„ ๋ฒ—์–ด๋‚œ ํ›„์—๋Š” `yield` ์ดํ›„์˜ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +์œ„์™€ ๊ฐ™์€ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ € ๋˜๋Š” ๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ๋งŒ๋“ค๋ฉด, `with` ๋ธ”๋ก์— ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— `yield` ์ด์ „์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , `with` ๋ธ”๋ก์„ ๋ฒ—์–ด๋‚œ ํ›„์—๋Š” `yield` ์ดํ›„์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. -์œ„์˜ ์ฝ”๋“œ ์˜ˆ์ œ์—์„œ๋Š” ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , FastAPI์— ์ „๋‹ฌํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. +์œ„์˜ ์ฝ”๋“œ ์˜ˆ์ œ์—์„œ๋Š” ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , FastAPI์— ์ „๋‹ฌํ•˜์—ฌ FastAPI๊ฐ€ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. -`FastAPI` ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ `lifespan` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” **๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €**๋ฅผ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์—, ์ƒˆ๋กœ์šด `lifespan` ๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ FastAPI์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`FastAPI` ์•ฑ์˜ `lifespan` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” **๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €**๋ฅผ ๋ฐ›์œผ๋ฏ€๋กœ, ์ƒˆ `lifespan` ๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -{* ../../docs_src/events/tutorial003.py hl[22] *} +{* ../../docs_src/events/tutorial003_py39.py hl[22] *} -## ๋Œ€์ฒด ์ด๋ฒคํŠธ (์‚ฌ์šฉ ์ค‘๋‹จ) +## ๋Œ€์ฒด ์ด๋ฒคํŠธ(์‚ฌ์šฉ ์ค‘๋‹จ) { #alternative-events-deprecated } /// warning | ๊ฒฝ๊ณ  -*์‹œ์ž‘*๊ณผ *์ข…๋ฃŒ*๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ถŒ์žฅ ๋ฐฉ๋ฒ•์€ ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ `FastAPI` ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ `lifespan` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. `lifespan` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๋ฉด `startup`๊ณผ `shutdown` ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ๋” ์ด์ƒ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. `lifespan`์„ ์‚ฌ์šฉํ• ์ง€, ๋ชจ๋“  ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ• ์ง€ ์„ ํƒํ•ด์•ผ ํ•˜๋ฉฐ ๋‘˜ ๋‹ค ์‚ฌ์šฉํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. +*์‹œ์ž‘*๊ณผ *์ข…๋ฃŒ*๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ถŒ์žฅ ๋ฐฉ๋ฒ•์€ ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ `FastAPI` ์•ฑ์˜ `lifespan` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. `lifespan` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๋ฉด `startup`๊ณผ `shutdown` ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ๋” ์ด์ƒ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. `lifespan`๋งŒ ์“ฐ๊ฑฐ๋‚˜ ์ด๋ฒคํŠธ๋งŒ ์“ฐ๊ฑฐ๋‚˜ ๋‘˜ ์ค‘ ํ•˜๋‚˜์ด์ง€, ๋‘˜ ๋‹ค๋Š” ์•„๋‹™๋‹ˆ๋‹ค. -์ด ๋ถ€๋ถ„์€ ๊ฑด๋„ˆ๋›ฐ์…”๋„ ์ข‹์Šต๋‹ˆ๋‹ค. +์ด ๋ถ€๋ถ„์€ ์•„๋งˆ ๊ฑด๋„ˆ๋›ฐ์…”๋„ ๋ฉ๋‹ˆ๋‹ค. /// *์‹œ์ž‘*๊ณผ *์ข…๋ฃŒ* ๋™์•ˆ ์‹คํ–‰๋  ์ด ๋กœ์ง์„ ์ •์˜ํ•˜๋Š” ๋Œ€์ฒด ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. -์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋˜๊ธฐ ์ „์— ๋˜๋Š” ์ข…๋ฃŒ๋  ๋•Œ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ(ํ•จ์ˆ˜)๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋˜๊ธฐ ์ „์— ๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ข…๋ฃŒ๋  ๋•Œ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ(ํ•จ์ˆ˜)๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋“ค์€ `async def` ๋˜๋Š” ์ผ๋ฐ˜ `def`๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### `startup` ์ด๋ฒคํŠธ +### `startup` ์ด๋ฒคํŠธ { #startup-event } ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋˜๊ธฐ ์ „์— ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด, `"startup"` ์ด๋ฒคํŠธ๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/events/tutorial001.py hl[8] *} +{* ../../docs_src/events/tutorial001_py39.py hl[8] *} -์ด ๊ฒฝ์šฐ, `startup` ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋Š” "database"๋ผ๋Š” ํ•ญ๋ชฉ(๋‹จ์ง€ `dict`)์„ ์ผ๋ถ€ ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ, `startup` ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋Š” "database"(๊ทธ๋ƒฅ `dict`) ํ•ญ๋ชฉ์„ ์ผ๋ถ€ ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋ชจ๋“  `startup` ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์š”์ฒญ์„ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  `startup` ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์š”์ฒญ์„ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -### `shutdown` ์ด๋ฒคํŠธ +### `shutdown` ์ด๋ฒคํŠธ { #shutdown-event } ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ข…๋ฃŒ๋  ๋•Œ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด, `"shutdown"` ์ด๋ฒคํŠธ๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/events/tutorial002.py hl[6] *} +{* ../../docs_src/events/tutorial002_py39.py hl[6] *} -์—ฌ๊ธฐ์„œ, `shutdown` ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋Š” `"Application shutdown"`์ด๋ผ๋Š” ํ…์ŠคํŠธ๋ฅผ `log.txt` ํŒŒ์ผ์— ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์„œ `shutdown` ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋Š” ํ…์ŠคํŠธ ํ•œ ์ค„ `"Application shutdown"`์„ `log.txt` ํŒŒ์ผ์— ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. /// info | ์ •๋ณด -`open()` ํ•จ์ˆ˜์—์„œ `mode="a"`๋Š” "์ถ”๊ฐ€"๋ฅผ ์˜๋ฏธํ•˜๋ฏ€๋กœ, ํŒŒ์ผ์— ์žˆ๋Š” ๊ธฐ์กด ๋‚ด์šฉ์€ ๋ฎ์–ด์“ฐ์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ์ค„์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. +`open()` ํ•จ์ˆ˜์—์„œ `mode="a"`๋Š” "append"(์ถ”๊ฐ€)๋ฅผ ์˜๋ฏธํ•˜๋ฏ€๋กœ, ๊ธฐ์กด ๋‚ด์šฉ์„ ๋ฎ์–ด์“ฐ์ง€ ์•Š๊ณ  ํŒŒ์ผ์— ์žˆ๋˜ ๋‚ด์šฉ ๋’ค์— ์ค„์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. /// /// tip | ํŒ -์ด ๊ฒฝ์šฐ, ์šฐ๋ฆฌ๋Š” ํ‘œ์ค€ ํŒŒ์ด์ฌ `open()` ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ์—๋Š” ํŒŒ์ผ๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ํ‘œ์ค€ ํŒŒ์ด์ฌ `open()` ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ I/O(์ž…์ถœ๋ ฅ) ์ž‘์—…์ด ํฌํ•จ๋˜์–ด ์žˆ์–ด ๋””์Šคํฌ์— ๊ธฐ๋ก๋˜๋Š” ๊ฒƒ์„ "๊ธฐ๋‹ค๋ฆฌ๋Š”" ๊ณผ์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ I/O(input/output)๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์–ด ๋””์Šคํฌ์— ๊ธฐ๋ก๋˜๋Š” ๊ฒƒ์„ "๊ธฐ๋‹ค๋ฆฌ๋Š”" ๊ณผ์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ `open()`์€ `async`์™€ `await`๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋ฅผ `async def` ๋Œ€์‹  ์ผ๋ฐ˜ `def`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋ž˜์„œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋Š” `async def` ๋Œ€์‹  ํ‘œ์ค€ `def`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. /// -### `startup`๊ณผ `shutdown`์„ ํ•จ๊ป˜ ์‚ฌ์šฉ +### `startup`๊ณผ `shutdown`์„ ํ•จ๊ป˜ { #startup-and-shutdown-together } -*์‹œ์ž‘*๊ณผ *์ข…๋ฃŒ* ๋กœ์ง์ด ์—ฐ๊ฒฐ๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฌด์–ธ๊ฐ€๋ฅผ ์‹œ์ž‘ํ•œ ํ›„ ๋๋‚ด๊ฑฐ๋‚˜, ์ž์›์„ ํš๋“ํ•œ ํ›„ ํ•ด์ œํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +*์‹œ์ž‘*๊ณผ *์ข…๋ฃŒ* ๋กœ์ง์€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ๋ฌด์–ธ๊ฐ€๋ฅผ ์‹œ์ž‘ํ–ˆ๋‹ค๊ฐ€ ๋๋‚ด๊ฑฐ๋‚˜, ์ž์›์„ ํš๋“ํ–ˆ๋‹ค๊ฐ€ ํ•ด์ œํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๋Ÿฌํ•œ ์ž‘์—…์„ ๋ณ„๋„์˜ ํ•จ์ˆ˜๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์„œ๋กœ ๋กœ์ง์ด๋‚˜ ๋ณ€์ˆ˜๋ฅผ ๊ณต์œ ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋” ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค. ๊ฐ’๋“ค์„ ์ „์—ญ ๋ณ€์ˆ˜์— ์ €์žฅํ•˜๊ฑฐ๋‚˜ ๋น„์Šทํ•œ ํŠธ๋ฆญ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋กœ์ง์ด๋‚˜ ๋ณ€์ˆ˜๋ฅผ ํ•จ๊ป˜ ๊ณต์œ ํ•˜์ง€ ์•Š๋Š” ๋ถ„๋ฆฌ๋œ ํ•จ์ˆ˜์—์„œ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉด, ์ „์—ญ ๋ณ€์ˆ˜์— ๊ฐ’์„ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ๋น„์Šทํ•œ ํŠธ๋ฆญ์ด ํ•„์š”ํ•ด์ ธ ๋” ์–ด๋ ต์Šต๋‹ˆ๋‹ค. -๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ `lifespan`์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. +๊ทธ ๋•Œ๋ฌธ์—, ์ด์ œ๋Š” ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ `lifespan`์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. -## ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +## ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ { #technical-details } ํ˜ธ๊ธฐ์‹ฌ ๋งŽ์€ ๋ถ„๋“ค์„ ์œ„ํ•œ ๊ธฐ์ˆ ์ ์ธ ์„ธ๋ถ€์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. ๐Ÿค“ -ASGI ๊ธฐ์ˆ  ์‚ฌ์–‘์— ๋”ฐ๋ฅด๋ฉด, ์ด๋Š” <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan Protocol</a>์˜ ์ผ๋ถ€์ด๋ฉฐ, `startup`๊ณผ `shutdown`์ด๋ผ๋Š” ์ด๋ฒคํŠธ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. +๋‚ด๋ถ€์ ์œผ๋กœ ASGI ๊ธฐ์ˆ  ์‚ฌ์–‘์—์„œ๋Š” ์ด๊ฒƒ์ด <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan Protocol</a>์˜ ์ผ๋ถ€์ด๋ฉฐ, `startup`๊ณผ `shutdown`์ด๋ผ๋Š” ์ด๋ฒคํŠธ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. /// info | ์ •๋ณด -Starlette์˜ `lifespan` ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•ด ๋” ์ฝ๊ณ  ์‹ถ๋‹ค๋ฉด <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette์˜ Lifespan ๋ฌธ์„œ</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +Starlette `lifespan` ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•ด์„œ๋Š” <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette์˜ Lifespan ๋ฌธ์„œ</a>์—์„œ ๋” ์ฝ์–ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด ๋ฌธ์„œ์—๋Š” ์ฝ”๋“œ์˜ ๋‹ค๋ฅธ ์˜์—ญ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” lifespan ์ƒํƒœ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ ์ฝ”๋“œ์˜ ๋‹ค๋ฅธ ์˜์—ญ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” lifespan ์ƒํƒœ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. /// -## ์„œ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ +## ์„œ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ { #sub-applications } -๐Ÿšจ ์ด lifespan ์ด๋ฒคํŠธ(`startup`๊ณผ `shutdown`)๋Š” ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•ด์„œ๋งŒ ์‹คํ–‰๋˜๋ฉฐ, [์„œ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ - Mounts](sub-applications.md){.internal-link target=_blank}์—๋Š” ์‹คํ–‰๋˜์ง€ ์•Š์Œ์„ ์œ ์˜ํ•˜์„ธ์š”. +๐Ÿšจ ์ด lifespan ์ด๋ฒคํŠธ(startup ๋ฐ shutdown)๋Š” ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•ด์„œ๋งŒ ์‹คํ–‰๋˜๋ฉฐ, [์„œ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ - Mounts](sub-applications.md){.internal-link target=_blank}์—๋Š” ์‹คํ–‰๋˜์ง€ ์•Š์Œ์„ ์œ ์˜ํ•˜์„ธ์š”. diff --git a/docs/ko/docs/advanced/index.md b/docs/ko/docs/advanced/index.md index 31704727ca..78ef5ffece 100644 --- a/docs/ko/docs/advanced/index.md +++ b/docs/ko/docs/advanced/index.md @@ -1,6 +1,6 @@ -# ์‹ฌํ™” ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ - ๋„์ž…๋ถ€ +# ์‹ฌํ™” ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ - ๋„์ž…๋ถ€ { #advanced-user-guide } -## ์ถ”๊ฐ€ ๊ธฐ๋Šฅ +## ์ถ”๊ฐ€ ๊ธฐ๋Šฅ { #additional-features } ๋ฉ”์ธ [์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ](../tutorial/index.md){.internal-link target=_blank}๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด **FastAPI**์˜ ๋ชจ๋“  ์ฃผ์š” ๊ธฐ๋Šฅ์„ ๋‘˜๋Ÿฌ๋ณด์‹œ๊ธฐ์— ์ถฉ๋ถ„ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. @@ -14,14 +14,8 @@ /// -## ์ž์Šต์„œ๋ฅผ ๋จผ์ € ์ฝ์œผ์‹ญ์‹œ์˜ค +## ์ž์Šต์„œ๋ฅผ ๋จผ์ € ์ฝ์œผ์‹ญ์‹œ์˜ค { #read-the-tutorial-first } ์—ฌ๋Ÿฌ๋ถ„์€ ๋ฉ”์ธ [์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ](../tutorial/index.md){.internal-link target=_blank}์˜ ์ง€์‹์œผ๋กœ **FastAPI**์˜ ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์–ด์ง€๋Š” ์žฅ๋“ค์€ ์—ฌ๋Ÿฌ๋ถ„์ด ๋ฉ”์ธ ์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ๋ฅผ ์ด๋ฏธ ์ฝ์œผ์…จ์œผ๋ฉฐ ์ฃผ์š” ์•„์ด๋””์–ด๋ฅผ ์•Œ๊ณ  ๊ณ„์‹ ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. - -## TestDriven.io ๊ฐ•์ขŒ - -์—ฌ๋Ÿฌ๋ถ„์ด ๋ฌธ์„œ์˜ ์ด ๋ถ€๋ถ„์„ ๋ณด์™„ํ•˜์‹œ๊ธฐ ์œ„ํ•ด ์‹ฌํ™”-๊ธฐ์ดˆ ๊ฐ•์ขŒ ์ˆ˜๊ฐ•์„ ํฌ๋งํ•˜์‹ ๋‹ค๋ฉด ๋‹ค์Œ์„ ์ฐธ๊ณ  ํ•˜์‹œ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค: **TestDriven.io**์˜ <a href="https://testdriven.io/courses/tdd-fastapi/" class="external-link" target="_blank">FastAPI์™€ Docker๋ฅผ ์‚ฌ์šฉํ•œ ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ</a>. - -๊ทธ๋“ค์€ ํ˜„์žฌ ์ „์ฒด ์ˆ˜์ต์˜ 10ํผ์„ผํŠธ๋ฅผ **FastAPI** ๊ฐœ๋ฐœ์— ๊ธฐ๋ถ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ ๐Ÿ˜„ diff --git a/docs/ko/docs/advanced/response-change-status-code.md b/docs/ko/docs/advanced/response-change-status-code.md index 1ba9aa3ccb..4dfadde9df 100644 --- a/docs/ko/docs/advanced/response-change-status-code.md +++ b/docs/ko/docs/advanced/response-change-status-code.md @@ -1,31 +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_model`์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•˜๊ณ  ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” `Response` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Response` ํŒŒ๋ผ๋ฏธํ„ฐ ์‚ฌ์šฉํ•˜๊ธฐ +## `Response` ํŒŒ๋ผ๋ฏธํ„ฐ ์‚ฌ์šฉํ•˜๊ธฐ { #use-a-response-parameter } -*๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์— `Response` ํƒ€์ž…์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ฟ ํ‚ค์™€ ํ—ค๋”์— ๋Œ€ํ•ด ์„ ์–ธํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ) +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์— `Response` ํƒ€์ž…์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ฟ ํ‚ค์™€ ํ—ค๋”์— ๋Œ€ํ•ด ์„ ์–ธํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ) ๊ทธ๋ฆฌ๊ณ  ์ด *์ž„์‹œ* ์‘๋‹ต ๊ฐ์ฒด์—์„œ `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`, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ ๋“ฑ)๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ํ‰์†Œ์ฒ˜๋Ÿผ ํ•„์š”ํ•œ ์–ด๋–ค ๊ฐ์ฒด๋“  ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(`dict`, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ ๋“ฑ). `response_model`์„ ์„ ์–ธํ–ˆ๋‹ค๋ฉด ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด๋Š” ์—ฌ์ „ํžˆ ํ•„ํ„ฐ๋ง๋˜๊ณ  ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค. -**FastAPI**๋Š” ์ด *์ž„์‹œ* ์‘๋‹ต ๊ฐ์ฒด์—์„œ ์ƒํƒœ ์ฝ”๋“œ(์ฟ ํ‚ค์™€ ํ—ค๋” ํฌํ•จ)๋ฅผ ์ถ”์ถœํ•˜์—ฌ, `response_model`๋กœ ํ•„ํ„ฐ๋ง๋œ ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ตœ์ข… ์‘๋‹ต์— ๋„ฃ์Šต๋‹ˆ๋‹ค. +**FastAPI**๋Š” ์ด *์ž„์‹œ* ์‘๋‹ต ๊ฐ์ฒด์—์„œ ์ƒํƒœ ์ฝ”๋“œ(์ฟ ํ‚ค์™€ ํ—ค๋” ํฌํ•จ)๋ฅผ ์ถ”์ถœํ•˜์—ฌ, `response_model`๋กœ ํ•„ํ„ฐ๋ง๋œ ๋ฐ˜ํ™˜ ๊ฐ’์„ ํฌํ•จํ•˜๋Š” ์ตœ์ข… ์‘๋‹ต์— ๋„ฃ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์˜์กด์„ฑ์—์„œ๋„ `Response` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ ์–ธํ•˜๊ณ  ๊ทธ ์•ˆ์—์„œ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹จ, ๋งˆ์ง€๋ง‰์œผ๋กœ ์„ค์ •๋œ ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ ์šฐ์„  ์ ์šฉ๋œ๋‹ค๋Š” ์ ์„ ์œ ์˜ํ•˜์„ธ์š”. diff --git a/docs/ko/docs/advanced/response-cookies.md b/docs/ko/docs/advanced/response-cookies.md index 50da713fe6..eef74276fd 100644 --- a/docs/ko/docs/advanced/response-cookies.md +++ b/docs/ko/docs/advanced/response-cookies.md @@ -1,49 +1,51 @@ -# ์‘๋‹ต ์ฟ ํ‚ค +# ์‘๋‹ต ์ฟ ํ‚ค { #response-cookies } -## `Response` ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ +## `Response` ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ { #use-a-response-parameter } -*๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์—์„œ `Response` ํƒ€์ž…์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ `Response` ํƒ€์ž…์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•ด๋‹น *์ž„์‹œ* ์‘๋‹ต ๊ฐ์ฒด์—์„œ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} -๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•„์š”ํ•œ ๊ฐ์ฒด(`dict`, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ ๋“ฑ)๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ ์ผ๋ฐ˜์ ์œผ๋กœ ํ•˜๋“ฏ์ด ํ•„์š”ํ•œ ์–ด๋–ค ๊ฐ์ฒด๋“  ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(`dict`, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ ๋“ฑ). ๊ทธ๋ฆฌ๊ณ  `response_model`์„ ์„ ์–ธํ–ˆ๋‹ค๋ฉด ๋ฐ˜ํ™˜ํ•œ ๊ฐ์ฒด๋ฅผ ๊ฑฐ๋ฅด๊ณ  ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐ ์—ฌ์ „ํžˆ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -**FastAPI**๋Š” ๊ทธ *์ž„์‹œ* ์‘๋‹ต์—์„œ ์ฟ ํ‚ค(๋˜ํ•œ ํ—ค๋” ๋ฐ ์ƒํƒœ ์ฝ”๋“œ)๋ฅผ ์ถ”์ถœํ•˜๊ณ , ๋ฐ˜ํ™˜๋œ ๊ฐ’์ด ํฌํ•จ๋œ ์ตœ์ข… ์‘๋‹ต์— ์ด๋ฅผ ๋„ฃ์Šต๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ `response_model`๋กœ ๊ฑธ๋Ÿฌ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๊ทธ *์ž„์‹œ* ์‘๋‹ต์—์„œ ์ฟ ํ‚ค(๋˜ํ•œ ํ—ค๋” ๋ฐ ์ƒํƒœ ์ฝ”๋“œ)๋ฅผ ์ถ”์ถœํ•˜๊ณ , `response_model`๋กœ ํ•„ํ„ฐ๋ง๋œ ๋ฐ˜ํ™˜ ๊ฐ’์ด ํฌํ•จ๋œ ์ตœ์ข… ์‘๋‹ต์— ์ด๋ฅผ ๋„ฃ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์˜์กด๊ด€๊ณ„์—์„œ `Response` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๊ณ , ํ•ด๋‹น ์˜์กด์„ฑ์—์„œ ์ฟ ํ‚ค(๋ฐ ํ—ค๋”)๋ฅผ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Response`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ +## `Response`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #return-a-response-directly } ์ฝ”๋“œ์—์„œ `Response`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ๋•Œ๋„ ์ฟ ํ‚ค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด [Response๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ](response-directly.md){.internal-link target=_blank}์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ ์‘๋‹ต์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} -/// tip + +{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} + +/// tip | ํŒ `Response` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ, FastAPI๋Š” ์ด๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. ๋”ฐ๋ผ์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ์œ ํ˜•์ธ์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: `JSONResponse`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ, JSON๊ณผ ํ˜ธํ™˜๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. -๋˜ํ•œ `response_model`๋กœ ๊ฑธ๋Ÿฌ์ ธ์•ผ ํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š๋„๋ก ํ™•์ธํ•˜์„ธ์š”. +๋˜ํ•œ `response_model`๋กœ ํ•„ํ„ฐ๋ง๋˜์–ด์•ผ ํ–ˆ๋˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜์ง€ ์•Š๋„๋ก ํ•˜์„ธ์š”. /// -### ์ถ”๊ฐ€ ์ •๋ณด +### ์ถ”๊ฐ€ ์ •๋ณด { #more-info } -/// note | ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `from starlette.responses import Response` ๋˜๋Š” `from starlette.responses import JSONResponse`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `fastapi.responses`๋กœ ๋™์ผํ•œ `starlette.responses`๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋Œ€๋ถ€๋ถ„์˜ ์‘๋‹ต์€ Starlette์—์„œ ์ง์ ‘ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `fastapi.responses`๋กœ ๋™์ผํ•œ `starlette.responses`๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋Œ€๋ถ€๋ถ„์˜ ์‘๋‹ต์€ Starlette์—์„œ ์ง์ ‘ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ `Response`๋Š” ํ—ค๋”์™€ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ, **FastAPI**๋Š” ์ด๋ฅผ `fastapi.Response`๋กœ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. /// -์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์˜ต์…˜์€ <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">Starlette ๋ฌธ์„œ</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์˜ต์…˜์€ <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">Starlette์˜ ๋ฌธ์„œ</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/advanced/response-directly.md b/docs/ko/docs/advanced/response-directly.md index 08d63c43ce..abf06bb18b 100644 --- a/docs/ko/docs/advanced/response-directly.md +++ b/docs/ko/docs/advanced/response-directly.md @@ -1,20 +1,20 @@ -# ์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ +# ์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #return-a-response-directly } -**FastAPI**์—์„œ *๊ฒฝ๋กœ ์ž‘์—…(path operation)*์„ ์ƒ์„ฑํ•  ๋•Œ, ์ผ๋ฐ˜์ ์œผ๋กœ `dict`, `list`, Pydantic ๋ชจ๋ธ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ ๋“ฑ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**FastAPI**์—์„œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ(path operation)*๋ฅผ ์ƒ์„ฑํ•  ๋•Œ, ์ผ๋ฐ˜์ ์œผ๋กœ `dict`, `list`, Pydantic ๋ชจ๋ธ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ ๋“ฑ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ธฐ๋ณธ์ ์œผ๋กœ **FastAPI**๋Š” [JSON ํ˜ธํ™˜ ๊ฐ€๋Šฅ ์ธ์ฝ”๋”](../tutorial/encoder.md){.internal-link target=_blank}์— ์„ค๋ช…๋œ `jsonable_encoder`๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๋‹น ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ž๋™์œผ๋กœ `JSON`์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +๊ธฐ๋ณธ์ ์œผ๋กœ **FastAPI**๋Š” [JSON ํ˜ธํ™˜ ๊ฐ€๋Šฅ ์ธ์ฝ”๋”](../tutorial/encoder.md){.internal-link target=_blank}์— ์„ค๋ช…๋œ `jsonable_encoder`๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๋‹น ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ž๋™์œผ๋กœ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -๊ทธ๋Ÿฐ ๋‹ค์Œ, JSON ํ˜ธํ™˜ ๋ฐ์ดํ„ฐ(์˜ˆ: `dict`)๋ฅผ `JSONResponse`์— ๋„ฃ์–ด ์‚ฌ์šฉ์ž์˜ ์‘๋‹ต์„ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ, ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” JSON ํ˜ธํ™˜ ๋ฐ์ดํ„ฐ(์˜ˆ: `dict`)๋ฅผ `JSONResponse`์— ๋„ฃ์–ด ํด๋ผ์ด์–ธํŠธ๋กœ ์‘๋‹ต์„ ์ „์†กํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ *๊ฒฝ๋กœ ์ž‘์—…*์—์„œ `JSONResponse`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ `JSONResponse`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž ์ •์˜ ํ—ค๋”๋‚˜ ์ฟ ํ‚ค๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Response` ๋ฐ˜ํ™˜ํ•˜๊ธฐ +## `Response` ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #return-a-response } ์‚ฌ์‹ค, `Response` ๋˜๋Š” ๊ทธ ํ•˜์œ„ ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -/// tip +/// tip | ํŒ `JSONResponse` ์ž์ฒด๋„ `Response`์˜ ํ•˜์œ„ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. @@ -26,38 +26,40 @@ Pydantic ๋ชจ๋ธ๋กœ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋‚ด์šฉ์„ ๋‹ค๋ฅธ ์ด๋กœ ์ธํ•ด ๋งŽ์€ ์œ ์—ฐ์„ฑ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ๋ฐ์ดํ„ฐ ์œ ํ˜•์ด๋“  ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๊ณ , ๋ฐ์ดํ„ฐ ์„ ์–ธ์ด๋‚˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Response`์—์„œ `jsonable_encoder` ์‚ฌ์šฉํ•˜๊ธฐ +## `Response`์—์„œ `jsonable_encoder` ์‚ฌ์šฉํ•˜๊ธฐ { #using-the-jsonable-encoder-in-a-response } -**FastAPI**๋Š” ๋ฐ˜ํ™˜ํ•˜๋Š” `Response`์— ์•„๋ฌด๋Ÿฐ ๋ณ€ํ™˜์„ ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ๊ทธ ๋‚ด์šฉ์ด ์ค€๋น„๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๋ฐ˜ํ™˜ํ•˜๋Š” `Response`์— ์•„๋ฌด๋Ÿฐ ๋ณ€๊ฒฝ๋„ ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ๊ทธ ๋‚ด์šฉ์ด ์ค€๋น„๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, Pydantic ๋ชจ๋ธ์„ `dict`๋กœ ๋ณ€ํ™˜ํ•ด `JSONResponse`์— ๋„ฃ์ง€ ์•Š์œผ๋ฉด JSON ํ˜ธํ™˜ ์œ ํ˜•์œผ๋กœ ๋ณ€ํ™˜๋œ ๋ฐ์ดํ„ฐ ์œ ํ˜•(์˜ˆ: `datetime`, `UUID` ๋“ฑ)์ด ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, Pydantic ๋ชจ๋ธ์„ ๋จผ์ € `dict`๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  `datetime`, `UUID` ๋“ฑ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ JSON ํ˜ธํ™˜ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉด Pydantic ๋ชจ๋ธ์„ `JSONResponse`์— ๋„ฃ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ, ๋ฐ์ดํ„ฐ๋ฅผ ์‘๋‹ต์— ์ „๋‹ฌํ•˜๊ธฐ ์ „์— `jsonable_encoder`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} -/// note | ๊ธฐ์ˆ ์  ์„ธ๋ถ€ ์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `from starlette.responses import JSONResponse`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `starlette.responses`๋ฅผ `fastapi.responses`๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋Œ€๋ถ€๋ถ„์˜ ๊ฐ€๋Šฅํ•œ ์‘๋‹ต์€ Starlette์—์„œ ์ง์ ‘ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `starlette.responses`๋ฅผ `fastapi.responses`๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์‘๋‹ต์€ Starlette์—์„œ ์ง์ ‘ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. /// -## ์‚ฌ์šฉ์ž ์ •์˜ `Response` ๋ฐ˜ํ™˜ํ•˜๊ธฐ -์œ„ ์˜ˆ์ œ๋Š” ํ•„์š”ํ•œ ๋ชจ๋“  ๋ถ€๋ถ„์„ ๋ณด์—ฌ์ฃผ์ง€๋งŒ, ์•„์ง ์œ ์šฉํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์‹ค ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋ฉด **FastAPI**๊ฐ€ ์ด๋ฅผ `JSONResponse`์— ๋„ฃ๊ณ  `dict`๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋“ฑ ๋ชจ๋“  ์ž‘์—…์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. +## ์‚ฌ์šฉ์ž ์ •์˜ `Response` ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #returning-a-custom-response } -์ด์ œ, ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. +์œ„ ์˜ˆ์ œ๋Š” ํ•„์š”ํ•œ ๋ชจ๋“  ๋ถ€๋ถ„์„ ๋ณด์—ฌ์ฃผ์ง€๋งŒ, ์•„์ง์€ ๊ทธ๋‹ค์ง€ ์œ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. `item`์„ ๊ทธ๋ƒฅ ์ง์ ‘ ๋ฐ˜ํ™˜ํ–ˆ์–ด๋„ **FastAPI**๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ์ด๋ฅผ `JSONResponse`์— ๋„ฃ๊ณ  `dict`๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์„ ๋ชจ๋‘ ์ˆ˜ํ–‰ํ•ด ์ฃผ์—ˆ์„ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a> ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. +์ด์ œ, ์ด๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a> ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. 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}์—์„œ ์„ค๋ช…๋œ ๋Œ€๋กœ ๋ฌธ์„œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ดํ›„ ๋‹จ๋ฝ์—์„œ ์ž๋™ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜, ๋ฌธ์„œํ™” ๋“ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์‚ฌ์šฉ์ž ์ •์˜ `Response`๋ฅผ ์„ ์–ธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ดํ›„ ์„น์…˜์—์„œ ์ž๋™ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜, ๋ฌธ์„œํ™” ๋“ฑ์„ ๊ณ„์† ์‚ฌ์šฉํ•˜๋ฉด์„œ ์ด๋Ÿฌํ•œ ์‚ฌ์šฉ์ž ์ •์˜ `Response`๋ฅผ ์‚ฌ์šฉํ•˜๋Š”/์„ ์–ธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/advanced/response-headers.md b/docs/ko/docs/advanced/response-headers.md index e4e022c9b7..1c36db9b95 100644 --- a/docs/ko/docs/advanced/response-headers.md +++ b/docs/ko/docs/advanced/response-headers.md @@ -1,12 +1,12 @@ -# ์‘๋‹ต ํ—ค๋” +# ์‘๋‹ต ํ—ค๋” { #response-headers } -## `Response` ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ +## `Response` ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ { #use-a-response-parameter } -์—ฌ๋Ÿฌ๋ถ„์€ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์—์„œ `Response` ํƒ€์ž…์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์ฟ ํ‚ค์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). +์—ฌ๋Ÿฌ๋ถ„์€ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ `Response` ํƒ€์ž…์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์ฟ ํ‚ค์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). ๊ทธ๋Ÿฐ ๋‹ค์Œ, ์—ฌ๋Ÿฌ๋ถ„์€ ํ•ด๋‹น *์ž„์‹œ* ์‘๋‹ต ๊ฐ์ฒด์—์„œ ํ—ค๋”๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *} +{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *} ๊ทธ ํ›„, ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋“ฏ์ด ํ•„์š”ํ•œ ๊ฐ์ฒด(`dict`, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ ๋“ฑ)๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -16,26 +16,26 @@ ๋˜ํ•œ, ์ข…์†์„ฑ์—์„œ `Response` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๊ณ  ๊ทธ ์•ˆ์—์„œ ํ—ค๋”(๋ฐ ์ฟ ํ‚ค)๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Response` ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ +## `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 | ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `from starlette.responses import Response`๋‚˜ `from starlette.responses import JSONResponse`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” `starlette.responses`๋ฅผ `fastapi.responses`๋กœ ๊ฐœ๋ฐœ์ž์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด ์ง์ ‘ ์ œ๊ณตํ•˜์ง€๋งŒ, ๋Œ€๋ถ€๋ถ„์˜ ์‘๋‹ต์€ Starlette์—์„œ ์ง์ ‘ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. +**FastAPI**๋Š” ํ•ด๋‹น *์ž„์‹œ* ์‘๋‹ต์—์„œ ํ—ค๋”(์ฟ ํ‚ค์™€ ์ƒํƒœ ์ฝ”๋“œ๋„ ํฌํ•จ)๋ฅผ ์ถ”์ถœํ•˜์—ฌ, ์—ฌ๋Ÿฌ๋ถ„์ด ๋ฐ˜ํ™˜ํ•œ ๊ฐ’์„ ํฌํ•จํ•˜๋Š” ์ตœ์ข… ์‘๋‹ต์— `response_model`๋กœ ํ•„ํ„ฐ๋ง๋œ ๊ฐ’์„ ๋„ฃ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  `Response`๋Š” ํ—ค๋”์™€ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐ ์ž์ฃผ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, **FastAPI**๋Š” `fastapi.Response`๋กœ๋„ ์ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. /// -## ์ปค์Šคํ…€ ํ—ค๋” +## ์ปค์Šคํ…€ ํ—ค๋” { #custom-headers } -<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">โ€˜X-โ€™ ์ ‘๋‘์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ</a> ์ปค์Šคํ…€ ์‚ฌ์„ค ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">`X-` ์ ‘๋‘์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ</a> ์ปค์Šคํ…€ ์‚ฌ์„ค ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. -ํ•˜์ง€๋งŒ, ์—ฌ๋Ÿฌ๋ถ„์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ์›ํ•˜๋Š” ์ปค์Šคํ…€ ํ—ค๋”๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, CORS ์„ค์ •์— ์ด๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}์—์„œ ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”). `expose_headers` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette์˜ CORS ์„ค๋ช…์„œ</a>์— ๋ฌธ์„œํ™”๋œ ๋Œ€๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ, ์—ฌ๋Ÿฌ๋ถ„์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ์›ํ•˜๋Š” ์ปค์Šคํ…€ ํ—ค๋”๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, CORS ์„ค์ •์— ์ด๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}์—์„œ ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”). <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette์˜ CORS ๋ฌธ์„œ</a>์— ๋ฌธ์„œํ™”๋œ `expose_headers` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. diff --git a/docs/ko/docs/advanced/sub-applications.md b/docs/ko/docs/advanced/sub-applications.md index c5835de15c..e1554ca5d6 100644 --- a/docs/ko/docs/advanced/sub-applications.md +++ b/docs/ko/docs/advanced/sub-applications.md @@ -1,67 +1,67 @@ -# ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ - ๋งˆ์šดํŠธ +# ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ - ๋งˆ์šดํŠธ { #sub-applications-mounts } -๋งŒ์•ฝ ๊ฐ๊ฐ์˜ ๋…๋ฆฝ์ ์ธ OpenAPI์™€ ๋ฌธ์„œ UI๋ฅผ ๊ฐ–๋Š” ๋‘ ๊ฐœ์˜ ๋…๋ฆฝ์ ์ธ FastAPI ์‘์šฉํ”„๋กœ๊ทธ๋žจ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, ๋ฉ”์ธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํ•˜๋‚˜ (๋˜๋Š” ๊ทธ ์ด์ƒ์˜) ํ•˜์œ„-์‘์šฉํ”„๋กœ๊ทธ๋žจ(๋“ค)์„ โ€œ๋งˆ์šดํŠธ"ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ฐ๊ฐ์˜ ๋…๋ฆฝ์ ์ธ OpenAPI์™€ ๋ฌธ์„œ UI๋ฅผ ๊ฐ–๋Š” ๋‘ ๊ฐœ์˜ ๋…๋ฆฝ์ ์ธ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, ๋ฉ”์ธ ์•ฑ์„ ๋‘๊ณ  ํ•˜๋‚˜(๋˜๋Š” ๊ทธ ์ด์ƒ)์˜ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์„ "๋งˆ์šดํŠธ"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## **FastAPI** ์‘์šฉํ”„๋กœ๊ทธ๋žจ ๋งˆ์šดํŠธ +## **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋งˆ์šดํŠธ { #mounting-a-fastapi-application } -โ€œ๋งˆ์šดํŠธ"์ด๋ž€ ์™„์ „ํžˆ โ€œ๋…๋ฆฝ์ ์ธ" ์‘์šฉํ”„๋กœ๊ทธ๋žจ์„ ํŠน์ • ๊ฒฝ๋กœ์— ์ถ”๊ฐ€ํ•˜์—ฌ ํ•ด๋‹น ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์—์„œ ์„ ์–ธ๋œ *๊ฒฝ๋กœ ๋™์ž‘*์„ ํ†ตํ•ด ํ•ด๋‹น ๊ฒฝ๋กœ ์•„๋ž˜์— ์žˆ๋Š” ๋ชจ๋“  ์ž‘์—…๋“ค์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +"๋งˆ์šดํŠธ"๋ž€ ์™„์ „ํžˆ "๋…๋ฆฝ์ ์ธ" ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํŠน์ • ๊ฒฝ๋กœ์— ์ถ”๊ฐ€ํ•˜๊ณ , ๊ทธ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์— ์„ ์–ธ๋œ _๊ฒฝ๋กœ ์ฒ˜๋ฆฌ_๋กœ ํ•ด๋‹น ๊ฒฝ๋กœ ์•„๋ž˜์˜ ๋ชจ๋“  ๊ฒƒ์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -### ์ตœ์ƒ๋‹จ ์‘์šฉํ”„๋กœ๊ทธ๋žจ +### ์ตœ์ƒ์œ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ { #top-level-application } -๋จผ์ €, ๋ฉ”์ธ, ์ตœ์ƒ๋‹จ์˜ **FastAPI** ์‘์šฉํ”„๋กœ๊ทธ๋žจ๊ณผ ์ด๊ฒƒ์˜ *๊ฒฝ๋กœ ๋™์ž‘*์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: +๋จผ์ €, ๋ฉ”์ธ ์ตœ์ƒ์œ„ **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 ์‘์šฉํ”„๋กœ๊ทธ๋žจ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ด๊ฒƒ์€ โ€œ๋งˆ์šดํŠธโ€๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค: +์ด ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์€ ๋˜ ๋‹ค๋ฅธ ํ‘œ์ค€ 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`๋ฅผ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค. +์ตœ์ƒ์œ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ `app`์—์„œ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ `subapi`๋ฅผ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค. -์ด ์˜ˆ์‹œ์—์„œ, ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์…˜์€ `/subapi` ๊ฒฝ๋กœ์— ๋งˆ์šดํŠธ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค: +์ด ๊ฒฝ์šฐ `/subapi` ๊ฒฝ๋กœ์— ๋งˆ์šดํŠธ๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} -### ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ API ๋ฌธ์„œ ํ™•์ธ +### ์ž๋™ API ๋ฌธ์„œ ํ™•์ธ { #check-the-automatic-api-docs } -์ด์ œ, `uvicorn`์œผ๋กœ ๋ฉ”์ธ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•˜์‹ญ์‹œ์˜ค. ๋‹น์‹ ์˜ ํŒŒ์ผ์ด `main.py`๋ผ๋ฉด, ์ด๋ ‡๊ฒŒ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค: +์ด์ œ ํŒŒ์ผ๊ณผ ํ•จ๊ป˜ `fastapi` ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์„ธ์š”: <div class="termy"> ```console -$ uvicorn main:app --reload +$ fastapi dev main.py <span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` </div> -๊ทธ๋ฆฌ๊ณ  <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>์—์„œ ๋ฌธ์„œ๋ฅผ ์—ฌ์‹ญ์‹œ์˜ค. +๊ทธ๋ฆฌ๊ณ  <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>์—์„œ ๋ฌธ์„œ๋ฅผ ์—ฌ์„ธ์š”. -๋ฉ”์ธ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์˜ *๊ฒฝ๋กœ ๋™์ž‘*๋งŒ์„ ํฌํ•จํ•˜๋Š”, ๋ฉ”์ธ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์— ๋Œ€ํ•œ ์ž๋™ API ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋ฉ”์ธ ์•ฑ์˜ ์ž๋™ API ๋ฌธ์„œ๋ฅผ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ด๋ฉฐ, ๋ฉ”์ธ ์•ฑ ์ž์ฒด์˜ _๊ฒฝ๋กœ ์ฒ˜๋ฆฌ_๋งŒ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค: -<img src="https://fastapi.tiangolo.com//img/tutorial/sub-applications/image01.png"> +<img src="/img/tutorial/sub-applications/image01.png"> -๋‹ค์Œ์œผ๋กœ, <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>์—์„œ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์˜ ๋ฌธ์„œ๋ฅผ ์—ฌ์‹ญ์‹œ์˜ค. +๊ทธ ๋‹ค์Œ, <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>์—์„œ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์˜ ๋ฌธ์„œ๋ฅผ ์—ฌ์„ธ์š”. -ํ•˜์œ„ ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ `/subapi` ์•„๋ž˜์— ์„ ์–ธ๋œ *๊ฒฝ๋กœ ๋™์ž‘* ์„ ํฌํ•จํ•˜๋Š”, ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์— ๋Œ€ํ•œ ์ž๋™ API ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์˜ ์ž๋™ API ๋ฌธ์„œ๋ฅผ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ด๋ฉฐ, ํ•˜์œ„ ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ `/subapi` ์•„๋ž˜์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํฌํ•จ๋œ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ ์ž์ฒด์˜ _๊ฒฝ๋กœ ์ฒ˜๋ฆฌ_๋งŒ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค: -<img src="https://fastapi.tiangolo.com//img/tutorial/sub-applications/image02.png"> +<img src="/img/tutorial/sub-applications/image02.png"> -๋‘ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค ์ค‘ ์–ด๋А ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ, ๋ธŒ๋ผ์šฐ์ €๋Š” ํŠน์ • ์‘์šฉํ”„๋กœ๊ทธ๋žจ ๋˜๋Š” ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ๊ณผ ๊ฐ๊ฐ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๋‘ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค ์ค‘ ์–ด๋А ๊ฒƒ๊ณผ ์ƒํ˜ธ์ž‘์šฉ์„ ์‹œ๋„ํ•˜๋”๋ผ๋„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ฐ ํŠน์ • ์•ฑ ๋˜๋Š” ํ•˜์œ„ ์•ฑ๊ณผ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -### ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ: `root_path` +### ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ: `root_path` { #technical-details-root-path } -์œ„์— ์„ค๋ช…๋œ ๊ฒƒ๊ณผ ๊ฐ™์ด ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์„ ๋งˆ์šดํŠธํ•˜๋Š” ๊ฒฝ์šฐ, FastAPI๋Š” `root_path`๋ผ๊ณ  ํ•˜๋Š” ASGI ๋ช…์„ธ์˜ ๋งค์ปค๋‹ˆ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์— ๋Œ€ํ•œ ๋งˆ์šดํŠธ ๊ฒฝ๋กœ ํ†ต์‹ ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. +์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์„ ๋งˆ์šดํŠธํ•˜๋ฉด, FastAPI๋Š” ASGI ๋ช…์„ธ์˜ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ธ `root_path`๋ฅผ ์‚ฌ์šฉํ•ด ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์— ๋Œ€ํ•œ ๋งˆ์šดํŠธ ๊ฒฝ๋กœ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. -์ด๋ฅผ ํ†ตํ•ด, ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์€ ๋ฌธ์„œ UI๋ฅผ ์œ„ํ•ด ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ธ์ง€ํ•ฉ๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์€ ๋ฌธ์„œ UI๋ฅผ ์œ„ํ•ด ํ•ด๋‹น ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์—๋„ ์—ญ์‹œ ๋‹ค๋ฅธ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์„ ๋งˆ์šดํŠธํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ FastAPI๊ฐ€ ๋ชจ๋“  `root_path` ๋“ค์„ ์ž๋™์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ๊ฒƒ์€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๋˜ํ•œ ํ•˜์œ„ ์‘์šฉํ”„๋กœ๊ทธ๋žจ๋„ ์ž์ฒด์ ์œผ๋กœ ํ•˜์œ„ ์•ฑ์„ ๋งˆ์šดํŠธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, FastAPI๊ฐ€ ์ด ๋ชจ๋“  `root_path`๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. -`root_path`์™€ ์ด๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ๋Š” [ํ”„๋ก์‹œ์˜ ๋’ท๋‹จ](./behind-a-proxy.md){.internal-link target=_blank} ์„น์…˜์—์„œ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`root_path`์™€ ์ด๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ๋Š” [ํ”„๋ก์‹œ ๋’ค](behind-a-proxy.md){.internal-link target=_blank} ์„น์…˜์—์„œ ๋” ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/advanced/templates.md b/docs/ko/docs/advanced/templates.md index 6126357132..fffffa6a54 100644 --- a/docs/ko/docs/advanced/templates.md +++ b/docs/ko/docs/advanced/templates.md @@ -1,4 +1,4 @@ -# ํ…œํ”Œ๋ฆฟ +# ํ…œํ”Œ๋ฆฟ { #templates } **FastAPI**์™€ ํ•จ๊ป˜ ์›ํ•˜๋Š” ์–ด๋–ค ํ…œํ”Œ๋ฆฟ ์—”์ง„๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -6,10 +6,9 @@ ์„ค์ •์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ๊ฐ€ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(Starlette ์ œ๊ณต). -## ์˜์กด์„ฑ ์„ค์น˜ - -๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•˜๊ณ (virtual environment{.internal-link target=_blank}), ํ™œ์„ฑํ™”ํ•œ ํ›„ jinja2๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: +## ์˜์กด์„ฑ ์„ค์น˜ { #install-dependencies } +[๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ์ƒ์„ฑํ•˜๊ณ , ํ™œ์„ฑํ™”ํ•œ ํ›„ `jinja2`๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: <div class="termy"> @@ -21,39 +20,38 @@ $ pip install jinja2 </div> -## ์‚ฌ์šฉํ•˜๊ธฐ `Jinja2Templates` +## `Jinja2Templates` ์‚ฌ์šฉํ•˜๊ธฐ { #using-jinja2templates } * `Jinja2Templates`๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. * ๋‚˜์ค‘์— ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” `templates` ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -* ํ…œํ”Œ๋ฆฟ์„ ๋ฐ˜ํ™˜ํ•  ๊ฒฝ๋กœ ์ž‘์—…์— `Request` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. +* ํ…œํ”Œ๋ฆฟ์„ ๋ฐ˜ํ™˜ํ•  *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— `Request` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. * ์ƒ์„ฑํ•œ `templates`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `TemplateResponse`๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ…œํ”Œ๋ฆฟ์˜ ์ด๋ฆ„, ์š”์ฒญ ๊ฐ์ฒด ๋ฐ Jinja2 ํ…œํ”Œ๋ฆฟ ๋‚ด์—์„œ ์‚ฌ์šฉ๋  ํ‚ค-๊ฐ’ ์Œ์ด ํฌํ•จ๋œ "์ปจํ…์ŠคํŠธ" ๋”•์…”๋„ˆ๋ฆฌ๋„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. - -```Python hl_lines="4 11 15-18" -{!../../docs_src/templates/tutorial001.py!} -``` +{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} /// note | ์ฐธ๊ณ  -FastAPI 0.108.0 ์ด์ „๊ณผ Starlette 0.29.0์—์„œ๋Š” `name`์ด ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜์˜€์Šต๋‹ˆ๋‹ค. +FastAPI 0.108.0 ์ด์ „, Starlette 0.29.0์—์„œ๋Š” `name`์ด ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜์˜€์Šต๋‹ˆ๋‹ค. -๋˜ํ•œ ์ด์ „ ๋ฒ„์ „์—์„œ๋Š” `request` ๊ฐ์ฒด๊ฐ€ Jinja2์˜ ์ปจํ…์ŠคํŠธ์—์„œ ํ‚ค-๊ฐ’ ์Œ์˜ ์ผ๋ถ€๋กœ ์ „๋‹ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ ๊ทธ ์ด์ „ ๋ฒ„์ „์—์„œ๋Š” `request` ๊ฐ์ฒด๊ฐ€ Jinja2์˜ ์ปจํ…์ŠคํŠธ์—์„œ ํ‚ค-๊ฐ’ ์Œ์˜ ์ผ๋ถ€๋กœ ์ „๋‹ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. /// /// tip | ํŒ -`response_class=HTMLResponse`๋ฅผ ์„ ์–ธํ•˜๋ฉด ๋ฌธ์„œ UI ์‘๋‹ต์ด HTML์ž„์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`response_class=HTMLResponse`๋ฅผ ์„ ์–ธํ•˜๋ฉด ๋ฌธ์„œ UI๊ฐ€ ์‘๋‹ต์ด HTML์ž„์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€ ์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + `from starlette.templating import Jinja2Templates`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ํŽธ๋ฆฌํ•จ์œผ๋กœ `fastapi.templating` ๋Œ€์‹  `starlette.templating`์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์‘๋‹ต์€ Starlette์—์„œ ์ง์ ‘ ์˜ต๋‹ˆ๋‹ค. `Request` ๋ฐ `StaticFiles`๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ํŽธ๋ฆฌํ•จ์œผ๋กœ `fastapi.templating`๊ณผ ๋™์ผํ•˜๊ฒŒ `starlette.templating`์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์‘๋‹ต์€ Starlette์—์„œ ์ง์ ‘ ์˜ต๋‹ˆ๋‹ค. `Request` ๋ฐ `StaticFiles`๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. + /// -## ํ…œํ”Œ๋ฆฟ ์ž‘์„ฑํ•˜๊ธฐ +## ํ…œํ”Œ๋ฆฟ ์ž‘์„ฑํ•˜๊ธฐ { #writing-templates } ๊ทธ๋Ÿฐ ๋‹ค์Œ `templates/item.html`์— ํ…œํ”Œ๋ฆฟ์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด: @@ -61,7 +59,7 @@ FastAPI 0.108.0 ์ด์ „๊ณผ Starlette 0.29.0์—์„œ๋Š” `name`์ด ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ {!../../docs_src/templates/templates/item.html!} ``` -### ํ…œํ”Œ๋ฆฟ ์ปจํ…์ŠคํŠธ ๊ฐ’ +### ํ…œํ”Œ๋ฆฟ ์ปจํ…์ŠคํŠธ ๊ฐ’ { #template-context-values } ๋‹ค์Œ๊ณผ ๊ฐ™์€ HTML์—์„œ: @@ -85,9 +83,9 @@ Item ID: {{ id }} Item ID: 42 ``` -### ํ…œํ”Œ๋ฆฟ `url_for` ์ธ์ˆ˜ +### ํ…œํ”Œ๋ฆฟ `url_for` ์ธ์ˆ˜ { #template-url-for-arguments } -ํ…œํ”Œ๋ฆฟ ๋‚ด์—์„œ `url_for()`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, ์ด๋Š” *๊ฒฝ๋กœ ์ž‘์—… ํ•จ์ˆ˜*์—์„œ ์‚ฌ์šฉ๋  ์ธ์ˆ˜์™€ ๋™์ผํ•œ ์ธ์ˆ˜๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. +ํ…œํ”Œ๋ฆฟ ๋‚ด์—์„œ `url_for()`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, ์ด๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ์‚ฌ์šฉ๋  ์ธ์ˆ˜์™€ ๋™์ผํ•œ ์ธ์ˆ˜๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ถ€๋ถ„์—์„œ: @@ -99,14 +97,15 @@ Item ID: 42 {% endraw %} -...์ด๋Š” *๊ฒฝ๋กœ ์ž‘์—… ํ•จ์ˆ˜* `read_item(id=id)`๊ฐ€ ์ฒ˜๋ฆฌํ•  ๋™์ผํ•œ URL๋กœ ๋งํฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +...์ด๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* `read_item(id=id)`๊ฐ€ ์ฒ˜๋ฆฌํ•  ๋™์ผํ•œ URL๋กœ ๋งํฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ID๊ฐ€ `42`์ผ ๊ฒฝ์šฐ, ์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค: + ```html <a href="/items/42"> ``` -## ํ…œํ”Œ๋ฆฟ๊ณผ ์ •์  ํŒŒ์ผ +## ํ…œํ”Œ๋ฆฟ๊ณผ ์ •์  ํŒŒ์ผ { #templates-and-static-files } ํ…œํ”Œ๋ฆฟ ๋‚ด์—์„œ `url_for()`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์˜ˆ๋ฅผ ๋“ค์–ด `name="static"`์œผ๋กœ ๋งˆ์šดํŠธํ•œ `StaticFiles`์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -114,7 +113,7 @@ Item ID: 42 {!../../docs_src/templates/templates/item.html!} ``` -์ด ์˜ˆ์ œ์—์„œ๋Š” `static/styles.css`์— ์žˆ๋Š” CSS ํŒŒ์ผ์— ์—ฐ๊ฒฐ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค: +์ด ์˜ˆ์ œ์—์„œ๋Š” ๋‹ค์Œ์„ ํ†ตํ•ด `static/styles.css`์— ์žˆ๋Š” CSS ํŒŒ์ผ์— ๋งํฌํ•ฉ๋‹ˆ๋‹ค: ```CSS hl_lines="4" {!../../docs_src/templates/static/styles.css!} @@ -122,6 +121,6 @@ Item ID: 42 ๊ทธ๋ฆฌ๊ณ  `StaticFiles`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ํ•ด๋‹น CSS ํŒŒ์ผ์€ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ `/static/styles.css` URL๋กœ ์ž๋™ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. -## ๋” ๋งŽ์€ ์„ธ๋ถ€ ์‚ฌํ•ญ +## ๋” ๋งŽ์€ ์„ธ๋ถ€ ์‚ฌํ•ญ { #more-details } ํ…œํ”Œ๋ฆฟ ํ…Œ์ŠคํŠธ๋ฅผ ํฌํ•จํ•œ ๋” ๋งŽ์€ ์„ธ๋ถ€ ์‚ฌํ•ญ์€ <a href="https://www.starlette.dev/templates/" class="external-link" target="_blank">Starlette์˜ ํ…œํ”Œ๋ฆฟ ๋ฌธ์„œ</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. diff --git a/docs/ko/docs/advanced/testing-dependencies.md b/docs/ko/docs/advanced/testing-dependencies.md index 780e19431f..ed90fe472d 100644 --- a/docs/ko/docs/advanced/testing-dependencies.md +++ b/docs/ko/docs/advanced/testing-dependencies.md @@ -1,14 +1,14 @@ -# ํ…Œ์ŠคํŠธ ์˜์กด์„ฑ ์˜ค๋ฒ„๋ผ์ด๋“œ +# ์˜ค๋ฒ„๋ผ์ด๋“œ๋กœ ์˜์กด์„ฑ ํ…Œ์ŠคํŠธํ•˜๊ธฐ { #testing-dependencies-with-overrides } -## ํ…Œ์ŠคํŠธ ์ค‘ ์˜์กด์„ฑ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๊ธฐ +## ํ…Œ์ŠคํŠธ ์ค‘ ์˜์กด์„ฑ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๊ธฐ { #overriding-dependencies-during-testing } -ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค ๋ณด๋ฉด ์˜์กด์„ฑ์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค ๋ณด๋ฉด ํ…Œ์ŠคํŠธ ์ค‘์— ์˜์กด์„ฑ์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์›๋ž˜ ์˜์กด์„ฑ์„ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค(๋˜๋Š” ๊ทธ ์˜์กด์„ฑ์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํ•˜์œ„ ์˜์กด์„ฑ๊นŒ์ง€๋„ ์‹คํ–‰๋˜์ง€ ์•Š๊ธธ ์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). ๋Œ€์‹ , ํ…Œ์ŠคํŠธ ๋™์•ˆ(ํŠน์ • ํ…Œ์ŠคํŠธ์—์„œ๋งŒ) ์‚ฌ์šฉ๋  ๋‹ค๋ฅธ ์˜์กด์„ฑ์„ ์ œ๊ณตํ•˜๊ณ , ์›๋ž˜ ์˜์กด์„ฑ์ด ์‚ฌ์šฉ๋˜๋˜ ๊ณณ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์„ ์ œ๊ณตํ•˜๊ธฐ๋ฅผ ์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### ์‚ฌ์šฉ ์‚ฌ๋ก€: ์™ธ๋ถ€ ์„œ๋น„์Šค +### ์‚ฌ์šฉ ์‚ฌ๋ก€: ์™ธ๋ถ€ ์„œ๋น„์Šค { #use-cases-external-service } ์˜ˆ๋ฅผ ๋“ค์–ด, ์™ธ๋ถ€ ์ธ์ฆ ์ œ๊ณต์ž๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค. @@ -18,11 +18,11 @@ ์™ธ๋ถ€ ์ œ๊ณต์ž๋ฅผ ํ•œ ๋ฒˆ๋งŒ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ์ง€๋งŒ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ๋ฐ˜๋“œ์‹œ ํ˜ธ์ถœํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ ํ•ด๋‹น ๊ณต๊ธ‰์ž๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ข…์†์„ฑ์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๊ณ  ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด์„œ๋งŒ ๋ชจ์˜ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์‚ฌ์šฉ์ž ์ง€์ • ์ข…์†์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ ํ•ด๋‹น ๊ณต๊ธ‰์ž๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์˜์กด์„ฑ์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๊ณ  ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด์„œ๋งŒ ๋ชจ์˜ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์‚ฌ์šฉ์ž ์ง€์ • ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### `app.dependency_overrides` ์†์„ฑ ์‚ฌ์šฉํ•˜๊ธฐ +### `app.dependency_overrides` ์†์„ฑ ์‚ฌ์šฉํ•˜๊ธฐ { #use-the-app-dependency-overrides-attribute } -์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•ด **FastAPI** ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—๋Š” `app.dependency_overrides`๋ผ๋Š” ์†์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ„๋‹จํ•œ `dict`์ž…๋‹ˆ๋‹ค. +์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•ด **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” `app.dependency_overrides`๋ผ๋Š” ์†์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ„๋‹จํ•œ `dict`์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์˜์กด์„ฑ์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๋ ค๋ฉด, ์›๋ž˜ ์˜์กด์„ฑ(ํ•จ์ˆ˜)์„ ํ‚ค๋กœ ์„ค์ •ํ•˜๊ณ  ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์˜์กด์„ฑ(๋‹ค๋ฅธ ํ•จ์ˆ˜)์„ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. @@ -34,7 +34,7 @@ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์–ด๋””์—์„œ๋“  ์‚ฌ์šฉ๋œ ์˜์กด์„ฑ์— ๋Œ€ํ•ด ์˜ค๋ฒ„๋ผ์ด๋“œ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์›๋ž˜ ์˜์กด์„ฑ์€ *๊ฒฝ๋กœ ๋™์ž‘ ํ•จ์ˆ˜*, *๊ฒฝ๋กœ ๋™์ž‘ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*(๋ฐ˜ํ™˜๊ฐ’์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ), `.include_router()` ํ˜ธ์ถœ ๋“ฑ์—์„œ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์›๋ž˜ ์˜์กด์„ฑ์€ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*(๋ฐ˜ํ™˜๊ฐ’์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ), `.include_router()` ํ˜ธ์ถœ ๋“ฑ์—์„œ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. FastAPI๋Š” ์—ฌ์ „ํžˆ ์ด๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -42,7 +42,7 @@ FastAPI๋Š” ์—ฌ์ „ํžˆ ์ด๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ, `app.dependency_overrides`๋ฅผ ๋นˆ `dict`๋กœ ์„ค์ •ํ•˜์—ฌ ์˜ค๋ฒ„๋ผ์ด๋“œ๋ฅผ ์žฌ์„ค์ •(์ œ๊ฑฐ)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -```python +```Python app.dependency_overrides = {} ``` diff --git a/docs/ko/docs/advanced/testing-events.md b/docs/ko/docs/advanced/testing-events.md index 502762f23b..8dbd4f6e66 100644 --- a/docs/ko/docs/advanced/testing-events.md +++ b/docs/ko/docs/advanced/testing-events.md @@ -1,5 +1,12 @@ -# ์ด๋ฒคํŠธ ํ…Œ์ŠคํŠธ: ์‹œ์ž‘ - ์ข…๋ฃŒ +# ์ด๋ฒคํŠธ ํ…Œ์ŠคํŠธ: ๋ผ์ดํ”„์ŠคํŒฌ ๋ฐ ์‹œ์ž‘ - ์ข…๋ฃŒ { #testing-events-lifespan-and-startup-shutdown } -ํ…Œ์ŠคํŠธ์—์„œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ(`startup` ๋ฐ `shutdown`)๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, `with` ๋ฌธ๊ณผ ํ•จ๊ป˜ `TestClient`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ…Œ์ŠคํŠธ์—์„œ `lifespan`์„ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, `with` ๋ฌธ๊ณผ ํ•จ๊ป˜ `TestClient`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../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 ๋ฌธ์„œ ์‚ฌ์ดํŠธ์—์„œ ํ…Œ์ŠคํŠธ์—์„œ ๋ผ์ดํ”„์ŠคํŒฌ ์‹คํ–‰ํ•˜๊ธฐ."](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/ko/docs/advanced/testing-websockets.md b/docs/ko/docs/advanced/testing-websockets.md index 9b67824295..1cb3cad67e 100644 --- a/docs/ko/docs/advanced/testing-websockets.md +++ b/docs/ko/docs/advanced/testing-websockets.md @@ -1,13 +1,13 @@ -# WebSocket ํ…Œ์ŠคํŠธํ•˜๊ธฐ +# WebSocket ํ…Œ์ŠคํŠธํ•˜๊ธฐ { #testing-websockets } -`TestClient`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ WebSocket์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ฐ™์€ `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 | ์ฐธ๊ณ  -์ž์„ธํ•œ ๋‚ด์šฉ์€ Starlette์˜ <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank"> WebSocket ํ…Œ์ŠคํŠธ</a>์— ๊ด€ํ•œ ์„ค๋ช…์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. +์ž์„ธํ•œ ๋‚ด์šฉ์€ Starlette์˜ <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">testing WebSockets</a> ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. /// diff --git a/docs/ko/docs/advanced/using-request-directly.md b/docs/ko/docs/advanced/using-request-directly.md index b88a83bf42..e0a5e99f88 100644 --- a/docs/ko/docs/advanced/using-request-directly.md +++ b/docs/ko/docs/advanced/using-request-directly.md @@ -1,10 +1,10 @@ -# `Request` ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ธฐ +# `Request` ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ธฐ { #using-the-request-directly } ์ง€๊ธˆ๊นŒ์ง€ ์š”์ฒญ์—์„œ ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ๊ฐ ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•˜์—ฌ ์‚ฌ์šฉํ•ด ์™”์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ณณ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค: -* ๊ฒฝ๋กœ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ๋ถ€ํ„ฐ. +* ๊ฒฝ๋กœ๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ. * ํ—ค๋”. * ์ฟ ํ‚ค. * ๊ธฐํƒ€ ๋“ฑ๋“ฑ. @@ -13,29 +13,29 @@ ํ•˜์ง€๋งŒ `Request` ๊ฐ์ฒด์— ์ง์ ‘ ์ ‘๊ทผํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Request` ๊ฐ์ฒด์— ๋Œ€ํ•œ ์„ธ๋ถ€ ์‚ฌํ•ญ +## `Request` ๊ฐ์ฒด์— ๋Œ€ํ•œ ์„ธ๋ถ€ ์‚ฌํ•ญ { #details-about-the-request-object } **FastAPI**๋Š” ์‹ค์ œ๋กœ ๋‚ด๋ถ€์— **Starlette**์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ๊ทธ ์œ„์— ์—ฌ๋Ÿฌ ๋„๊ตฌ๋ฅผ ๋ง๋ถ™์ธ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์—ฌ๋Ÿฌ๋ถ„์ด ํ•„์š”ํ•  ๋•Œ Starlette์˜ <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">`Request`</a> ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -`Request` ๊ฐ์ฒด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒฝ์šฐ(์˜ˆ: ๋ณธ๋ฌธ์„ ์ฝ๊ธฐ)์—๋Š” FastAPI๊ฐ€ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๊ฑฐ๋‚˜ ๋ณ€ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋ฌธ์„œํ™”(OpenAPI๋ฅผ ํ†ตํ•œ ๋ฌธ์„œ ์ž๋™ํ™”(๋กœ ์ƒ์„ฑ๋œ) API ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค)๋„ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ ์ด๋Š” `Request` ๊ฐ์ฒด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒฝ์šฐ(์˜ˆ: ๋ณธ๋ฌธ์„ ์ฝ๊ธฐ) FastAPI๊ฐ€ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๊ฑฐ๋‚˜ ๋ณ€ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋ฌธ์„œํ™”(OpenAPI๋ฅผ ํ†ตํ•œ ์ž๋™ API ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์šฉ)๋„ ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์˜๋ฏธ์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‹ค๋ฅธ ๋งค๊ฐœ๋ณ€์ˆ˜(์˜ˆ: Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•œ ๋ณธ๋ฌธ)๋Š” ์—ฌ์ „ํžˆ ๊ฒ€์ฆ, ๋ณ€ํ™˜, ์ฃผ์„ ์ถ”๊ฐ€ ๋“ฑ์ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ํŠน์ •ํ•œ ๊ฒฝ์šฐ์—๋Š” `Request` ๊ฐ์ฒด์— ์ง์ ‘ ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ํŠน์ •ํ•œ ๊ฒฝ์šฐ์—๋Š” `Request` ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Request` ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ธฐ +## `Request` ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ธฐ { #use-the-request-object-directly } -์—ฌ๋Ÿฌ๋ถ„์ด ํด๋ผ์ด์–ธํŠธ์˜ IP ์ฃผ์†Œ/ํ˜ธ์ŠคํŠธ ์ •๋ณด๋ฅผ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜* ๋‚ด๋ถ€์—์„œ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ๋ถ„์ด ํด๋ผ์ด์–ธํŠธ์˜ 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` ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `Request` ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•˜๋ฉด **FastAPI**๊ฐ€ ํ•ด๋‹น ๋งค๊ฐœ๋ณ€์ˆ˜์— `Request`๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. /// tip | ํŒ -์ด ๊ฒฝ์šฐ, ์š”์ฒญ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ํ•จ๊ป˜ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ, ์š”์ฒญ ๋งค๊ฐœ๋ณ€์ˆ˜ ์˜†์— ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. ๋”ฐ๋ผ์„œ, ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์ถ”์ถœ๋˜๊ณ  ๊ฒ€์ฆ๋˜๋ฉฐ ์ง€์ •๋œ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜๋˜๊ณ  OpenAPI๋กœ ์ฃผ์„์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. @@ -43,14 +43,14 @@ /// -## `Request` ์„ค๋ช…์„œ +## `Request` ์„ค๋ช…์„œ { #request-documentation } -์—ฌ๋Ÿฌ๋ถ„์€ `Request` ๊ฐ์ฒด์— ๋Œ€ํ•œ ๋” ์ž์„ธํ•œ ๋‚ด์šฉ์„ <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">๊ณต์‹ Starlette ์„ค๋ช…์„œ ์‚ฌ์ดํŠธ</a>์—์„œ ์ฝ์–ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ๋ถ„์€ <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">๊ณต์‹ Starlette ์„ค๋ช…์„œ ์‚ฌ์ดํŠธ์˜ `Request` ๊ฐ์ฒด</a>์— ๋Œ€ํ•œ ๋” ์ž์„ธํ•œ ๋‚ด์šฉ์„ ์ฝ์–ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `from starlette.requests import Request`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” ์—ฌ๋Ÿฌ๋ถ„(๊ฐœ๋ฐœ์ž)๋ฅผ ์œ„ํ•œ ํŽธ์˜๋ฅผ ์œ„ํ•ด ์ด๋ฅผ ์ง์ ‘ ์ œ๊ณตํ•˜์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” Starlette์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**FastAPI**๋Š” ์—ฌ๋Ÿฌ๋ถ„(๊ฐœ๋ฐœ์ž)๋ฅผ ์œ„ํ•œ ํŽธ์˜๋ฅผ ์œ„ํ•ด ์ด๋ฅผ ์ง์ ‘ ์ œ๊ณตํ•˜์ง€๋งŒ, Starlette์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. /// diff --git a/docs/ko/docs/advanced/websockets.md b/docs/ko/docs/advanced/websockets.md index d9d0dd95c4..b6817870b7 100644 --- a/docs/ko/docs/advanced/websockets.md +++ b/docs/ko/docs/advanced/websockets.md @@ -1,10 +1,10 @@ -# WebSockets +# WebSockets { #websockets } ์—ฌ๋Ÿฌ๋ถ„์€ **FastAPI**์—์„œ <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">WebSockets</a>๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `WebSockets` ์„ค์น˜ +## `websockets` ์„ค์น˜ { #install-websockets } -[๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank)๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, `websockets`๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”: +[๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ์ƒ์„ฑํ•˜๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, `websockets`("WebSocket" ํ”„๋กœํ† ์ฝœ์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” Python ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”: <div class="termy"> @@ -16,13 +16,13 @@ $ pip install websockets </div> -## WebSockets ํด๋ผ์ด์–ธํŠธ +## WebSockets ํด๋ผ์ด์–ธํŠธ { #websockets-client } -### ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ +### ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ { #in-production } ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ๋•์…˜ ์‹œ์Šคํ…œ์—์„œ๋Š” React, Vue.js ๋˜๋Š” Angular์™€ ๊ฐ™์€ ์ตœ์‹  ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ์ƒ์„ฑ๋œ ํ”„๋ŸฐํŠธ์—”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. -๋ฐฑ์—”๋“œ์™€ WebSockets์„ ์‚ฌ์šฉํ•ด ํ†ต์‹ ํ•˜๋ ค๋ฉด ์•„๋งˆ๋„ ํ”„๋ŸฐํŠธ์—”๋“œ์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๋ฐฑ์—”๋“œ์™€ WebSockets์„ ์‚ฌ์šฉํ•ด ํ†ต์‹ ํ•˜๋ ค๋ฉด ์•„๋งˆ๋„ ํ”„๋ŸฐํŠธ์—”๋“œ์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ์ฝ”๋“œ๋กœ WebSocket ๋ฐฑ์—”๋“œ์™€ ์ง์ ‘ ํ†ต์‹ ํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋ฐ”์ผ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๊ฐ€์งˆ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -30,23 +30,23 @@ $ pip install websockets --- -ํ•˜์ง€๋งŒ ์ด๋ฒˆ ์˜ˆ์ œ์—์„œ๋Š” ์ผ๋ถ€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ํฌํ•จํ•œ ๊ฐ„๋‹จํ•œ HTML ๋ฌธ์„œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ฒƒ์„ ๊ธด ๋ฌธ์ž์—ด ์•ˆ์— ๋„ฃ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์ด๋ฒˆ ์˜ˆ์ œ์—์„œ๋Š” ์ผ๋ถ€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ํฌํ•จํ•œ ๋งค์šฐ ๊ฐ„๋‹จํ•œ HTML ๋ฌธ์„œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ฒƒ์„ ๊ธด ๋ฌธ์ž์—ด ์•ˆ์— ๋„ฃ์Šต๋‹ˆ๋‹ค. ๋ฌผ๋ก , ์ด๋Š” ์ตœ์ ์˜ ๋ฐฉ๋ฒ•์ด ์•„๋‹ˆ๋ฉฐ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์œ„์—์„œ ์„ค๋ช…ํ•œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. +ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์œ„์—์„œ ์„ค๋ช…ํ•œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋Š” 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 | ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `from starlette.websockets import WebSocket`์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -54,17 +54,17 @@ $ 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`๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค: +ํŒŒ์ผ ์ด๋ฆ„์ด `main.py`๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ๋‹ค์Œ์œผ๋กœ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค: <div class="termy"> @@ -76,7 +76,7 @@ $ fastapi dev main.py </div> -๋ธŒ๋ผ์šฐ์ €์—์„œ <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>์„ ์—ด์–ด๋ณด์„ธ์š”. +๋ธŒ๋ผ์šฐ์ €์—์„œ <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>์„ ์—ฌ์„ธ์š”. ๊ฐ„๋‹จํ•œ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜ํƒ€๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค: @@ -86,7 +86,7 @@ $ fastapi dev main.py <img src="/img/tutorial/websockets/image02.png"> -**FastAPI** WebSocket ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด ์‘๋‹ต์„ ๋Œ๋ ค์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค: +๊ทธ๋ฆฌ๊ณ  WebSockets๊ฐ€ ํฌํ•จ๋œ **FastAPI** ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด ์‘๋‹ต์„ ๋Œ๋ ค์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค: <img src="/img/tutorial/websockets/image03.png"> @@ -94,9 +94,9 @@ $ fastapi dev main.py <img src="/img/tutorial/websockets/image04.png"> -๋ชจ๋“  ๋ฉ”์‹œ์ง€๋Š” ๋™์ผํ•œ WebSocket ์—ฐ๊ฒฐ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋Š” ๋™์ผํ•œ WebSocket ์—ฐ๊ฒฐ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -## `Depends` ๋ฐ ๊ธฐํƒ€ ์‚ฌ์šฉํ•˜๊ธฐ +## `Depends` ๋ฐ ๊ธฐํƒ€ ์‚ฌ์šฉํ•˜๊ธฐ { #using-depends-and-others } WebSocket ์—”๋“œํฌ์ธํŠธ์—์„œ `fastapi`์—์„œ ๋‹ค์Œ์„ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -107,21 +107,21 @@ WebSocket ์—”๋“œํฌ์ธํŠธ์—์„œ `fastapi`์—์„œ ๋‹ค์Œ์„ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•  * `Path` * `Query` -์ด๋“ค์€ ๋‹ค๋ฅธ FastAPI ์—”๋“œํฌ์ธํŠธ/*๊ฒฝ๋กœ ์ž‘๋™*๊ณผ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค: +์ด๋“ค์€ ๋‹ค๋ฅธ FastAPI ์—”๋“œํฌ์ธํŠธ/*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค: {* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} /// info | ์ •๋ณด -WebSocket์—์„œ๋Š” `HTTPException`์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์ด ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  `WebSocketException`์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. +WebSocket์ด๊ธฐ ๋•Œ๋ฌธ์— `HTTPException`์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์€ ์ ์ ˆํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  `WebSocketException`์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๋ช…์„ธ์„œ์— ์ •์˜๋œ <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">์œ ํšจํ•œ ์ฝ”๋“œ</a>๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -### ์ข…์†์„ฑ์„ ๊ฐ€์ง„ WebSockets ํ…Œ์ŠคํŠธ +### ์ข…์†์„ฑ์„ ๊ฐ€์ง„ WebSockets ์‹œ๋„ํ•ด๋ณด๊ธฐ { #try-the-websockets-with-dependencies } -ํŒŒ์ผ ์ด๋ฆ„์ด `main.py`๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค: +ํŒŒ์ผ ์ด๋ฆ„์ด `main.py`๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ๋‹ค์Œ์œผ๋กœ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค: <div class="termy"> @@ -133,9 +133,9 @@ $ fastapi dev main.py </div> -๋ธŒ๋ผ์šฐ์ €์—์„œ <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>์„ ์—ด์–ด๋ณด์„ธ์š”. +๋ธŒ๋ผ์šฐ์ €์—์„œ <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>์„ ์—ฌ์„ธ์š”. -๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์—ฌ๊ธฐ์—์„œ ๋‹ค์Œ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: * ๊ฒฝ๋กœ์— ์‚ฌ์šฉ๋œ "Item ID". * ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉ๋œ "Token". @@ -146,13 +146,13 @@ $ fastapi dev main.py /// -์ด์ œ WebSocket์— ์—ฐ๊ฒฐํ•˜๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†ก ๋ฐ ์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด WebSocket์— ์—ฐ๊ฒฐํ•˜๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†ก ๋ฐ ์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <img src="/img/tutorial/websockets/image05.png"> -## ์—ฐ๊ฒฐ ํ•ด์ œ ๋ฐ ๋‹ค์ค‘ ํด๋ผ์ด์–ธํŠธ ์ฒ˜๋ฆฌ +## ์—ฐ๊ฒฐ ํ•ด์ œ ๋ฐ ๋‹ค์ค‘ ํด๋ผ์ด์–ธํŠธ ์ฒ˜๋ฆฌ { #handling-disconnections-and-multiple-clients } -WebSocket ์—ฐ๊ฒฐ์ด ๋‹ซํžˆ๋ฉด, `await websocket.receive_text()`๊ฐ€ `WebSocketDisconnect` ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ์ด๋ฅผ ์žก์•„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +WebSocket ์—ฐ๊ฒฐ์ด ๋‹ซํžˆ๋ฉด, `await websocket.receive_text()`๊ฐ€ `WebSocketDisconnect` ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ด ์˜ˆ์ œ์ฒ˜๋Ÿผ ์ด๋ฅผ ์žก์•„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. {* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} @@ -160,7 +160,7 @@ WebSocket ์—ฐ๊ฒฐ์ด ๋‹ซํžˆ๋ฉด, `await websocket.receive_text()`๊ฐ€ `WebSocketDis * ์—ฌ๋Ÿฌ ๋ธŒ๋ผ์šฐ์ € ํƒญ์—์„œ ์•ฑ์„ ์—ฝ๋‹ˆ๋‹ค. * ๊ฐ ํƒญ์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. -* ํ•œ ํƒญ์„ ๋‹ซ์•„๋ณด์„ธ์š”. +* ๊ทธ๋Ÿฐ ๋‹ค์Œ ํƒญ ์ค‘ ํ•˜๋‚˜๋ฅผ ๋‹ซ์•„๋ณด์„ธ์š”. `WebSocketDisconnect` ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ, ๋‹ค๋ฅธ ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์‹ ํ•ฉ๋‹ˆ๋‹ค: @@ -170,17 +170,17 @@ Client #1596980209979 left the chat /// tip | ํŒ -์œ„ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ ์—ฌ๋Ÿฌ WebSocket ์—ฐ๊ฒฐ์— ๋ฉ”์‹œ์ง€๋ฅผ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. +์œ„ ์•ฑ์€ ์—ฌ๋Ÿฌ WebSocket ์—ฐ๊ฒฐ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ๋Š” ์ตœ์†Œํ•œ์˜ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ ๋ชจ๋“  ๊ฒƒ์„ ๋ฉ”๋ชจ๋ฆฌ์˜ ๋‹จ์ผ ๋ฆฌ์ŠคํŠธ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ, ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‹คํ–‰ ์ค‘์ธ ๋™์•ˆ๋งŒ ๋™์ž‘ํ•˜๋ฉฐ ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค์—์„œ๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๋ชจ๋“  ๊ฒƒ์„ ๋ฉ”๋ชจ๋ฆฌ์˜ ๋‹จ์ผ ๋ฆฌ์ŠคํŠธ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ, ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‹คํ–‰ ์ค‘์ธ ๋™์•ˆ๋งŒ ๋™์ž‘ํ•˜๋ฉฐ ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค์—์„œ๋งŒ ์ž‘๋™ํ•œ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. -FastAPI์™€ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉด์„œ ๋” ๊ฒฌ๊ณ ํ•˜๊ณ  Redis, PostgreSQL ๋“ฑ์„ ์ง€์›ํ•˜๋Š” ๋„๊ตฌ๋ฅผ ์ฐพ๊ณ  ์žˆ๋‹ค๋ฉด, <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. +FastAPI์™€ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉด์„œ ๋” ๊ฒฌ๊ณ ํ•˜๊ณ  Redis, PostgreSQL ๋“ฑ์„ ์ง€์›ํ•˜๋Š” ๋„๊ตฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด, <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. /// -## ์ถ”๊ฐ€ ์ •๋ณด +## ์ถ”๊ฐ€ ์ •๋ณด { #more-info } -๋‹ค์Œ ์˜ต์…˜์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์„ ๋ณด๋ ค๋ฉด Starlette์˜ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”: +๋‹ค์Œ ์˜ต์…˜์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๋ ค๋ฉด Starlette์˜ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”: * <a href="https://www.starlette.dev/websockets/" class="external-link" target="_blank">`WebSocket` ํด๋ž˜์Šค</a>. * <a href="https://www.starlette.dev/endpoints/#websocketendpoint" class="external-link" target="_blank">ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ WebSocket ์ฒ˜๋ฆฌ</a>. diff --git a/docs/ko/docs/advanced/wsgi.md b/docs/ko/docs/advanced/wsgi.md index 3e9de3e6ca..89cf57cfef 100644 --- a/docs/ko/docs/advanced/wsgi.md +++ b/docs/ko/docs/advanced/wsgi.md @@ -1,10 +1,10 @@ -# WSGI ํฌํ•จํ•˜๊ธฐ - Flask, Django ๊ทธ ์™ธ +# 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` ์‚ฌ์šฉํ•˜๊ธฐ +## `WSGIMiddleware` ์‚ฌ์šฉํ•˜๊ธฐ { #using-wsgimiddleware } `WSGIMiddleware`๋ฅผ ๋ถˆ๋Ÿฌ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. @@ -12,9 +12,9 @@ ๊ทธ ํ›„, ํ•ด๋‹น ๊ฒฝ๋กœ์— ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/wsgi/tutorial001.py hl[2:3,23] *} +{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *} -## ํ™•์ธํ•˜๊ธฐ +## ํ™•์ธํ•˜๊ธฐ { #check-it } ์ด์ œ `/v1/` ๊ฒฝ๋กœ์— ์žˆ๋Š” ๋ชจ๋“  ์š”์ฒญ์€ Flask ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. @@ -26,7 +26,7 @@ Hello, World from Flask! ``` -๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ์œผ๋กœ ์ด๋™ํ•˜๋ฉด <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a> Flask์˜ ์‘๋‹ต์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ์œผ๋กœ ์ด๋™ํ•˜๋ฉด <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a> **FastAPI**์˜ ์‘๋‹ต์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```JSON { diff --git a/docs/ko/docs/benchmarks.md b/docs/ko/docs/benchmarks.md index aff8ae70ef..2d4fdbeddb 100644 --- a/docs/ko/docs/benchmarks.md +++ b/docs/ko/docs/benchmarks.md @@ -1,10 +1,10 @@ -# ๋ฒค์น˜๋งˆํฌ +# ๋ฒค์น˜๋งˆํฌ { #benchmarks } -๋…๋ฆฝ์ ์ธ TechEmpower ๋ฒค์น˜๋งˆํฌ์— ๋”ฐ๋ฅด๋ฉด **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด Uvicorn์„ ์‚ฌ์šฉํ•˜์—ฌ <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">๊ฐ€์žฅ ๋น ๋ฅธ Python ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜</a>๋กœ ์‹คํ–‰๋˜๋ฉฐ, Starlette์™€ Uvicorn ์ž์ฒด(๋‚ด๋ถ€์ ์œผ๋กœ FastAPI๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ)๋ณด๋‹ค ์กฐ๊ธˆ ์•„๋ž˜์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค. +๋…๋ฆฝ์ ์ธ TechEmpower ๋ฒค์น˜๋งˆํฌ์— ๋”ฐ๋ฅด๋ฉด **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด Uvicorn์„ ์‚ฌ์šฉํ•˜์—ฌ <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ€์žฅ ๋น ๋ฅธ Python ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜</a>๋กœ ์‹คํ–‰๋˜๋ฉฐ, Starlette์™€ Uvicorn ์ž์ฒด(๋‚ด๋ถ€์ ์œผ๋กœ FastAPI๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ)๋ณด๋‹ค ์กฐ๊ธˆ ์•„๋ž˜์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ฒค์น˜๋งˆํฌ์™€ ๋น„๊ต๋ฅผ ํ™•์ธํ•  ๋•Œ ๋‹ค์Œ ์‚ฌํ•ญ์„ ์—ผ๋‘์— ๋‘์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -## ๋ฒค์น˜๋งˆํฌ์™€ ์†๋„ +## ๋ฒค์น˜๋งˆํฌ์™€ ์†๋„ { #benchmarks-and-speed } ๋ฒค์น˜๋งˆํฌ๋ฅผ ํ™•์ธํ•  ๋•Œ, ์ผ๋ฐ˜์ ์œผ๋กœ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์œ ํ˜•์˜ ๋„๊ตฌ๊ฐ€ ๋™๋“ฑํ•œ ๊ฒƒ์œผ๋กœ ๋น„๊ต๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -16,7 +16,7 @@ * **Uvicorn**: ASGI ์„œ๋ฒ„ * **Starlette**: (Uvicorn ์‚ฌ์šฉ) ์›น ๋งˆ์ดํฌ๋กœ ํ”„๋ ˆ์ž„์›Œํฌ - * **FastAPI**: (Starlette ์‚ฌ์šฉ) API ๊ตฌ์ถ•์„ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋“ฑ ์—ฌ๋Ÿฌ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ API ๋งˆ์ดํฌ๋กœ ํ”„๋ ˆ์ž„์›Œํฌ + * **FastAPI**: (Starlette ์‚ฌ์šฉ) ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋“ฑ API๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ์—ฌ๋Ÿฌ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ API ๋งˆ์ดํฌ๋กœ ํ”„๋ ˆ์ž„์›Œํฌ * **Uvicorn**: * ์„œ๋ฒ„ ์ž์ฒด ์™ธ์—๋Š” ๋งŽ์€ ์ถ”๊ฐ€ ์ฝ”๋“œ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ตœ๊ณ ์˜ ์„ฑ๋Šฅ์„ ๋ฐœํœ˜ํ•ฉ๋‹ˆ๋‹ค. @@ -29,6 +29,6 @@ * **FastAPI**: * Starlette๊ฐ€ Uvicorn์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ Uvicorn๋ณด๋‹ค ๋นจ๋ผ์งˆ ์ˆ˜ ์—†๋Š” ๊ฒƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, **FastAPI**๋Š” Starlette๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๋” ๋น ๋ฅผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. * FastAPI๋Š” Starlette์— ์ถ”๊ฐ€์ ์œผ๋กœ ๋” ๋งŽ์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. API๋ฅผ ๊ตฌ์ถ•ํ•  ๋•Œ ๊ฑฐ์˜ ํ•ญ์ƒ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ฐ ์ง๋ ฌํ™”์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ๋“ค์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฌธ์„œ ์ž๋™ํ™” ๊ธฐ๋Šฅ๋„ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค(๋ฌธ์„œ ์ž๋™ํ™”๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ์‹œ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ  ์‹œ์ž‘ ์‹œ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค). - * FastAPI๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ง์ ‘ Starlette(๋˜๋Š” Sanic, Flask, Responder ๋“ฑ)๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ฐ ์ง๋ ฌํ™”๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ตœ์ข… ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ FastAPI๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋งŽ์€ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ฐ ์ง๋ ฌํ™”๊ฐ€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์ž‘์„ฑ๋œ ์ฝ”๋“œ ์ค‘ ๊ฐ€์žฅ ๋งŽ์€ ๋ถ€๋ถ„์„ ์ฐจ์ง€ํ•ฉ๋‹ˆ๋‹ค. - * ๋”ฐ๋ผ์„œ FastAPI๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๊ฐœ๋ฐœ ์‹œ๊ฐ„, ๋ฒ„๊ทธ, ์ฝ”๋“œ ๋ผ์ธ์„ ์ค„์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, FastAPI๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„ ๋•Œ์™€ ๋™์ผํ•˜๊ฑฐ๋‚˜ ๋” ๋‚˜์€ ์„ฑ๋Šฅ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ฝ”๋“œ์—์„œ ๋ชจ๋‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—). - * FastAPI๋ฅผ ๋น„๊ตํ•  ๋•Œ๋Š” Flask-apispec, NestJS, Molten ๋“ฑ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, ์ง๋ ฌํ™” ๋ฐ ๋ฌธ์„œํ™”๊ฐ€ ํ†ตํ•ฉ๋œ ์ž๋™ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, ์ง๋ ฌํ™” ๋ฐ ๋ฌธ์„œํ™”๋ฅผ ์ œ๊ณตํ•˜๋Š” ์›น ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ํ”„๋ ˆ์ž„์›Œํฌ(๋˜๋Š” ๋„๊ตฌ ์ง‘ํ•ฉ)์™€ ๋น„๊ตํ•˜์„ธ์š”. + * FastAPI๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ง์ ‘ Starlette(๋˜๋Š” ๋‹ค๋ฅธ ๋„๊ตฌ, ์˜ˆ: Sanic, Flask, Responder ๋“ฑ)๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ฐ ์ง๋ ฌํ™”๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ตœ์ข… ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ FastAPI๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋งŽ์€ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ฐ ์ง๋ ฌํ™”๊ฐ€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์ž‘์„ฑ๋œ ์ฝ”๋“œ ์ค‘ ๊ฐ€์žฅ ๋งŽ์€ ๋ถ€๋ถ„์„ ์ฐจ์ง€ํ•ฉ๋‹ˆ๋‹ค. + * ๋”ฐ๋ผ์„œ FastAPI๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๊ฐœ๋ฐœ ์‹œ๊ฐ„, ๋ฒ„๊ทธ, ์ฝ”๋“œ ๋ผ์ธ์„ ์ค„์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, FastAPI๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„ ๋•Œ์™€ ๋™์ผํ•œ ์„ฑ๋Šฅ(๋˜๋Š” ๋” ๋‚˜์€ ์„ฑ๋Šฅ)์„ ์–ป์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค(์ฝ”๋“œ์—์„œ ๋ชจ๋‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—). + * FastAPI๋ฅผ ๋น„๊ตํ•  ๋•Œ๋Š” Flask-apispec, NestJS, Molten ๋“ฑ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, ์ง๋ ฌํ™” ๋ฐ ๋ฌธ์„œํ™”๋ฅผ ์ œ๊ณตํ•˜๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋ ˆ์ž„์›Œํฌ(๋˜๋Š” ๋„๊ตฌ ์ง‘ํ•ฉ)์™€ ๋น„๊ตํ•˜์„ธ์š”. ํ†ตํ•ฉ๋œ ์ž๋™ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, ์ง๋ ฌํ™” ๋ฐ ๋ฌธ์„œํ™”๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/deployment/cloud.md b/docs/ko/docs/deployment/cloud.md index dbc814bbdb..0705e120c4 100644 --- a/docs/ko/docs/deployment/cloud.md +++ b/docs/ko/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 **์ƒํƒœ๊ณ„**๊ฐ€ ์ง€์†์ ์ด๊ณ  ๊ฑด์ „ํ•œ **๋ฐœ์ „**์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>**๋Š” **FastAPI**๋ฅผ ๋งŒ๋“  ๋™์ผํ•œ ์ž‘์„ฑ์ž์™€ ํŒ€์ด ๊ตฌ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค. -์ด๋Š” FastAPI์™€ **์ปค๋ฎค๋‹ˆํ‹ฐ** (์—ฌ๋Ÿฌ๋ถ„)์— ๋Œ€ํ•œ ์ง„์ •ํ•œ ํ—Œ์‹ ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ๊ทธ๋“ค์€ ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ **์ข‹์€ ์„œ๋น„์Šค**๋ฅผ ์ œ๊ณตํ•  ๋ฟ ๋งŒ์ด ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ๋ถ„์ด **ํ›Œ๋ฅญํ•˜๊ณ  ๊ฑด๊ฐ•ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์ธ** FastAPI ๋ฅผ ์‚ฌ์šฉํ•˜๊ธธ ์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๐Ÿ™‡ +์ตœ์†Œํ•œ์˜ ๋…ธ๋ ฅ์œผ๋กœ API๋ฅผ **๊ตฌ์ถ•**, **๋ฐฐํฌ**, **์ ‘๊ทผ**ํ•˜๋Š” ๊ณผ์ •์„ ๊ฐ„์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. -์•„๋ž˜์™€ ๊ฐ™์€ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ๊ฐ ์„œ๋น„์Šค์˜ ๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ฅผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +FastAPI๋กœ ์•ฑ์„ ๋นŒ๋“œํ•  ๋•Œ์˜ ๋™์ผํ•œ **๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜**์„ ํด๋ผ์šฐ๋“œ์— **๋ฐฐํฌ**ํ•˜๋Š” ๋ฐ์—๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰ + +FastAPI Cloud๋Š” *FastAPI and friends* ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ์˜ ์ฃผ์š” ํ›„์›์ž์ด์ž ์ž๊ธˆ ์ œ๊ณต์ž์ž…๋‹ˆ๋‹ค. โœจ + +## ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด - ํ›„์›์ž๋“ค { #cloud-providers-sponsors } + +๋‹ค๋ฅธ ๋ช‡๋ช‡ ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด๋“ค๋„ โœจ [**FastAPI๋ฅผ ํ›„์›ํ•ฉ๋‹ˆ๋‹ค**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} โœจ. ๐Ÿ™‡ + +๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ผ ํ•˜๊ณ  ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ ์œ„ํ•ด ์ด๋“ค๋„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a> +* <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" class="external-link" target="_blank">Railway</a> diff --git a/docs/ko/docs/deployment/docker.md b/docs/ko/docs/deployment/docker.md index e8b2746c5f..be04c923a5 100644 --- a/docs/ko/docs/deployment/docker.md +++ b/docs/ko/docs/deployment/docker.md @@ -1,17 +1,17 @@ -# ์ปจํ…Œ์ด๋„ˆ์˜ FastAPI - ๋„์ปค +# ์ปจํ…Œ์ด๋„ˆ์˜ FastAPI - ๋„์ปค { #fastapi-in-containers-docker } -FastAPI ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ๋•Œ ์ผ๋ฐ˜์ ์ธ ์ ‘๊ทผ ๋ฐฉ๋ฒ•์€ **๋ฆฌ๋ˆ…์Šค ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์ฃผ๋กœ <a href="https://www.docker.com/" class="external-link" target="_blank">**๋„์ปค**</a>๋ฅผ ์‚ฌ์šฉํ•ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•ด๋‹น ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋ช‡๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ๋•Œ ์ผ๋ฐ˜์ ์ธ ์ ‘๊ทผ ๋ฐฉ๋ฒ•์€ **๋ฆฌ๋ˆ…์Šค ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๋ฅผ ๋นŒ๋“œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ณดํ†ต <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>๋ฅผ ์‚ฌ์šฉํ•ด ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•ด๋‹น ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋ช‡ ๊ฐ€์ง€ ๊ฐ€๋Šฅํ•œ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋กœ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋ฆฌ๋ˆ…์Šค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์—๋Š” **๋ณด์•ˆ**, **๋ฐ˜๋ณต ๊ฐ€๋Šฅ์„ฑ**, **๋‹จ์ˆœํ•จ** ๋“ฑ์˜ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. +๋ฆฌ๋ˆ…์Šค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด **๋ณด์•ˆ**, **์žฌํ˜„ ๊ฐ€๋Šฅ์„ฑ**, **๋‹จ์ˆœํ•จ** ๋“ฑ ์—ฌ๋Ÿฌ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. /// tip | ํŒ -์‹œ๊ฐ„์— ์ซ“๊ธฐ๊ณ  ์žˆ๊ณ  ์ด๋ฏธ ์ด๋Ÿฐ๊ฒƒ๋“ค์„ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด [`Dockerfile`๐Ÿ‘‡](#build-a-docker-image-for-fastapi)๋กœ ์ ํ”„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์‹œ๊ฐ„์ด ์—†๊ณ  ์ด๋ฏธ ์ด๋Ÿฐ ๋‚ด์šฉ๋“ค์„ ์•Œ๊ณ  ๊ณ„์‹ ๊ฐ€์š”? ์•„๋ž˜์˜ [`Dockerfile` ๐Ÿ‘‡](#build-a-docker-image-for-fastapi)๋กœ ์ด๋™ํ•˜์„ธ์š”. /// <details> -<summary>๋„์ปคํŒŒ์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๐Ÿ‘€</summary> +<summary>Dockerfile Preview ๐Ÿ‘€</summary> ```Dockerfile FROM python:3.9 @@ -24,128 +24,125 @@ 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"] ``` </details> -## ์ปจํ…Œ์ด๋„ˆ๋ž€ +## ์ปจํ…Œ์ด๋„ˆ๋ž€ { #what-is-a-container } -์ปจํ…Œ์ด๋„ˆ(์ฃผ๋กœ ๋ฆฌ๋ˆ…์Šค ์ปจํ…Œ์ด๋„ˆ)๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์˜์กด์„ฑ๊ณผ ํ•„์š”ํ•œ ํŒŒ์ผ๋“ค์„ ๋ชจ๋‘ ํŒจํ‚ค์ง•ํ•˜๋Š” ๋งค์šฐ **๊ฐ€๋ฒผ์šด** ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ปจํ…Œ์ด๋„ˆ๋Š” ๊ฐ™์€ ์‹œ์Šคํ…œ์— ์žˆ๋Š” ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ(๋‹ค๋ฅธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋‚˜ ์š”์†Œ๋“ค)์™€ ๋…๋ฆฝ์ ์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. +์ปจํ…Œ์ด๋„ˆ(์ฃผ๋กœ ๋ฆฌ๋ˆ…์Šค ์ปจํ…Œ์ด๋„ˆ)๋Š” ๋ชจ๋“  ์˜์กด์„ฑ๊ณผ ํ•„์š”ํ•œ ํŒŒ์ผ์„ ํฌํ•จํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํŒจํ‚ค์ง•ํ•˜๋ฉด์„œ, ๊ฐ™์€ ์‹œ์Šคํ…œ์˜ ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ(๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋‚˜ ์ปดํฌ๋„ŒํŠธ)์™€๋Š” ๋ถ„๋ฆฌ๋œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ๋งค์šฐ **๊ฐ€๋ฒผ์šด** ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. -๋ฆฌ๋ˆ…์Šค ์ปจํ…Œ์ด๋„ˆ๋Š” ํ˜ธ์ŠคํŠธ(๋จธ์‹ , ๊ฐ€์ƒ ๋จธ์‹ , ํด๋ผ์šฐ๋“œ ์„œ๋ฒ„ ๋“ฑ)์™€ ๊ฐ™์€ ๋ฆฌ๋ˆ…์Šค ์ปค๋„์„ ์‚ฌ์šฉํ•ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋ง์€ ๋ฆฌ๋ˆ…์Šค ์ปจํ…Œ์ด๋„ˆ๊ฐ€ (์ „์ฒด ์šด์˜์ฒด์ œ๋ฅผ ๋ชจ๋ฐฉํ•˜๋Š” ๋‹ค๋ฅธ ๊ฐ€์ƒ ๋จธ์‹ ๊ณผ ๋น„๊ตํ–ˆ์„ ๋•Œ) ๋งค์šฐ ๊ฐ€๋ณ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +๋ฆฌ๋ˆ…์Šค ์ปจํ…Œ์ด๋„ˆ๋Š” ํ˜ธ์ŠคํŠธ(๋จธ์‹ , ๊ฐ€์ƒ ๋จธ์‹ , ํด๋ผ์šฐ๋“œ ์„œ๋ฒ„ ๋“ฑ)์™€ ๊ฐ™์€ ๋ฆฌ๋ˆ…์Šค ์ปค๋„์„ ์‚ฌ์šฉํ•ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์ „์ฒด ์šด์˜์ฒด์ œ๋ฅผ ์—๋ฎฌ๋ ˆ์ด์…˜ํ•˜๋Š” ์™„์ „ํ•œ ๊ฐ€์ƒ ๋จธ์‹ ์— ๋น„ํ•ด ๋งค์šฐ ๊ฐ€๋ณ์Šต๋‹ˆ๋‹ค. -์ด ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด, ์ปจํ…Œ์ด๋„ˆ๋Š” ์ง์ ‘ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•œ ์ •๋„์˜ **์ ์€ ์ž์›**์„ ์†Œ๋น„ํ•ฉ๋‹ˆ๋‹ค (๊ฐ€์ƒ ๋จธ์‹ ์€ ํ›จ์”ฌ ๋งŽ์€ ์ž์›์„ ์†Œ๋น„ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค). +์ด ๋ฐฉ์‹์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•œ ์ˆ˜์ค€์˜ **์ ์€ ์ž์›**์„ ์†Œ๋น„ํ•ฉ๋‹ˆ๋‹ค(๊ฐ€์ƒ ๋จธ์‹ ์€ ํ›จ์”ฌ ๋” ๋งŽ์€ ์ž์›์„ ์†Œ๋น„ํ•ฉ๋‹ˆ๋‹ค). -์ปจํ…Œ์ด๋„ˆ๋Š” ๋˜ํ•œ ๊ทธ๋“ค๋งŒ์˜ **๋…๋ฆฝ๋œ** ์‹คํ–‰ ํ”„๋กœ์„ธ์Šค (์ผ๋ฐ˜์ ์œผ๋กœ ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค๋กœ ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค), ํŒŒ์ผ ์‹œ์Šคํ…œ, ๊ทธ๋ฆฌ๊ณ  ๋„คํŠธ์›Œํฌ๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ ๋ฐฐํฌ, ๋ณด์•ˆ, ๊ฐœ๋ฐœ ๋ฐ ๊ธฐํƒ€ ๊ณผ์ •์„ ๋‹จ์ˆœํ™” ํ•ฉ๋‹ˆ๋‹ค. +๋˜ํ•œ ์ปจํ…Œ์ด๋„ˆ๋Š” ์ž์ฒด์ ์ธ **๊ฒฉ๋ฆฌ๋œ** ์‹คํ–‰ ํ”„๋กœ์„ธ์Šค(๋ณดํ†ต ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค), ํŒŒ์ผ ์‹œ์Šคํ…œ, ๋„คํŠธ์›Œํฌ๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ ๋ฐฐํฌ, ๋ณด์•ˆ, ๊ฐœ๋ฐœ ๋“ฑ์„ ๋‹จ์ˆœํ™”ํ•ฉ๋‹ˆ๋‹ค. -## ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ž€ +## ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ž€ { #what-is-a-container-image } -**์ปจํ…Œ์ด๋„ˆ**๋Š” **์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๋ฅผ ์‹คํ–‰ํ•œ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. +**์ปจํ…Œ์ด๋„ˆ**๋Š” **์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. -์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ž€ ์ปจํ…Œ์ด๋„ˆ์— ํ•„์š”ํ•œ ๋ชจ๋“  ํŒŒ์ผ, ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ทธ๋ฆฌ๊ณ  ๋””ํดํŠธ ๋ช…๋ น/ํ”„๋กœ๊ทธ๋žจ์˜ **์ •์ ** ๋ฒ„์ „์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ **์ •์ **์ด๋ž€ ๋ง์€ ์ปจํ…Œ์ด๋„ˆ **์ด๋ฏธ์ง€**๊ฐ€ ์ž‘๋™๋˜๊ฑฐ๋‚˜ ์‹คํ–‰๋˜์ง€ ์•Š์œผ๋ฉฐ, ๋‹จ์ง€ ํŒจํ‚ค์ง€ ํŒŒ์ผ๊ณผ ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ๋ผ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋Š” ์ปจํ…Œ์ด๋„ˆ์— ์žˆ์–ด์•ผ ํ•˜๋Š” ๋ชจ๋“  ํŒŒ์ผ, ํ™˜๊ฒฝ ๋ณ€์ˆ˜, ๊ธฐ๋ณธ ๋ช…๋ น/ํ”„๋กœ๊ทธ๋žจ์˜ **์ •์ ** ๋ฒ„์ „์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ **์ •์ **์ด๋ผ๋Š” ๊ฒƒ์€ ์ปจํ…Œ์ด๋„ˆ **์ด๋ฏธ์ง€**๊ฐ€ ์‹คํ–‰ ์ค‘์ด๊ฑฐ๋‚˜ ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ํŒจํ‚ค์ง•๋œ ํŒŒ์ผ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์ผ ๋ฟ์ด๋ผ๋Š” ๋œป์ž…๋‹ˆ๋‹ค. -์ €์žฅ๋œ ์ •์  ์ปจํ…์ธ ์ธ **์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**์™€ ๋Œ€์กฐ๋˜๊ฒŒ, **์ปจํ…Œ์ด๋„ˆ**๋ž€ ๋ณดํ†ต ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ์ž‘๋™ ์ธ์Šคํ„ด์Šค๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +์ €์žฅ๋œ ์ •์  ์ฝ˜ํ…์ธ ์ธ "**์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**"์™€ ๋‹ฌ๋ฆฌ, "**์ปจํ…Œ์ด๋„ˆ**"๋Š” ๋ณดํ†ต ์‹คํ–‰ ์ค‘์ธ ์ธ์Šคํ„ด์Šค, ์ฆ‰ **์‹คํ–‰๋˜๋Š”** ๋Œ€์ƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -**์ปจํ…Œ์ด๋„ˆ**๊ฐ€ (**์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๋กœ ๋ถ€ํ„ฐ) ์‹œ์ž‘๋˜๊ณ  ์‹คํ–‰๋˜๋ฉด, ์ปจํ…Œ์ด๋„ˆ๋Š” ํŒŒ์ผ์ด๋‚˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ณ€ํ™”๋Š” ์˜ค์ง ์ปจํ…Œ์ด๋„ˆ์—์„œ๋งŒ ์กด์žฌํ•˜๋ฉฐ, ๊ทธ ๊ธฐ๋ฐ˜์ด ๋˜๋Š” ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€์—๋Š” ์ง€์†๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค (์ฆ‰ ๋””์Šคํฌ์—๋Š” ์ €์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค). +**์ปจํ…Œ์ด๋„ˆ**๊ฐ€ ์‹œ์ž‘๋˜์–ด ์‹คํ–‰ ์ค‘์ด๋ฉด(**์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๋กœ๋ถ€ํ„ฐ ์‹œ์ž‘๋จ) ํŒŒ์ผ, ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋“ฑ์„ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ณ€๊ฒฝ์€ ํ•ด๋‹น ์ปจํ…Œ์ด๋„ˆ์—๋งŒ ์กด์žฌํ•˜๋ฉฐ, ๊ธฐ๋ฐ˜์ด ๋˜๋Š” ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€์—๋Š” ์ง€์†๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(๋””์Šคํฌ์— ์ €์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค). -์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋Š” **ํ”„๋กœ๊ทธ๋žจ** ํŒŒ์ผ๊ณผ ์ปจํ…์ธ , ์ฆ‰ `python`๊ณผ ์–ด๋–ค ํŒŒ์ผ `main.py`์— ๋น„๊ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋Š” **ํ”„๋กœ๊ทธ๋žจ** ํŒŒ์ผ๊ณผ ๊ทธ ์ฝ˜ํ…์ธ , ์˜ˆ๋ฅผ ๋“ค์–ด `python`๊ณผ ์–ด๋–ค ํŒŒ์ผ `main.py`์— ๋น„์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  (**์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**์™€ ๋Œ€๋น„ํ•ด์„œ) **์ปจํ…Œ์ด๋„ˆ**๋Š” ์ด๋ฏธ์ง€์˜ ์‹ค์ œ ์‹คํ–‰ ์ธ์Šคํ„ด์Šค๋กœ **ํ”„๋กœ์„ธ์Šค**์— ๋น„๊ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์‹ค, ์ปจํ…Œ์ด๋„ˆ๋Š” **ํ”„๋กœ์„ธ์Šค ๋Ÿฌ๋‹**์ด ์žˆ์„ ๋•Œ๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค (๊ทธ๋ฆฌ๊ณ  ๋ณดํ†ต ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค ์ž…๋‹ˆ๋‹ค). ์ปจํ…Œ์ด๋„ˆ๋Š” ๋‚ด๋ถ€์—์„œ ์‹คํ–‰๋˜๋Š” ํ”„๋กœ์„ธ์Šค๊ฐ€ ์—†์œผ๋ฉด ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  **์ปจํ…Œ์ด๋„ˆ** ์ž์ฒด๋Š”(**์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**์™€ ๋‹ฌ๋ฆฌ) ์ด๋ฏธ์ง€์˜ ์‹ค์ œ ์‹คํ–‰ ์ธ์Šคํ„ด์Šค๋กœ์„œ **ํ”„๋กœ์„ธ์Šค**์— ๋น„์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์ปจํ…Œ์ด๋„ˆ๋Š” **์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค**๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค(๋ณดํ†ต ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค์ž…๋‹ˆ๋‹ค). ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์— ์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์—†์œผ๋ฉด ์ปจํ…Œ์ด๋„ˆ๋Š” ์ค‘์ง€๋ฉ๋‹ˆ๋‹ค. -## ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ +## ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ { #container-images } -๋„์ปค๋Š” **์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**์™€ **์ปจํ…Œ์ด๋„ˆ**๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š”๋ฐ ์ฃผ์š” ๋„๊ตฌ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋˜์–ด์™”์Šต๋‹ˆ๋‹ค. +Docker๋Š” **์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**์™€ **์ปจํ…Œ์ด๋„ˆ**๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ์ฃผ์š” ๋„๊ตฌ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  <a href="https://hub.docker.com/" class="external-link" target="_blank">๋„์ปค ํ—ˆ๋ธŒ</a>์— ๋‹ค์–‘ํ•œ ๋„๊ตฌ, ํ™˜๊ฒฝ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๊ทธ๋ฆฌ๊ณ  ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•ด ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ **๊ณต์‹ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๊ฐ€ ๊ณต๊ฐœ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a>์—๋Š” ๋‹ค์–‘ํ•œ ๋„๊ตฌ, ํ™˜๊ฒฝ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ **๊ณต์‹ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๊ฐ€ ๊ณต๊ฐœ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ๊ณต์‹ <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">ํŒŒ์ด์ฌ ์ด๋ฏธ์ง€</a>๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, ๊ณต์‹ <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">Python Image</a>๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. -๋˜ํ•œ ๋‹ค๋ฅธ ๋Œ€์ƒ, ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์œ„ํ•œ ์ด๋ฏธ์ง€๋“ค๋„ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ ๋‹ค์–‘ํ•œ ์šฉ๋„์˜ ๋‹ค๋ฅธ ์ด๋ฏธ์ง€๋„ ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด: * <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a> * <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a> * <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a> * <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a> ๋“ฑ -๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ ๋„๊ตฌ๋“ค์„ **๊ฒฐํ•ฉ**ํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์—, **๊ณต์‹ ์ด๋ฏธ์ง€๋“ค**์„ ์‚ฌ์šฉํ•˜๊ณ  ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ ๋„๊ตฌ๋ฅผ **๊ฒฐํ•ฉ**ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ๊ฐ€ ๋งค์šฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‹œํ—˜ํ•ด ๋ณผ ๋•Œ๋„ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ **๊ณต์‹ ์ด๋ฏธ์ง€**๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์„ค์ •๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. -์ด๋Ÿฐ ๋ฐฉ๋ฒ•์œผ๋กœ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์— ์ปจํ…Œ์ด๋„ˆ์™€ ๋„์ปค์— ๋Œ€ํ•ด ๋ฐฐ์šธ ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋‹ค์–‘ํ•œ ๋„๊ตฌ์™€ ์š”์†Œ๋“ค์— ๋Œ€ํ•œ ์ง€์‹์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋งŽ์€ ๊ฒฝ์šฐ ์ปจํ…Œ์ด๋„ˆ์™€ Docker๋ฅผ ํ•™์Šตํ•˜๊ณ , ๊ทธ ์ง€์‹์„ ์—ฌ๋Ÿฌ ๋‹ค๋ฅธ ๋„๊ตฌ์™€ ์ปดํฌ๋„ŒํŠธ์— ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ, ์„œ๋กœ ๋‹ค๋ฅธ **๋‹ค์ค‘ ์ปจํ…Œ์ด๋„ˆ**๋ฅผ ์ƒ์„ฑํ•œ ๋‹ค์Œ ์ด๋“ค์„ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํŒŒ์ด์ฌ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜, ๋ฆฌ์•กํŠธ ํ”„๋ก ํŠธ์—”๋“œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ์›น ์„œ๋ฒ„์— ๋Œ€ํ•œ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋งŒ๋“ค์–ด ์ด๋“ค์˜ ๋‚ด๋ถ€ ๋„คํŠธ์›Œํฌ๋กœ ๊ฐ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, Python ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜, React ํ”„๋ก ํŠธ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์žˆ๋Š” ์›น ์„œ๋ฒ„ ๋“ฑ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฒƒ๋“ค์„ ๋‹ด์€ **์—ฌ๋Ÿฌ ์ปจํ…Œ์ด๋„ˆ**๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋‚ด๋ถ€ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋ชจ๋“  ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ(๋„์ปค๋‚˜ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค)์€ ์ด๋Ÿฌํ•œ ๋„คํŠธ์›Œํ‚น ํŠน์„ฑ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +Docker๋‚˜ Kubernetes ๊ฐ™์€ ๋ชจ๋“  ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์—๋Š” ์ด๋Ÿฌํ•œ ๋„คํŠธ์›Œํ‚น ๊ธฐ๋Šฅ์ด ํ†ตํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. -## ์ปจํ…Œ์ด๋„ˆ์™€ ํ”„๋กœ์„ธ์Šค +## ์ปจํ…Œ์ด๋„ˆ์™€ ํ”„๋กœ์„ธ์Šค { #containers-and-processes } -**์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๋Š” ๋ณดํ†ต **์ปจํ…Œ์ด๋„ˆ**๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์™€ ๋””ํดํŠธ ์ปค๋งจ๋“œ/ํ”„๋กœ๊ทธ๋žจ๊ณผ ๊ทธ ํ”„๋กœ๊ทธ๋žจ์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ปค๋งจ๋“œ ๋ผ์ธ์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•  ๋•Œ ํ•„์š”ํ•œ ๊ฐ’๋“ค๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. +**์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๋Š” ๋ณดํ†ต **์ปจํ…Œ์ด๋„ˆ**๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๊ธฐ๋ณธ ํ”„๋กœ๊ทธ๋žจ/๋ช…๋ น๊ณผ ํ•ด๋‹น ํ”„๋กœ๊ทธ๋žจ์— ์ „๋‹ฌํ•  ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ์ปค๋งจ๋“œ ๋ผ์ธ์—์„œ ์‹คํ–‰ํ•  ๋•Œ์™€ ๋งค์šฐ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. -**์ปจํ…Œ์ด๋„ˆ**๊ฐ€ ์‹œ์ž‘๋˜๋ฉด, ํ•ด๋‹น ์ปค๋งจ๋“œ/ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค (๊ทธ๋Ÿฌ๋‚˜ ๋‹ค๋ฅธ ์ปค๋งจ๋“œ/ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•˜๋„๋ก ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). +**์ปจํ…Œ์ด๋„ˆ**๊ฐ€ ์‹œ์ž‘๋˜๋ฉด ํ•ด๋‹น ๋ช…๋ น/ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค(๋‹ค๋งŒ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜์—ฌ ๋‹ค๋ฅธ ๋ช…๋ น/ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•˜๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค). -์ปจํ…Œ์ด๋„ˆ๋Š” **๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค**(์ปค๋งจ๋“œ ๋˜๋Š” ํ”„๋กœ๊ทธ๋žจ)์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +์ปจํ…Œ์ด๋„ˆ๋Š” **๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค**(๋ช…๋ น ๋˜๋Š” ํ”„๋กœ๊ทธ๋žจ)๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. -์ปจํ…Œ์ด๋„ˆ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ **๋‹จ์ผ ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ, ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค์˜ ์„œ๋ธŒ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ์ด ๋ฐฉ๋ฒ•์œผ๋กœ ํ•˜๋‚˜์˜ ์ปจํ…Œ์ด๋„ˆ์— **๋‹ค์ค‘ ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ปจํ…Œ์ด๋„ˆ๋Š” ๋ณดํ†ต **๋‹จ์ผ ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์ง€์ง€๋งŒ, ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค์—์„œ ์„œ๋ธŒํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, ๊ทธ๋Ÿฌ๋ฉด ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ์— **์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค**๊ฐ€ ์กด์žฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ **์ตœ์†Œํ•œ ํ•˜๋‚˜์˜ ์‹คํ–‰์ค‘์ธ ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์ง€์ง€ ์•Š๊ณ ์„œ๋Š” ์‹คํ–‰์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ค‘๋‹จ๋˜๋ฉด, ์ปจํ…Œ์ด๋„ˆ๋„ ์ค‘๋‹จ๋ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ **์ตœ์†Œ ํ•˜๋‚˜์˜ ์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค** ์—†์ด ์‹คํ–‰ ์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ฐ€์งˆ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ค‘์ง€๋˜๋ฉด ์ปจํ…Œ์ด๋„ˆ๋„ ์ค‘์ง€๋ฉ๋‹ˆ๋‹ค. -## FastAPI๋ฅผ ์œ„ํ•œ ๋„์ปค ์ด๋ฏธ์ง€ ๋นŒ๋“œํ•˜๊ธฐ +## FastAPI๋ฅผ ์œ„ํ•œ ๋„์ปค ์ด๋ฏธ์ง€ ๋นŒ๋“œํ•˜๊ธฐ { #build-a-docker-image-for-fastapi } -์ด์ œ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค! ๐Ÿš€ +์ข‹์Šต๋‹ˆ๋‹ค, ์ด์ œ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค! ๐Ÿš€ -**๊ณต์‹ ํŒŒ์ด์ฌ** ์ด๋ฏธ์ง€์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ, FastAPI๋ฅผ ์œ„ํ•œ **๋„์ปค ์ด๋ฏธ์ง€**๋ฅผ **๋งจ ์ฒ˜์Œ๋ถ€ํ„ฐ** ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์ด๊ฒ ์Šต๋‹ˆ๋‹ค. +**๊ณต์‹ Python** ์ด๋ฏธ์ง€์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ FastAPI์šฉ **Docker ์ด๋ฏธ์ง€**๋ฅผ **์ฒ˜์Œ๋ถ€ํ„ฐ** ๋นŒ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. -**๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ**์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ๋“ค์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด: +์ด๋Š” **๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ**์— ํ•˜๊ณ  ์‹ถ์€ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด: -* **์ฟ ๋ฒ„๋„คํ‹ฐ์Šค** ๋˜๋Š” ์œ ์‚ฌํ•œ ๋„๊ตฌ ์‚ฌ์šฉํ•˜๊ธฐ -* **๋ผ์ฆˆ๋ฒ ๋ฆฌ ํŒŒ์ด**๋กœ ์‹คํ–‰ํ•˜๊ธฐ -* ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ์‹คํ–‰ํ•  ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค ์‚ฌ์šฉํ•˜๊ธฐ ๋“ฑ +* **Kubernetes** ๋˜๋Š” ์œ ์‚ฌํ•œ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ +* **Raspberry Pi**์—์„œ ์‹คํ–‰ํ•  ๋•Œ +* ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋Œ€์‹  ์‹คํ–‰ํ•ด์ฃผ๋Š” ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋“ฑ -### ์š”๊ตฌ ํŒจํ‚ค์ง€ +### ํŒจํ‚ค์ง€ ์š”๊ตฌ์‚ฌํ•ญ { #package-requirements } -์ผ๋ฐ˜์ ์œผ๋กœ๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํŠน์ • ํŒŒ์ผ์„ ์œ„ํ•œ **ํŒจํ‚ค์ง€ ์š”๊ตฌ ์กฐ๊ฑด**์ด ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๋ณดํ†ต ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ **ํŒจํ‚ค์ง€ ์š”๊ตฌ์‚ฌํ•ญ**์„ ์–ด๋–ค ํŒŒ์ผ์— ์ ์–ด ๋‘ก๋‹ˆ๋‹ค. -๊ทธ ์š”๊ตฌ ์กฐ๊ฑด์„ **์„ค์น˜**ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๋ถ„์ด ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ์— ๋”ฐ๋ผ ๋‹ค๋ฅผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ด๋Š” ์ฃผ๋กœ ๊ทธ ์š”๊ตฌ์‚ฌํ•ญ์„ **์„ค์น˜**ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. -๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•์€ ํŒจํ‚ค์ง€ ์ด๋ฆ„๊ณผ ๋ฒ„์ „์ด ์ค„ ๋ณ„๋กœ ๊ธฐ๋ก๋œ `requirements.txt` ํŒŒ์ผ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•์€ ํŒจํ‚ค์ง€ ์ด๋ฆ„๊ณผ ๋ฒ„์ „์„ ํ•œ ์ค„์— ํ•˜๋‚˜์”ฉ ์ ์–ด ๋‘” `requirements.txt` ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. -๋ฒ„์ „์˜ ๋ฒ”์œ„๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” [FastAPI ๋ฒ„์ „๋“ค์— ๋Œ€ํ•˜์—ฌ](versions.md){.internal-link target=_blank}์— ์“ฐ์—ฌ์ง„ ๊ฒƒ๊ณผ ๊ฐ™์€ ์•„์ด๋””์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +๋ฒ„์ „ ๋ฒ”์œ„๋ฅผ ์„ค์ •ํ•  ๋•Œ๋Š” [FastAPI ๋ฒ„์ „๋“ค์— ๋Œ€ํ•˜์—ฌ](versions.md){.internal-link target=_blank}์—์„œ ์ฝ์€ ๊ฒƒ๊ณผ ๊ฐ™์€ ์•„์ด๋””์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, `requirements.txt` ํŒŒ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์˜ˆ๋ฅผ ๋“ค์–ด `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`๋กœ ํŒจํ‚ค์ง€ ์˜์กด์„ฑ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด: <div class="termy"> ```console $ pip install -r requirements.txt ---> 100% -Successfully installed fastapi pydantic uvicorn +Successfully installed fastapi pydantic ``` </div> /// info | ์ •๋ณด -ํŒจํ‚ค์ง€ ์ข…์†์„ฑ์„ ์ •์˜ํ•˜๊ณ  ์„ค์น˜ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•๊ณผ ๋„๊ตฌ๋Š” ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค. - -๋‚˜์ค‘์— ์•„๋ž˜ ์„ธ์…˜์—์„œ Poetry๋ฅผ ์‚ฌ์šฉํ•œ ์˜ˆ์‹œ๋ฅผ ๋ณด์ด๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿ‘‡ +ํŒจํ‚ค์ง€ ์˜์กด์„ฑ์„ ์ •์˜ํ•˜๊ณ  ์„ค์น˜ํ•˜๋Š” ๋‹ค๋ฅธ ํ˜•์‹๊ณผ ๋„๊ตฌ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. /// -### **FastAPI** ์ฝ”๋“œ ์ƒ์„ฑํ•˜๊ธฐ +### **FastAPI** ์ฝ”๋“œ ์ƒ์„ฑํ•˜๊ธฐ { #create-the-fastapi-code } -* `app` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. -* ๋นˆ ํŒŒ์ผ `__init__.py`์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -* ๋‹ค์Œ๊ณผ ๊ฐ™์€ `main.py`์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: +* `app` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ  ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค. +* ๋นˆ ํŒŒ์ผ `__init__.py`๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. +* ๋‹ค์Œ ๋‚ด์šฉ์œผ๋กœ `main.py` ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค: ```Python from typing import Union @@ -165,79 +162,109 @@ def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` -### ๋„์ปคํŒŒ์ผ +### Dockerfile { #dockerfile } -์ด์ œ ๊ฐ™์€ ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŒŒ์ผ `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. ๊ณต์‹ ํŒŒ์ด์ฌ ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€์—์„œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. +1. ๊ณต์‹ Python ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€์—์„œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. -2. ํ˜„์žฌ ์›Œํ‚น ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ `/code`๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. +2. ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ `/code`๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. - ์—ฌ๊ธฐ์— `requirements.txt` ํŒŒ์ผ๊ณผ `app` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์œ„์น˜์‹œํ‚ฌ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + ์—ฌ๊ธฐ์— `requirements.txt` ํŒŒ์ผ๊ณผ `app` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋‘˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -3. ์š”๊ตฌ ์กฐ๊ฑด๊ณผ ํŒŒ์ผ์„ `/code` ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค. +3. ์š”๊ตฌ์‚ฌํ•ญ ํŒŒ์ผ์„ `/code` ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค. - ์ฒ˜์Œ์—๋Š” **์˜ค์ง** ์š”๊ตฌ ์กฐ๊ฑด์ด ํ•„์š”ํ•œ ํŒŒ์ผ๋งŒ ๋ณต์‚ฌํ•˜๊ณ , ์ด์™ธ์˜ ์ฝ”๋“œ๋Š” ๊ทธ๋Œ€๋กœ ๋‘ก๋‹ˆ๋‹ค. + ์ฒ˜์Œ์—๋Š” ์š”๊ตฌ์‚ฌํ•ญ ํŒŒ์ผ๋งŒ **๋‹จ๋…์œผ๋กœ** ๋ณต์‚ฌํ•˜๊ณ , ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋Š” ๋ณต์‚ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. - ์ด ํŒŒ์ผ์ด **์ž์ฃผ ๋ฐ”๋€Œ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—**, ๋„์ปค๋Š” ํŒŒ์ผ์„ ํƒ์ง€ํ•˜์—ฌ ์ด ๋‹จ๊ณ„์˜ **์บ์‹œ**๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋„ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. + ์ด ํŒŒ์ผ์€ **์ž์ฃผ ๋ฐ”๋€Œ์ง€ ์•Š๊ธฐ** ๋•Œ๋ฌธ์— Docker๋Š” ์ด๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ์ด ๋‹จ๊ณ„์—์„œ **์บ์‹œ**๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋„ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. -4. ์š”๊ตฌ ์กฐ๊ฑด ํŒŒ์ผ์— ์žˆ๋Š” ํŒจํ‚ค์ง€ ์ข…์†์„ฑ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. +4. ์š”๊ตฌ์‚ฌํ•ญ ํŒŒ์ผ์— ์žˆ๋Š” ํŒจํ‚ค์ง€ ์˜์กด์„ฑ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. - `--no-cache-dir` ์˜ต์…˜์€ `pip`์—๊ฒŒ ๋‹ค์šด๋กœ๋“œํ•œ ํŒจํ‚ค์ง€๋“ค์„ ๋กœ์ปฌ ํ™˜๊ฒฝ์— ์ €์žฅํ•˜์ง€ ์•Š๋„๋ก ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋งˆ์น˜ ๊ฐ™์€ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ธฐ ์œ„ํ•ด ์˜ค์ง `pip`๋งŒ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™์ง€๋งŒ, ์ปจํ…Œ์ด๋„ˆ๋กœ ์ž‘์—…ํ•˜๋Š” ๊ฒฝ์šฐ ๊ทธ๋ ‡์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. + `--no-cache-dir` ์˜ต์…˜์€ `pip`๊ฐ€ ๋‹ค์šด๋กœ๋“œํ•œ ํŒจํ‚ค์ง€๋ฅผ ๋กœ์ปฌ์— ์ €์žฅํ•˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” `pip`๊ฐ€ ๊ฐ™์€ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ์‹คํ–‰๋  ๋•Œ๋งŒ ์˜๋ฏธ๊ฐ€ ์žˆ์ง€๋งŒ, ์ปจํ…Œ์ด๋„ˆ ์ž‘์—…์—์„œ๋Š” ๊ทธ๋ ‡์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. - /// note | ๋…ธํŠธ + /// note | ์ฐธ๊ณ  - `--no-cache-dir` ๋Š” ์˜ค์ง `pip`์™€ ๊ด€๋ จ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๋„์ปค๋‚˜ ์ปจํ…Œ์ด๋„ˆ์™€๋Š” ๋ฌด๊ด€ํ•ฉ๋‹ˆ๋‹ค. + `--no-cache-dir`๋Š” `pip`์—๋งŒ ๊ด€๋ จ๋˜์–ด ์žˆ์œผ๋ฉฐ Docker๋‚˜ ์ปจํ…Œ์ด๋„ˆ์™€๋Š” ๊ด€๋ จ์ด ์—†์Šต๋‹ˆ๋‹ค. /// - `--upgrade` ์˜ต์…˜์€ `pip`์—๊ฒŒ ์„ค์น˜๋œ ํŒจํ‚ค์ง€๋“ค์„ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. + `--upgrade` ์˜ต์…˜์€ ์ด๋ฏธ ์„ค์น˜๋œ ํŒจํ‚ค์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด `pip`๊ฐ€ ์ด๋ฅผ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. - ์ด์ „ ๋‹จ๊ณ„์—์„œ ํŒŒ์ผ์„ ๋ณต์‚ฌํ•œ ๊ฒƒ์ด **๋„์ปค ์บ์‹œ**์— ์˜ํ•ด ํƒ์ง€๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด ๋‹จ๊ณ„์—์„œ๋„ ๊ฐ€๋Šฅํ•œ ํ•œ **๋„์ปค ์บ์‹œ**๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + ์ด์ „ ๋‹จ๊ณ„์—์„œ ํŒŒ์ผ์„ ๋ณต์‚ฌํ•œ ๊ฒƒ์ด **Docker ์บ์‹œ**์— ์˜ํ•ด ๊ฐ์ง€๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ด ๋‹จ๊ณ„์—์„œ๋„ ๊ฐ€๋Šฅํ•˜๋ฉด **Docker ์บ์‹œ๋ฅผ ์‚ฌ์šฉ**ํ•ฉ๋‹ˆ๋‹ค. - ์ด ๋‹จ๊ณ„์—์„œ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด **๋งค๋ฒˆ** ๋ชจ๋“  ์ข…์†์„ฑ์„ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๊ณ  ์„ค์น˜ํ•  ํ•„์š”๊ฐ€ ์—†์–ด, ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ง€์†์ ์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๋ฐ์— ๋“œ๋Š” **์‹œ๊ฐ„**์„ ๋งŽ์ด **์ ˆ์•ฝ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + ์ด ๋‹จ๊ณ„์—์„œ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐœ๋ฐœ ์ค‘์— ์ด๋ฏธ์ง€๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ๋นŒ๋“œํ•  ๋•Œ, ์˜์กด์„ฑ์„ **๋งค๋ฒˆ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์„ค์น˜ํ•˜๋Š”** ๋Œ€์‹  ๋งŽ์€ **์‹œ๊ฐ„**์„ **์ ˆ์•ฝ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -5. `/code` ๋””๋ ‰ํ„ฐ๋ฆฌ์— `./app` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค. +5. `./app` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ `/code` ๋””๋ ‰ํ„ฐ๋ฆฌ ์•ˆ์œผ๋กœ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค. - **์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š”** ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋„์ปค **์บ์‹œ**๋Š” ์ด ๋‹จ๊ณ„๋‚˜ **์ดํ›„์˜ ๋‹จ๊ณ„์—์„œ** ์ž˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + ์ด ๋””๋ ‰ํ„ฐ๋ฆฌ์—๋Š” **๊ฐ€์žฅ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š”** ์ฝ”๋“œ๊ฐ€ ๋ชจ๋‘ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, Docker **์บ์‹œ**๋Š” ์ด ๋‹จ๊ณ„๋‚˜ **์ดํ›„ ๋‹จ๊ณ„๋“ค**์—์„œ๋Š” ์‰ฝ๊ฒŒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. - ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ ๋นŒ๋“œ ์‹œ๊ฐ„์„ ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•ด `Dockerfile`์˜ **๊ฑฐ์˜ ๋ ๋ถ€๋ถ„**์— ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. + ๋”ฐ๋ผ์„œ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ ๋นŒ๋“œ ์‹œ๊ฐ„์„ ์ตœ์ ํ™”ํ•˜๋ ค๋ฉด `Dockerfile`์˜ **๋๋ถ€๋ถ„ ๊ทผ์ฒ˜**์— ๋‘๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. -6. `uvicorn` ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด **์ปค๋งจ๋“œ**๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. +6. ๋‚ด๋ถ€์ ์œผ๋กœ Uvicorn์„ ์‚ฌ์šฉํ•˜๋Š” `fastapi run`์„ ์‚ฌ์šฉํ•˜๋„๋ก **๋ช…๋ น**์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. - `CMD`๋Š” ๋ฌธ์ž์—ด ๋ฆฌ์ŠคํŠธ๋ฅผ ์ž…๋ ฅ๋ฐ›๊ณ , ๊ฐ ๋ฌธ์ž์—ด์€ ์ปค๋งจ๋“œ ๋ผ์ธ์˜ ๊ฐ ์ค„์— ์ž…๋ ฅํ•  ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. + `CMD`๋Š” ๋ฌธ์ž์—ด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ›์œผ๋ฉฐ, ๊ฐ ๋ฌธ์ž์—ด์€ ์ปค๋งจ๋“œ ๋ผ์ธ์—์„œ ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„ํ•ด ์ž…๋ ฅํ•˜๋Š” ํ•ญ๋ชฉ๋“ค์ž…๋‹ˆ๋‹ค. - ์ด ์ปค๋งจ๋“œ๋Š” **ํ˜„์žฌ ์›Œํ‚น ๋””๋ ‰ํ„ฐ๋ฆฌ**์—์„œ ์‹คํ–‰๋˜๋ฉฐ, ์ด๋Š” ์œ„์—์„œ `WORKDIR /code`๋กœ ์„ค์ •ํ•œ `/code` ๋””๋ ‰ํ„ฐ๋ฆฌ์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. - - ํ”„๋กœ๊ทธ๋žจ์ด `/code`์—์„œ ์‹œ์ž‘ํ•˜๊ณ  ๊ทธ ์†์— `./app` ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ๋“ค์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์—, **Uvicorn**์€ ์ด๋ฅผ ๋ณด๊ณ  `app`์„ `app.main`์œผ๋กœ๋ถ€ํ„ฐ **๋ถˆ๋Ÿฌ ์˜ฌ** ๊ฒƒ์ž…๋‹ˆ๋‹ค. + ์ด ๋ช…๋ น์€ **ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ„ฐ๋ฆฌ**์—์„œ ์‹คํ–‰๋˜๋ฉฐ, ์ด๋Š” ์œ„์—์„œ `WORKDIR /code`๋กœ ์„ค์ •ํ•œ `/code` ๋””๋ ‰ํ„ฐ๋ฆฌ์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. /// tip | ํŒ -๊ฐ ์ฝ”๋“œ ๋ผ์ธ์„ ์ฝ”๋“œ์˜ ์ˆซ์ž ๋ฒ„๋ธ”์„ ํด๋ฆญํ•˜์—ฌ ๋ฆฌ๋ทฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ‘† +์ฝ”๋“œ์˜ ๊ฐ ์ˆซ์ž ๋ฒ„๋ธ”์„ ํด๋ฆญํ•ด ๊ฐ ์ค„์ด ํ•˜๋Š” ์ผ์„ ํ™•์ธํ•˜์„ธ์š”. ๐Ÿ‘† /// -์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค: +/// warning | ๊ฒฝ๊ณ  + +์•„๋ž˜์—์„œ ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ `CMD` ์ง€์‹œ์–ด๋Š” **ํ•ญ์ƒ** **exec form**์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +/// + +#### `CMD` ์‚ฌ์šฉํ•˜๊ธฐ - Exec Form { #use-cmd-exec-form } + +Docker ์ง€์‹œ์–ด <a href="https://docs.docker.com/reference/dockerfile/#cmd" class="external-link" target="_blank">`CMD`</a>๋Š” ๋‘ ๊ฐ€์ง€ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +โœ… **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๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ข…๋ฃŒ(graceful shutdown)๋˜๊ณ  [lifespan ์ด๋ฒคํŠธ](../advanced/events.md){.internal-link target=_blank}๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜๋„๋ก ํ•˜๋ ค๋ฉด, ํ•ญ์ƒ **exec** form์„ ์‚ฌ์šฉํ•˜์„ธ์š”. + +์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://docs.docker.com/reference/dockerfile/#shell-and-exec-form" class="external-link" target="_blank">shell and exec form์— ๋Œ€ํ•œ Docker ๋ฌธ์„œ</a>๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +์ด๋Š” `docker compose`๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ฝค ๋ˆˆ์— ๋Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ข€ ๋” ๊ธฐ์ˆ ์ ์ธ ์ƒ์„ธ ๋‚ด์šฉ์€ Docker Compose FAQ ์„น์…˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”: <a href="https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop" class="external-link" target="_blank">Why do my services take 10 seconds to recreate or stop?</a>. + +#### ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ์กฐ { #directory-structure } + +์ด์ œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ์กฐ๊ฐ€ ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: ``` . @@ -248,51 +275,51 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] โ””โ”€โ”€ requirements.txt ``` -#### TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ์˜ ๋ฐฐํ›„ +#### TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ์˜ ๋ฐฐํ›„ { #behind-a-tls-termination-proxy } -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด ์ปจํ…Œ์ด๋„ˆ๋ฅผ 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-cache } -์ด `Dockerfile`์—๋Š” ์ค‘์š”ํ•œ ํŠธ๋ฆญ์ด ์žˆ๋Š”๋ฐ, ์ฒ˜์Œ์—๋Š” **์˜์กด์„ฑ์ด ์žˆ๋Š” ํŒŒ์ผ๋งŒ** ๋ณต์‚ฌํ•˜๊ณ , ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋Š” ๊ทธ๋Œ€๋กœ ๋‘ก๋‹ˆ๋‹ค. ์™œ ์ด๋Ÿฐ ๋ฐฉ๋ฒ•์„ ์จ์•ผํ•˜๋Š”์ง€ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. +์ด `Dockerfile`์—๋Š” ์ค‘์š”ํ•œ ํŠธ๋ฆญ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋จผ์ € **์˜์กด์„ฑ ํŒŒ์ผ๋งŒ** ๋ณต์‚ฌํ•˜๊ณ , ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋Š” ๋ณต์‚ฌํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์™œ ๊ทธ๋Ÿฐ์ง€ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ```Dockerfile COPY ./requirements.txt /code/requirements.txt ``` -๋„์ปค์™€ ๋‹ค๋ฅธ ๋„๊ตฌ๋“ค์€ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ **์ฆ๊ฐ€ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋นŒ๋“œ**ํ•ฉ๋‹ˆ๋‹ค. `Dockerfile`์˜ ๋งจ ์œ— ๋ถ€๋ถ„๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด, ๋ ˆ์ด์–ด ์œ„์— ์ƒˆ๋กœ์šด ๋ ˆ์ด์–ด๋ฅผ ๋”ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ, `Dockerfile`์˜ ๊ฐ ์ง€์‹œ ์‚ฌํ•ญ์œผ๋กœ ๋ถ€ํ„ฐ ์ƒ์„ฑ๋œ ์–ด๋–ค ํŒŒ์ผ์ด๋“  ๋”ํ•ด๊ฐ‘๋‹ˆ๋‹ค. +Docker์™€ ๋‹ค๋ฅธ ๋„๊ตฌ๋“ค์€ `Dockerfile`์˜ ์œ„์—์„œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด, ๊ฐ ์ง€์‹œ์–ด๊ฐ€ ๋งŒ๋“  ํŒŒ์ผ์„ ํฌํ•จํ•˜๋ฉฐ **๋ ˆ์ด์–ด๋ฅผ ํ•˜๋‚˜์”ฉ ์œ„์— ์Œ“๋Š” ๋ฐฉ์‹์œผ๋กœ** ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ **์ ์ง„์ ์œผ๋กœ** ๋นŒ๋“œํ•ฉ๋‹ˆ๋‹ค. -๋„์ปค ๊ทธ๋ฆฌ๊ณ  ์ด์™€ ์œ ์‚ฌํ•œ ๋„๊ตฌ๋“ค์€ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ์— **๋‚ด๋ถ€ ์บ์‹œ**๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์–ด๋–ค ํŒŒ์ผ์ด ๋งˆ์ง€๋ง‰์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•œ ๋•Œ๋กœ๋ถ€ํ„ฐ ๋ฐ”๋€Œ์ง€ ์•Š์•˜๋‹ค๋ฉด, ํŒŒ์ผ์„ ๋‹ค์‹œ ๋ณต์‚ฌํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋ ˆ์ด์–ด๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋งˆ์ง€๋ง‰์— ์ƒ์„ฑํ–ˆ๋˜ **๊ฐ™์€ ๋ ˆ์ด์–ด๋ฅผ ์žฌ์‚ฌ์šฉ**ํ•ฉ๋‹ˆ๋‹ค. +Docker์™€ ์œ ์‚ฌํ•œ ๋„๊ตฌ๋“ค์€ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•  ๋•Œ **๋‚ด๋ถ€ ์บ์‹œ**๋„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ํŒŒ์ผ์ด ๋งˆ์ง€๋ง‰์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ–ˆ์„ ๋•Œ๋ถ€ํ„ฐ ๋ฐ”๋€Œ์ง€ ์•Š์•˜๋‹ค๋ฉด, ํŒŒ์ผ์„ ๋‹ค์‹œ ๋ณต์‚ฌํ•˜๊ณ  ์ƒˆ ๋ ˆ์ด์–ด๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋งŒ๋“œ๋Š” ๋Œ€์‹ , ์ด์ „์— ๋งŒ๋“  **๊ฐ™์€ ๋ ˆ์ด์–ด๋ฅผ ์žฌ์‚ฌ์šฉ**ํ•ฉ๋‹ˆ๋‹ค. -๋‹จ์ง€ ํŒŒ์ผ ๋ณต์‚ฌ๋ฅผ ์ง€์–‘ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ํšจ์œจ์ด ๋งŽ์ด ํ–ฅ์ƒ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ์ง€๋งŒ, ๊ทธ ๋‹จ๊ณ„์—์„œ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, **๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์บ์‹œ๋ฅผ ์‚ฌ์šฉ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜์กด์„ฑ์„ ์„ค์น˜ํ•˜๋Š” ์ง€์‹œ ์‚ฌํ•ญ์„ ์œ„ํ•œ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +ํŒŒ์ผ ๋ณต์‚ฌ๋ฅผ ํ”ผํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ํฐ ๊ฐœ์„ ์ด ์ƒ๊ธฐ์ง€๋Š” ์•Š์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•ด๋‹น ๋‹จ๊ณ„์—์„œ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— **๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋„ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜** ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์˜์กด์„ฑ์„ ์„ค์น˜ํ•˜๋Š” ์ง€์‹œ์–ด์—์„œ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```Dockerfile RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt ``` -ํŒจํ‚ค์ง€๋ฅผ ํฌํ•จํ•˜๋Š” ํŒŒ์ผ์€ **์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**. ๋”ฐ๋ผ์„œ ํ•ด๋‹น ํŒŒ์ผ๋งŒ ๋ณต์‚ฌํ•˜๋ฏ€๋กœ์„œ, ๋„์ปค๋Š” ๊ทธ ๋‹จ๊ณ„์˜ **์บ์‹œ๋ฅผ ์‚ฌ์šฉ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํŒจํ‚ค์ง€ ์š”๊ตฌ์‚ฌํ•ญ ํŒŒ์ผ์€ **์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**. ๋”ฐ๋ผ์„œ ๊ทธ ํŒŒ์ผ๋งŒ ๋ณต์‚ฌํ•˜๋ฉด Docker๋Š” ๊ทธ ๋‹จ๊ณ„์—์„œ **์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜** ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ ๋‹ค์Œ์œผ๋กœ, ๋„์ปค๋Š” **๋‹ค์Œ ๋‹จ๊ณ„์—์„œ** ์˜์กด์„ฑ์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์„ค์น˜ํ•˜๋Š” **์บ์‹œ๋ฅผ ์‚ฌ์šฉ**ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์ด ๊ณผ์ •์—์„œ ์šฐ๋ฆฌ๋Š” **๋งŽ์€ ์‹œ๊ฐ„์„ ์ ˆ์•ฝ**ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. โœจ ...๊ทธ๋ฆฌ๊ณ  ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ง€๋ฃจํ•จ๋„ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ช๐Ÿ˜† +๊ทธ๋ฆฌ๊ณ  Docker๋Š” ๊ทธ ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ ์˜์กด์„ฑ์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์„ค์น˜ํ•  ๋•Œ๋„ **์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜** ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์—ฌ๊ธฐ์—์„œ **๋งŽ์€ ์‹œ๊ฐ„์„ ์ ˆ์•ฝ**ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. โœจ ...๊ทธ๋ฆฌ๊ณ  ๊ธฐ๋‹ค๋ฆฌ๋ฉฐ ์ง€๋ฃจํ•ด์ง€๋Š” ๊ฒƒ๋„ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ช๐Ÿ˜† -ํŒจํ‚ค์ง€ ์˜์กด์„ฑ์„ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๊ณ  ์„ค์น˜ํ•˜๋Š” ๋ฐ์ด๋Š” **์ˆ˜ ๋ถ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ**, **์บ์‹œ**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ๋Œ€ **์ˆ˜ ์ดˆ๋งŒ์—** ๋๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํŒจํ‚ค์ง€ ์˜์กด์„ฑ์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์„ค์น˜ํ•˜๋Š” ๋ฐ์—๋Š” **๋ช‡ ๋ถ„**์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ, **์บ์‹œ**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋งŽ์•„์•ผ **๋ช‡ ์ดˆ**๋ฉด ๋๋‚ฉ๋‹ˆ๋‹ค. -๋˜ํ•œ ์—ฌ๋Ÿฌ๋ถ„์ด ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ์ฝ”๋“œ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๋ฐ˜์˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๊ณ„์†ํ•ด์„œ ๋นŒ๋“œํ•˜๋ฉด, ์ ˆ์•ฝ๋œ ์‹œ๊ฐ„์€ ์ถ•์ ๋˜์–ด ๋”์šฑ ์ปค์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๋˜ํ•œ ๊ฐœ๋ฐœ ์ค‘์— ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๊ณ„์† ๋นŒ๋“œํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ, ์ด๋ ‡๊ฒŒ ์ ˆ์•ฝ๋˜๋Š” ์‹œ๊ฐ„์€ ๋ˆ„์ ๋˜์–ด ์ƒ๋‹นํžˆ ์ปค์ง‘๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ๋‚˜์„œ `Dockerfile`์˜ ๊ฑฐ์˜ ๋ ๋ถ€๋ถ„์—์„œ, ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด **๊ฐ€์žฅ ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ณ€๊ฒฝ**๋˜๋Š” ๋ถ€๋ถ„์ด๋ฉฐ, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์— ์ด ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋Š” ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— ๋‘ก๋‹ˆ๋‹ค. +๊ทธ ๋‹ค์Œ `Dockerfile`์˜ ๋๋ถ€๋ถ„ ๊ทผ์ฒ˜์—์„œ ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ **๊ฐ€์žฅ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š”** ๋ถ€๋ถ„์ด๋ฏ€๋กœ, ๊ฑฐ์˜ ํ•ญ์ƒ ์ด ๋‹จ๊ณ„ ์ดํ›„์—๋Š” ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋๋ถ€๋ถ„์— ๋‘ก๋‹ˆ๋‹ค. ```Dockerfile COPY ./app /code/app ``` -### ๋„์ปค ์ด๋ฏธ์ง€ ์ƒ์„ฑํ•˜๊ธฐ +### ๋„์ปค ์ด๋ฏธ์ง€ ์ƒ์„ฑํ•˜๊ธฐ { #build-the-docker-image } -์ด์ œ ๋ชจ๋“  ํŒŒ์ผ์ด ์ œ์ž๋ฆฌ์— ์žˆ์œผ๋‹ˆ, ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•ฉ๋‹ˆ๋‹ค. +์ด์ œ ๋ชจ๋“  ํŒŒ์ผ์ด ์ œ์ž๋ฆฌ์— ์žˆ์œผ๋‹ˆ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•ด๋ด…์‹œ๋‹ค. -* (์—ฌ๋Ÿฌ๋ถ„์˜ `Dockerfile`๊ณผ `app` ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ์œ„์น˜ํ•œ) ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. +* ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค(`Dockerfile`์ด ์žˆ๊ณ  `app` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ํฌํ•จํ•˜๋Š” ์œ„์น˜). * FastAPI ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•ฉ๋‹ˆ๋‹ค: <div class="termy"> @@ -307,13 +334,13 @@ $ docker build -t myimage . /// tip | ํŒ -๋งจ ๋์— ์žˆ๋Š” `.` ์— ์ฃผ๋ชฉํ•ฉ์‹œ๋‹ค. ์ด๋Š” `./`์™€ ๋™๋“ฑํ•˜๋ฉฐ, ๋„์ปค์—๊ฒŒ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜๊ธฐ ์œ„ํ•œ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. +๋์— ์žˆ๋Š” `.`์— ์ฃผ๋ชฉํ•˜์„ธ์š”. ์ด๋Š” `./`์™€ ๋™์ผํ•˜๋ฉฐ, Docker์—๊ฒŒ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•  ๋•Œ ์‚ฌ์šฉํ•  ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ์—๋Š” ํ˜„์žฌ ๋””๋ ‰ํ„ฐ๋ฆฌ(`.`)์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ ํ˜„์žฌ ๋””๋ ‰ํ„ฐ๋ฆฌ(`.`)์ž…๋‹ˆ๋‹ค. /// -### ๋„์ปค ์ปจํ…Œ์ด๋„ˆ ์‹œ์ž‘ํ•˜๊ธฐ +### ๋„์ปค ์ปจํ…Œ์ด๋„ˆ ์‹œ์ž‘ํ•˜๊ธฐ { #start-the-docker-container } * ์—ฌ๋Ÿฌ๋ถ„์˜ ์ด๋ฏธ์ง€์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค: @@ -325,35 +352,35 @@ $ docker run -d --name mycontainer -p 80:80 myimage </div> -## ์ฒดํฌํ•˜๊ธฐ +## ํ™•์ธํ•˜๊ธฐ { #check-it } -์—ฌ๋Ÿฌ๋ถ„์˜ ๋„์ปค ์ปจํ…Œ์ด๋„ˆ URL์—์„œ ์‹คํ–‰ ์‚ฌํ•ญ์„ ์ฒดํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> ๋˜๋Š” <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (๋˜๋Š” ๋™์ผํ•˜๊ฒŒ, ์—ฌ๋Ÿฌ๋ถ„์˜ ๋„์ปค ํ˜ธ์ŠคํŠธ๋ฅผ ์ด์šฉํ•ด์„œ ์ฒดํฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค). +Docker ์ปจํ…Œ์ด๋„ˆ์˜ URL์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> ๋˜๋Š” <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a>(๋˜๋Š” Docker ํ˜ธ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ๋™๋“ฑํ•˜๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). -์•„๋ž˜์™€ ๋น„์Šทํ•œ ๊ฒƒ์„ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค: +์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒƒ์„ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค: ```JSON {"item_id": 5, "q": "somequery"} ``` -## ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ API ๋ฌธ์„œ +## ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ API ๋ฌธ์„œ { #interactive-api-docs } -์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> ๋˜๋Š” <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a>๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๋˜๋Š”, ์—ฌ๋Ÿฌ๋ถ„์˜ ๋„์ปค ํ˜ธ์ŠคํŠธ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). +์ด์ œ <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> ๋˜๋Š” <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a>(๋˜๋Š” Docker ํ˜ธ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ๋™๋“ฑํ•˜๊ฒŒ ์ ‘๊ทผ)๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ๋ถ„์€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ API(<a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>์—์„œ ์ œ๊ณต๋œ)๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ API ๋ฌธ์„œ(<a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> ์ œ๊ณต)๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -## ๋Œ€์•ˆ API ๋ฌธ์„œ +## ๋Œ€์•ˆ API ๋ฌธ์„œ { #alternative-api-docs } -๋˜ํ•œ ์—ฌ๋Ÿฌ๋ถ„์€ <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> ๋˜๋Š” <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a>์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๋˜๋Š”, ์—ฌ๋Ÿฌ๋ถ„์˜ ๋„์ปค ํ˜ธ์ŠคํŠธ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). +๋˜ํ•œ <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> ๋˜๋Š” <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a>(๋˜๋Š” Docker ํ˜ธ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ๋™๋“ฑํ•˜๊ฒŒ ์ ‘๊ทผ)๋กœ ์ด๋™ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ๋ถ„์€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ๋Œ€์•ˆ ๋ฌธ์„œ(<a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>์—์„œ ์ œ๊ณต๋œ)๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋Œ€์•ˆ ์ž๋™ ๋ฌธ์„œ(<a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> ์ œ๊ณต)๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## ๋‹จ์ผ ํŒŒ์ผ FastAPI๋กœ ๋„์ปค ์ด๋ฏธ์ง€ ์ƒ์„ฑํ•˜๊ธฐ +## ๋‹จ์ผ ํŒŒ์ผ FastAPI๋กœ ๋„์ปค ์ด๋ฏธ์ง€ ๋นŒ๋“œํ•˜๊ธฐ { #build-a-docker-image-with-a-single-file-fastapi } -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์˜ FastAPI๊ฐ€ ํ•˜๋‚˜์˜ ํŒŒ์ผ์ด๋ผ๋ฉด, ์˜ˆ๋ฅผ ๋“ค์–ด `./app` ๋””๋ ‰ํ„ฐ๋ฆฌ ์—†์ด `main.py` ํŒŒ์ผ๋งŒ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค๋ฉด, ํŒŒ์ผ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ์œ ์‚ฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: +FastAPI๊ฐ€ ๋‹จ์ผ ํŒŒ์ผ(์˜ˆ: `./app` ๋””๋ ‰ํ„ฐ๋ฆฌ ์—†์ด `main.py`๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ)์ด๋ผ๋ฉด, ํŒŒ์ผ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ``` . @@ -362,7 +389,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage โ””โ”€โ”€ requirements.txt ``` -๊ทธ๋Ÿฌ๋ฉด ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ `Dockerfile` ๋‚ด์— ์žˆ๋Š” ํŒŒ์ผ์„ ๋ณต์‚ฌํ•˜๊ธฐ ์œ„ํ•ด ๊ทธ์ € ์ƒ์‘ํ•˜๋Š” ๊ฒฝ๋กœ๋ฅผ ๋ฐ”๊พธ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: +๊ทธ๋Ÿฐ ๋‹ค์Œ `Dockerfile`์—์„œ ํ•ด๋‹น ํŒŒ์ผ์„ ๋ณต์‚ฌํ•˜๋„๋ก ๊ฒฝ๋กœ๋งŒ ๋งž๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: ```{ .dockerfile .annotate hl_lines="10 13" } FROM python:3.9 @@ -373,359 +400,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์„ ์‹คํ–‰ํ•ด `app` ๊ฐ์ฒด๋ฅผ (`app.main` ๋Œ€์‹ ) `main`์œผ๋กœ ๋ถ€ํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. +2. ๋‹จ์ผ ํŒŒ์ผ `main.py`์— ์žˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ œ๊ณต(serve)ํ•˜๊ธฐ ์œ„ํ•ด `fastapi run`์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -๊ทธ ๋‹ค์Œ Uvicorn ์ปค๋งจ๋“œ๋ฅผ ์กฐ์ •ํ•ด์„œ FastAPI ๊ฐ์ฒด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š”๋ฐ `app.main` ๋Œ€์‹ ์— ์ƒˆ๋กœ์šด ๋ชจ๋“ˆ `main`์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. +`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๋Š” ๋ณดํ†ต ๋‹ค๋ฅธ ๋„๊ตฌ์— ์˜ํ•ด **์™ธ๋ถ€์ ์œผ๋กœ** ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. -**HTTPS**์™€ **์ธ์ฆ์„œ**์˜ **์ž๋™** ์ทจ๋“์„ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์€ ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์˜ˆ๋ฅผ ๋“ค์–ด <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>์„ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ **HTTPS**์™€ **์ธ์ฆ์„œ**์˜ **์ž๋™** ํš๋“์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// tip | ํŒ -Traefik์€ ๋„์ปค, ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค, ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๋„๊ตฌ์™€ ํ†ตํ•ฉ๋˜์–ด ์žˆ์–ด ์—ฌ๋Ÿฌ๋ถ„์˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํฌํ•จํ•˜๋Š” HTTPS๋ฅผ ์…‹์—…ํ•˜๊ณ  ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. +Traefik์€ Docker, Kubernetes ๋“ฑ๊ณผ ํ†ตํ•ฉ๋˜์–ด ์žˆ์–ด, ์ด๋ฅผ ์‚ฌ์šฉํ•ด ์ปจํ…Œ์ด๋„ˆ์— HTTPS๋ฅผ ์„ค์ •ํ•˜๊ณ  ๊ตฌ์„ฑํ•˜๊ธฐ๊ฐ€ ๋งค์šฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. /// -๋Œ€์•ˆ์ ์œผ๋กœ, HTTPS๋Š” ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž์— ์˜ํ•ด ์„œ๋น„์Šค์˜ ์ผํ™˜์œผ๋กœ ๋‹ค๋ฃจ์–ด์งˆ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค (์ด๋•Œ๋„ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์—ฌ์ „ํžˆ ์ปจํ…Œ์ด๋„ˆ์—์„œ ์‹คํ–‰๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค). +๋˜๋Š” HTTPS๋ฅผ ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž๊ฐ€ ์„œ๋น„์Šค์˜ ์ผ๋ถ€๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค(์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์—ฌ์ „ํžˆ ์ปจํ…Œ์ด๋„ˆ์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค). -## ๊ตฌ๋™๊ณผ ์žฌ์‹œ์ž‘ +## ์‹œ์ž‘ ์‹œ ์ž๋™ ์‹คํ–‰๊ณผ ์žฌ์‹œ์ž‘ { #running-on-startup-and-restarts } -์—ฌ๋Ÿฌ๋ถ„์˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ **์‹œ์ž‘ํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š”** ๋ฐ์— ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋„๊ตฌ๋Š” ๋”ฐ๋กœ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ณดํ†ต ์ปจํ…Œ์ด๋„ˆ๋ฅผ **์‹œ์ž‘ํ•˜๊ณ  ์‹คํ–‰**ํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•˜๋Š” ๋‹ค๋ฅธ ๋„๊ตฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๋Š” **๋„์ปค** ์ž์ฒด์ผ ์ˆ˜๋„ ์žˆ๊ณ , **๋„์ปค ์ปดํฌ์ฆˆ**, **์ฟ ๋ฒ„๋„คํ‹ฐ์Šค**, **ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค** ๋“ฑ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ง์ ‘ **Docker**์ผ ์ˆ˜๋„ ์žˆ๊ณ , **Docker Compose**, **Kubernetes**, **ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค** ๋“ฑ์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -๋Œ€๋ถ€๋ถ„ (๋˜๋Š” ์ „์ฒด) ๊ฒฝ์šฐ์—, ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ตฌ๋™ํ•˜๊ฑฐ๋‚˜ ๊ณ ์žฅ์‹œ์— ์žฌ์‹œ์ž‘ํ•˜๋„๋ก ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋„์ปค์—์„œ๋Š”, ์ปค๋งจ๋“œ ๋ผ์ธ ์˜ต์…˜ `--restart` ์ž…๋‹ˆ๋‹ค. +๋Œ€๋ถ€๋ถ„(๋˜๋Š” ์ „๋ถ€)์˜ ๊ฒฝ์šฐ, ์‹œ์ž‘ ์‹œ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์‹คํŒจ ์‹œ ์žฌ์‹œ์ž‘์„ ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Docker์—์„œ๋Š” ์ปค๋งจ๋“œ ๋ผ์ธ ์˜ต์…˜ `--restart`์ž…๋‹ˆ๋‹ค. -์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ ์„œ๋Š”, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ๋™ํ•˜๊ณ  ์žฌ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ๋ฒˆ๊ฑฐ๋กญ๊ณ  ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ **์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด** ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์— ์ด๋Ÿฐ ๊ธฐ๋Šฅ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. โœจ +์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘ ์‹œ ์ž๋™ ์‹คํ–‰ํ•˜๊ณ  ์žฌ์‹œ์ž‘๊นŒ์ง€ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋ฒˆ๊ฑฐ๋กญ๊ณ  ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ **์ปจํ…Œ์ด๋„ˆ๋กœ ์ž‘์—…ํ•  ๋•Œ**๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๊ทธ ๊ธฐ๋Šฅ์ด ๊ธฐ๋ณธ์œผ๋กœ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. โœจ -## ๋ณต์ œ - ํ”„๋กœ์„ธ์Šค ๊ฐœ์ˆ˜ +## ๋ณต์ œ - ํ”„๋กœ์„ธ์Šค ๊ฐœ์ˆ˜ { #replication-number-of-processes } -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด **์ฟ ๋ฒ„๋„คํ‹ฐ์Šค**์™€ ๋จธ์‹  <abbr title="A group of machines that are configured to be connected and work together in some way.">ํด๋Ÿฌ์Šคํ„ฐ</abbr>, ๋„์ปค ์Šค์™ ๋ชจ๋“œ, ๋…ธ๋งˆ๋“œ, ๋˜๋Š” ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ ๋จธ์‹  ์œ„์— ๋ถ„์‚ฐ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ณต์žกํ•œ ์‹œ์Šคํ…œ์„ ๋‹ค๋ฃจ๊ณ  ์žˆ๋‹ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์€ ๊ฐ ์ปจํ…Œ์ด๋„ˆ์—์„œ (์›Œ์ปค์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” Gunicorn ๊ฐ™์€) **ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €** ๋Œ€์‹  **ํด๋Ÿฌ์Šคํ„ฐ ๋ ˆ๋ฒจ**์—์„œ **๋ณต์ œ๋ฅผ ๋‹ค๋ฃจ**๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**Kubernetes**, Docker Swarm Mode, Nomad ๋“ฑ์˜ ๋ณต์žกํ•œ ์‹œ์Šคํ…œ์œผ๋กœ ์—ฌ๋Ÿฌ ๋จธ์‹ ์— ๋ถ„์‚ฐ๋œ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” <abbr title="A group of machines that are configured to be connected and work together in some way.">cluster</abbr>๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๊ฐ ์ปจํ…Œ์ด๋„ˆ์—์„œ(**์›Œ์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Uvicorn** ๊ฐ™์€) **ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €**๋ฅผ ์“ฐ๋Š” ๋Œ€์‹ , **ํด๋Ÿฌ์Šคํ„ฐ ๋ ˆ๋ฒจ**์—์„œ **๋ณต์ œ๋ฅผ ์ฒ˜๋ฆฌ**ํ•˜๊ณ  ์‹ถ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. -์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์™€ ๊ฐ™์€ ๋ถ„์‚ฐ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ์ค‘ ์ผ๋ถ€๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์— ๋Œ€ํ•œ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ**์„ ์ง€์›ํ•˜๋ฉด์„œ **์ปจํ…Œ์ด๋„ˆ ๋ณต์ œ**๋ฅผ ๋‹ค๋ฃจ๋Š” ํ†ตํ•ฉ๋œ ๋ฐฉ๋ฒ•์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋‘ **ํด๋Ÿฌ์Šคํ„ฐ ๋ ˆ๋ฒจ**์—์„œ ๋ง์ด์ฃ . +Kubernetes ๊ฐ™์€ ๋ถ„์‚ฐ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์€ ๋ณดํ†ต ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์— ๋Œ€ํ•œ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ**์„ ์ง€์›ํ•˜๋ฉด์„œ๋„, **์ปจํ…Œ์ด๋„ˆ ๋ณต์ œ**๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ†ตํ•ฉ๋œ ๋ฐฉ๋ฒ•์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋‘ **ํด๋Ÿฌ์Šคํ„ฐ ๋ ˆ๋ฒจ**์—์„œ์š”. -์ด๋Ÿฐ ๊ฒฝ์šฐ์—, ์—ฌ๋Ÿฌ๋ถ„์€ [์œ„์—์„œ ๋ฌ˜์‚ฌ๋œ ๊ฒƒ](#dockerfile)์ฒ˜๋Ÿผ **์ฒ˜์Œ๋ถ€ํ„ฐ ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ** ๋นŒ๋“œํ•ด์„œ, ์˜์กด์„ฑ์„ ์„ค์น˜ํ•˜๊ณ , Uvicorn ์›Œ์ปค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” Gunicorn ๋Œ€์‹  **๋‹จ์ผ Uvicorn ํ”„๋กœ์„ธ์Šค**๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” [์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ](#dockerfile) ์˜์กด์„ฑ์„ ์„ค์น˜ํ•˜๊ณ , ์—ฌ๋Ÿฌ Uvicorn ์›Œ์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  **๋‹จ์ผ Uvicorn ํ”„๋กœ์„ธ์Šค**๋ฅผ ์‹คํ–‰ํ•˜๋Š” **์ฒ˜์Œ๋ถ€ํ„ฐ ๋งŒ๋“  Docker ์ด๋ฏธ์ง€**๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -### ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ +### ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ { #load-balancer } -์ปจํ…Œ์ด๋„ˆ๋กœ ์ž‘์—…ํ•  ๋•Œ, ์—ฌ๋Ÿฌ๋ถ„์€ ์ผ๋ฐ˜์ ์œผ๋กœ **๋ฉ”์ธ ํฌํŠธ์˜ ์ƒํ™ฉ์„ ๊ฐ์ง€ํ•˜๋Š”** ์š”์†Œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” **HTTPS**๋ฅผ ๋‹ค๋ฃจ๋Š” **TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ**์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ์ผ ์ˆ˜๋„ ์žˆ๊ณ , ์œ ์‚ฌํ•œ ๋‹ค๋ฅธ ๋„๊ตฌ์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋ณดํ†ต **๋ฉ”์ธ ํฌํŠธ์—์„œ ๋Œ€๊ธฐ(listening)ํ•˜๋Š”** ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. **HTTPS**๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ **TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ** ์—ญํ• ์„ ํ•˜๋Š” ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ์ผ ์ˆ˜๋„ ์žˆ๊ณ , ์œ ์‚ฌํ•œ ๋„๊ตฌ์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด ์š”์†Œ๊ฐ€ ์š”์ฒญ๋“ค์˜ **๋กœ๋“œ**๋ฅผ ์ฝ์–ด๋“ค์ด๊ณ  ๊ฐ ์›Œ์ปค์—๊ฒŒ (๋ฐ”๋ผ๊ฑด๋Œ€) **๊ท ํ˜•์ ์œผ๋กœ** ๋ถ„๋ฐฐํ•œ๋‹ค๋ฉด, ์ด ์š”์†Œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๋ผ๊ณ  ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. +์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์š”์ฒญ์˜ **๋ถ€ํ•˜(load)**๋ฅผ ๋ฐ›์•„ ์›Œ์ปค๋“ค์— (๊ฐ€๋Šฅํ•˜๋ฉด) **๊ท ํ˜• ์žˆ๊ฒŒ** ๋ถ„์‚ฐํ•œ๋‹ค๋ฉด, ๋ณดํ†ต **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. /// tip | ํŒ -HTTPS๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋œ **TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ** ์š”์†Œ ๋˜ํ•œ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +HTTPS์— ์‚ฌ์šฉ๋˜๋Š” ๋™์ผํ•œ **TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ** ์ปดํฌ๋„ŒํŠธ๊ฐ€ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**์ด๊ธฐ๋„ ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. /// -๋˜ํ•œ ์ปจํ…Œ์ด๋„ˆ๋กœ ์ž‘์—…ํ•  ๋•Œ, ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ์‹œ์Šคํ…œ์€ ์ด๋ฏธ ํ•ด๋‹น **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๋กœ ๋ถ€ํ„ฐ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์— ํ•ด๋‹นํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ๋กœ **๋„คํŠธ์›Œํฌ ํ†ต์‹ **(์˜ˆ๋ฅผ ๋“ค์–ด, HTTP ์š”์ฒญ)์„ ์ „์†กํ•˜๋Š” ๋‚ด๋ถ€์ ์ธ ๋„๊ตฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค (์—ฌ๊ธฐ์„œ๋„ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ๋Š” **TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ**์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). +๋˜ํ•œ ์ปจํ…Œ์ด๋„ˆ๋กœ ์ž‘์—…ํ•  ๋•Œ, ์ด๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ์€ ์ด๋ฏธ ํ•ด๋‹น **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**(๋˜๋Š” **TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ**)์—์„œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์ด ์žˆ๋Š” ์ปจํ…Œ์ด๋„ˆ๋กœ **๋„คํŠธ์›Œํฌ ํ†ต์‹ **(์˜ˆ: HTTP ์š”์ฒญ)์„ ์ „๋‹ฌํ•˜๋Š” ๋‚ด๋ถ€ ๋„๊ตฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -### ํ•˜๋‚˜์˜ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ - ๋‹ค์ค‘ ์›Œ์ปค ์ปจํ…Œ์ด๋„ˆ +### ํ•˜๋‚˜์˜ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ - ์—ฌ๋Ÿฌ ์›Œ์ปค ์ปจํ…Œ์ด๋„ˆ { #one-load-balancer-multiple-worker-containers } -**์ฟ ๋ฒ„๋„คํ‹ฐ์Šค**๋‚˜ ๋˜๋Š” ๋‹ค๋ฅธ ๋ถ„์‚ฐ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์œผ๋กœ ์ž‘์—…ํ•  ๋•Œ, ์‹œ์Šคํ…œ ๋‚ด๋ถ€์˜ ๋„คํŠธ์›Œํ‚น ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ด์šฉํ•จ์œผ๋กœ์จ ๋ฉ”์ธ **ํฌํŠธ**๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์žˆ๋Š” ๋‹จ์ผ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๋Š” ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์—์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š” **์—ฌ๋Ÿฌ๊ฐœ์˜ ์ปจํ…Œ์ด๋„ˆ**์— ํ†ต์‹ (์š”์ฒญ๋“ค)์„ ์ „์†กํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +**Kubernetes** ๊ฐ™์€ ๋ถ„์‚ฐ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์—์„œ๋Š” ๋‚ด๋ถ€ ๋„คํŠธ์›Œํ‚น ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•ด, ๋ฉ”์ธ **ํฌํŠธ**์—์„œ ๋Œ€๊ธฐํ•˜๋Š” ๋‹จ์ผ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์„ ์‹คํ–‰ํ•˜๋Š” **์—ฌ๋Ÿฌ ์ปจํ…Œ์ด๋„ˆ**๋กœ ํ†ต์‹ (์š”์ฒญ)์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์—์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š” ๊ฐ๊ฐ์˜ ์ปจํ…Œ์ด๋„ˆ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ **ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค**๋งŒ ๊ฐ€์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค (์˜ˆ๋ฅผ ๋“ค์–ด, FastAPI ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‹คํ–‰๋˜๋Š” ํ•˜๋‚˜์˜ Uvicorn ํ”„๋กœ์„ธ์Šค์ฒ˜๋Ÿผ). ์ด ์ปจํ…Œ์ด๋„ˆ๋“ค์€ ๋ชจ๋‘ ๊ฐ™์€ ๊ฒƒ์„ ์‹คํ–‰ํ•˜๋Š” ์ ์—์„œ **๋™์ผํ•œ ์ปจํ…Œ์ด๋„ˆ**์ด์ง€๋งŒ, ํ”„๋กœ์„ธ์Šค, ๋ฉ”๋ชจ๋ฆฌ ๋“ฑ์€ ๊ณต์œ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์œผ๋กœ ์—ฌ๋Ÿฌ๋ถ„์€ CPU์˜ **์„œ๋กœ ๋‹ค๋ฅธ ์ฝ”์–ด๋“ค** ๋˜๋Š” **์„œ๋กœ ๋‹ค๋ฅธ ๋จธ์‹ ๋“ค**์„ **๋ณ‘๋ ฌํ™”**ํ•˜๋Š” ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์•ฑ์„ ์‹คํ–‰ํ•˜๋Š” ๊ฐ ์ปจํ…Œ์ด๋„ˆ๋Š” ๋ณดํ†ต **ํ”„๋กœ์„ธ์Šค ํ•˜๋‚˜๋งŒ** ๊ฐ€์ง‘๋‹ˆ๋‹ค(์˜ˆ: FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋Š” Uvicorn ํ”„๋กœ์„ธ์Šค). ๋ชจ๋‘ ๊ฐ™์€ ๊ฒƒ์„ ์‹คํ–‰ํ•˜๋Š” **๋™์ผํ•œ ์ปจํ…Œ์ด๋„ˆ**์ด์ง€๋งŒ, ๊ฐ์ž ๊ณ ์œ ํ•œ ํ”„๋กœ์„ธ์Šค, ๋ฉ”๋ชจ๋ฆฌ ๋“ฑ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด CPU์˜ **์„œ๋กœ ๋‹ค๋ฅธ ์ฝ”์–ด** ๋˜๋Š” **์„œ๋กœ ๋‹ค๋ฅธ ๋จธ์‹ **์—์„œ **๋ณ‘๋ ฌํ™”**์˜ ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋˜ํ•œ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๊ฐ€ ์žˆ๋Š” ๋ถ„์‚ฐ ์ปจํ…Œ์ด๋„ˆ ์‹œ์Šคํ…œ์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์— ์žˆ๋Š” ์ปจํ…Œ์ด๋„ˆ ๊ฐ๊ฐ์— **์ฐจ๋ก€๋Œ€๋กœ ์š”์ฒญ์„ ๋ถ„์‚ฐ**์‹œํ‚ฌ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฐ ์š”์ฒญ์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์—์„œ ์‹คํ–‰๋˜๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ **๋ณต์ œ๋œ ์ปจํ…Œ์ด๋„ˆ๋“ค** ์ค‘ ํ•˜๋‚˜์— ์˜ํ•ด ๋‹ค๋ฃจ์–ด์งˆ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๊ฐ€ ์žˆ๋Š” ๋ถ„์‚ฐ ์ปจํ…Œ์ด๋„ˆ ์‹œ์Šคํ…œ์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์„ ์‹คํ–‰ํ•˜๋Š” ๊ฐ ์ปจํ…Œ์ด๋„ˆ์— **๋ฒˆ๊ฐˆ์•„๊ฐ€๋ฉฐ** ์š”์ฒญ์„ **๋ถ„์‚ฐ**ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฐ ์š”์ฒญ์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์„ ์‹คํ–‰ํ•˜๋Š” ์—ฌ๋Ÿฌ **๋ณต์ œ๋œ ์ปจํ…Œ์ด๋„ˆ** ์ค‘ ํ•˜๋‚˜์—์„œ ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ์ผ๋ฐ˜์ ์œผ๋กœ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๋Š” ์—ฌ๋Ÿฌ๋ถ„์˜ ํด๋Ÿฌ์Šคํ„ฐ์— ์žˆ๋Š” *๋‹ค๋ฅธ* ์•ฑ์œผ๋กœ ๊ฐ€๋Š” ์š”์ฒญ๋“ค๋„ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์œผ๋ฉฐ (์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์œผ๋กœ ๊ฐ€๊ฑฐ๋‚˜ ๋‹ค๋ฅธ URL ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒฝ์šฐ), ์ด ํ†ต์‹ ๋“ค์„ ํด๋Ÿฌ์Šคํ„ฐ์— ์žˆ๋Š” *๋ฐ”๋กœ ๊ทธ ๋‹ค๋ฅธ* ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์ œ๋Œ€๋กœ ์ „์†กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ ๋ณดํ†ต ์ด **๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ**๋Š” ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด *๋‹ค๋ฅธ* ์•ฑ์œผ๋กœ ๊ฐ€๋Š” ์š”์ฒญ(์˜ˆ: ๋‹ค๋ฅธ ๋„๋ฉ”์ธ, ๋˜๋Š” ๋‹ค๋ฅธ URL ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ ์•„๋ž˜๋กœ ๊ฐ€๋Š” ์š”์ฒญ)๋„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ทธ ํ†ต์‹ ์„ ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ์‹คํ–‰ ์ค‘์ธ *๊ทธ ๋‹ค๋ฅธ* ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์˜ฌ๋ฐ”๋ฅธ ์ปจํ…Œ์ด๋„ˆ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐ€์ง€๋Š” ์ปจํ…Œ์ด๋„ˆ +### ์ปจํ…Œ์ด๋„ˆ๋‹น ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค { #one-process-per-container } -์ด ์‹œ๋‚˜๋ฆฌ์˜ค์˜ ๊ฒฝ์šฐ, ์—ฌ๋Ÿฌ๋ถ„์€ ์ด๋ฏธ ํด๋Ÿฌ์Šคํ„ฐ ๋ ˆ๋ฒจ์—์„œ ๋ณต์ œ๋ฅผ ๋‹ค๋ฃจ๊ณ  ์žˆ์„ ๊ฒƒ์ด๋ฏ€๋กœ **์ปจํ…Œ์ด๋„ˆ ๋‹น ๋‹จ์ผ (Uvicorn) ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์ง€๊ณ ์ž ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ด ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” ์ด๋ฏธ ํด๋Ÿฌ์Šคํ„ฐ ๋ ˆ๋ฒจ์—์„œ ๋ณต์ œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, **์ปจํ…Œ์ด๋„ˆ๋‹น ๋‹จ์ผ (Uvicorn) ํ”„๋กœ์„ธ์Šค**๋ฅผ ๋‘๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ, ์—ฌ๋Ÿฌ๋ถ„์€ Gunicorn ์ด๋‚˜ Uvicorn ์›Œ์ปค, ๋˜๋Š” Uvicorn ์›Œ์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Uvicorn ๋งค๋‹ˆ์ €์™€ ๊ฐ™์€ ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €๋ฅผ ๊ฐ€์ง€๊ณ  ์‹ถ์–ดํ•˜์ง€ **์•Š์„** ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์€ ์ปจํ…Œ์ด๋„ˆ ๋‹น **๋‹จ์ผ Uvicorn ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์ง€๊ณ  ์‹ถ์–ดํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค (๊ทธ๋Ÿฌ๋‚˜ ์•„๋งˆ๋„ ๋‹ค์ค‘ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ฐ€์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค). +๋”ฐ๋ผ์„œ ์ด ๊ฒฝ์šฐ ์ปจํ…Œ์ด๋„ˆ์—์„œ `--workers` ์ปค๋งจ๋“œ ๋ผ์ธ ์˜ต์…˜ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์—ฌ๋Ÿฌ ์›Œ์ปค๋ฅผ ๋‘๊ณ  ์‹ถ์ง€๋Š” **์•Š์„** ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ปจํ…Œ์ด๋„ˆ๋‹น **๋‹จ์ผ Uvicorn ํ”„๋กœ์„ธ์Šค**๋งŒ ๋‘๊ณ (ํ•˜์ง€๋งŒ ์ปจํ…Œ์ด๋„ˆ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค) ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -์ด๋ฏธ ์—ฌ๋Ÿฌ๋ถ„์ด ํด๋Ÿฌ์Šคํ„ฐ ์‹œ์Šคํ…œ์„ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, (Uvicorn ์›Œ์ปค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” Gunicorn ์ด๋‚˜ Uvicorn ์ฒ˜๋Ÿผ) ์ปจํ…Œ์ด๋„ˆ ๋‚ด์— ๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒƒ์€ **๋ถˆํ•„์š”ํ•œ ๋ณต์žก์„ฑ**๋งŒ ๋”ํ•˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์— (์—ฌ๋Ÿฌ ์›Œ์ปค๋ฅผ ์œ„ํ•œ) ๋˜ ๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €๋ฅผ ๋‘๋Š” ๊ฒƒ์€, ์ด๋ฏธ ํด๋Ÿฌ์Šคํ„ฐ ์‹œ์Šคํ…œ์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ๋Š” **๋ถˆํ•„์š”ํ•œ ๋ณต์žก์„ฑ**๋งŒ ์ถ”๊ฐ€ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. -### ๋‹ค์ค‘ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐ€์ง€๋Š” ์ปจํ…Œ์ด๋„ˆ์™€ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ๋“ค +### ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐ€์ง„ ์ปจํ…Œ์ด๋„ˆ์™€ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ { #containers-with-multiple-processes-and-special-cases } -๋‹น์—ฐํ•œ ๋ง์ด์ง€๋งŒ, ์—ฌ๋Ÿฌ๋ถ„์ด ๋‚ด๋ถ€์ ์œผ๋กœ **Uvicorn ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋“ค**๋ฅผ ์‹œ์ž‘ํ•˜๋Š” **Gunicorn ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €**๋ฅผ ๊ฐ€์ง€๋Š” ๋‹จ์ผ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์›ํ•˜๋Š” **ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ**๋„ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๋ฌผ๋ก  ์ปจํ…Œ์ด๋„ˆ ํ•˜๋‚˜์— ์—ฌ๋Ÿฌ **Uvicorn ์›Œ์ปค ํ”„๋กœ์„ธ์Šค**๋ฅผ ๋‘๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ๋Š” **ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ**๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์—, ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ **Gunicorn**์„ ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €๋กœ ํฌํ•จํ•˜๋Š” **๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€**๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €๋Š” ๋‹ค์ค‘ **Uvicorn ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋“ค**์„ ์‹คํ–‰ํ•˜๋ฉฐ, ๋””ํดํŠธ ์„ธํŒ…์œผ๋กœ ํ˜„์žฌ CPU ์ฝ”์–ด์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ ์ž๋™์œผ๋กœ ์›Œ์ปค ๊ฐœ์ˆ˜๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ์‚ฌํ•ญ์— ๋Œ€ํ•ด์„œ๋Š” ์•„๋ž˜์˜ [Gunicorn๊ณผ ํ•จ๊ป˜ํ•˜๋Š” ๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€ - Uvicorn](#official-docker-image-with-gunicorn-uvicorn)์—์„œ ๋” ๋‹ค๋ฃจ๊ฒ ์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” `--workers` ์ปค๋งจ๋“œ ๋ผ์ธ ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด ์‹คํ–‰ํ•  ์›Œ์ปค ์ˆ˜๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -์ด๋Ÿฐ ๊ฒฝ์šฐ์— ํ•ด๋‹นํ•˜๋Š” ๋ช‡๊ฐ€์ง€ ์˜ˆ์‹œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: +```{ .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"] +``` -#### Prometheus์™€ ๋‹ค๋ฅธ ์ด์œ ๋“ค +1. ์—ฌ๊ธฐ์„œ๋Š” `--workers` ์ปค๋งจ๋“œ ๋ผ์ธ ์˜ต์…˜์œผ๋กœ ์›Œ์ปค ์ˆ˜๋ฅผ 4๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ๋ถ„์€ **๋‹จ์ผ ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์ง€๋Š” **๋‹ค์ค‘ ์ปจํ…Œ์ด๋„ˆ** ๋Œ€์‹  **๋‹ค์ค‘ ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์ง€๋Š” **๋‹จ์ผ ์ปจํ…Œ์ด๋„ˆ**๋ฅผ ์ฑ„ํƒํ•˜๋Š” **๋‹ค๋ฅธ ์ด์œ **๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋Ÿฐ ๋ฐฉ์‹์ด ์˜๋ฏธ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: -์˜ˆ๋ฅผ ๋“ค์–ด (์—ฌ๋Ÿฌ๋ถ„์˜ ์žฅ์น˜ ์„ค์ •์— ๋”ฐ๋ผ) Prometheus ์ต์Šคํฌํ„ฐ์™€ ๊ฐ™์ด ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ์— ๋“ค์–ด์˜ค๋Š” **๊ฐ ์š”์ฒญ์— ๋Œ€ํ•ด** ์ ‘๊ทผ๊ถŒํ•œ์„ ๊ฐ€์ง€๋Š” ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +#### ๋‹จ์ˆœํ•œ ์•ฑ { #a-simple-app } -์ด ๊ฒฝ์šฐ์— ์—ฌ๋Ÿฌ๋ถ„์ด **์—ฌ๋Ÿฌ๊ฐœ์˜ ์ปจํ…Œ์ด๋„ˆ๋“ค**์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, Prometheus๊ฐ€ **๋ฉ”ํŠธ๋ฆญ์„ ์ฝ์–ด ๋“ค์ผ ๋•Œ**, ๋””ํดํŠธ๋กœ **๋งค๋ฒˆ ํ•˜๋‚˜์˜ ์ปจํ…Œ์ด๋„ˆ**(ํŠน์ • ๋ฆฌํ€˜์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ”๋กœ ๊ทธ ์ปจํ…Œ์ด๋„ˆ)๋กœ ๋ถ€ํ„ฐ ์ฝ์–ด๋“ค์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๋ชจ๋“  ๋ณต์ œ๋œ ์ปจํ…Œ์ด๋„ˆ์— ๋Œ€ํ•ด **์ถ•์ ๋œ ๋ฉ”ํŠธ๋ฆญ๋“ค**์„ ์ฝ์–ด๋“ค์ด๋Š” ๊ฒƒ๊ณผ ๋Œ€๋น„๋ฉ๋‹ˆ๋‹ค. +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **์ถฉ๋ถ„ํžˆ ๋‹จ์ˆœ**ํ•ด์„œ ํด๋Ÿฌ์Šคํ„ฐ๊ฐ€ ์•„๋‹Œ **๋‹จ์ผ ์„œ๋ฒ„**์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์ปจํ…Œ์ด๋„ˆ์— ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €๋ฅผ ๋‘๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ ‡๋‹ค๋ฉด ์ด ๊ฒฝ์šฐ์—๋Š” **๋‹ค์ค‘ ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์ง€๋Š” **ํ•˜๋‚˜์˜ ์ปจํ…Œ์ด๋„ˆ**๋ฅผ ๋‘์–ด์„œ ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ์—์„œ ๋ชจ๋“  ๋‚ด๋ถ€ ํ”„๋กœ์„ธ์Šค์— ๋Œ€ํ•œ Prometheus ๋ฉ”ํŠธ๋ฆญ์„ ์ˆ˜์ง‘ํ•˜๋Š” ๋กœ์ปฌ ๋„๊ตฌ(์˜ˆ๋ฅผ ๋“ค์–ด Prometheus ์ต์Šคํฌํ„ฐ ๊ฐ™์€)๋ฅผ ๋‘์–ด์„œ ์ด ๋ฉ”๊ทธ๋ฆญ๋“ค์„ ํ•˜๋‚˜์˜ ์ปจํ…Œ์ด๋„ˆ์— ๋‚ด์—์„œ ๊ณต์œ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๋” ๋‹จ์ˆœํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +#### Docker Compose { #docker-compose } + +**Docker Compose**๋กœ ํด๋Ÿฌ์Šคํ„ฐ๊ฐ€ ์•„๋‹Œ **๋‹จ์ผ ์„œ๋ฒ„**์— ๋ฐฐํฌํ•˜๋Š” ๊ฒฝ์šฐ, ๊ณต์œ  ๋„คํŠธ์›Œํฌ์™€ **๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ**์„ ์œ ์ง€ํ•˜๋ฉด์„œ(Docker Compose๋กœ) ์ปจํ…Œ์ด๋„ˆ ๋ณต์ œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์‰ฌ์šด ๋ฐฉ๋ฒ•์ด ์—†์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ ‡๋‹ค๋ฉด **ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €**๊ฐ€ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ **์—ฌ๋Ÿฌ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค**๋ฅผ ์‹œ์ž‘ํ•˜๋Š” **๋‹จ์ผ ์ปจํ…Œ์ด๋„ˆ**๋ฅผ ์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. --- -์š”์ ์€, ์ด ์ค‘์˜ **์–ด๋А๊ฒƒ๋„** ์—ฌ๋Ÿฌ๋ถ„๋“ค์ด ๋ฐ˜๋“œ์‹œ ๋”ฐ๋ผ์•ผํ•˜๋Š” **ํ™•์ •๋œ ์‚ฌ์‹ค**์ด ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์€ ์ด ์•„์ด๋””์–ด๋“ค์„ **์—ฌ๋Ÿฌ๋ถ„์˜ ๊ณ ์œ ํ•œ ์ด์šฉ ์‚ฌ๋ก€๋ฅผ ํ‰๊ฐ€**ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•˜๊ณ , ์—ฌ๋Ÿฌ๋ถ„์˜ ์‹œ์Šคํ…œ์— ๊ฐ€์žฅ ์ ํ•ฉํ•œ ์ ‘๊ทผ๋ฒ•์ด ์–ด๋–ค ๊ฒƒ์ธ์ง€ ๊ฒฐ์ •ํ•˜๋ฉฐ, ๋‹ค์Œ์˜ ๊ฐœ๋…๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +ํ•ต์‹ฌ์€, ์ด๊ฒƒ๋“ค ์ค‘ **์–ด๋А ๊ฒƒ๋„** ๋ฌด์กฐ๊ฑด ๋”ฐ๋ผ์•ผ ํ•˜๋Š” **์ ˆ๋Œ€์ ์ธ ๊ทœ์น™**์€ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ์•„์ด๋””์–ด๋“ค์„ ์‚ฌ์šฉํ•ด **์—ฌ๋Ÿฌ๋ถ„์˜ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ํ‰๊ฐ€**ํ•˜๊ณ , ์—ฌ๋Ÿฌ๋ถ„์˜ ์‹œ์Šคํ…œ์— ๊ฐ€์žฅ ์ ํ•ฉํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•˜๋ฉด์„œ ๋‹ค์Œ ๊ฐœ๋…์„ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ• ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: * ๋ณด์•ˆ - HTTPS -* ๊ตฌ๋™ํ•˜๊ธฐ +* ์‹œ์ž‘ ์‹œ ์ž๋™ ์‹คํ–‰ * ์žฌ์‹œ์ž‘ -* ๋ณต์ œ (์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ๊ฐœ์ˆ˜) +* ๋ณต์ œ(์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ์ˆ˜) * ๋ฉ”๋ชจ๋ฆฌ -* ์‹œ์ž‘ํ•˜๊ธฐ ์ „ ๋‹จ๊ณ„๋“ค +* ์‹œ์ž‘ ์ „ ์‚ฌ์ „ ๋‹จ๊ณ„ -## ๋ฉ”๋ชจ๋ฆฌ +## ๋ฉ”๋ชจ๋ฆฌ { #memory } -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด **์ปจํ…Œ์ด๋„ˆ ๋‹น ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค**๋ฅผ ์‹คํ–‰ํ•œ๋‹ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์€ ๊ฐ ์ปจํ…Œ์ด๋„ˆ(๋ณต์ œ๋œ ๊ฒฝ์šฐ์—๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ปจํ…Œ์ด๋„ˆ๋“ค)์— ๋Œ€ํ•ด ์ž˜ ์ •์˜๋˜๊ณ , ์•ˆ์ •์ ์ด๋ฉฐ, ์ œํ•œ๋œ ์šฉ๋Ÿ‰์˜ ๋ฉ”๋ชจ๋ฆฌ ์†Œ๋น„๋Ÿ‰์„ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**์ปจํ…Œ์ด๋„ˆ๋‹น ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค**๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ๊ฐ ์ปจํ…Œ์ด๋„ˆ(๋ณต์ œ๋œ ๊ฒฝ์šฐ ์—ฌ๋Ÿฌ ๊ฐœ)๋งˆ๋‹ค ์†Œ๋น„ํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ์–‘์ด ๋Œ€์ฒด๋กœ ์ž˜ ์ •์˜๋˜๊ณ  ์•ˆ์ •์ ์ด๋ฉฐ ์ œํ•œ๋œ ๊ฐ’์ด ๋ฉ๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋ฉด ์—ฌ๋Ÿฌ๋ถ„์˜ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ(์˜ˆ๋ฅผ ๋“ค์–ด **์ฟ ๋ฒ„๋„คํ‹ฐ์Šค**) ์„ค์ •์—์„œ ์•ž์„œ ์ •์˜๋œ ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ๊ณผ ์š”๊ตฌ์‚ฌํ•ญ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋ฐฉ๋ฒ•์œผ๋กœ **๊ฐ€์šฉ ๋จธ์‹ **์ด ํ•„์š”๋กœํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ์™€ ํด๋Ÿฌ์Šคํ„ฐ์— ์žˆ๋Š” ๊ฐ€์šฉ ๋จธ์‹ ๋“ค์„ ์—ผ๋‘์— ๋‘๊ณ  **์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋ณต์ œ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ(์˜ˆ: **Kubernetes**) ์„ค์ •์—์„œ ๋™์ผํ•˜๊ฒŒ ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ๊ณผ ์š”๊ตฌ์‚ฌํ•ญ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋จธ์‹ ์— ์žˆ๋Š” ๋ฉ”๋ชจ๋ฆฌ์™€ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ์–‘์„ ๊ณ ๋ คํ•ด **์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋ณต์ œ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **๋‹จ์ˆœ**ํ•˜๋‹ค๋ฉด, ์ด๊ฒƒ์€ **๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์„** ๊ฒƒ์ด๊ณ , ๊ณ ์ •๋œ ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ์„ ๊ตฌ์ฒดํ™”ํ•  ํ•„์š”๋„ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—ฌ๋Ÿฌ๋ถ„์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด (์˜ˆ๋ฅผ ๋“ค์–ด **๋จธ์‹  ๋Ÿฌ๋‹** ๋ชจ๋ธ๊ฐ™์ด) **๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์†Œ์š”ํ•œ๋‹ค๋ฉด**, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์–‘์˜ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  **๊ฐ ๋จธ์‹ ์—์„œ** ์‚ฌ์šฉํ•˜๋Š” **์ปจํ…Œ์ด๋„ˆ์˜ ์ˆ˜**๋ฅผ ์กฐ์ •ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค (๊ทธ๋ฆฌ๊ณ  ํ•„์š”์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ๋ถ„์˜ ํด๋Ÿฌ์Šคํ„ฐ์— ๋จธ์‹ ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **๋‹จ์ˆœ**ํ•˜๋‹ค๋ฉด ์ด๋Š” ์•„๋งˆ๋„ **๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์„** ๊ฒƒ์ด๊ณ , ์—„๊ฒฉํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ์„ ์ง€์ •ํ•  ํ•„์š”๊ฐ€ ์—†์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ **๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด**(์˜ˆ: **๋จธ์‹  ๋Ÿฌ๋‹** ๋ชจ๋ธ), ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์†Œ๋น„ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , **๊ฐ ๋จธ์‹ **์—์„œ ์‹คํ–‰๋˜๋Š” **์ปจํ…Œ์ด๋„ˆ ์ˆ˜**๋ฅผ ์กฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(ํ•„์š”ํ•˜๋‹ค๋ฉด ํด๋Ÿฌ์Šคํ„ฐ์— ๋จธ์‹ ์„ ๋” ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค). -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด **์ปจํ…Œ์ด๋„ˆ ๋‹น ์—ฌ๋Ÿฌ๊ฐœ์˜ ํ”„๋กœ์„ธ์Šค**๋ฅผ ์‹คํ–‰ํ•œ๋‹ค๋ฉด (์˜ˆ๋ฅผ ๋“ค์–ด ๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€ ์ฒ˜๋Ÿผ), ์—ฌ๋Ÿฌ๋ถ„์€ ์‹œ์ž‘๋œ ํ”„๋กœ์„ธ์Šค ๊ฐœ์ˆ˜๊ฐ€ ๊ฐ€์šฉํ•œ ๊ฒƒ ๋ณด๋‹ค **๋” ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์†Œ๋น„**ํ•˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +**์ปจํ…Œ์ด๋„ˆ๋‹น ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค**๋ฅผ ์‹คํ–‰ํ•œ๋‹ค๋ฉด, ์‹œ์ž‘๋˜๋Š” ํ”„๋กœ์„ธ์Šค ์ˆ˜๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒƒ๋ณด๋‹ค **๋” ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์†Œ๋น„ํ•˜์ง€** ์•Š๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -## ์‹œ์ž‘ํ•˜๊ธฐ ์ „ ๋‹จ๊ณ„๋“ค๊ณผ ์ปจํ…Œ์ด๋„ˆ +## ์‹œ์ž‘ ์ „ ๋‹จ๊ณ„์™€ ์ปจํ…Œ์ด๋„ˆ { #previous-steps-before-starting-and-containers } -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด ์ปจํ…Œ์ด๋„ˆ(์˜ˆ๋ฅผ ๋“ค์–ด ๋„์ปค, ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค)๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ์š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. +์ปจํ…Œ์ด๋„ˆ(์˜ˆ: Docker, Kubernetes)๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ์š” ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋‘ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. -### ๋‹ค์ค‘ ์ปจํ…Œ์ด๋„ˆ +### ์—ฌ๋Ÿฌ ์ปจํ…Œ์ด๋„ˆ { #multiple-containers } -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด **์—ฌ๋Ÿฌ๊ฐœ์˜ ์ปจํ…Œ์ด๋„ˆ**๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, ์•„๋งˆ๋„ ๊ฐ๊ฐ์˜ ์ปจํ…Œ์ด๋„ˆ๋Š” **ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด, **์ฟ ๋ฒ„๋„คํ‹ฐ์Šค** ํด๋Ÿฌ์Šคํ„ฐ์—์„œ). ๊ทธ๋Ÿฌ๋ฉด ์—ฌ๋Ÿฌ๋ถ„์€ ๋ณต์ œ๋œ ์›Œ์ปค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ **์ด์ „์—**, ํ•˜๋‚˜์˜ ์ปจํ…Œ์ด๋„ˆ์— ์žˆ๋Š” **์ด์ „์˜ ๋‹จ๊ณ„๋“ค์„** ์ˆ˜ํ–‰ํ•˜๋Š” ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐ€์ง€๋Š” **๋ณ„๋„์˜ ์ปจํ…Œ์ด๋„ˆ๋“ค**์„ ๊ฐ€์ง€๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**์—ฌ๋Ÿฌ ์ปจํ…Œ์ด๋„ˆ**๊ฐ€ ์žˆ๊ณ  ๊ฐ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๋ณดํ†ต **๋‹จ์ผ ํ”„๋กœ์„ธ์Šค**๋ฅผ ์‹คํ–‰ํ•œ๋‹ค๋ฉด(์˜ˆ: **Kubernetes** ํด๋Ÿฌ์Šคํ„ฐ), ๋ณต์ œ๋œ ์›Œ์ปค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ **์ „์—**, ๋‹จ์ผ ์ปจํ…Œ์ด๋„ˆ์—์„œ ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค๋กœ **์‹œ์ž‘ ์ „ ์‚ฌ์ „ ๋‹จ๊ณ„**๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” **๋ณ„๋„์˜ ์ปจํ…Œ์ด๋„ˆ**๋ฅผ ๋‘๊ณ  ์‹ถ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. /// info | ์ •๋ณด -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์•„๋งˆ๋„ ์ด๋Š” <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +Kubernetes๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์ด๋Š” ์•„๋งˆ๋„ <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. /// -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ด์šฉ ์‚ฌ๋ก€์—์„œ ์ด์ „ ๋‹จ๊ณ„๋“ค์„ **๋ณ‘๋ ฌ์ ์œผ๋กœ ์—ฌ๋Ÿฌ๋ฒˆ** ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ์— ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค๋ฉด (์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด์ „์„ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์ค€๋น„๋˜์—ˆ๋Š”์ง€ ํ™•์ธ๋งŒ ํ•˜๋Š” ๊ฒฝ์šฐ), ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ์ด ๋‹จ๊ณ„๋“ค์„ ๊ฐ ์ปจํ…Œ์ด๋„ˆ์— ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์‚ฌ์šฉ ์‚ฌ๋ก€์—์„œ ์‹œ์ž‘ ์ „ ์‚ฌ์ „ ๋‹จ๊ณ„๋ฅผ **์—ฌ๋Ÿฌ ๋ฒˆ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰**ํ•ด๋„ ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค๋ฉด(์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์ค€๋น„๋˜์—ˆ๋Š”์ง€ ํ™•์ธ๋งŒ ํ•˜๋Š” ๊ฒฝ์šฐ), ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ง์ „์— ๊ฐ ์ปจํ…Œ์ด๋„ˆ์— ๊ทธ ๋‹จ๊ณ„๋ฅผ ๋„ฃ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -### ๋‹จ์ผ ์ปจํ…Œ์ด๋„ˆ +### ๋‹จ์ผ ์ปจํ…Œ์ด๋„ˆ { #single-container } -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์˜ ์…‹์—…์ด **๋‹ค์ค‘ ํ”„๋กœ์„ธ์Šค**(๋˜๋Š” ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค)๋ฅผ ์‹œ์ž‘ํ•˜๋Š” **ํ•˜๋‚˜์˜ ์ปจํ…Œ์ด๋„ˆ**๋ฅผ ๊ฐ€์ง€๋Š” ๋‹จ์ˆœํ•œ ์…‹์—…์ด๋ผ๋ฉด, ์‚ฌ์ „ ๋‹จ๊ณ„๋“ค์„ ์•ฑ์„ ํฌํ•จํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ง์ „์— ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€๋Š” ์ด๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +**๋‹จ์ผ ์ปจํ…Œ์ด๋„ˆ**์—์„œ ์—ฌ๋Ÿฌ **์›Œ์ปค ํ”„๋กœ์„ธ์Šค**(๋˜๋Š” ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค)๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ๋‹จ์ˆœํ•œ ์…‹์—…์ด๋ผ๋ฉด, ์•ฑ์ด ์žˆ๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ง์ „์— ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ์—์„œ ์‹œ์ž‘ ์ „ ์‚ฌ์ „ ๋‹จ๊ณ„๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## Gunicorn๊ณผ ํ•จ๊ป˜ํ•˜๋Š” ๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€ - Uvicorn +### ๋ฒ ์ด์Šค ๋„์ปค ์ด๋ฏธ์ง€ { #base-docker-image } -์•ž ์ฑ•ํ„ฐ์—์„œ ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…๋œ ๊ฒƒ ์ฒ˜๋Ÿผ, Uvicorn ์›Œ์ปค์™€ ๊ฐ™์ด ์‹คํ–‰๋˜๋Š” Gunicorn์„ ํฌํ•จํ•˜๋Š” ๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: [์„œ๋ฒ„ ์›Œ์ปค - Uvicorn๊ณผ ํ•จ๊ป˜ํ•˜๋Š” Gunicorn](server-workers.md){.internal-link target=_blank}. +๊ณผ๊ฑฐ์—๋Š” ๊ณต์‹ FastAPI Docker ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. ํ•˜์ง€๋งŒ ์ด์ œ๋Š” deprecated๋˜์—ˆ์Šต๋‹ˆ๋‹ค. โ›”๏ธ -์ด ์ด๋ฏธ์ง€๋Š” ์ฃผ๋กœ ์œ„์—์„œ ์„ค๋ช…๋œ ์ƒํ™ฉ์—์„œ ์œ ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: [๋‹ค์ค‘ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐ€์ง€๋Š” ์ปจํ…Œ์ด๋„ˆ์™€ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ๋“ค](#containers-with-multiple-processes-and-special-cases). +์•„๋งˆ๋„ ์ด ๋ฒ ์ด์Šค ๋„์ปค ์ด๋ฏธ์ง€(๋˜๋Š” ์œ ์‚ฌํ•œ ๋‹ค๋ฅธ ์ด๋ฏธ์ง€)๋Š” **์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”** ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. -* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. +**Kubernetes**(๋˜๋Š” ๋‹ค๋ฅธ ๋„๊ตฌ)๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ํด๋Ÿฌ์Šคํ„ฐ ๋ ˆ๋ฒจ์—์„œ ์—ฌ๋Ÿฌ **์ปจํ…Œ์ด๋„ˆ**๋กœ **๋ณต์ œ**๋ฅผ ์ด๋ฏธ ์„ค์ •ํ•ด ๋‘” ๊ฒฝ์šฐ๋ผ๋ฉด, ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ **์ฒ˜์Œ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜๋Š” ๊ฒƒ**์ด ๋” ๋‚ซ์Šต๋‹ˆ๋‹ค: [FastAPI๋ฅผ ์œ„ํ•œ ๋„์ปค ์ด๋ฏธ์ง€ ๋นŒ๋“œํ•˜๊ธฐ](#build-a-docker-image-for-fastapi). -/// warning | ๊ฒฝ๊ณ  +๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ ์›Œ์ปค๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด, `--workers` ์ปค๋งจ๋“œ ๋ผ์ธ ์˜ต์…˜์„ ๊ฐ„๋‹จํžˆ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ๋ถ„์ด ์ด ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€ ๋˜๋Š” ๋‹ค๋ฅธ ์œ ์‚ฌํ•œ ์ด๋ฏธ์ง€๋ฅผ ํ•„์š”๋กœ ํ•˜์ง€ **์•Š์„** ๋†’์€ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์œผ๋ฉฐ, [์œ„์—์„œ ์„ค๋ช…๋œ ๊ฒƒ์ฒ˜๋Ÿผ: FastAPI๋ฅผ ์œ„ํ•œ ๋„์ปค ์ด๋ฏธ์ง€ ๋นŒ๋“œํ•˜๊ธฐ](#build-a-docker-image-for-fastapi) ์ฒ˜์Œ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜๋Š” ๊ฒƒ์ด ๋” ๋‚˜์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +/// note Technical Details | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +์ด Docker ์ด๋ฏธ์ง€๋Š” Uvicorn์ด ์ฃฝ์€ ์›Œ์ปค๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์žฌ์‹œ์ž‘ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜์ง€ ์•Š๋˜ ์‹œ๊ธฐ์— ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ Gunicorn๊ณผ Uvicorn์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ๊ณ , Gunicorn์ด Uvicorn ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์žฌ์‹œ์ž‘ํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ์ƒ๋‹นํ•œ ๋ณต์žก์„ฑ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์ด์ œ Uvicorn(๊ทธ๋ฆฌ๊ณ  `fastapi` ๋ช…๋ น)์€ `--workers`๋ฅผ ์ง€์›ํ•˜๋ฏ€๋กœ, ๋ฒ ์ด์Šค ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  ์ง์ ‘ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜์ง€ ์•Š์„ ์ด์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค(์ฝ”๋“œ ์–‘๋„ ์‚ฌ์‹ค์ƒ ๊ฑฐ์˜ ๊ฐ™์Šต๋‹ˆ๋‹ค ๐Ÿ˜…). /// -์ด ์ด๋ฏธ์ง€๋Š” ๊ฐ€๋Šฅํ•œ CPU ์ฝ”์–ด์— ๊ธฐ๋ฐ˜ํ•œ **๋ช‡๊ฐœ์˜ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค**๋ฅผ ์„ค์ •ํ•˜๋Š” **์ž๋™-ํŠœ๋‹** ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +## ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ ๋ฐฐํฌํ•˜๊ธฐ { #deploy-the-container-image } -์ด ์ด๋ฏธ์ง€๋Š” **๋ฏผ๊ฐํ•œ ๋””ํดํŠธ** ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ, ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ ์—ฌ์ „ํžˆ **ํ™˜๊ฒฝ ๋ณ€์ˆ˜** ๋˜๋Š” ์„ค์ • ํŒŒ์ผ์„ ํ†ตํ•ด ์„ค์ •๊ฐ’์„ ์ˆ˜์ •ํ•˜๊ณ  ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -๋˜ํ•œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ†ตํ•ด <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**์‹œ์ž‘ํ•˜๊ธฐ ์ „ ์‚ฌ์ „ ๋‹จ๊ณ„**</a>๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. - -/// tip | ํŒ - -๋ชจ๋“  ์„ค์ •๊ณผ ์˜ต์…˜์„ ๋ณด๋ ค๋ฉด, ๋„์ปค ์ด๋ฏธ์ง€ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. - -/// - -### ๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€์— ์žˆ๋Š” ํ”„๋กœ์„ธ์Šค ๊ฐœ์ˆ˜ - -์ด ์ด๋ฏธ์ง€์— ์žˆ๋Š” **ํ”„๋กœ์„ธ์Šค ๊ฐœ์ˆ˜**๋Š” ๊ฐ€์šฉํ•œ 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 -``` - -### ์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ - -์—ฌ๋Ÿฌ๋ถ„๋“ค์ด **์ฟ ๋ฒ„๋„คํ‹ฐ์Šค**(๋˜๋Š” ์œ ์‚ฌํ•œ ๋‹ค๋ฅธ ๋„๊ตฌ) ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ํด๋Ÿฌ์Šคํ„ฐ ๋ ˆ๋ฒจ์—์„œ ๋‹ค์ค‘ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ด์šฉํ•ด ์ด๋ฏธ **์‚ฌ๋ณธ**์„ ์„ค์ •ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ๊ณต์‹ ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€(๋˜๋Š” ์œ ์‚ฌํ•œ ๋‹ค๋ฅธ ์ด๋ฏธ์ง€)๋ฅผ ์‚ฌ์šฉํ•˜์ง€ **์•Š๋Š”** ๊ฒƒ ์ข‹์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์— ์—ฌ๋Ÿฌ๋ถ„์€ ๋‹ค์Œ์— ์„ค๋ช…๋œ ๊ฒƒ ์ฒ˜๋Ÿผ **์ฒ˜์Œ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜๋Š” ๊ฒƒ**์ด ๋” ๋‚ซ์Šต๋‹ˆ๋‹ค: [FastAPI๋ฅผ ์œ„ํ•œ ๋„์ปค ์ด๋ฏธ์ง€ ๋นŒ๋“œํ•˜๊ธฐ](#build-a-docker-image-for-fastapi). - -์ด ์ด๋ฏธ์ง€๋Š” ์œ„์˜ [๋‹ค์ค‘ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐ€์ง€๋Š” ์ปจํ…Œ์ด๋„ˆ์™€ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ๋“ค](#containers-with-multiple-processes-and-special-cases)์—์„œ ์„ค๋ช…๋œ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ์— ๋Œ€ํ•ด์„œ๋งŒ ์ฃผ๋กœ ์œ ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด **์ถฉ๋ถ„ํžˆ ๋‹จ์ˆœ**ํ•ด์„œ CPU์— ๊ธฐ๋ฐ˜ํ•œ ๋””ํดํŠธ ํ”„๋กœ์„ธ์Šค ๊ฐœ์ˆ˜๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์ž˜ ์ž‘๋™ํ•œ๋‹ค๋ฉด, ํด๋Ÿฌ์Šคํ„ฐ ๋ ˆ๋ฒจ์—์„œ ์ˆ˜๋™์œผ๋กœ ์‚ฌ๋ณธ์„ ์„ค์ •ํ•  ํ•„์š”๊ฐ€ ์—†์„ ๊ฒƒ์ด๊ณ , ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์—์„œ ํ•˜๋‚˜ ์ด์ƒ์˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰ํ•˜์ง€๋„ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋˜๋Š” ๋งŒ์•ฝ์— ์—ฌ๋Ÿฌ๋ถ„์ด **๋„์ปค ์ปดํฌ์ฆˆ**๋กœ ๋ฐฐํฌํ•˜๊ฑฐ๋‚˜, ๋‹จ์ผ ์„œ๋ฒ„์—์„œ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. - -## ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ ๋ฐฐํฌํ•˜๊ธฐ - -์ปจํ…Œ์ด๋„ˆ (๋„์ปค) ์ด๋ฏธ์ง€๋ฅผ ์™„์„ฑํ•œ ๋’ค์— ์ด๋ฅผ ๋ฐฐํฌํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. +์ปจํ…Œ์ด๋„ˆ(Docker) ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“  ํ›„์—๋Š” ์ด๋ฅผ ๋ฐฐํฌํ•˜๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: -* ๋‹จ์ผ ์„œ๋ฒ„์—์„œ **๋„์ปค ์ปดํฌ์ฆˆ**๋กœ ๋ฐฐํฌํ•˜๊ธฐ -* **์ฟ ๋ฒ„๋„คํ‹ฐ์Šค** ํด๋Ÿฌ์Šคํ„ฐ๋กœ ๋ฐฐํฌํ•˜๊ธฐ -* ๋„์ปค ์Šค์™ ๋ชจ๋“œ ํด๋Ÿฌ์Šคํ„ฐ๋กœ ๋ฐฐํฌํ•˜๊ธฐ -* ๋…ธ๋งˆ๋“œ ๊ฐ™์€ ๋‹ค๋ฅธ ๋„๊ตฌ๋กœ ๋ฐฐํฌํ•˜๊ธฐ -* ์—ฌ๋Ÿฌ๋ถ„์˜ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋ฐฐํฌํ•ด์ฃผ๋Š” ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๋กœ ๋ฐฐํฌํ•˜๊ธฐ +* ๋‹จ์ผ ์„œ๋ฒ„์—์„œ **Docker Compose**๋กœ +* **Kubernetes** ํด๋Ÿฌ์Šคํ„ฐ๋กœ +* Docker Swarm Mode ํด๋Ÿฌ์Šคํ„ฐ๋กœ +* Nomad ๊ฐ™์€ ๋‹ค๋ฅธ ๋„๊ตฌ๋กœ +* ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ›์•„ ๋ฐฐํฌํ•ด์ฃผ๋Š” ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๋กœ -## Poetry์˜ ๋„์ปค ์ด๋ฏธ์ง€ +## `uv`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋„์ปค ์ด๋ฏธ์ง€ { #docker-image-with-uv } -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„๋“ค์ด ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a>๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋„์ปค์˜ ๋ฉ€ํ‹ฐ-์Šคํ…Œ์ด์ง€ ๋นŒ๋”ฉ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +ํ”„๋กœ์ ํŠธ๋ฅผ ์„ค์น˜ํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, <a href="https://docs.astral.sh/uv/guides/integration/docker/" class="external-link" target="_blank">uv Docker guide</a>๋ฅผ ๋”ฐ๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -```{ .dockerfile .annotate } -# (1) -FROM python:3.9 as requirements-stage +## ์š”์•ฝ { #recap } -# (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 | ํŒ - -๋ฒ„๋ธ” ์ˆซ์ž๋ฅผ ํด๋ฆญํ•ด ๊ฐ ์ค„์ด ํ•˜๋Š” ์ผ์„ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -/// - -**๋„์ปค ์Šคํ…Œ์ด์ง€**๋ž€ `Dockefile`์˜ ์ผ๋ถ€๋กœ์„œ ๋‚˜์ค‘์— ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ํŒŒ์ผ๋“ค์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ **์ผ์‹œ์ ์ธ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€**๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. - -์ฒซ ์Šคํ…Œ์ด์ง€๋Š” ์˜ค์ง **Poetry๋ฅผ ์„ค์น˜**ํ•˜๊ณ  Poetry์˜ `pyproject.toml` ํŒŒ์ผ๋กœ๋ถ€ํ„ฐ ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ์„ ์œ„ํ•œ **`requirements.txt`๋ฅผ ์ƒ์„ฑ**ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. - -์ด `requirements.txt` ํŒŒ์ผ์€ **๋‹ค์Œ ์Šคํ…Œ์ด์ง€**์—์„œ `pip`๋กœ ์‚ฌ์šฉ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. - -๋งˆ์ง€๋ง‰ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€์—๋Š” **์˜ค์ง ๋งˆ์ง€๋ง‰ ์Šคํ…Œ์ด์ง€๋งŒ** ๋ณด์กด๋ฉ๋‹ˆ๋‹ค. ์ด์ „ ์Šคํ…Œ์ด์ง€(๋“ค)์€ ๋ฒ„๋ ค์ง‘๋‹ˆ๋‹ค. - -Poetry๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ **๋„์ปค ๋ฉ€ํ‹ฐ-์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ**๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์€๋ฐ, ์—ฌ๋Ÿฌ๋ถ„๋“ค์˜ ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ์„ ์„ค์น˜ํ•˜๊ธฐ ์œ„ํ•ด ๋งˆ์ง€๋ง‰ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€์— **์˜ค์ง** `requirements.txt` ํŒŒ์ผ๋งŒ ํ•„์š”ํ•˜์ง€, Poetry์™€ ๊ทธ ์˜์กด์„ฑ์€ ์žˆ์„ ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. - -์ด ๋‹ค์Œ (๋˜ํ•œ ๋งˆ์ง€๋ง‰) ์Šคํ…Œ์ด์ง€์—์„œ ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ ์ด์ „์— ์„ค๋ช…๋œ ๊ฒƒ๊ณผ ๋น„์Šทํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ฐฉ์‹์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -### TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ์˜ ๋ฐฐํ›„ - Poetry - -์ด์ „์— ์–ธ๊ธ‰ํ•œ ๊ฒƒ๊ณผ ๊ฐ™์ด, ๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด ์ปจํ…Œ์ด๋„ˆ๋ฅผ Nginx ๋˜๋Š” Traefik๊ณผ ๊ฐ™์€ TLS ์ข…๋ฃŒ ํ”„๋ก์‹œ (๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ) ๋’ค์—์„œ ์‹คํ–‰ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์ปค๋งจ๋“œ์— `--proxy-headers` ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค: - -```Dockerfile -CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] -``` - -## ์š”์•ฝ - -์ปจํ…Œ์ด๋„ˆ ์‹œ์Šคํ…œ(์˜ˆ๋ฅผ ๋“ค์–ด **๋„์ปค**๋‚˜ **์ฟ ๋ฒ„๋„คํ‹ฐ์Šค**)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  **๋ฐฐํฌ ๊ฐœ๋…**์„ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์€ ๊ฝค ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค: +์ปจํ…Œ์ด๋„ˆ ์‹œ์Šคํ…œ(์˜ˆ: **Docker**, **Kubernetes**)์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  **๋ฐฐํฌ ๊ฐœ๋…**์„ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ด ์ƒ๋‹นํžˆ ๋‹จ์ˆœํ•ด์ง‘๋‹ˆ๋‹ค: * HTTPS -* ๊ตฌ๋™ํ•˜๊ธฐ +* ์‹œ์ž‘ ์‹œ ์ž๋™ ์‹คํ–‰ * ์žฌ์‹œ์ž‘ -* ๋ณต์ œ (์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ๊ฐœ์ˆ˜) +* ๋ณต์ œ(์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ์ˆ˜) * ๋ฉ”๋ชจ๋ฆฌ -* ์‹œ์ž‘ํ•˜๊ธฐ ์ „ ๋‹จ๊ณ„๋“ค +* ์‹œ์ž‘ ์ „ ์‚ฌ์ „ ๋‹จ๊ณ„ -๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์—์„œ ์—ฌ๋Ÿฌ๋ถ„์€ ์–ด๋–ค ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๊ณต์‹ ํŒŒ์ด์ฌ ๋„์ปค ์ด๋ฏธ์ง€์— ๊ธฐ๋ฐ˜ํ•ด **์ฒ˜์Œ๋ถ€ํ„ฐ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œ**ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ๊ณต์‹ Python Docker ์ด๋ฏธ์ง€์— ๊ธฐ๋ฐ˜ํ•ด **์ฒ˜์Œ๋ถ€ํ„ฐ ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œ**ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. -`Dockerfile`์— ์žˆ๋Š” ์ง€์‹œ ์‚ฌํ•ญ์„ **์ˆœ์„œ๋Œ€๋กœ** ๋‹ค๋ฃจ๊ณ  **๋„์ปค ์บ์‹œ**๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์—ฌ๋Ÿฌ๋ถ„์€ **๋นŒ๋“œ ์‹œ๊ฐ„์„ ์ตœ์†Œํ™”**ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋กœ์จ ์ƒ์‚ฐ์„ฑ์„ ์ตœ๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (๊ทธ๋ฆฌ๊ณ  ์ง€๋ฃจํ•จ์„ ํ”ผํ•  ์ˆ˜ ์žˆ์ฃ ) ๐Ÿ˜Ž - -ํŠน๋ณ„ํ•œ ๊ฒฝ์šฐ์—๋Š”, FastAPI๋ฅผ ์œ„ํ•œ ๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ +`Dockerfile`์—์„œ ์ง€์‹œ์–ด์˜ **์ˆœ์„œ**์™€ **Docker ์บ์‹œ**๋ฅผ ์‹ ๊ฒฝ ์“ฐ๋ฉด **๋นŒ๋“œ ์‹œ๊ฐ„์„ ์ตœ์†Œํ™”**ํ•ด ์ƒ์‚ฐ์„ฑ์„ ์ตœ๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๊ทธ๋ฆฌ๊ณ  ์ง€๋ฃจํ•จ๋„ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). ๐Ÿ˜Ž diff --git a/docs/ko/docs/deployment/index.md b/docs/ko/docs/deployment/index.md index 87b05b68f1..e0d2375344 100644 --- a/docs/ko/docs/deployment/index.md +++ b/docs/ko/docs/deployment/index.md @@ -1,21 +1,23 @@ -# ๋ฐฐํฌํ•˜๊ธฐ - ๋“ค์–ด๊ฐ€๋ฉด์„œ +# ๋ฐฐํฌ { #deployment } -**FastAPI**์„ ๋ฐฐํฌํ•˜๋Š” ๊ฒƒ์€ ๋น„๊ต์  ์‰ฝ์Šต๋‹ˆ๋‹ค. +**FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•˜๋Š” ๊ฒƒ์€ ๋น„๊ต์  ์‰ฝ์Šต๋‹ˆ๋‹ค. -## ๋ฐฐํฌ์˜ ์˜๋ฏธ +## ๋ฐฐํฌ์˜ ์˜๋ฏธ { #what-does-deployment-mean } -**๋ฐฐํฌ**๋ž€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ **์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ**ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋‹จ๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ **๋ฐฐํฌ**ํ•œ๋‹ค๋Š” ๊ฒƒ์€ **์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ**ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋‹จ๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -**์›น API**์˜ ๊ฒฝ์šฐ, ์ผ๋ฐ˜์ ์œผ๋กœ **์‚ฌ์šฉ์ž**๊ฐ€ ์ค‘๋‹จ์ด๋‚˜ ์˜ค๋ฅ˜ ์—†์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํšจ์œจ์ ์œผ๋กœ **์ ‘๊ทผ**ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ข‹์€ ์„ฑ๋Šฅ, ์•ˆ์ •์„ฑ ๋“ฑ์„ ์ œ๊ณตํ•˜๋Š” **์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ๊ณผ** ํ•จ๊ป˜ **์›๊ฒฉ ์‹œ์Šคํ…œ**์— ์ด๋ฅผ ์„ค์น˜ํ•˜๋Š” ์ž‘์—…์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +**์›น API**์˜ ๊ฒฝ์šฐ, ์ผ๋ฐ˜์ ์œผ๋กœ **์›๊ฒฉ ๋จธ์‹ **์— ์ด๋ฅผ ์„ค์น˜ํ•˜๊ณ , ์ข‹์€ ์„ฑ๋Šฅ, ์•ˆ์ •์„ฑ ๋“ฑ์„ ์ œ๊ณตํ•˜๋Š” **์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ**๊ณผ ํ•จ๊ป˜ ๊ตฌ์„ฑํ•˜์—ฌ **์‚ฌ์šฉ์ž**๊ฐ€ ์ค‘๋‹จ์ด๋‚˜ ๋ฌธ์ œ ์—†์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํšจ์œจ์ ์œผ๋กœ **์ ‘๊ทผ**ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๊ฒƒ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. -์ด๋Š” ์ง€์†์ ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ , ์ง€์šฐ๊ณ , ์ˆ˜์ •ํ•˜๊ณ , ๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ ์ค‘์ง€ํ–ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋Š” ๋“ฑ์˜ **๊ฐœ๋ฐœ** ๋‹จ๊ณ„์™€ ๋Œ€์กฐ๋ฉ๋‹ˆ๋‹ค. +์ด๋Š” ์ง€์†์ ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ , ๋ง๊ฐ€๋œจ๋ฆฌ๊ณ  ๊ณ ์น˜๊ณ , ๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ ์ค‘์ง€ํ–ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋Š” ๋“ฑ์˜ **๊ฐœ๋ฐœ** ๋‹จ๊ณ„์™€ ๋Œ€์กฐ๋ฉ๋‹ˆ๋‹ค. -## ๋ฐฐํฌ ์ „๋žต +## ๋ฐฐํฌ ์ „๋žต { #deployment-strategies } -์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ๋‚˜ ํŠน์ • ์‚ฌ๋ก€์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. +๊ตฌ์ฒด์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€์™€ ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. -๋ฐฐํฌ๋„๊ตฌ๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ ์ง์ ‘ **์„œ๋ฒ„์— ๋ฐฐํฌ**ํ•˜๊ฑฐ๋‚˜, ๋ฐฐํฌ์ž‘์—…์˜ ์ผ๋ถ€๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” **ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค** ๋˜๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ ๋„๊ตฌ๋ฅผ ์กฐํ•ฉํ•ด ์ง์ ‘ **์„œ๋ฒ„๋ฅผ ๋ฐฐํฌ**ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ์ž‘์—…์˜ ์ผ๋ถ€๋ฅผ ๋Œ€์‹ ํ•ด ์ฃผ๋Š” **ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค**๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, ๋‹ค๋ฅธ ๊ฐ€๋Šฅํ•œ ์„ ํƒ์ง€๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ๋•Œ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ์ฃผ์š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค (๋Œ€๋ถ€๋ถ„ ๋‹ค๋ฅธ ์œ ํ˜•์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค). +์˜ˆ๋ฅผ ๋“ค์–ด, FastAPI ๋’ค์— ์žˆ๋Š” ์ €ํฌ ํŒ€์€ FastAPI๋กœ ์ž‘์—…ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜์„ ์œ ์ง€ํ•˜๋ฉด์„œ, FastAPI ์•ฑ์„ ํด๋ผ์šฐ๋“œ์— ๊ฐ€๋Šฅํ•œ ํ•œ ๊ฐ„์†Œํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋„๋ก <a href="https://fastapicloud.com" class="external-link" target="_blank">**FastAPI Cloud**</a>๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. -๋‹ค์Œ ์ฐจ๋ก€์— ์ž์„ธํ•œ ๋‚ด์šฉ๊ณผ ์ด๋ฅผ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ๊ธฐ์ˆ ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โœจ +**FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ๋•Œ ์•„๋งˆ ์—ผ๋‘์— ๋‘์–ด์•ผ ํ•  ๋ช‡ ๊ฐ€์ง€ ์ฃผ์š” ๊ฐœ๋…์„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค(๋Œ€๋ถ€๋ถ„์€ ๋‹ค๋ฅธ ์œ ํ˜•์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค). + +๋‹ค์Œ ์„น์…˜์—์„œ ์—ผ๋‘์— ๋‘˜ ๋” ๋งŽ์€ ์„ธ๋ถ€์‚ฌํ•ญ๊ณผ ์ด๋ฅผ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ๊ธฐ์ˆ ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โœจ diff --git a/docs/ko/docs/deployment/server-workers.md b/docs/ko/docs/deployment/server-workers.md index b40b25cd8f..e98cfd1142 100644 --- a/docs/ko/docs/deployment/server-workers.md +++ b/docs/ko/docs/deployment/server-workers.md @@ -1,130 +1,87 @@ -# ์„œ๋ฒ„ ์›Œ์ปค - ๊ตฌ๋‹ˆ์ฝ˜๊ณผ ์œ ๋น„์ฝ˜ +# ์„œ๋ฒ„ ์›Œ์ปค - ์›Œ์ปค์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” Uvicorn { #server-workers-uvicorn-with-workers } -์ „๋‹จ๊ณ„์—์„œ์˜ ๋ฐฐํฌ ๊ฐœ๋…๋“ค์„ ๋‹ค์‹œ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: +์ด์ „์˜ ๋ฐฐํฌ ๊ฐœ๋…๋“ค์„ ๋‹ค์‹œ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: * ๋ณด์•ˆ - HTTPS -* ์„œ๋ฒ„ ์‹œ์ž‘๊ณผ ๋™์‹œ์— ์‹คํ–‰ํ•˜๊ธฐ +* ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ์‹คํ–‰ * ์žฌ์‹œ์ž‘ -* **๋ณต์ œ๋ณธ (์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค์˜ ์ˆซ์ž)** +* **๋ณต์ œ(์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ์ˆ˜)** * ๋ฉ”๋ชจ๋ฆฌ -* ์‹œ์ž‘ํ•˜๊ธฐ ์ „์˜ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋“ค +* ์‹œ์ž‘ํ•˜๊ธฐ ์ „์˜ ์ด์ „ ๋‹จ๊ณ„ -์ง€๊ธˆ๊นŒ์ง€ ๋ฌธ์„œ์˜ ๋ชจ๋“  ํŠœํ† ๋ฆฌ์–ผ์„ ์ฐธ๊ณ ํ•˜์—ฌ **๋‹จ์ผ ํ”„๋กœ์„ธ์Šค**๋กœ Uvicorn๊ณผ ๊ฐ™์€ **์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ**์„ ์‹คํ–‰ํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ง€๊ธˆ๊นŒ์ง€ ๋ฌธ์„œ์˜ ๋ชจ๋“  ํŠœํ† ๋ฆฌ์–ผ์„ ์ฐธ๊ณ ํ•˜๋ฉด์„œ, `fastapi` ๋ช…๋ น์ฒ˜๋Ÿผ Uvicorn์„ ์‹คํ–‰ํ•˜๋Š” **์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ**์„ ์‚ฌ์šฉํ•ด **๋‹จ์ผ ํ”„๋กœ์„ธ์Šค**๋กœ ์‹คํ–‰ํ•ด ์™”์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. -์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ๋•Œ **๋‹ค์ค‘ ์ฝ”์–ด**๋ฅผ ํ™œ์šฉํ•˜๊ณ  ๋” ๋งŽ์€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก **ํ”„๋กœ์„ธ์Šค ๋ณต์ œ๋ณธ**์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ๋•Œ๋Š” **๋‹ค์ค‘ ์ฝ”์–ด**๋ฅผ ํ™œ์šฉํ•˜๊ณ  ๋” ๋งŽ์€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก **ํ”„๋กœ์„ธ์Šค ๋ณต์ œ**๋ฅผ ํ•˜๊ณ  ์‹ถ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. -์ „ ๊ณผ์ •์ด์—ˆ๋˜ [๋ฐฐํฌ ๊ฐœ๋…๋“ค](concepts.md){.internal-link target=_blank}์—์„œ ๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. +์ด์ „ ์žฅ์˜ [๋ฐฐํฌ ๊ฐœ๋…๋“ค](concepts.md){.internal-link target=_blank}์—์„œ ๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ, ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ „๋žต์ด ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค. -์ง€๊ธˆ๋ถ€ํ„ฐ <a href="https://gunicorn.org/" class="external-link" target="_blank">**๊ตฌ๋‹ˆ์ฝ˜**</a>์„ **์œ ๋น„์ฝ˜ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค**์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๋ ค๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์„œ๋Š” `fastapi` ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ `uvicorn` ๋ช…๋ น์„ ์ง์ ‘ ์‚ฌ์šฉํ•ด์„œ, **์›Œ์ปค ํ”„๋กœ์„ธ์Šค**์™€ ํ•จ๊ป˜ **Uvicorn**์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. /// info | ์ •๋ณด -๋งŒ์•ฝ ๋„์ปค์™€ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด ๋‹ค์Œ ์ฑ•ํ„ฐ [FastAPI์™€ ์ปจํ…Œ์ด๋„ˆ - ๋„์ปค](docker.md){.internal-link target=_blank}์—์„œ ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +Docker๋‚˜ Kubernetes ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ๋‹ค์Œ ์žฅ์ธ [์ปจํ…Œ์ด๋„ˆ์—์„œ์˜ FastAPI - ๋„์ปค](docker.md){.internal-link target=_blank}์—์„œ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. -ํŠนํžˆ, ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์—์„œ ์‹คํ–‰ํ•  ๋•Œ๋Š” ๊ตฌ๋‹ˆ์ฝ˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋Œ€์‹  ์ปจํ…Œ์ด๋„ˆ๋‹น ํ•˜๋‚˜์˜ ์œ ๋น„์ฝ˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด ์žฅ์˜ ๋’ท๋ถ€๋ถ„์—์„œ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. +ํŠนํžˆ **Kubernetes**์—์„œ ์‹คํ–‰ํ•  ๋•Œ๋Š” ์›Œ์ปค๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋ณด๋‹ค๋Š”, ๋Œ€์‹  **์ปจํ…Œ์ด๋„ˆ๋‹น ๋‹จ์ผ Uvicorn ํ”„๋กœ์„ธ์Šค ํ•˜๋‚˜**๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฌ์ง€๋งŒ, ํ•ด๋‹น ๋‚ด์šฉ์€ ๊ทธ ์žฅ์˜ ๋’ค์—์„œ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. /// -## ๊ตฌ๋‹ˆ์ฝ˜๊ณผ ์œ ๋น„์ฝ˜ ์›Œ์ปค +## ์—ฌ๋Ÿฌ ์›Œ์ปค { #multiple-workers } -**Gunicorn**์€ **WSGI ํ‘œ์ค€**์„ ์ฃผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๊ตฌ๋‹ˆ์ฝ˜์ด ํ”Œ๋ผ์Šคํฌ์™€ ์Ÿ๊ณ ์™€ ๊ฐ™์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๊ตฌ๋‹ˆ์ฝ˜ ์ž์ฒด๋Š” ์ตœ์‹  **<a href="https://asgi.readthedocs.io/en/latest/" class="external-link" target="_blank">ASGI ํ‘œ์ค€</a>**์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— FastAPI์™€ ํ˜ธํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +`--workers` ์ปค๋งจ๋“œ๋ผ์ธ ์˜ต์…˜์œผ๋กœ ์—ฌ๋Ÿฌ ์›Œ์ปค๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -ํ•˜์ง€๋งŒ ๊ตฌ๋‹ˆ์ฝ˜์€ **ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ์ž**์—ญํ• ์„ ํ•˜๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ํŠน์ • **์›Œ์ปค ํ”„๋กœ์„ธ์Šค ํด๋ž˜์Šค**๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๊ตฌ๋‹ˆ์ฝ˜์€ ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜๋‚˜ ์ด์ƒ์˜ **์›Œ์ปค ํ”„๋กœ์„ธ์Šค**๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. +//// tab | `fastapi` -๊ทธ๋ฆฌ๊ณ  **์œ ๋น„์ฝ˜**์€ **๊ตฌ๋‹ˆ์ฝ˜๊ณผ ํ˜ธํ™˜๋˜๋Š” ์›Œ์ปค ํด๋ž˜์Šค**๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. - -์ด ์กฐํ•ฉ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ๋‹ˆ์ฝ˜์€ **ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ์ž** ์—ญํ• ์„ ํ•˜๋ฉฐ **ํฌํŠธ**์™€ **IP**๋ฅผ ๊ด€์ฐฐํ•˜๊ณ , **์œ ๋น„์ฝ˜ ํด๋ž˜์Šค**๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋กœ ํ†ต์‹  ์ •๋ณด๋ฅผ **์ „์†ก**ํ•ฉ๋‹ˆ๋‹ค. - -๊ทธ๋ฆฌ๊ณ  ๋‚˜์„œ ๊ตฌ๋‹ˆ์ฝ˜๊ณผ ํ˜ธํ™˜๋˜๋Š” **์œ ๋น„์ฝ˜ ์›Œ์ปค** ํด๋ž˜์Šค๋Š” ๊ตฌ๋‹ˆ์ฝ˜์ด ๋ณด๋‚ธ ๋ฐ์ดํ„ฐ๋ฅผ FastAPI์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ASGI ํ‘œ์ค€์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ผ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. - -## ๊ตฌ๋‹ˆ์ฝ˜๊ณผ ์œ ๋น„์ฝ˜ ์„ค์น˜ํ•˜๊ธฐ +`fastapi` ๋ช…๋ น์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด: <div class="termy"> ```console -$ pip install "uvicorn[standard]" gunicorn +$ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:solid">main.py</u> ----> 100% + <span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server ๐Ÿš€ + + Searching for package file structure from directories with + <font color="#3465A4">__init__.py</font> files + Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> ๐Ÿ main.py + + <span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the + following code: + + <u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u> + + <span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font> + + Logs: + + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C to + quit<b>)</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started parent process <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27367</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. ``` </div> -์ด ๋ช…๋ น์–ด๋Š” ์œ ๋น„์ฝ˜ `standard` ์ถ”๊ฐ€ ํŒจํ‚ค์ง€(์ข‹์€ ์„ฑ๋Šฅ์„ ์œ„ํ•œ)์™€ ๊ตฌ๋‹ˆ์ฝ˜์„ ์„ค์น˜ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +//// -## ๊ตฌ๋‹ˆ์ฝ˜์„ ์œ ๋น„์ฝ˜ ์›Œ์ปค์™€ ํ•จ๊ป˜ ์‹คํ–‰ํ•˜๊ธฐ +//// tab | `uvicorn` -์„ค์น˜ ํ›„ ๊ตฌ๋‹ˆ์ฝ˜ ์‹คํ–‰ํ•˜๊ธฐ: - -<div class="termy"> - -```console -$ gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80 - -[19499] [INFO] Starting gunicorn 20.1.0 -[19499] [INFO] Listening at: http://0.0.0.0:80 (19499) -[19499] [INFO] Using worker: uvicorn.workers.UvicornWorker -[19511] [INFO] Booting worker with pid: 19511 -[19513] [INFO] Booting worker with pid: 19513 -[19514] [INFO] Booting worker with pid: 19514 -[19515] [INFO] Booting worker with pid: 19515 -[19511] [INFO] Started server process [19511] -[19511] [INFO] Waiting for application startup. -[19511] [INFO] Application startup complete. -[19513] [INFO] Started server process [19513] -[19513] [INFO] Waiting for application startup. -[19513] [INFO] Application startup complete. -[19514] [INFO] Started server process [19514] -[19514] [INFO] Waiting for application startup. -[19514] [INFO] Application startup complete. -[19515] [INFO] Started server process [19515] -[19515] [INFO] Waiting for application startup. -[19515] [INFO] Application startup complete. -``` - -</div> - -๊ฐ ์˜ต์…˜์ด ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”์ง€ ์‚ดํŽด๋ด…์‹œ๋‹ค: - -* ์ด๊ฒƒ์€ ์œ ๋น„์ฝ˜๊ณผ ๋˜‘๊ฐ™์€ ๋ฌธ๋ฒ•์ž…๋‹ˆ๋‹ค. `main`์€ ํŒŒ์ด์ฌ ๋ชจ๋“ˆ ๋„ค์ž„ "`main`"์„ ์˜๋ฏธํ•˜๋ฏ€๋กœ `main.py`ํŒŒ์ผ์„ ๋œปํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  `app`์€ **FastAPI** ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋“ค์–ด ์žˆ๋Š” ๋ณ€์ˆ˜์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. - * `main:app`์ด ํŒŒ์ด์ฌ์˜ `import` ๋ฌธ๋ฒ•๊ณผ ํก์‚ฌํ•œ ๋ฉด์ด ์žˆ๋‹ค๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: - - ```Python - from main import app - ``` - - * ๊ณง, `main:app`์•ˆ์— ์žˆ๋Š” ์ฝœ๋ก ์˜ ์˜๋ฏธ๋Š” ํŒŒ์ด์ฌ์—์„œ `from main import app`์—์„œ์˜ `import`์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. -* `--workers`: ์‚ฌ์šฉํ•  ์›Œ์ปค ํ”„๋กœ์„ธ์Šค์˜ ๊ฐœ์ˆ˜์ด๋ฉฐ ์ˆซ์ž๋งŒํผ์˜ ์œ ๋น„์ฝ˜ ์›Œ์ปค๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” 4๊ฐœ์˜ ์›Œ์ปค๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. -* `--worker-class`: ์›Œ์ปค ํ”„๋กœ์„ธ์Šค์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌ๋‹ˆ์ฝ˜๊ณผ ํ˜ธํ™˜๋˜๋Š” ์›Œ์ปคํด๋ž˜์Šค. - * ์ด๋Ÿฐ์‹์œผ๋กœ ๊ตฌ๋‹ˆ์ฝ˜์ด importํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค๋ฅผ ์ „๋‹ฌํ•ด์ค๋‹ˆ๋‹ค: - - ```Python - import uvicorn.workers.UvicornWorker - ``` - -* `--bind`: ๊ตฌ๋‹ˆ์ฝ˜์ด ๊ด€์ฐฐํ•  IP์™€ ํฌํŠธ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ฝœ๋ก  (`:`)์„ ์‚ฌ์šฉํ•˜์—ฌ IP์™€ ํฌํŠธ๋ฅผ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. - * ๋งŒ์•ฝ์— `--bind 0.0.0.0:80` (๊ตฌ๋‹ˆ์ฝ˜ ์˜ต์…˜) ๋Œ€์‹  ์œ ๋น„์ฝ˜์„ ์ง์ ‘ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด `--host 0.0.0.0`๊ณผ `--port 80`์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. - -์ถœ๋ ฅ์—์„œ ๊ฐ ํ”„๋กœ์„ธ์Šค์— ๋Œ€ํ•œ **PID** (process ID)๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๋‹จ์ˆœํ•œ ์ˆซ์ž์ž…๋‹ˆ๋‹ค) - -์ถœ๋ ฅ ๋‚ด์šฉ: - -* ๊ตฌ๋‹ˆ์ฝ˜ **ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €**๋Š” PID `19499`๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. (์ง์ ‘ ์‹คํ–‰ํ•  ๊ฒฝ์šฐ ์ˆซ์ž๊ฐ€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค) -* ๋‹ค์Œ์œผ๋กœ `Listening at: http://0.0.0.0:80`์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. -* ๊ทธ๋Ÿฐ ๋‹ค์Œ ์‚ฌ์šฉํ•ด์•ผํ•  `uvicorn.workers.UvicornWorker`์˜ ์›Œ์ปคํด๋ž˜์Šค๋ฅผ ํƒ์ง€ํ•ฉ๋‹ˆ๋‹ค. -* ๊ทธ๋ฆฌ๊ณ  PID `19511`, `19513`, `19514`, ๊ทธ๋ฆฌ๊ณ  `19515`๋ฅผ ๊ฐ€์ง„ **4๊ฐœ์˜ ์›Œ์ปค**๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. - - -๋˜ํ•œ ๊ตฌ๋‹ˆ์ฝ˜์€ ์›Œ์ปค์˜ ์ˆ˜๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด **์ฃฝ์€ ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  **์žฌ์‹œ์ž‘**ํ•˜๋Š” ์ž‘์—…์„ ์ฑ…์ž„์ง‘๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ด๋ฒˆ ์žฅ ์ƒ๋‹จ ๋ชฉ๋ก์˜ **์žฌ์‹œ์ž‘** ๊ฐœ๋…์„ ๋ถ€๋ถ„์ ์œผ๋กœ ๋„์™€์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. - -๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ํ•„์š”ํ•  ๊ฒฝ์šฐ ์™ธ๋ถ€์—์„œ **๊ตฌ๋‹ˆ์ฝ˜์„ ์žฌ์‹œ์ž‘**ํ•˜๊ณ , ํ˜น์€ **์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ ์‹คํ–‰**ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ณ  ์‹ถ์–ดํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. - -## ์œ ๋น„์ฝ˜๊ณผ ์›Œ์ปค - -์œ ๋น„์ฝ˜์€ ๋ช‡ ๊ฐœ์˜ **์›Œ์ปค ํ”„๋กœ์„ธ์Šค**์™€ ํ•จ๊ป˜ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์„ ํƒ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. - -๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ์œ ๋น„์ฝ˜์€ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ฐ์— ์žˆ์–ด์„œ ๊ตฌ๋‹ˆ์ฝ˜๋ณด๋‹ค ๋” ์ œํ•œ์ ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ์ˆ˜์ค€(ํŒŒ์ด์ฌ ์ˆ˜์ค€)์˜ ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๊ตฌ๋‹ˆ์ฝ˜์„ ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ์ž๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. - -๋ณดํ†ต ์ด๋ ‡๊ฒŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +`uvicorn` ๋ช…๋ น์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด ์ข‹๋‹ค๋ฉด: <div class="termy"> @@ -148,36 +105,35 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 </div> -์ƒˆ๋กœ์šด ์˜ต์…˜์ธ `--workers`์€ ์œ ๋น„์ฝ˜์—๊ฒŒ 4๊ฐœ์˜ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ์•Œ๋ ค์ค๋‹ˆ๋‹ค. +//// -๊ฐ ํ”„๋กœ์„ธ์Šค์˜ **PID**๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. `27365`๋Š” ์ƒ์œ„ ํ”„๋กœ์„ธ์Šค(**ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €**), ๊ทธ๋ฆฌ๊ณ  ๊ฐ๊ฐ์˜ ์›Œ์ปคํ”„๋กœ์„ธ์Šค๋Š” `27368`, `27369`, `27370`, ๊ทธ๋ฆฌ๊ณ  `27367`์ž…๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์„œ ์ƒˆ๋กœ์šด ์˜ต์…˜์€ `--workers`๋ฟ์ด๋ฉฐ, Uvicorn์—๊ฒŒ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค 4๊ฐœ๋ฅผ ์‹œ์ž‘ํ•˜๋ผ๊ณ  ์•Œ๋ ค์ค๋‹ˆ๋‹ค. -## ๋ฐฐํฌ ๊ฐœ๋…๋“ค +๋˜ํ•œ ๊ฐ ํ”„๋กœ์„ธ์Šค์˜ **PID**๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ƒ์œ„ ํ”„๋กœ์„ธ์Šค(์ด๊ฒƒ์ด **ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ์ž**)์˜ PID๋Š” `27365`์ด๊ณ , ๊ฐ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค์˜ PID๋Š” `27368`, `27369`, `27370`, `27367`์ž…๋‹ˆ๋‹ค. -์—ฌ๊ธฐ์—์„œ๋Š” **์œ ๋น„์ฝ˜ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค**๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” **๊ตฌ๋‹ˆ์ฝ˜**(๋˜๋Š” ์œ ๋น„์ฝ˜)์„ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ **๋ณ‘๋ ฌํ™”**ํ•˜๊ณ , CPU **๋ฉ€ํ‹ฐ ์ฝ”์–ด**์˜ ์žฅ์ ์„ ํ™œ์šฉํ•˜๊ณ , **๋” ๋งŽ์€ ์š”์ฒญ**์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. +## ๋ฐฐํฌ ๊ฐœ๋…๋“ค { #deployment-concepts } -์›Œ์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋ฐฐํฌ ๊ฐœ๋… ๋ชฉ๋ก์—์„œ ์ฃผ๋กœ **๋ณต์ œ๋ณธ** ๋ถ€๋ถ„๊ณผ **์žฌ์‹œ์ž‘**์— ์•ฝ๊ฐ„ ๋„์›€์ด ๋˜์ง€๋งŒ ๋‹ค๋ฅธ ๋ฐฐํฌ ๊ฐœ๋…๋“ค๋„ ๋‹ค๋ฃจ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: +์—ฌ๊ธฐ์„œ๋Š” ์—ฌ๋Ÿฌ **์›Œ์ปค**๋ฅผ ์‚ฌ์šฉํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰์„ **๋ณ‘๋ ฌํ™”**ํ•˜๊ณ , CPU์˜ **๋‹ค์ค‘ ์ฝ”์–ด**๋ฅผ ํ™œ์šฉํ•˜๋ฉฐ, **๋” ๋งŽ์€ ์š”์ฒญ**์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ดค์Šต๋‹ˆ๋‹ค. + +์œ„์˜ ๋ฐฐํฌ ๊ฐœ๋… ๋ชฉ๋ก์—์„œ ์›Œ์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์ฃผ๋กœ **๋ณต์ œ** ๋ถ€๋ถ„์— ๋„์›€์ด ๋˜๊ณ , **์žฌ์‹œ์ž‘**์—๋„ ์•ฝ๊ฐ„ ๋„์›€์ด ๋˜์ง€๋งŒ, ๋‚˜๋จธ์ง€ ํ•ญ๋ชฉ๋“ค๋„ ์—ฌ์ „ํžˆ ์‹ ๊ฒฝ ์จ์•ผ ํ•ฉ๋‹ˆ๋‹ค: * **๋ณด์•ˆ - HTTPS** -* **์„œ๋ฒ„ ์‹œ์ž‘๊ณผ ๋™์‹œ์— ์‹คํ–‰ํ•˜๊ธฐ** +* **์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ์‹คํ–‰** * ***์žฌ์‹œ์ž‘*** -* ๋ณต์ œ๋ณธ (์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค์˜ ์ˆซ์ž) +* ๋ณต์ œ(์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ์ˆ˜) * **๋ฉ”๋ชจ๋ฆฌ** -* **์‹œ์ž‘ํ•˜๊ธฐ ์ „์˜ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋“ค** +* **์‹œ์ž‘ํ•˜๊ธฐ ์ „์˜ ์ด์ „ ๋‹จ๊ณ„** +## ์ปจํ…Œ์ด๋„ˆ์™€ ๋„์ปค { #containers-and-docker } -## ์ปจํ…Œ์ด๋„ˆ์™€ ๋„์ปค +๋‹ค์Œ ์žฅ์ธ [์ปจํ…Œ์ด๋„ˆ์—์„œ์˜ FastAPI - ๋„์ปค](docker.md){.internal-link target=_blank}์—์„œ๋Š” ๋‹ค๋ฅธ **๋ฐฐํฌ ๊ฐœ๋…๋“ค**์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ์ „๋žต์„ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. -๋‹ค์Œ ์žฅ์ธ [FastAPI์™€ ์ปจํ…Œ์ด๋„ˆ - ๋„์ปค](docker.md){.internal-link target=_blank}์—์„œ ๋‹ค๋ฅธ **๋ฐฐํฌ ๊ฐœ๋…๋“ค**์„ ๋‹ค๋ฃจ๋Š” ์ „๋žต๋“ค์„ ์•Œ๋ ค๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. +๋‹จ์ผ Uvicorn ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด, **์ฒ˜์Œ๋ถ€ํ„ฐ ์—ฌ๋Ÿฌ๋ถ„๋งŒ์˜ ์ด๋ฏธ์ง€๋ฅผ ์ง์ ‘ ๋นŒ๋“œ**ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ„๋‹จํ•œ ๊ณผ์ •์ด๋ฉฐ, **Kubernetes** ๊ฐ™์€ ๋ถ„์‚ฐ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ๋•Œ ์•„๋งˆ๋„ ์ด๋ ‡๊ฒŒ ํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -๋˜ํ•œ ๊ฐ„๋‹จํ•œ ์ผ€์ด์Šค์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”, **๊ตฌ๋‹ˆ์ฝ˜๊ณผ ์œ ๋น„์ฝ˜ ์›Œ์ปค**๊ฐ€ ํฌํ•จ๋ผ ์žˆ๋Š” **๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€**์™€ ํ•จ๊ป˜ ๋ช‡ ๊ฐ€์ง€ ๊ธฐ๋ณธ ๊ตฌ์„ฑ์„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. +## ์š”์•ฝ { #recap } -๊ทธ๋ฆฌ๊ณ  ๋‹จ์ผ ์œ ๋น„์ฝ˜ ํ”„๋กœ์„ธ์Šค(๊ตฌ๋‹ˆ์ฝ˜ ์—†์ด)๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก **์‚ฌ์šฉ์ž ์ž์‹ ์˜ ์ด๋ฏธ์ง€๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ตฌ์ถ•**ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ„๋‹จํ•œ ๊ณผ์ •์ด๋ฉฐ, **์ฟ ๋ฒ„๋„คํ‹ฐ์Šค**์™€ ๊ฐ™์€ ๋ถ„์‚ฐ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ๋•Œ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์ž…๋‹ˆ๋‹ค. +`fastapi` ๋˜๋Š” `uvicorn` ๋ช…๋ น์—์„œ `--workers` CLI ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด ์—ฌ๋Ÿฌ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, **๋ฉ€ํ‹ฐ ์ฝ”์–ด CPU**๋ฅผ ํ™œ์šฉํ•ด **์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ์š”์•ฝ +๋‹ค๋ฅธ ๋ฐฐํฌ ๊ฐœ๋…๋“ค์„ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ **์ž์ฒด ๋ฐฐํฌ ์‹œ์Šคํ…œ**์„ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒฝ์šฐ, ์ด๋Ÿฌํ•œ ๋„๊ตฌ์™€ ์•„์ด๋””์–ด๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋‹น์‹ ์€ **๊ตฌ๋‹ˆ์ฝ˜**(๋˜๋Š” ์œ ๋น„์ฝ˜)์„ ์œ ๋น„์ฝ˜ ์›Œ์ปค์™€ ํ•จ๊ป˜ ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ์ž๋กœ ์‚ฌ์šฉํ•˜์—ฌ **๋ฉ€ํ‹ฐ-์ฝ”์–ด CPU**๋ฅผ ํ™œ์šฉํ•˜๋Š” **๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -๋‹ค๋ฅธ ๋ฐฐํฌ ๊ฐœ๋…์„ ์ง์ ‘ ๋‹ค๋ฃจ๋ฉด์„œ **์ž์‹ ๋งŒ์˜ ๋ฐฐํฌ ์‹œ์Šคํ…œ**์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ ์ด๋Ÿฌํ•œ ๋„๊ตฌ์™€ ๊ฐœ๋…๋“ค์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -๋‹ค์Œ ์žฅ์—์„œ ์ปจํ…Œ์ด๋„ˆ(์˜ˆ: ๋„์ปค ๋ฐ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค)์™€ ํ•จ๊ป˜ํ•˜๋Š” **FastAPI**์— ๋Œ€ํ•ด ๋ฐฐ์›Œ๋ณด์„ธ์š”. ์ด๋Ÿฌํ•œ ํˆด์—๋Š” ๋‹ค๋ฅธ **๋ฐฐํฌ ๊ฐœ๋…**๋“ค์„ ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. โœจ +๋‹ค์Œ ์žฅ์—์„œ ์ปจํ…Œ์ด๋„ˆ(์˜ˆ: Docker ๋ฐ Kubernetes)์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” **FastAPI**์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์„ธ์š”. ํ•ด๋‹น ๋„๊ตฌ๋“ค์ด ๋‹ค๋ฅธ **๋ฐฐํฌ ๊ฐœ๋…๋“ค**๋„ ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โœจ diff --git a/docs/ko/docs/deployment/versions.md b/docs/ko/docs/deployment/versions.md index 559a892ab9..173ba925cf 100644 --- a/docs/ko/docs/deployment/versions.md +++ b/docs/ko/docs/deployment/versions.md @@ -1,94 +1,93 @@ -# FastAPI ๋ฒ„์ „๋“ค์— ๋Œ€ํ•˜์—ฌ +# FastAPI ๋ฒ„์ „๋“ค์— ๋Œ€ํ•˜์—ฌ { #about-fastapi-versions } -**FastAPI** ๋Š” ์ด๋ฏธ ๋งŽ์€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ๊ณผ ์‹œ์Šคํ…œ๋“ค์„ ๋งŒ๋“œ๋Š”๋ฐ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  100%์˜ ํ…Œ์ŠคํŠธ ์ •ํ™•์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๊ฒƒ์€ ์•„์ง๊นŒ์ง€๋„ ๋น ๋ฅด๊ฒŒ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +**FastAPI**๋Š” ์ด๋ฏธ ๋งŽ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ์‹œ์Šคํ…œ์—์„œ ํ”„๋กœ๋•์…˜์œผ๋กœ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋Š” 100%๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐœ๋ฐœ์€ ์—ฌ์ „ํžˆ ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -์ƒˆ๋กœ์šด ํŠน์ง•๋“ค์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ถ”๊ฐ€๋˜๊ณ , ์˜ค๋ฅ˜๋“ค์ด ์ง€์†์ ์œผ๋กœ ์ˆ˜์ •๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฝ”๋“œ๊ฐ€ ๊ณ„์†์ ์œผ๋กœ ํ–ฅ์ƒ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์ด ์ž์ฃผ ์ถ”๊ฐ€๋˜๊ณ , ๋ฒ„๊ทธ๊ฐ€ ๊ทœ์น™์ ์œผ๋กœ ์ˆ˜์ •๋˜๋ฉฐ, ์ฝ”๋“œ๋Š” ๊ณ„์†ํ•ด์„œ ์ง€์†์ ์œผ๋กœ ๊ฐœ์„ ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๊ฒƒ์ด ์•„์ง๋„ ์ตœ์‹  ๋ฒ„์ „์ด `0.x.x`์ธ ์ด์œ ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๊ฐ๊ฐ์˜ ๋ฒ„์ „๋“ค์ด ์ž ์žฌ์ ์œผ๋กœ ๋ณ€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ด๋Š” <a href="https://semver.org/" class="external-link" target="_blank">์œ ์˜์  ๋ฒ„์ „</a> ๊ด€์Šต์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. +๊ทธ๋ž˜์„œ ํ˜„์žฌ ๋ฒ„์ „์ด ์•„์ง `0.x.x`์ธ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ ๋ฒ„์ „์ด ์ž ์žฌ์ ์œผ๋กœ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์ด ๊นจ์ง€๋Š” ๋ณ€๊ฒฝ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Œ์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” <a href="https://semver.org/" class="external-link" target="_blank">Semantic Versioning</a> ๊ด€๋ก€๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. -์ง€๊ธˆ ๋ฐ”๋กœ **FastAPI**๋กœ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ (์•„๋งˆ ์ง€๊ธˆ๊นŒ์ง€ ๊ทธ๋ž˜ ์™”๋˜ ๊ฒƒ์ฒ˜๋Ÿผ), ์‚ฌ์šฉํ•˜๋Š” ๋ฒ„์ „์ด ์ฝ”๋“œ์™€ ์ž˜ ๋งž๋Š”์ง€ ํ™•์ธํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. +์ง€๊ธˆ ๋ฐ”๋กœ **FastAPI**๋กœ ํ”„๋กœ๋•์…˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๊ทธ๋ฆฌ๊ณ  ์•„๋งˆ๋„ ํ•œ๋™์•ˆ ๊ทธ๋ ‡๊ฒŒ ํ•ด์˜ค์…จ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค). ๋‹ค๋งŒ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋Š” ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. -## `fastapi` ๋ฒ„์ „์„ ํ‘œ์‹œ +## `fastapi` ๋ฒ„์ „์„ ๊ณ ์ •ํ•˜๊ธฐ { #pin-your-fastapi-version } -๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผํ•  ๊ฒƒ์€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด ์ž˜ ์ž‘๋™ํ•˜๋Š” ๊ฐ€์žฅ ์ตœ์‹ ์˜ ๊ตฌ์ฒด์ ์ธ **FastAPI** ๋ฒ„์ „์„ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผ ํ•  ์ผ์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์•Œ๊ณ  ์žˆ๋Š” **FastAPI**์˜ ์ตœ์‹  ๊ตฌ์ฒด ๋ฒ„์ „์— ๋งž์ถฐ ์‚ฌ์šฉ ์ค‘์ธ ๋ฒ„์ „์„ "๊ณ ์ •(pin)"ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— `0.45.0` ๋ฒ„์ „์„ ์‚ฌ์šฉํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, ์•ฑ์—์„œ `0.112.0` ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -๋งŒ์•ฝ์— `requirements.txt` ํŒŒ์ผ์„ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฒ„์ „์„ ๋ช…์„ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +`requirements.txt` ํŒŒ์ผ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฒ„์ „์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```txt -fastapi==0.45.0 +fastapi[standard]==0.112.0 ``` -์ด๊ฒƒ์€ `0.45.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](../release-notes.md){.internal-link target=_blank}์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ๋ฒ„์ „๋“ค์— ๋Œ€ํ•ด { #about-versions } + +Semantic Versioning ๊ด€๋ก€์— ๋”ฐ๋ฅด๋ฉด, `1.0.0` ๋ฏธ๋งŒ์˜ ์–ด๋–ค ๋ฒ„์ „์ด๋“  ์ž ์žฌ์ ์œผ๋กœ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์ด ๊นจ์ง€๋Š” ๋ณ€๊ฒฝ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +FastAPI๋Š” ๋˜ํ•œ "PATCH" ๋ฒ„์ „ ๋ณ€๊ฒฝ์€ ๋ฒ„๊ทธ ์ˆ˜์ •๊ณผ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์ด ๊นจ์ง€์ง€ ์•Š๋Š” ๋ณ€๊ฒฝ์„ ์œ„ํ•œ ๊ฒƒ์ด๋ผ๋Š” ๊ด€๋ก€๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. + +/// tip | ํŒ + +"PATCH"๋Š” ๋งˆ์ง€๋ง‰ ์ˆซ์ž์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด `0.2.3`์—์„œ PATCH ๋ฒ„์ „์€ `3`์ž…๋‹ˆ๋‹ค. + +/// + +๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฒ„์ „์„ ๊ณ ์ •ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: ```txt fastapi>=0.45.0,<0.46.0 ``` -์ด๊ฒƒ์€ `0.45.0` ๋ฒ„์ „๊ณผ ๊ฐ™๊ฑฐ๋‚˜ ๋†’์œผ๋ฉด์„œ `0.46.0` ๋ฒ„์ „ ๋ณด๋‹ค๋Š” ๋‚ฎ์€ ๋ฒ„์ „์„ ์‚ฌ์šฉํ–ˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, `0.45.2` ๋ฒ„์ „๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ํ•ด๋‹น ์กฐ๊ฑด์„ ๋งŒ์กฑํ•ฉ๋‹ˆ๋‹ค. - -๋งŒ์•ฝ์— Poetry, Pipenv, ๋˜๋Š” ๊ทธ๋ฐ–์˜ ๋‹ค์–‘ํ•œ ์„ค์น˜ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ํŒจํ‚ค์ง€์— ๊ตฌ์ฒด์ ์ธ ๋ฒ„์ „์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. - -## ์ด์šฉ๊ฐ€๋Šฅํ•œ ๋ฒ„์ „๋“ค - -[Release Notes](../release-notes.md){.internal-link target=_blank}๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฒ„์ „๋“ค์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.(์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ€์žฅ ์ตœ์‹ ์˜ ๋ฒ„์ „์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.) - - -## ๋ฒ„์ „๋“ค์— ๋Œ€ํ•ด - -์œ ์˜์  ๋ฒ„์ „ ๊ด€์Šต์„ ๋”ฐ๋ผ์„œ, `1.0.0` ์ดํ•˜์˜ ๋ชจ๋“  ๋ฒ„์ „๋“ค์€ ์ž ์žฌ์ ์œผ๋กœ ๊ธ‰๋ณ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -FastAPI๋Š” ์˜ค๋ฅ˜๋ฅผ ์ˆ˜์ •ํ•˜๊ณ , ์ผ๋ฐ˜์ ์ธ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์œ„ํ•ด "ํŒจ์น˜"๋ฒ„์ „์˜ ๊ด€์Šต์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. +ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์ด ๊นจ์ง€๋Š” ๋ณ€๊ฒฝ๊ณผ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์€ "MINOR" ๋ฒ„์ „์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. /// tip | ํŒ -์—ฌ๊ธฐ์„œ ๋งํ•˜๋Š” "ํŒจ์น˜"๋ž€ ๋ฒ„์ „์˜ ๋งˆ์ง€๋ง‰ ์ˆซ์ž๋กœ, ์˜ˆ๋ฅผ ๋“ค์–ด `0.2.3` ๋ฒ„์ „์—์„œ "ํŒจ์น˜"๋Š” `3`์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +"MINOR"๋Š” ๊ฐ€์šด๋ฐ ์ˆซ์ž์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด `0.2.3`์—์„œ MINOR ๋ฒ„์ „์€ `2`์ž…๋‹ˆ๋‹ค. /// -๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฒ„์ „์„ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +## FastAPI ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๊ธฐ { #upgrading-the-fastapi-versions } + +์•ฑ์— ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +**FastAPI**์—์„œ๋Š” ๋งค์šฐ ์‰ฝ์Šต๋‹ˆ๋‹ค(Starlette ๋•๋ถ„์—). ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•ด ๋ณด์„ธ์š”: [Testing](../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์™€ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. + +์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ๋งž๋Š” `1.0.0` ์ดˆ๊ณผ์˜ ์–ด๋–ค Pydantic ๋ฒ„์ „์œผ๋กœ๋“  ๊ณ ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด: ```txt -fastapi>=0.45.0,<0.46.0 -``` - -์ˆ˜์ •๋œ ์‚ฌํ•ญ๊ณผ ์ƒˆ๋กœ์šด ์š”์†Œ๋“ค์ด "๋งˆ์ด๋„ˆ" ๋ฒ„์ „์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. - -/// tip | ํŒ - -"๋งˆ์ด๋„ˆ"๋ž€ ๋ฒ„์ „ ๋„˜๋ฒ„์˜ ๊ฐ€์šด๋ฐ ์ˆซ์ž๋กœ, ์˜ˆ๋ฅผ ๋“ค์–ด์„œ `0.2.3`์˜ "๋งˆ์ด๋„ˆ" ๋ฒ„์ „์€ `2`์ž…๋‹ˆ๋‹ค. - -/// - -## FastAPI ๋ฒ„์ „์˜ ์—…๊ทธ๋ ˆ์ด๋“œ - -์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๊ฒ€์‚ฌํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. - -(Starlette ๋•๋ถ„์—), **FastAPI** ๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ต‰์žฅํžˆ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. [Testing](../tutorial/testing.md){.internal-link target=_blank}๋ฌธ์„œ๋ฅผ ํ™•์ธํ•ด ๋ณด์‹ญ์‹œ์˜ค: - -๊ฒ€์‚ฌ๋ฅผ ํ•ด๋ณด๊ณ  ๋‚œ ํ›„์—, **FastAPI** ๋ฒ„์ „์„ ๋” ์ตœ์‹ ์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฝ”๋“œ๋“ค์ด ํ…Œ์ŠคํŠธ์— ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธ์„ ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. - -๋งŒ์•ฝ์— ๋ชจ๋“  ๊ฒƒ์ด ์ •์ƒ ์ž‘๋™ํ•˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ๋ณ€๊ฒฝํ•˜๊ณ , ๋ชจ๋“  ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•œ๋‹ค๋ฉด, ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ `fastapi`๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -## Starlette์— ๋Œ€ํ•ด - -`starlette`์˜ ๋ฒ„์ „์€ ํ‘œ์‹œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. - -์„œ๋กœ๋‹ค๋ฅธ ๋ฒ„์ „์˜ **FastAPI**๊ฐ€ ๊ตฌ์ฒด์ ์ด๊ณ  ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ Starlette์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. - -๊ทธ๋Ÿฌ๋ฏ€๋กœ **FastAPI**๊ฐ€ ์•Œ๋งž์€ Starlette ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜์‹ญ์‹œ์˜ค. - -## Pydantic์— ๋Œ€ํ•ด - -Pydantic์€ **FastAPI** ๋ฅผ ์œ„ํ•œ ๊ฒ€์‚ฌ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ Pydantic(`1.0.0`์ด์ƒ)์€ ํ•ญ์ƒ FastAPI์™€ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. - -์ž‘์—…์„ ํ•˜๊ณ  ์žˆ๋Š” `1.0.0` ์ด์ƒ์˜ ๋ชจ๋“  ๋ฒ„์ „๊ณผ `2.0.0` ์ดํ•˜์˜ Pydantic ๋ฒ„์ „์„ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: - -```txt -pydantic>=1.2.0,<2.0.0 +pydantic>=2.7.0,<3.0.0 ``` diff --git a/docs/ko/docs/environment-variables.md b/docs/ko/docs/environment-variables.md index 1e6af3ceba..dc231acb64 100644 --- a/docs/ko/docs/environment-variables.md +++ b/docs/ko/docs/environment-variables.md @@ -1,4 +1,4 @@ -# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ +# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ { #environment-variables } /// tip | ํŒ @@ -6,11 +6,11 @@ /// -ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” ํŒŒ์ด์ฌ ์ฝ”๋“œ์˜ **๋ฐ”๊นฅ**์ธ, **์šด์˜ ์ฒด์ œ**์— ์กด์žฌํ•˜๋Š” ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. ํŒŒ์ด์ฌ ์ฝ”๋“œ๋‚˜ ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ™˜๊ฒฝ ๋ณ€์ˆ˜(๋˜๋Š” "**env var**"๋ผ๊ณ ๋„ ํ•ฉ๋‹ˆ๋‹ค)๋Š” ํŒŒ์ด์ฌ ์ฝ”๋“œ์˜ **๋ฐ”๊นฅ**์ธ, **์šด์˜ ์ฒด์ œ**์— ์กด์žฌํ•˜๋Š” ๋ณ€์ˆ˜์ด๋ฉฐ, ํŒŒ์ด์ฌ ์ฝ”๋“œ(๋˜๋Š” ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋žจ์—์„œ๋„)์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ **์„ค์ •**์„ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜, ํŒŒ์ด์ฌ์˜ **์„ค์น˜** ๊ณผ์ •์˜ ์ผ๋ถ€๋กœ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. +ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ **์„ค์ •**์„ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜, ํŒŒ์ด์ฌ์˜ **์„ค์น˜** ๊ณผ์ •์˜ ์ผ๋ถ€๋กœ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ +## ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ { #create-and-use-env-vars } ํŒŒ์ด์ฌ ์—†์ด๋„, **์…ธ (ํ„ฐ๋ฏธ๋„)** ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ **์ƒ์„ฑ** ํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -50,9 +50,9 @@ Hello Wade Wilson //// -## ํŒŒ์ด์ฌ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ฝ๊ธฐ +## ํŒŒ์ด์ฌ์—์„œ env var ์ฝ๊ธฐ { #read-env-vars-in-python } -ํŒŒ์ด์ฌ **๋ฐ”๊นฅ**์ธ ํ„ฐ๋ฏธ๋„์—์„œ(๋‹ค๋ฅธ ๋„๊ตฌ๋กœ๋„ ๊ฐ€๋Šฅ) ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑ๋„ ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ์ด๋ฅผ **ํŒŒ์ด์ฌ์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.** +ํŒŒ์ด์ฌ **๋ฐ”๊นฅ**์ธ ํ„ฐ๋ฏธ๋„์—์„œ(๋˜๋Š” ๋‹ค๋ฅธ ์–ด๋–ค ๋ฐฉ๋ฒ•์œผ๋กœ๋“ ) ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ , ๊ทธ๋Ÿฐ ๋‹ค์Œ **ํŒŒ์ด์ฌ์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค**. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ `main.py` ํŒŒ์ผ์ด ์žˆ๋‹ค๊ณ  ํ•ฉ์‹œ๋‹ค: @@ -67,7 +67,7 @@ print(f"Hello {name} from Python") <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> ์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” ๋ฐ˜ํ™˜ํ•  ๊ธฐ๋ณธ๊ฐ’์ž…๋‹ˆ๋‹ค. -์—ฌ๊ธฐ์„œ๋Š” `"World"`๋ฅผ ๋„ฃ์—ˆ๊ธฐ์— ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ์จ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋„ฃ์ง€ ์•Š์œผ๋ฉด `None` ์ด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. +์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์€ `None`์ด๋ฉฐ, ์—ฌ๊ธฐ์„œ๋Š” ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ `"World"`๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. /// @@ -129,7 +129,7 @@ Hello Wade Wilson from Python ํ™˜๊ฒฝ๋ณ€์ˆ˜๋Š” ์ฝ”๋“œ ๋ฐ”๊นฅ์—์„œ ์„ค์ •๋  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ฝ”๋“œ์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ๊ณ , ๋‚˜๋จธ์ง€ ํŒŒ์ผ๊ณผ ํ•จ๊ป˜ ์ €์žฅ(`git`์— ์ปค๋ฐ‹)ํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฏ€๋กœ, ๊ตฌ์„ฑ์ด๋‚˜ **์„ค์ •** ์— ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค. -**ํŠน์ • ํ”„๋กœ๊ทธ๋žจ ํ˜ธ์ถœ**์— ๋Œ€ํ•ด์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ํ”„๋กœ๊ทธ๋žจ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ํ•ด๋‹น ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ **ํŠน์ • ํ”„๋กœ๊ทธ๋žจ ํ˜ธ์ถœ**์— ๋Œ€ํ•ด์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, ํ•ด๋‹น ํ”„๋กœ๊ทธ๋žจ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ํ•ด๋‹น ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด ํ”„๋กœ๊ทธ๋žจ ๋ฐ”๋กœ ์•ž, ๊ฐ™์€ ์ค„์— ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: @@ -157,17 +157,17 @@ Hello World from Python /// -## ํƒ€์ž…๊ณผ ๊ฒ€์ฆ +## ํƒ€์ž…๊ณผ ๊ฒ€์ฆ { #types-and-validation } -์ด ํ™˜๊ฒฝ๋ณ€์ˆ˜๋“ค์€ ์˜ค์ง **ํ…์ŠคํŠธ ๋ฌธ์ž์—ด**๋กœ๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ ๋ฌธ์ž์—ด์€ ํŒŒ์ด์ฌ ์™ธ๋ถ€์— ์žˆ์œผ๋ฉฐ ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋žจ ๋ฐ ๋‚˜๋จธ์ง€ ์‹œ์Šคํ…œ(Linux, Windows, macOS ๋“ฑ ๋‹ค๋ฅธ ์šด์˜ ์ฒด์ œ)๊ณผ ํ˜ธํ™˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +์ด ํ™˜๊ฒฝ๋ณ€์ˆ˜๋“ค์€ ์˜ค์ง **ํ…์ŠคํŠธ ๋ฌธ์ž์—ด**๋กœ๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ ๋ฌธ์ž์—ด์€ ํŒŒ์ด์ฌ ์™ธ๋ถ€์— ์žˆ์œผ๋ฉฐ ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋žจ ๋ฐ ๋‚˜๋จธ์ง€ ์‹œ์Šคํ…œ(๊ทธ๋ฆฌ๊ณ  Linux, Windows, macOS ๊ฐ™์€ ์„œ๋กœ ๋‹ค๋ฅธ ์šด์˜ ์ฒด์ œ์—์„œ๋„)๊ณผ ํ˜ธํ™˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ํŒŒ์ด์ฌ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ๋ถ€ํ„ฐ ์ฝ์€ **๋ชจ๋“  ๊ฐ’**์€ **`str`**์ด ๋˜๊ณ , ๋‹ค๋ฅธ ํƒ€์ž…์œผ๋กœ์˜ ๋ณ€ํ™˜์ด๋‚˜ ๊ฒ€์ฆ์€ ์ฝ”๋“œ์—์„œ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -**์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •**์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ [๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ - ์„ค์ • ๋ฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜](./advanced/settings.md){.internal-link target=\_blank} ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •**์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ [๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ - ์„ค์ • ๋ฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜](./advanced/settings.md){.internal-link target=_blank} ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `PATH` ํ™˜๊ฒฝ ๋ณ€์ˆ˜ +## `PATH` ํ™˜๊ฒฝ ๋ณ€์ˆ˜ { #path-environment-variable } -**`PATH`**๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š”, **ํŠน๋ณ„ํ•œ** ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์šด์˜์ฒด์ œ(Linux, Windows, macOS ๋“ฑ)์—์„œ ์‹คํ–‰ํ•  ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ธฐ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. +**`PATH`**๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š”, **ํŠน๋ณ„ํ•œ** ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์šด์˜์ฒด์ œ(Linux, macOS, Windows)์—์„œ ์‹คํ–‰ํ•  ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ธฐ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ณ€์ˆ˜ `PATH`์˜ ๊ฐ’์€ Linux์™€ macOS์—์„œ๋Š” ์ฝœ๋ก  `:`, Windows์—์„œ๋Š” ์„ธ๋ฏธ์ฝœ๋ก  `;`์œผ๋กœ ๊ตฌ๋ถ„๋œ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๊ตฌ์„ฑ๋œ ๊ธด ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. @@ -181,11 +181,11 @@ Hello World from Python ์ด๋Š” ์‹œ์Šคํ…œ์ด ๋‹ค์Œ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ์•„์•ผ ํ•จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค: -- `/usr/local/bin` -- `/usr/bin` -- `/bin` -- `/usr/sbin` -- `/sbin` +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` //// @@ -197,9 +197,9 @@ C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System3 ์ด๋Š” ์‹œ์Šคํ…œ์ด ๋‹ค์Œ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ์•„์•ผ ํ•จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค: -- `C:\Program Files\Python312\Scripts` -- `C:\Program Files\Python312` -- `C:\Windows\System32` +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` //// @@ -209,7 +209,7 @@ C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System3 ์ฐพ์œผ๋ฉด **์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค**. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด **๋‹ค๋ฅธ ๋””๋ ‰ํ† ๋ฆฌ**์—์„œ ๊ณ„์† ์ฐพ์Šต๋‹ˆ๋‹ค. -### ํŒŒ์ด์ฌ ์„ค์น˜์™€ `PATH` ์—…๋ฐ์ดํŠธ +### ํŒŒ์ด์ฌ ์„ค์น˜์™€ `PATH` ์—…๋ฐ์ดํŠธ { #installing-python-and-updating-the-path } ํŒŒ์ด์ฌ์„ ์„ค์น˜ํ•  ๋•Œ, ์•„๋งˆ `PATH` ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ๊ฒƒ์ด๋ƒ๊ณ  ๋ฌผ์–ด๋ดค์„ ๊ฒ๋‹ˆ๋‹ค. @@ -285,13 +285,13 @@ $ C:\opt\custompython\bin\python //// -์ด ์ •๋ณด๋Š” [๊ฐ€์ƒ ํ™˜๊ฒฝ](virtual-environments.md){.internal-link target=\_blank} ์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ๋•Œ ์œ ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ด ์ •๋ณด๋Š” [๊ฐ€์ƒ ํ™˜๊ฒฝ](virtual-environments.md){.internal-link target=_blank} ์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ๋•Œ ์œ ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -## ๊ฒฐ๋ก  +## ๊ฒฐ๋ก  { #conclusion } -์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ณ  **ํ™˜๊ฒฝ ๋ณ€์ˆ˜**๊ฐ€ ๋ฌด์—‡์ด๊ณ  ํŒŒ์ด์ฌ์—์„œ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ดํ•ดํ•˜์…จ์„ ๊ฒ๋‹ˆ๋‹ค. +์ด ๋ฌธ์„œ๋ฅผ ํ†ตํ•ด **ํ™˜๊ฒฝ ๋ณ€์ˆ˜**๊ฐ€ ๋ฌด์—‡์ด๊ณ  ํŒŒ์ด์ฌ์—์„œ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ดํ•ดํ•˜์…จ์„ ๊ฒ๋‹ˆ๋‹ค. -๋˜ํ•œ <a href="https://ko.wikipedia.org/wiki/ํ™˜๊ฒฝ_๋ณ€์ˆ˜" class="external-link" target="_blank">ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์œ„ํ‚คํ”ผ๋””์•„(ํ•œ๊ตญ์–ด)</a>์—์„œ ์ด์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ <a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์œ„ํ‚คํ”ผ๋””์•„</a>์—์„œ ์ด์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŽ์€ ๊ฒฝ์šฐ์—์„œ, ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์–ด๋–ป๊ฒŒ ์œ ์šฉํ•˜๊ณ  ์ ์šฉ ๊ฐ€๋Šฅํ•œ์ง€ ๋ฐ”๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ์•Œ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐœ๋ฐœํ•  ๋•Œ ๋‹ค์–‘ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ๊ณ„์† ๋‚˜ํƒ€๋‚˜๋ฏ€๋กœ ์ด์— ๋Œ€ํ•ด ์•„๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/how-to/conditional-openapi.md b/docs/ko/docs/how-to/conditional-openapi.md index 79c7f0dd2d..16e6833661 100644 --- a/docs/ko/docs/how-to/conditional-openapi.md +++ b/docs/ko/docs/how-to/conditional-openapi.md @@ -1,46 +1,41 @@ -# ์กฐ๊ฑด๋ถ€์ ์ธ OpenAPI +# ์กฐ๊ฑด๋ถ€ OpenAPI { #conditional-openapi } -ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ์„ค์ • ๋ฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์กฐ๊ฑด๋ถ€๋กœ OpenAPI๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์™„์ „ํžˆ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ์„ค์ • ๋ฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ OpenAPI๋ฅผ ์กฐ๊ฑด๋ถ€๋กœ ๊ตฌ์„ฑํ•˜๊ณ  ์™„์ „ํžˆ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -## ๋ณด์•ˆ, API ๋ฐ docs์— ๋Œ€ํ•ด์„œ +## ๋ณด์•ˆ, API ๋ฐ docs์— ๋Œ€ํ•ด์„œ { #about-security-apis-and-docs } ํ”„๋กœ๋•์…˜์—์„œ, ๋ฌธ์„œํ™”๋œ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค(UI)๋ฅผ ์ˆจ๊ธฐ๋Š” ๊ฒƒ์ด API๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ๋ฐฉ๋ฒ•์ด *๋˜์–ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค*. -์ด๋Š” API์— ์ถ”๊ฐ€์ ์ธ ๋ณด์•ˆ์„ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉฐ, *๊ฒฝ๋กœ ์ž‘์—…*์€ ์—ฌ์ „ํžˆ ๋™์ผํ•œ ์œ„์น˜์—์„œ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋Š” API์— ์ถ”๊ฐ€์ ์ธ ๋ณด์•ˆ์„ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉฐ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋Š” ์—ฌ์ „ํžˆ ๋™์ผํ•œ ์œ„์น˜์—์„œ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ์— ๋ณด์•ˆ ๊ฒฐํ•จ์ด ์žˆ๋‹ค๋ฉด, ๊ทธ ๊ฒฐํ•จ์€ ์—ฌ์ „ํžˆ ์กด์žฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -๋ฌธ์„œ๋ฅผ ์ˆจ๊ธฐ๋Š” ๊ฒƒ์€ API์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค๋ฉฐ, ํ”„๋กœ๋•์…˜์—์„œ ๋””๋ฒ„๊น…์„ ๋” ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋‹จ์ˆœํžˆ <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">'๋ชจํ˜ธ์„ฑ์— ์˜ํ•œ ๋ณด์•ˆ'</a>์˜ ํ•œ ํ˜•ํƒœ๋กœ ๊ฐ„์ฃผ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ฌธ์„œ๋ฅผ ์ˆจ๊ธฐ๋Š” ๊ฒƒ์€ API์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค๋ฉฐ, ํ”„๋กœ๋•์…˜์—์„œ ๋””๋ฒ„๊น…์„ ๋” ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋‹จ์ˆœํžˆ <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">Security through obscurity</a>์˜ ํ•œ ํ˜•ํƒœ๋กœ ๊ฐ„์ฃผ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. API๋ฅผ ๋ณดํ˜ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค: -* ์š”์ฒญ ๋ณธ๋ฌธ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•ด ์ž˜ ์ •์˜๋œ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜์„ธ์š”. - +* ์š”์ฒญ ๋ณธ๋ฌธ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•ด ์ž˜ ์ •์˜๋œ Pydantic ๋ชจ๋ธ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. * ์ข…์†์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๊ถŒํ•œ๊ณผ ์—ญํ• ์„ ๊ตฌ์„ฑํ•˜์„ธ์š”. - -* ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ ˆ๋Œ€ ์ €์žฅํ•˜์ง€ ๋ง๊ณ , ์˜ค์ง ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋งŒ ์ €์žฅํ•˜์„ธ์š”. - -* Passlib๊ณผ JWT ํ† ํฐ๊ณผ ๊ฐ™์€ ์ž˜ ์•Œ๋ ค์ง„ ์•”ํ˜ธํ™” ๋„๊ตฌ๋“ค์„ ๊ตฌํ˜„ํ•˜๊ณ  ์‚ฌ์šฉํ•˜์„ธ์š”. - +* ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ ˆ๋Œ€ ์ €์žฅํ•˜์ง€ ๋ง๊ณ , ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ๋งŒ ์ €์žฅํ•˜์„ธ์š”. +* pwdlib์™€ JWT ํ† ํฐ ๋“ฑ๊ณผ ๊ฐ™์€ ์ž˜ ์•Œ๋ ค์ง„ ์•”ํ˜ธํ™” ๋„๊ตฌ๋“ค์„ ๊ตฌํ˜„ํ•˜๊ณ  ์‚ฌ์šฉํ•˜์„ธ์š”. * ํ•„์š”ํ•œ ๊ณณ์— OAuth2 ๋ฒ”์œ„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋” ์„ธ๋ถ„ํ™”๋œ ๊ถŒํ•œ ์ œ์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. +* ...๋“ฑ๋“ฑ. -* ๋“ฑ๋“ฑ.... +๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ํŠน์ • ํ™˜๊ฒฝ(์˜ˆ: ํ”„๋กœ๋•์…˜)์—์„œ ๋˜๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์˜ ์„ค์ •์— ๋”ฐ๋ผ API docs๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•˜๋Š” ๋งค์šฐ ํŠน์ •ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ํŠน์ • ํ™˜๊ฒฝ(์˜ˆ: ํ”„๋กœ๋•์…˜)์—์„œ ๋˜๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์˜ ์„ค์ •์— ๋”ฐ๋ผ API ๋ฌธ์„œ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•˜๋Š” ๋งค์šฐ ํŠน์ •ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +## ์„ค์ • ๋ฐ ํ™˜๊ฒฝ๋ณ€์ˆ˜์˜ ์กฐ๊ฑด๋ถ€ OpenAPI { #conditional-openapi-from-settings-and-env-vars } -## ์„ค์ • ๋ฐ ํ™˜๊ฒฝ๋ณ€์ˆ˜์˜ ์กฐ๊ฑด๋ถ€ OpenAPI - -๋™์ผํ•œ Pydantic ์„ค์ •์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์„ฑ๋œ OpenAPI ๋ฐ ๋ฌธ์„œ UI๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋™์ผํ•œ Pydantic ์„ค์ •์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์„ฑ๋œ OpenAPI ๋ฐ docs UI๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: -{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} ์—ฌ๊ธฐ์„œ `openapi_url` ์„ค์ •์„ ๊ธฐ๋ณธ๊ฐ’์ธ `"/openapi.json"`์œผ๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋’ค, ์šฐ๋ฆฌ๋Š” `FastAPI` ์•ฑ์„ ๋งŒ๋“ค ๋•Œ ๊ทธ๊ฒƒ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -ํ™˜๊ฒฝ ๋ณ€์ˆ˜ `OPENAPI_URL`์„ ๋นˆ ๋ฌธ์ž์—ด๋กœ ์„ค์ •ํ•˜์—ฌ OpenAPI(๋ฌธ์„œ UI ํฌํ•จ)๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: +๊ทธ๋Ÿฐ ๋‹ค์Œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ `OPENAPI_URL`์„ ๋นˆ ๋ฌธ์ž์—ด๋กœ ์„ค์ •ํ•˜์—ฌ OpenAPI(UI docs ํฌํ•จ)๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: <div class="termy"> diff --git a/docs/ko/docs/how-to/configure-swagger-ui.md b/docs/ko/docs/how-to/configure-swagger-ui.md index 5a57342cf2..174f976f67 100644 --- a/docs/ko/docs/how-to/configure-swagger-ui.md +++ b/docs/ko/docs/how-to/configure-swagger-ui.md @@ -1,4 +1,4 @@ -# Swagger UI ๊ตฌ์„ฑ +# Swagger UI ๊ตฌ์„ฑ { #configure-swagger-ui } ์ถ”๊ฐ€์ ์ธ <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI ๋งค๊ฐœ๋ณ€์ˆ˜</a>๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -8,7 +8,7 @@ FastAPI๋Š” ์ด ๊ตฌ์„ฑ์„ **JSON** ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ JavaScript์™€ ํ˜ธํ™˜๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Swagger UI์—์„œ ํ•„์š”๋กœ ํ•˜๋Š” ํ˜•์‹์ž…๋‹ˆ๋‹ค. -## ๊ตฌ๋ฌธ ๊ฐ•์กฐ ๋น„ํ™œ์„ฑํ™” +## ๊ตฌ๋ฌธ ๊ฐ•์กฐ ๋น„ํ™œ์„ฑํ™” { #disable-syntax-highlighting } ์˜ˆ๋ฅผ ๋“ค์–ด, Swagger UI์—์„œ ๊ตฌ๋ฌธ ๊ฐ•์กฐ ๊ธฐ๋Šฅ์„ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -18,41 +18,41 @@ FastAPI๋Š” ์ด ๊ตฌ์„ฑ์„ **JSON** ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ JavaScript์™€ ํ˜ธํ™˜ ๊ทธ๋Ÿฌ๋‚˜ `syntaxHighlight`๋ฅผ `False`๋กœ ์„ค์ •ํ•˜์—ฌ ๊ตฌ๋ฌธ ๊ฐ•์กฐ ๊ธฐ๋Šฅ์„ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} ...๊ทธ๋Ÿผ Swagger UI์—์„œ ๋” ์ด์ƒ ๊ตฌ๋ฌธ ๊ฐ•์กฐ ๊ธฐ๋Šฅ์ด ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: <img src="/img/tutorial/extending-openapi/image03.png"> -## ํ…Œ๋งˆ ๋ณ€๊ฒฝ +## ํ…Œ๋งˆ ๋ณ€๊ฒฝ { #change-the-theme } ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ `"syntaxHighlight.theme"` ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ๋ฌธ ๊ฐ•์กฐ ํ…Œ๋งˆ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์ค‘๊ฐ„์— ์ ์ด ํฌํ•จ๋œ ๊ฒƒ์„ ์ฐธ๊ณ ํ•˜์‹ญ์‹œ์˜ค). -{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} ์ด ์„ค์ •์€ ๊ตฌ๋ฌธ ๊ฐ•์กฐ ์ƒ‰์ƒ ํ…Œ๋งˆ๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค: <img src="/img/tutorial/extending-openapi/image04.png"> -## ๊ธฐ๋ณธ Swagger UI ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ณ€๊ฒฝ +## ๊ธฐ๋ณธ Swagger UI ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ณ€๊ฒฝ { #change-default-swagger-ui-parameters } FastAPI๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ์ ํ•ฉํ•œ ๋ช‡ ๊ฐ€์ง€ ๊ธฐ๋ณธ ๊ตฌ์„ฑ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๊ตฌ์„ฑ์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค: -{* ../../fastapi/openapi/docs.py ln[8:23] hl[17: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 ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ๊ณต์‹ <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">๋ฌธ์„œ</a>๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค. +์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  ๊ตฌ์„ฑ ์˜ต์…˜์„ ํ™•์ธํ•˜๋ ค๋ฉด, Swagger UI ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ๊ณต์‹ <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฌธ์„œ</a>๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค. -## JavaScript ์ „์šฉ ์„ค์ • +## JavaScript ์ „์šฉ ์„ค์ • { #javascript-only-settings } Swagger UI๋Š” **JavaScript ์ „์šฉ** ๊ฐ์ฒด(์˜ˆ: JavaScript ํ•จ์ˆ˜)๋กœ ๋‹ค๋ฅธ ๊ตฌ์„ฑ์„ ํ—ˆ์šฉํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. @@ -67,4 +67,4 @@ presets: [ ์ด๋“ค์€ ๋ฌธ์ž์—ด์ด ์•„๋‹Œ **JavaScript** ๊ฐ์ฒด์ด๋ฏ€๋กœ Python ์ฝ”๋“œ์—์„œ ์ง์ ‘ ์ „๋‹ฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -์ด์™€ ๊ฐ™์€ JavaScript ์ „์šฉ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ์œ„์˜ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  Swagger UI ๊ฒฝ๋กœ ์ž‘์—…์„ ์žฌ์ •์˜ํ•˜๊ณ  ํ•„์š”ํ•œ JavaScript๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด์™€ ๊ฐ™์€ JavaScript ์ „์šฉ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ์œ„์˜ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Swagger UI *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ๋ชจ๋‘ ์žฌ์ •์˜ํ•˜๊ณ  ํ•„์š”ํ•œ JavaScript๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”. diff --git a/docs/ko/docs/index.md b/docs/ko/docs/index.md index b6b4765dad..0c317f296d 100644 --- a/docs/ko/docs/index.md +++ b/docs/ko/docs/index.md @@ -1,11 +1,11 @@ -# FastAPI +# FastAPI { #fastapi } <style> .md-content .md-typeset h1 { display: none; } </style> <p align="center"> - <a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a> + <a href="https://fastapi.tiangolo.com/ko"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a> </p> <p align="center"> <em>FastAPI ํ”„๋ ˆ์ž„์›Œํฌ, ๊ณ ์„ฑ๋Šฅ, ๊ฐ„ํŽธํ•œ ํ•™์Šต, ๋น ๋ฅธ ์ฝ”๋“œ ์ž‘์„ฑ, ์ค€๋น„๋œ ํ”„๋กœ๋•์…˜</em> @@ -27,7 +27,7 @@ --- -**๋ฌธ์„œ**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a> +**๋ฌธ์„œ**: <a href="https://fastapi.tiangolo.com/ko" target="_blank">https://fastapi.tiangolo.com</a> **์†Œ์Šค ์ฝ”๋“œ**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a> @@ -37,36 +37,41 @@ FastAPI๋Š” ํ˜„๋Œ€์ ์ด๊ณ , ๋น ๋ฅด๋ฉฐ(๊ณ ์„ฑ๋Šฅ), ํŒŒ์ด์ฌ ํ‘œ์ค€ ํƒ€์ž… ํžŒํŠธ ์ฃผ์š” ํŠน์ง•์œผ๋กœ: -* **๋น ๋ฆ„**: (Starlette๊ณผ Pydantic ๋•๋ถ„์—) **NodeJS** ๋ฐ **Go**์™€ ๋Œ€๋“ฑํ•  ์ •๋„๋กœ ๋งค์šฐ ๋†’์€ ์„ฑ๋Šฅ. [์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ€์žฅ ๋น ๋ฅธ ํŒŒ์ด์ฌ ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜](#_11). - +* **๋น ๋ฆ„**: (Starlette๊ณผ Pydantic ๋•๋ถ„์—) **NodeJS** ๋ฐ **Go**์™€ ๋Œ€๋“ฑํ•  ์ •๋„๋กœ ๋งค์šฐ ๋†’์€ ์„ฑ๋Šฅ. [์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ€์žฅ ๋น ๋ฅธ ํŒŒ์ด์ฌ ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜](#performance). * **๋น ๋ฅธ ์ฝ”๋“œ ์ž‘์„ฑ**: ์•ฝ 200%์—์„œ 300%๊นŒ์ง€ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ ์†๋„ ์ฆ๊ฐ€. * * **์ ์€ ๋ฒ„๊ทธ**: ์‚ฌ๋žŒ(๊ฐœ๋ฐœ์ž)์— ์˜ํ•œ ์—๋Ÿฌ ์•ฝ 40% ๊ฐ์†Œ. * * **์ง๊ด€์ **: ํ›Œ๋ฅญํ•œ ํŽธ์ง‘๊ธฐ ์ง€์›. ๋ชจ๋“  ๊ณณ์—์„œ <abbr title="also known as auto-complete, autocompletion, IntelliSense">์ž๋™์™„์„ฑ</abbr>. ์ ์€ ๋””๋ฒ„๊น… ์‹œ๊ฐ„. * **์‰ฌ์›€**: ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ  ๋ฐฐ์šฐ๋„๋ก ์„ค๊ณ„. ์ ์€ ๋ฌธ์„œ ์ฝ๊ธฐ ์‹œ๊ฐ„. * **์งง์Œ**: ์ฝ”๋“œ ์ค‘๋ณต ์ตœ์†Œํ™”. ๊ฐ ๋งค๊ฐœ๋ณ€์ˆ˜ ์„ ์–ธ์˜ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ. ์ ์€ ๋ฒ„๊ทธ. * **๊ฒฌ๊ณ ํ•จ**: ์ค€๋น„๋œ ํ”„๋กœ๋•์…˜ ์šฉ ์ฝ”๋“œ๋ฅผ ์–ป์œผ์‹ญ์‹œ์˜ค. ์ž๋™ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์™€ ํ•จ๊ป˜. -* **ํ‘œ์ค€ ๊ธฐ๋ฐ˜**: API์— ๋Œ€ํ•œ (์™„์ „ํžˆ ํ˜ธํ™˜๋˜๋Š”) ๊ฐœ๋ฐฉํ˜• ํ‘œ์ค€ ๊ธฐ๋ฐ˜: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (์ด์ „์— Swagger๋กœ ์•Œ๋ ค์กŒ๋˜) ๋ฐ <a href="http://json-schema.org/" class="external-link" target="_blank">JSON ์Šคํ‚ค๋งˆ</a>. +* **ํ‘œ์ค€ ๊ธฐ๋ฐ˜**: API์— ๋Œ€ํ•œ (์™„์ „ํžˆ ํ˜ธํ™˜๋˜๋Š”) ๊ฐœ๋ฐฉํ˜• ํ‘œ์ค€ ๊ธฐ๋ฐ˜: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (์ด์ „์— Swagger๋กœ ์•Œ๋ ค์กŒ๋˜) ๋ฐ <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>. <small>* ๋‚ด๋ถ€ ๊ฐœ๋ฐœํŒ€์˜ ํ”„๋กœ๋•์…˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋นŒ๋“œํ•œ ํ…Œ์ŠคํŠธ์— ๊ทผ๊ฑฐํ•œ ์ธก์ •</small> -## ๊ณจ๋“œ ์Šคํฐ์„œ +## ์Šคํฐ์„œ { #sponsors } <!-- sponsors --> -{% if sponsors %} +### ํ‚ค์Šคํ†ค ์Šคํฐ์„œ { #keystone-sponsor } + +{% for sponsor in sponsors.keystone -%} +<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> +{% endfor -%} + +### ๊ณจ๋“œ ๋ฐ ์‹ค๋ฒ„ ์Šคํฐ์„œ { #gold-and-silver-sponsors } + {% for sponsor in sponsors.gold -%} <a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> {% endfor -%} {%- for sponsor in sponsors.silver -%} <a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> {% endfor %} -{% endif %} <!-- /sponsors --> -<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">๋‹ค๋ฅธ ์Šคํฐ์„œ</a> +<a href="https://fastapi.tiangolo.com/ko/fastapi-people/#sponsors" class="external-link" target="_blank">๋‹ค๋ฅธ ์Šคํฐ์„œ</a> -## ์˜๊ฒฌ๋“ค +## ์˜๊ฒฌ๋“ค { #opinions } "_[...] ์ €๋Š” ์š”์ฆ˜ **FastAPI**๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. [...] ์‚ฌ์‹ค ์šฐ๋ฆฌ ํŒ€์˜ **๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ ML ์„œ๋น„์Šค** ์ „๋ถ€๋ฅผ ๋ฐ”๊ฟ€ ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ๊ทธ์ค‘ ์ผ๋ถ€๋Š” ํ•ต์‹ฌ **Windows**์™€ ๋ช‡๋ช‡์˜ **Office** ์ œํ’ˆ๋“ค์ด ํ†ตํ•ฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค._" @@ -94,7 +99,7 @@ FastAPI๋Š” ํ˜„๋Œ€์ ์ด๊ณ , ๋น ๋ฅด๋ฉฐ(๊ณ ์„ฑ๋Šฅ), ํŒŒ์ด์ฌ ํ‘œ์ค€ ํƒ€์ž… ํžŒํŠธ "_์†”์งํžˆ, ๋‹น์‹ ์ด ๋งŒ๋“  ๊ฒƒ์€ ๋งค์šฐ ๊ฒฌ๊ณ ํ•˜๊ณ  ์„ธ๋ จ๋˜์–ด ๋ณด์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๋ฉด์—์„œ **Hug**๊ฐ€ ์ด๋ ‡๊ฒŒ ๋˜์—ˆ์œผ๋ฉด ํ•ฉ๋‹ˆ๋‹ค - ๊ทธ๊ฑธ ๋งŒ๋“  ๋ˆ„๊ตฐ๊ฐ€๋ฅผ ๋ณด๋Š” ๊ฒƒ์€ ๋งŽ์€ ์˜๊ฐ์„ ์ค๋‹ˆ๋‹ค._" -<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="http://www.hug.rest/" target="_blank">Hug</a> ์ œ์ž‘์ž</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div> +<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> ์ œ์ž‘์ž</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div> --- @@ -106,50 +111,54 @@ FastAPI๋Š” ํ˜„๋Œ€์ ์ด๊ณ , ๋น ๋ฅด๋ฉฐ(๊ณ ์„ฑ๋Šฅ), ํŒŒ์ด์ฌ ํ‘œ์ค€ ํƒ€์ž… ํžŒํŠธ --- -## **Typer**, FastAPI์˜ CLI +"_ํ”„๋กœ๋•์…˜ Python API๋ฅผ ๋งŒ๋“ค๊ณ ์ž ํ•œ๋‹ค๋ฉด, ์ €๋Š” **FastAPI**๋ฅผ ๊ฐ•๋ ฅํžˆ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค. **์•„๋ฆ„๋‹ต๊ฒŒ ์„ค๊ณ„**๋˜์—ˆ๊ณ , **์‚ฌ์šฉ์ด ๊ฐ„๋‹จ**ํ•˜๋ฉฐ, **ํ™•์žฅ์„ฑ์ด ๋งค์šฐ ๋›ฐ์–ด๋‚˜**๊ณ , ์šฐ๋ฆฌ์˜ API ์šฐ์„  ๊ฐœ๋ฐœ ์ „๋žต์—์„œ **ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ**๊ฐ€ ๋˜์—ˆ์œผ๋ฉฐ Virtual TAC Engineer ๊ฐ™์€ ๋งŽ์€ ์ž๋™ํ™”์™€ ์„œ๋น„์Šค๋ฅผ ์ด๋Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค._" + +<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div> + +--- + +## FastAPI ๋ฏธ๋‹ˆ ๋‹คํ๋ฉ˜ํ„ฐ๋ฆฌ { #fastapi-mini-documentary } + +2025๋…„ ๋ง์— ๊ณต๊ฐœ๋œ <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">FastAPI ๋ฏธ๋‹ˆ ๋‹คํ๋ฉ˜ํ„ฐ๋ฆฌ</a>๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜จ๋ผ์ธ์—์„œ ์‹œ์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a> + +## **Typer**, CLI๋ฅผ ์œ„ํ•œ FastAPI { #typer-the-fastapi-of-clis } <a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a> ์›น API ๋Œ€์‹  ํ„ฐ๋ฏธ๋„์—์„œ ์‚ฌ์šฉํ•  <abbr title="Command Line Interface">CLI</abbr> ์•ฑ์„ ๋งŒ๋“ค๊ณ  ์žˆ๋‹ค๋ฉด, <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>๋ฅผ ํ™•์ธํ•ด ๋ณด์‹ญ์‹œ์˜ค. -**Typer**๋Š” FastAPI์˜ ๋™์ƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  **FastAPI์˜ CLI**๊ฐ€ ๋˜๊ธฐ ์œ„ํ•ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค. โŒจ๏ธ ๐Ÿš€ +**Typer**๋Š” FastAPI์˜ ๋™์ƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  **CLI๋ฅผ ์œ„ํ•œ FastAPI**๊ฐ€ ๋˜๊ธฐ ์œ„ํ•ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค. โŒจ๏ธ ๐Ÿš€ -## ์š”๊ตฌ์‚ฌํ•ญ +## ์š”๊ตฌ์‚ฌํ•ญ { #requirements } FastAPI๋Š” ๊ฑฐ์ธ๋“ค์˜ ์–ด๊นจ ์œ„์— ์„œ ์žˆ์Šต๋‹ˆ๋‹ค: * ์›น ๋ถ€๋ถ„์„ ์œ„ํ•œ <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a>. * ๋ฐ์ดํ„ฐ ๋ถ€๋ถ„์„ ์œ„ํ•œ <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>. -## ์„ค์น˜ +## ์„ค์น˜ { #installation } + +<a href="https://fastapi.tiangolo.com/ko/virtual-environments/" class="external-link" target="_blank">๊ฐ€์ƒ ํ™˜๊ฒฝ</a>์„ ์ƒ์„ฑํ•˜๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ FastAPI๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”: <div class="termy"> ```console -$ pip install fastapi +$ pip install "fastapi[standard]" ---> 100% ``` </div> -ํ”„๋กœ๋•์…˜์„ ์œ„ํ•ด <a href="http://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a> ๋˜๋Š” <a href="https://github.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>๊ณผ ๊ฐ™์€ ASGI ์„œ๋ฒ„๋„ ํ•„์š”ํ•  ๊ฒ๋‹ˆ๋‹ค. +**Note**: ๋ชจ๋“  ํ„ฐ๋ฏธ๋„์—์„œ ๋™์ž‘ํ•˜๋„๋ก `"fastapi[standard]"`๋ฅผ ๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ ๋„ฃ์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. -<div class="termy"> +## ์˜ˆ์ œ { #example } -```console -$ pip install "uvicorn[standard]" +### ๋งŒ๋“ค๊ธฐ { #create-it } ----> 100% -``` - -</div> - -## ์˜ˆ์ œ - -### ๋งŒ๋“ค๊ธฐ - -* `main.py` ํŒŒ์ผ์„ ๋งŒ๋“œ์‹ญ์‹œ์˜ค: +๋‹ค์Œ ๋‚ด์šฉ์œผ๋กœ `main.py` ํŒŒ์ผ์„ ๋งŒ๋“œ์‹ญ์‹œ์˜ค: ```Python from typing import Union @@ -172,9 +181,9 @@ def read_item(item_id: int, q: Union[str, None] = None): <details markdown="1"> <summary>๋˜๋Š” <code>async def</code> ์‚ฌ์šฉํ•˜๊ธฐ...</summary> -์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ๊ฐ€ `async` / `await`์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, `async def`๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. +์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ๊ฐ€ `async` / `await`์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, `async def`๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค: -```Python hl_lines="9 14" +```Python hl_lines="9 14" from typing import Union from fastapi import FastAPI @@ -194,22 +203,35 @@ async def read_item(item_id: int, q: Union[str, None] = None): **Note**: -์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด, <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">๋ฌธ์„œ์—์„œ `async`์™€ `await`</a>์— ๊ด€ํ•œ _"๊ธ‰ํ•˜์„ธ์š”?"_ ์„น์…˜์„ ํ™•์ธํ•ด ๋ณด์‹ญ์‹œ์˜ค. +์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด, <a href="https://fastapi.tiangolo.com/ko/async/#in-a-hurry" target="_blank">๋ฌธ์„œ์—์„œ `async`์™€ `await`</a>์— ๊ด€ํ•œ _"๊ธ‰ํ•˜์„ธ์š”?"_ ์„น์…˜์„ ํ™•์ธํ•ด ๋ณด์‹ญ์‹œ์˜ค. </details> -### ์‹คํ–‰ํ•˜๊ธฐ +### ์‹คํ–‰ํ•˜๊ธฐ { #run-it } -์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜์‹ญ์‹œ์˜ค: +๋‹ค์Œ ๋ช…๋ น์œผ๋กœ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜์‹ญ์‹œ์˜ค: <div class="termy"> ```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. ``` @@ -217,17 +239,17 @@ INFO: Application startup complete. </div> <details markdown="1"> -<summary><code>uvicorn main:app --reload</code> ๋ช…๋ น์— ๊ด€ํ•˜์—ฌ...</summary> +<summary><code>fastapi dev main.py</code> ๋ช…๋ น์— ๊ด€ํ•˜์—ฌ...</summary> -๋ช…๋ น `uvicorn main:app`์€ ๋‹ค์Œ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค: +`fastapi dev` ๋ช…๋ น์€ `main.py` ํŒŒ์ผ์„ ์ฝ๊ณ , ๊ทธ ์•ˆ์˜ **FastAPI** ์•ฑ์„ ๊ฐ์ง€ํ•œ ๋‹ค์Œ, <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>์„ ์‚ฌ์šฉํ•ด ์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. -* `main`: `main.py` ํŒŒ์ผ (ํŒŒ์ด์ฌ "๋ชจ๋“ˆ"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋œ ํ›„ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ํ•˜๊ธฐ. ๊ฐœ๋ฐœํ™˜๊ฒฝ์—์„œ๋งŒ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. +๊ธฐ๋ณธ์ ์œผ๋กœ `fastapi dev`๋Š” ๋กœ์ปฌ ๊ฐœ๋ฐœ์„ ์œ„ํ•ด auto-reload๊ฐ€ ํ™œ์„ฑํ™”๋œ ์ƒํƒœ๋กœ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. + +์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://fastapi.tiangolo.com/ko/fastapi-cli/" target="_blank">FastAPI CLI ๋ฌธ์„œ</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. </details> -### ํ™•์ธํ•˜๊ธฐ +### ํ™•์ธํ•˜๊ธฐ { #check-it } ๋ธŒ๋ผ์šฐ์ €๋กœ <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>๋ฅผ ์—ด์–ด๋ณด์‹ญ์‹œ์˜ค. @@ -241,10 +263,10 @@ INFO: Application startup complete. * _๊ฒฝ๋กœ_ `/` ๋ฐ `/items/{item_id}`์—์„œ HTTP ์š”์ฒญ ๋ฐ›๊ธฐ. * ๋‘ _๊ฒฝ๋กœ_ ๋ชจ๋‘ `GET` <em>์—ฐ์‚ฐ</em>(HTTP _๋ฉ”์†Œ๋“œ_ ๋กœ ์•Œ๋ ค์ง„)์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. -* _๊ฒฝ๋กœ_ `/items/{item_id}`๋Š” _๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜_ `int`ํ˜• ์ด์–ด์•ผ ํ•˜๋Š” `item_id`๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -* _๊ฒฝ๋กœ_ `/items/{item_id}`๋Š” ์„ ํƒ์ ์ธ `str`ํ˜• ์ด์–ด์•ผ ํ•˜๋Š” _๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜_ `q`๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +* _๊ฒฝ๋กœ_ `/items/{item_id}`๋Š” `int`ํ˜• ์ด์–ด์•ผ ํ•˜๋Š” _๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜_ `item_id`๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +* _๊ฒฝ๋กœ_ `/items/{item_id}`๋Š” ์„ ํƒ์ ์ธ `str`ํ˜• _์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜_ `q`๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -### ๋Œ€ํ™”ํ˜• API ๋ฌธ์„œ +### ๋Œ€ํ™”ํ˜• API ๋ฌธ์„œ { #interactive-api-docs } ์ด์ œ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>๋กœ ๊ฐ€๋ณด์‹ญ์‹œ์˜ค. @@ -252,7 +274,7 @@ INFO: Application startup complete. ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### ๋Œ€์•ˆ API ๋ฌธ์„œ +### ๋Œ€์•ˆ API ๋ฌธ์„œ { #alternative-api-docs } ๊ทธ๋ฆฌ๊ณ  ์ด์ œ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>๋กœ ๊ฐ€๋ด…์‹œ๋‹ค. @@ -260,13 +282,13 @@ INFO: Application startup complete. ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## ์˜ˆ์ œ ์‹ฌํ™” +## ์˜ˆ์ œ ์—…๊ทธ๋ ˆ์ด๋“œ { #example-upgrade } -์ด์ œ `PUT` ์š”์ฒญ์— ์žˆ๋Š” ๋ณธ๋ฌธ(Body)์„ ๋ฐ›๊ธฐ ์œ„ํ•ด `main.py`๋ฅผ ์ˆ˜์ •ํ•ด๋ด…์‹œ๋‹ค. +์ด์ œ `PUT` ์š”์ฒญ์—์„œ ๋ณธ๋ฌธ์„ ๋ฐ›๊ธฐ ์œ„ํ•ด `main.py` ํŒŒ์ผ์„ ์ˆ˜์ •ํ•ด๋ด…์‹œ๋‹ค. -Pydantic์„ ์ด์šฉํ•ด ํŒŒ์ด์ฌ ํ‘œ์ค€ ํƒ€์ž…์œผ๋กœ ๋ณธ๋ฌธ์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. +Pydantic ๋•๋ถ„์— ํ‘œ์ค€ Python ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด ๋ณธ๋ฌธ์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. -```Python hl_lines="4 9 10 11 12 25 26 27" +```Python hl_lines="4 9-12 25-27" from typing import Union from fastapi import FastAPI @@ -296,25 +318,25 @@ 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 } ์ด์ œ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. -* ๋Œ€ํ™”ํ˜• 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) -### ๋Œ€์•ˆ API ๋ฌธ์„œ ์—…๊ทธ๋ ˆ์ด๋“œ +### ๋Œ€์•ˆ API ๋ฌธ์„œ ์—…๊ทธ๋ ˆ์ด๋“œ { #alternative-api-docs-upgrade } ๊ทธ๋ฆฌ๊ณ  ์ด์ œ, <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. @@ -322,7 +344,7 @@ def update_item(item_id: int, item: Item): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### ์š”์•ฝ +### ์š”์•ฝ { #recap } ์š”์•ฝํ•˜๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์€ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ํƒ€์ž…, ๋ณธ๋ฌธ ๋“ฑ์„ ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ์„œ **ํ•œ๋ฒˆ์—** ์„ ์–ธํ–ˆ์Šต๋‹ˆ๋‹ค. @@ -351,8 +373,8 @@ item: Item * ํƒ€์ž… ๊ฒ€์‚ฌ. * ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ: * ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์„ ๋•Œ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๋ช…ํ™•ํ•œ ์—๋Ÿฌ. - * ์ค‘์ฒฉ๋œ JSON ๊ฐ์ฒด์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ. -* ์ž…๋ ฅ ๋ฐ์ดํ„ฐ <abbr title="๋‹ค์Œ์œผ๋กœ ์•Œ๋ ค์ง„: ์ง๋ ฌํ™”, ํŒŒ์‹ฑ, ๋งˆ์ƒฌ๋ง">๋ณ€ํ™˜</abbr>: ๋„คํŠธ์›Œํฌ์—์„œ ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ ๋ฐ ํƒ€์ž…์œผ๋กœ ์ „์†ก. ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋“ค: + * ๊นŠ์ด ์ค‘์ฒฉ๋œ JSON ๊ฐ์ฒด์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ. +* ์ž…๋ ฅ ๋ฐ์ดํ„ฐ <abbr title="also known as: serialization, parsing, marshalling">๋ณ€ํ™˜</abbr>: ๋„คํŠธ์›Œํฌ์—์„œ ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ ๋ฐ ํƒ€์ž…์œผ๋กœ ์ „์†ก. ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋“ค: * JSON. * ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜. * ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜. @@ -360,7 +382,7 @@ item: Item * ํ—ค๋”. * ํผ(Forms). * ํŒŒ์ผ. -* ์ถœ๋ ฅ ๋ฐ์ดํ„ฐ <abbr title="๋‹ค์Œ์œผ๋กœ ์•Œ๋ ค์ง„: ์ง๋ ฌํ™”, ํŒŒ์‹ฑ, ๋งˆ์ƒฌ๋ง">๋ณ€ํ™˜</abbr>: ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ ๋ฐ ํƒ€์ž…์„ ๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ๋กœ ์ „ํ™˜(JSON ํ˜•์‹์œผ๋กœ): +* ์ถœ๋ ฅ ๋ฐ์ดํ„ฐ <abbr title="also known as: serialization, parsing, marshalling">๋ณ€ํ™˜</abbr>: ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ ๋ฐ ํƒ€์ž…์„ ๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ๋กœ ์ „ํ™˜(JSON ํ˜•์‹์œผ๋กœ): * ํŒŒ์ด์ฌ ํƒ€์ž… ๋ณ€ํ™˜ (`str`, `int`, `float`, `bool`, `list`, ๋“ฑ). * `datetime` ๊ฐ์ฒด. * `UUID` ๊ฐ์ฒด. @@ -377,13 +399,13 @@ item: Item * `GET` ๋ฐ `PUT` ์š”์ฒญ์— `item_id`๊ฐ€ ๊ฒฝ๋กœ์— ์žˆ๋Š”์ง€ ๊ฒ€์ฆ. * `GET` ๋ฐ `PUT` ์š”์ฒญ์— `item_id`๊ฐ€ `int` ํƒ€์ž…์ธ์ง€ ๊ฒ€์ฆ. * ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด ํด๋ผ์ด์–ธํŠธ๋Š” ์œ ์šฉํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์—๋Ÿฌ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* `GET` ์š”์ฒญ์— `q`๋ผ๋Š” ์„ ํƒ์ ์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๊ฒ€์‚ฌ(`http://127.0.0.1:8000/items/foo?q=somequery`์ฒ˜๋Ÿผ). +* `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` ํ˜•์ธ์ง€ ๊ฒ€์‚ฌ. + * `price`๋ฅผ ํ•„์ˆ˜ ์†์„ฑ์œผ๋กœ ๊ฐ–๊ณ  `float` ํ˜•์ด์–ด์•ผ ํ•˜๋Š”์ง€ ๊ฒ€์‚ฌ. + * ๋งŒ์•ฝ ์ฃผ์–ด์ง„๋‹ค๋ฉด, `is_offer`๋ฅผ ์„ ํƒ ์†์„ฑ์œผ๋กœ ๊ฐ–๊ณ  `bool` ํ˜•์ด์–ด์•ผ ํ•˜๋Š”์ง€ ๊ฒ€์‚ฌ. * ์ด ๋ชจ๋“  ๊ฒƒ์€ ๊นŠ์ด ์ค‘์ฒฉ๋œ JSON ๊ฐ์ฒด์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. * JSON์„ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์ž๋™ํ™”. * ๋‹ค์Œ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒƒ์„ OpenAPI๋กœ ๋ฌธ์„œํ™”: @@ -417,30 +439,88 @@ item: Item ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -๋” ๋งŽ์€ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•œ ๋ณด๋‹ค ์™„์ „ํ•œ ์˜ˆ์ œ์˜ ๊ฒฝ์šฐ, <a href="https://fastapi.tiangolo.com/tutorial/">ํŠœํ† ๋ฆฌ์–ผ - ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ</a>๋ฅผ ๋ณด์‹ญ์‹œ์˜ค. +๋” ๋งŽ์€ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•œ ๋ณด๋‹ค ์™„์ „ํ•œ ์˜ˆ์ œ์˜ ๊ฒฝ์šฐ, <a href="https://fastapi.tiangolo.com/ko/tutorial/">ํŠœํ† ๋ฆฌ์–ผ - ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ</a>๋ฅผ ๋ณด์‹ญ์‹œ์˜ค. **์Šคํฌ์ผ๋Ÿฌ ์ฃผ์˜**: ํŠœํ† ๋ฆฌ์–ผ - ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ๋Š”: * ์„œ๋กœ ๋‹ค๋ฅธ ์žฅ์†Œ์—์„œ **๋งค๊ฐœ๋ณ€์ˆ˜** ์„ ์–ธ: **ํ—ค๋”**, **์ฟ ํ‚ค**, **ํผ ํ•„๋“œ** ๊ทธ๋ฆฌ๊ณ  **ํŒŒ์ผ**. * `maximum_length` ๋˜๋Š” `regex`์ฒ˜๋Ÿผ **์œ ํšจ์„ฑ ์ œ์•ฝ**ํ•˜๋Š” ๋ฐฉ๋ฒ•. -* ๊ฐ•๋ ฅํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด **<abbr title="์ปดํฌ๋„ŒํŠธ, ๋ฆฌ์†Œ์Šค, ์ œ๊ณต์ž, ์„œ๋น„์Šค, injectables๋ผ ์•Œ๋ ค์ง„">์˜์กด์„ฑ ์ฃผ์ž…</abbr>** ์‹œ์Šคํ…œ. +* ๊ฐ•๋ ฅํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด **<abbr title="also known as components, resources, providers, services, injectables">์˜์กด์„ฑ ์ฃผ์ž…</abbr>** ์‹œ์Šคํ…œ. * **OAuth2** ์ง€์›์„ ํฌํ•จํ•œ **JWT tokens** ๋ฐ **HTTP Basic**์„ ๊ฐ–๋Š” ๋ณด์•ˆ๊ณผ ์ธ์ฆ. * (Pydantic ๋•๋ถ„์—) **๊นŠ์€ ์ค‘์ฒฉ JSON ๋ชจ๋ธ**์„ ์„ ์–ธํ•˜๋Š”๋ฐ ๋” ์ง„๋ณดํ•œ (ํ•˜์ง€๋งŒ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์‰ฌ์šด) ๊ธฐ์ˆ . +* <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> ๋ฐ ๊ธฐํƒ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€์˜ **GraphQL** ํ†ตํ•ฉ. * (Starlette ๋•๋ถ„์—) ๋งŽ์€ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ: * **์›น ์†Œ์ผ“** - * **GraphQL** * HTTPX ๋ฐ `pytest`์— ๊ธฐ๋ฐ˜ํ•œ ๊ทนํžˆ ์‰ฌ์šด ํ…Œ์ŠคํŠธ * **CORS** * **์ฟ ํ‚ค ์„ธ์…˜** * ...๊ธฐํƒ€ ๋“ฑ๋“ฑ. -## ์„ฑ๋Šฅ +### ์•ฑ ๋ฐฐํฌํ•˜๊ธฐ(์„ ํƒ ์‚ฌํ•ญ) { #deploy-your-app-optional } -๋…๋ฆฝ๋œ TechEmpower ๋ฒค์น˜๋งˆํฌ์—์„œ Uvicorn์—์„œ ์ž‘๋™ํ•˜๋Š” FastAPI ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ€์žฅ ๋น ๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜</a>๋กœ Starlette์™€ Uvicorn(FastAPI์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉ)์—๋งŒ ๋ฐ‘๋Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. (*) +์„ ํƒ์ ์œผ๋กœ FastAPI ์•ฑ์„ <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>์— ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์ง์ด๋ผ๋ฉด ๋Œ€๊ธฐ์ž ๋ช…๋‹จ์— ๋“ฑ๋กํ•ด ๋ณด์„ธ์š”. ๐Ÿš€ -์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">๋ฒค์น˜๋งˆํฌ</a> ์„น์…˜์„ ๋ณด์‹ญ์‹œ์˜ค. +์ด๋ฏธ **FastAPI Cloud** ๊ณ„์ •์ด ์žˆ๋‹ค๋ฉด(๋Œ€๊ธฐ์ž ๋ช…๋‹จ์—์„œ ์ดˆ๋Œ€ํ•ด ๋“œ๋ ธ์Šต๋‹ˆ๋‹ค ๐Ÿ˜‰), ํ•œ ๋ฒˆ์˜ ๋ช…๋ น์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ์„ ํƒ๊ฐ€๋Šฅํ•œ ์˜์กด์„ฑ +๋ฐฐํฌํ•˜๊ธฐ ์ „์—, ๋กœ๊ทธ์ธ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”: + +<div class="termy"> + +```console +$ fastapi login + +You are logged in to FastAPI Cloud ๐Ÿš€ +``` + +</div> + +๊ทธ๋Ÿฐ ๋‹ค์Œ ์•ฑ์„ ๋ฐฐํฌํ•˜์„ธ์š”: + +<div class="termy"> + +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +โœ… Deployment successful! + +๐Ÿ” Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +</div> + +์ด๊ฒŒ ์ „๋ถ€์ž…๋‹ˆ๋‹ค! ์ด์ œ ํ•ด๋‹น URL์—์„œ ์•ฑ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โœจ + +#### FastAPI Cloud ์†Œ๊ฐœ { #about-fastapi-cloud } + +**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>**๋Š” **FastAPI** ๋’ค์— ์žˆ๋Š” ๋™์ผํ•œ ์ž‘์„ฑ์ž์™€ ํŒ€์ด ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. + +์ตœ์†Œํ•œ์˜ ๋…ธ๋ ฅ์œผ๋กœ API๋ฅผ **๋นŒ๋“œ**, **๋ฐฐํฌ**, **์ ‘๊ทผ**ํ•˜๋Š” ๊ณผ์ •์„ ๊ฐ„์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. + +FastAPI๋กœ ์•ฑ์„ ๋นŒ๋“œํ•  ๋•Œ์˜ ๋™์ผํ•œ **๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜**์„ ํด๋ผ์šฐ๋“œ์— **๋ฐฐํฌ**ํ•˜๋Š” ๋ฐ๊นŒ์ง€ ํ™•์žฅํ•ด ์ค๋‹ˆ๋‹ค. ๐ŸŽ‰ + +FastAPI Cloud๋Š” *FastAPI and friends* ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ์˜ ์ฃผ์š” ์Šคํฐ์„œ์ด์ž ์ž๊ธˆ ์ œ๊ณต์ž์ž…๋‹ˆ๋‹ค. โœจ + +#### ๋‹ค๋ฅธ ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž์— ๋ฐฐํฌํ•˜๊ธฐ { #deploy-to-other-cloud-providers } + +FastAPI๋Š” ์˜คํ”ˆ ์†Œ์Šค์ด๋ฉฐ ํ‘œ์ค€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์„ ํƒํ•œ ์–ด๋–ค ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž์—๋„ FastAPI ์•ฑ์„ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž์˜ ๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ผ FastAPI ์•ฑ์„ ๋ฐฐํฌํ•˜์„ธ์š”. ๐Ÿค“ + +## ์„ฑ๋Šฅ { #performance } + +๋…๋ฆฝ๋œ TechEmpower ๋ฒค์น˜๋งˆํฌ์—์„œ Uvicorn์—์„œ ์ž‘๋™ํ•˜๋Š” FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ€์žฅ ๋น ๋ฅธ Python ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜</a>๋กœ Starlette์™€ Uvicorn(FastAPI์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉ)์—๋งŒ ๋ฐ‘๋Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. (*) + +์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://fastapi.tiangolo.com/ko/benchmarks/" class="internal-link" target="_blank">๋ฒค์น˜๋งˆํฌ</a> ์„น์…˜์„ ๋ณด์‹ญ์‹œ์˜ค. + +## ์˜์กด์„ฑ { #dependencies } + +FastAPI๋Š” Pydantic๊ณผ Starlette์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค. + +### `standard` ์˜์กด์„ฑ { #standard-dependencies } + +FastAPI๋ฅผ `pip install "fastapi[standard]"`๋กœ ์„ค์น˜ํ•˜๋ฉด `standard` ๊ทธ๋ฃน์˜ ์„ ํƒ์  ์˜์กด์„ฑ์ด ํ•จ๊ป˜ ์„ค์น˜๋ฉ๋‹ˆ๋‹ค. Pydantic์ด ์‚ฌ์šฉํ•˜๋Š”: @@ -448,21 +528,38 @@ Pydantic์ด ์‚ฌ์šฉํ•˜๋Š”: Starlette์ด ์‚ฌ์šฉํ•˜๋Š”: -* <a href="https://www.python-httpx.org" target="_blank"><code>HTTPX</code></a> - `TestClient`๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•„์š”. -* <a href="http://jinja.pocoo.org" target="_blank"><code>jinja2</code></a> - ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ ์„ค์ •์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•„์š”. -* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - `request.form()`๊ณผ ํ•จ๊ป˜ <abbr title="HTTP ์š”์ฒญ์—์„œ ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ๋กœ ๊ฐ€๋Š” ๋ฌธ์ž์—ด ๋ณ€ํ™˜">"parsing"</abbr>์˜ ์ง€์›์„ ์›ํ•˜๋ฉด ํ•„์š”. -* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - `SessionMiddleware` ์ง€์›์„ ์œ„ํ•ด ํ•„์š”. -* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - Starlette์˜ `SchemaGenerator` ์ง€์›์„ ์œ„ํ•ด ํ•„์š” (FastAPI์™€ ์“ธ๋•Œ๋Š” ํ•„์š” ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค). -* <a href="https://graphene-python.org/" target="_blank"><code>graphene</code></a> - `GraphQLApp` ์ง€์›์„ ์œ„ํ•ด ํ•„์š”. +* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - `TestClient`๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•„์š”. +* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ ์„ค์ •์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•„์š”. +* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - `request.form()`๊ณผ ํ•จ๊ป˜ form <abbr title="HTTP ์š”์ฒญ์—์„œ ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ๋กœ ๊ฐ€๋Š” ๋ฌธ์ž์—ด ๋ณ€ํ™˜">"parsing"</abbr> ์ง€์›์„ ์›ํ•˜๋ฉด ํ•„์š”. -FastAPI / Starlette์ด ์‚ฌ์šฉํ•˜๋Š”: +FastAPI๊ฐ€ ์‚ฌ์šฉํ•˜๋Š”: -* <a href="http://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋กœ๋“œํ•˜๊ณ  ์ œ๊ณตํ•˜๋Š” ์„œ๋ฒ„. -* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - `ORJSONResponse`์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•„์š”. +* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋กœ๋“œํ•˜๊ณ  ์ œ๊ณตํ•˜๋Š” ์„œ๋ฒ„๋ฅผ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ๊ณ ์„ฑ๋Šฅ ์„œ๋น™์— ํ•„์š”ํ•œ ์ผ๋ถ€ ์˜์กด์„ฑ(์˜ˆ: `uvloop`)์ด ํฌํ•จ๋œ `uvicorn[standard]`๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. +* `fastapi-cli[standard]` - `fastapi` ๋ช…๋ น์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + * ์—ฌ๊ธฐ์—๋Š” FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>์— ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” `fastapi-cloud-cli`๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +### `standard` ์˜์กด์„ฑ ์—†์ด { #without-standard-dependencies } + +`standard` ์„ ํƒ์  ์˜์กด์„ฑ์„ ํฌํ•จํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋ฉด, `pip install "fastapi[standard]"` ๋Œ€์‹  `pip install fastapi`๋กœ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### `fastapi-cloud-cli` ์—†์ด { #without-fastapi-cloud-cli } + +ํ‘œ์ค€ ์˜์กด์„ฑ๊ณผ ํ•จ๊ป˜ FastAPI๋ฅผ ์„ค์น˜ํ•˜๋˜ `fastapi-cloud-cli` ์—†์ด ์„ค์น˜ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, `pip install "fastapi[standard-no-fastapi-cloud-cli]"`๋กœ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์ถ”๊ฐ€ ์„ ํƒ์  ์˜์กด์„ฑ { #additional-optional-dependencies } + +์ถ”๊ฐ€๋กœ ์„ค์น˜ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ๋Š” ์˜์กด์„ฑ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ถ”๊ฐ€ ์„ ํƒ์  Pydantic ์˜์กด์„ฑ: + +* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - Pydantic์—์„œ ์‚ฌ์šฉํ•  ์ถ”๊ฐ€ ํƒ€์ž…์„ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +์ถ”๊ฐ€ ์„ ํƒ์  FastAPI ์˜์กด์„ฑ: + +* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - `ORJSONResponse`๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•„์š”. * <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - `UJSONResponse`๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•„์š”. -`pip install fastapi[all]`๋ฅผ ํ†ตํ•ด ์ด ๋ชจ๋‘๋ฅผ ์„ค์น˜ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -## ๋ผ์ด์„ผ์Šค +## ๋ผ์ด์„ผ์Šค { #license } ์ด ํ”„๋กœ์ ํŠธ๋Š” MIT ๋ผ์ด์„ผ์Šค ์กฐ์•ฝ์— ๋”ฐ๋ผ ๋ผ์ด์„ผ์Šค๊ฐ€ ๋ถ€์—ฌ๋ฉ๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/learn/index.md b/docs/ko/docs/learn/index.md index 7ac3a99b64..0b4d14ff47 100644 --- a/docs/ko/docs/learn/index.md +++ b/docs/ko/docs/learn/index.md @@ -1,5 +1,5 @@ -# ๋ฐฐ์šฐ๊ธฐ +# ๋ฐฐ์šฐ๊ธฐ { #learn } -์—ฌ๊ธฐ **FastAPI**๋ฅผ ๋ฐฐ์šฐ๊ธฐ ์œ„ํ•œ ์ž…๋ฌธ ์ž๋ฃŒ์™€ ์ž์Šต์„œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฌ๊ธฐ **FastAPI**๋ฅผ ๋ฐฐ์šฐ๊ธฐ ์œ„ํ•œ ์ž…๋ฌธ ์„น์…˜๊ณผ ์ž์Šต์„œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ๋ถ„์€ FastAPI๋ฅผ ๋ฐฐ์šฐ๊ธฐ ์œ„ํ•ด **์ฑ…**, **๊ฐ•์˜**, **๊ณต์‹ ์ž๋ฃŒ** ๊ทธ๋ฆฌ๊ณ  ์ถ”์ฒœ๋ฐ›์€ ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž +์—ฌ๋Ÿฌ๋ถ„์€ ์ด๊ฒƒ์„ FastAPI๋ฅผ ๋ฐฐ์šฐ๊ธฐ ์œ„ํ•œ **์ฑ…**, **๊ฐ•์˜**, **๊ณต์‹**์ด์ž ๊ถŒ์žฅ๋˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž diff --git a/docs/ko/docs/project-generation.md b/docs/ko/docs/project-generation.md index dd11fca703..73ea67d3e5 100644 --- a/docs/ko/docs/project-generation.md +++ b/docs/ko/docs/project-generation.md @@ -1,4 +1,4 @@ -# Full Stack FastAPI ํ…œํ”Œ๋ฆฟ +# Full Stack FastAPI ํ…œํ”Œ๋ฆฟ { #full-stack-fastapi-template } ํ…œํ”Œ๋ฆฟ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ํŠน์ • ์„ค์ •๊ณผ ํ•จ๊ป˜ ์ œ๊ณต๋˜์ง€๋งŒ, ์œ ์—ฐํ•˜๊ณ  ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋””์ž์ธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ํŠน์„ฑ๋“ค์€ ์—ฌ๋Ÿฌ๋ถ„์ด ํ”„๋กœ์ ํŠธ์˜ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž์ถฐ ์ˆ˜์ •, ์ ์šฉ์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๊ณ , ํ…œํ”Œ๋ฆฟ์ด ์™„๋ฒฝํ•œ ์‹œ์ž‘์ ์ด ๋˜๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ๐Ÿ @@ -6,23 +6,23 @@ GitHub ์ €์žฅ์†Œ: <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI ํ…œํ”Œ๋ฆฟ</a> -## Full Stack FastAPI ํ…œํ”Œ๋ฆฟ - ๊ธฐ์ˆ  ์Šคํƒ๊ณผ ๊ธฐ๋Šฅ๋“ค +## Full Stack 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 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค. -- ๐Ÿš€ [React](https://react.dev): ํ”„๋ก ํŠธ์—”๋“œ. - - ๐Ÿ’ƒ TypeScript, hooks, [Vite](https://vitejs.dev) ๋ฐ ๊ธฐํƒ€ ํ˜„๋Œ€์ ์ธ ํ”„๋ก ํŠธ์—”๋“œ ์Šคํƒ์„ ์‚ฌ์šฉ. - - ๐ŸŽจ [Chakra UI](https://chakra-ui.com): ํ”„๋ก ํŠธ์—”๋“œ ์ปดํฌ๋„ŒํŠธ. +- โšก Python ๋ฐฑ์—”๋“œ API๋ฅผ ์œ„ํ•œ [**FastAPI**](https://fastapi.tiangolo.com/ko). + - ๐Ÿงฐ Python SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒํ˜ธ์ž‘์šฉ์„ ์œ„ํ•œ [SQLModel](https://sqlmodel.tiangolo.com) (ORM). + - ๐Ÿ” FastAPI์— ์˜ํ•ด ์‚ฌ์šฉ๋˜๋Š”, ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ๊ณผ ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ [Pydantic](https://docs.pydantic.dev). + - ๐Ÿ’พ SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ์„œ์˜ [PostgreSQL](https://www.postgresql.org). +- ๐Ÿš€ ํ”„๋ก ํŠธ์—”๋“œ๋ฅผ ์œ„ํ•œ [React](https://react.dev). + - ๐Ÿ’ƒ TypeScript, hooks, Vite ๋ฐ ๊ธฐํƒ€ ํ˜„๋Œ€์ ์ธ ํ”„๋ก ํŠธ์—”๋“œ ์Šคํƒ์„ ์‚ฌ์šฉ. + - ๐ŸŽจ ํ”„๋ก ํŠธ์—”๋“œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์œ„ํ•œ [Tailwind CSS](https://tailwindcss.com) ๋ฐ [shadcn/ui](https://ui.shadcn.com). - ๐Ÿค– ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ํ”„๋ก ํŠธ์—”๋“œ ํด๋ผ์ด์–ธํŠธ. - - ๐Ÿงช E2E ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ [Playwright](https://playwright.dev). + - ๐Ÿงช End-to-End ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ [Playwright](https://playwright.dev). - ๐Ÿฆ‡ ๋‹คํฌ ๋ชจ๋“œ ์ง€์›. -- ๐Ÿ‹ [Docker Compose](https://www.docker.com): ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ํ”„๋กœ๋•์…˜(์šด์˜). +- ๐Ÿ‹ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ํ”„๋กœ๋•์…˜(์šด์˜)์„ ์œ„ํ•œ [Docker Compose](https://www.docker.com). - ๐Ÿ”’ ๊ธฐ๋ณธ์œผ๋กœ ์ง€์›๋˜๋Š” ์•ˆ์ „ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ. -- ๐Ÿ”‘ JWT ํ† ํฐ ์ธ์ฆ. +- ๐Ÿ”‘ JWT (JSON Web Token) ์ธ์ฆ. - ๐Ÿ“ซ ์ด๋ฉ”์ผ ๊ธฐ๋ฐ˜ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต๊ตฌ. -- โœ… [Pytest]๋ฅผ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ(https://pytest.org). -- ๐Ÿ“ž [Traefik](https://traefik.io): ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ / ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ. +- โœ… [Pytest](https://pytest.org)๋ฅผ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ. +- ๐Ÿ“ž ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ / ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ๋กœ์„œ์˜ [Traefik](https://traefik.io). - ๐Ÿšข Docker Compose๋ฅผ ์ด์šฉํ•œ ๋ฐฐํฌ ์ง€์นจ: ์ž๋™ HTTPS ์ธ์ฆ์„œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋ก ํŠธ์—”๋“œ Traefik ํ”„๋ก์‹œ ์„ค์ • ๋ฐฉ๋ฒ•์„ ํฌํ•จ. - ๐Ÿญ GitHub Actions๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ CI (์ง€์†์ ์ธ ํ†ตํ•ฉ) ๋ฐ CD (์ง€์†์ ์ธ ๋ฐฐํฌ). diff --git a/docs/ko/docs/python-types.md b/docs/ko/docs/python-types.md index 18d4b341e4..dc264df805 100644 --- a/docs/ko/docs/python-types.md +++ b/docs/ko/docs/python-types.md @@ -1,313 +1,466 @@ -# ํŒŒ์ด์ฌ ํƒ€์ž… ์†Œ๊ฐœ +# ํŒŒ์ด์ฌ ํƒ€์ž… ์†Œ๊ฐœ { #python-types-intro } -ํŒŒ์ด์ฌ์€ ์„ ํƒ์ ์œผ๋กœ "ํƒ€์ž… ํžŒํŠธ(type hints)"๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +ํŒŒ์ด์ฌ์€ ์„ ํƒ์ ์œผ๋กœ "ํƒ€์ž… ํžŒํŠธ(type hints)"(โ€œtype annotationsโ€๋ผ๊ณ ๋„ ํ•จ)๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. -์ด๋Ÿฌํ•œ **ํƒ€์ž… ํžŒํŠธ**๋“ค์€ ๋ณ€์ˆ˜์˜ <abbr title="์˜ˆ๋ฅผ ๋“ค๋ฉด: str, int, float, bool">ํƒ€์ž…</abbr>์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํŠน์ˆ˜ํ•œ ๊ตฌ๋ฌธ์ž…๋‹ˆ๋‹ค. +์ด๋Ÿฌํ•œ **"ํƒ€์ž… ํžŒํŠธ"** ๋˜๋Š” ์• ๋„ˆํ…Œ์ด์…˜์€ ๋ณ€์ˆ˜์˜ <abbr title="for example: str, int, float, bool">ํƒ€์ž…</abbr>์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํŠน์ˆ˜ํ•œ ๊ตฌ๋ฌธ์ž…๋‹ˆ๋‹ค. -๋ณ€์ˆ˜์˜ ํƒ€์ž…์„ ์ง€์ •ํ•˜๋ฉด ์—๋””ํ„ฐ์™€ ํˆด์ด ๋” ๋งŽ์€ ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +๋ณ€์ˆ˜์˜ ํƒ€์ž…์„ ์„ ์–ธํ•˜๋ฉด ์—๋””ํ„ฐ์™€ ๋„๊ตฌ๊ฐ€ ๋” ๋‚˜์€ ์ง€์›์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด ๋ฌธ์„œ๋Š” ํŒŒ์ด์ฌ ํƒ€์ž… ํžŒํŠธ์— ๋Œ€ํ•œ **๋น ๋ฅธ ์ž์Šต์„œ / ๋‚ด์šฉํ™˜๊ธฐ** ์ˆ˜์ค€์˜ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” **FastAPI**๋ฅผ ์“ฐ๊ธฐ ์œ„ํ•œ ์ตœ์†Œํ•œ์˜ ๋‚ด์šฉ๋งŒ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. +์ด ๋ฌธ์„œ๋Š” ํŒŒ์ด์ฌ ํƒ€์ž… ํžŒํŠธ์— ๋Œ€ํ•œ **๋น ๋ฅธ ์ž์Šต์„œ / ๋‚ด์šฉ ํ™˜๊ธฐ**์ž…๋‹ˆ๋‹ค. **FastAPI**์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ์ตœ์†Œํ•œ๋งŒ ๋‹ค๋ฃน๋‹ˆ๋‹ค... ์‹ค์ œ๋กœ๋Š” ์•„์ฃผ ์กฐ๊ธˆ๋งŒ ์žˆ์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. -**FastAPI**๋Š” ํƒ€์ž… ํžŒํŠธ์— ๊ธฐ๋ฐ˜์„ ๋‘๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋Š” ๋งŽ์€ ์žฅ์ ๊ณผ ์ด์ต์ด ์žˆ์Šต๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๋ชจ๋‘ ์ด๋Ÿฌํ•œ ํƒ€์ž… ํžŒํŠธ์— ๊ธฐ๋ฐ˜์„ ๋‘๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋Š” ๋งŽ์€ ์žฅ์ ๊ณผ ์ด์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -๋น„๋ก **FastAPI**๋ฅผ ์“ฐ์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•˜๋”๋ผ๋„, ์กฐ๊ธˆ์ด๋ผ๋„ ์•Œ์•„๋‘๋ฉด ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ **FastAPI**๋ฅผ ์ „ํ˜€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋”๋ผ๋„, ํƒ€์ž… ํžŒํŠธ๋ฅผ ์กฐ๊ธˆ๋งŒ ๋ฐฐ์›Œ๋„ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. /// note | ์ฐธ๊ณ  -ํŒŒ์ด์ฌ์— ๋Šฅ์ˆ™ํ•˜์…”์„œ ํƒ€์ž… ํžŒํŠธ์— ๋Œ€ํ•ด ๋ชจ๋‘ ์•„์‹ ๋‹ค๋ฉด, ๋‹ค์Œ ์ฑ•ํ„ฐ๋กœ ๊ฑด๋„ˆ๋›ฐ์„ธ์š”. +ํŒŒ์ด์ฌ์— ๋Šฅ์ˆ™ํ•˜๊ณ  ํƒ€์ž… ํžŒํŠธ์— ๋Œ€ํ•ด ์ด๋ฏธ ๋ชจ๋‘ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด, ๋‹ค์Œ ์žฅ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ์„ธ์š”. /// -## ๋™๊ธฐ ๋ถ€์—ฌ +## ๋™๊ธฐ ๋ถ€์—ฌ { #motivation } -๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ด…์‹œ๋‹ค: +๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋กœ ์‹œ์ž‘ํ•ด๋ด…์‹œ๋‹ค: -{* ../../docs_src/python_types/tutorial001.py *} +{* ../../docs_src/python_types/tutorial001_py39.py *} - -์ด ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ๊ฐ’: +์ด ํ”„๋กœ๊ทธ๋žจ์„ ํ˜ธ์ถœํ•˜๋ฉด ๋‹ค์Œ์ด ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค: ``` John Doe ``` -ํ•จ์ˆ˜๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค: +์ด ํ•จ์ˆ˜๋Š” ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค: * `first_name`๊ณผ `last_name`๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. -* `title()`๋กœ ๊ฐ ์ฒซ ๋ฌธ์ž๋ฅผ ๋Œ€๋ฌธ์ž๋กœ ๋ณ€ํ™˜์‹œํ‚ต๋‹ˆ๋‹ค. -* ๋‘ ๋‹จ์–ด๋ฅผ ์ค‘๊ฐ„์— ๊ณต๋ฐฑ์„ ๋‘๊ณ  <abbr title="๋‘ ๊ฐœ๋ฅผ ํ•˜๋‚˜๋กœ ์ฐจ๋ก€์ฐจ๋ก€ ์ด์–ด์ง€๊ฒŒ ํ•˜๋‹ค">์—ฐ๊ฒฐ</abbr>ํ•ฉ๋‹ˆ๋‹ค. +* `title()`๋กœ ๊ฐ๊ฐ์˜ ์ฒซ ๊ธ€์ž๋ฅผ ๋Œ€๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +* ๊ฐ€์šด๋ฐ์— ๊ณต๋ฐฑ์„ ๋‘๊ณ  <abbr title="Puts them together, as one. With the contents of one after the other.">์—ฐ๊ฒฐ</abbr>ํ•ฉ๋‹ˆ๋‹ค. -{* ../../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`๋ฅผ ๋ˆŒ๋ €์Šต๋‹ˆ๋‹ค. - -ํ•˜์ง€๋งŒ ์Šฌํ”„๊ฒŒ๋„ ์•„๋ฌด๋Ÿฐ ๋„์›€์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: +ํ•˜์ง€๋งŒ, ์Šฌํ”„๊ฒŒ๋„ ์“ธ๋งŒํ•œ ๊ฒŒ ์•„๋ฌด๊ฒƒ๋„ ์—†์Šต๋‹ˆ๋‹ค: <img src="/img/python-types/image01.png"> -### ํƒ€์ž… ์ถ”๊ฐ€ํ•˜๊ธฐ +### ํƒ€์ž… ์ถ”๊ฐ€ํ•˜๊ธฐ { #add-types } ์ด์ „ ๋ฒ„์ „์—์„œ ํ•œ ์ค„๋งŒ ์ˆ˜์ •ํ•ด๋ด…์‹œ๋‹ค. -์ €ํฌ๋Š” ์ด ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ถ€๋ถ„: +ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ธ ์ •ํ™•ํžˆ ์ด ๋ถ€๋ถ„์„: ```Python first_name, last_name ``` -์„ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฐ”๊ฟ€ ๊ฒ๋‹ˆ๋‹ค: +์—์„œ: ```Python first_name: str, last_name: str ``` +๋กœ ๋ฐ”๊พธ๊ฒ ์Šต๋‹ˆ๋‹ค. + ์ด๊ฒŒ ๋‹ค์ž…๋‹ˆ๋‹ค. -์ด๊ฒŒ "ํƒ€์ž… ํžŒํŠธ"์ž…๋‹ˆ๋‹ค: +์ด๊ฒƒ๋“ค์ด "ํƒ€์ž… ํžŒํŠธ"์ž…๋‹ˆ๋‹ค: -{* ../../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`๋กœ ์ž๋™์™„์„ฑ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋ฉด ๋‹ค์Œ์ด ๋ณด์ž…๋‹ˆ๋‹ค: <img src="/img/python-types/image02.png"> -์•„๋ž˜์™€ ๊ฐ™์ด "๊ทธ๋ ‡์ง€!"ํ•˜๋Š” ์˜ต์…˜์ด ๋‚˜์˜ฌ๋•Œ๊นŒ์ง€ ์Šคํฌ๋กค์„ ๋‚ด๋ ค์„œ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ทธ๋Ÿฌ๋ฉด ์Šคํฌ๋กคํ•˜๋ฉฐ ์˜ต์…˜์„ ๋ณด๋‹ค๊ฐ€, "๊ธฐ์–ต๋‚˜๋Š”" ๊ฒƒ์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <img src="/img/python-types/image03.png"> -## ๋” ํฐ ๋™๊ธฐ๋ถ€์—ฌ +## ๋” ํฐ ๋™๊ธฐ๋ถ€์—ฌ { #more-motivation } -์•„๋ž˜ ํ•จ์ˆ˜๋ฅผ ๋ณด๋ฉด, ์ด๋ฏธ ํƒ€์ž… ํžŒํŠธ๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์ด ํ•จ์ˆ˜๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”. ์ด๋ฏธ ํƒ€์ž… ํžŒํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/python_types/tutorial003.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} - -ํŽธ์ง‘๊ธฐ๊ฐ€ ๋ณ€์ˆ˜์˜ ํƒ€์ž…์„ ์•Œ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ž๋™์™„์„ฑ ๋ฟ ์•„๋‹ˆ๋ผ ์—๋Ÿฌ๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์—๋””ํ„ฐ๊ฐ€ ๋ณ€์ˆ˜์˜ ํƒ€์ž…์„ ์•Œ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ž๋™์™„์„ฑ๋งŒ ๋˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์˜ค๋ฅ˜ ๊ฒ€์‚ฌ๋„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <img src="/img/python-types/image04.png"> -์ด์ œ ๊ณ ์ณ์•ผํ•˜๋Š” ๊ฑธ ์•Œ๊ธฐ ๋•Œ๋ฌธ์—, `age`๋ฅผ `str(age)`๊ณผ ๊ฐ™์ด ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊พธ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: +์ด์ œ ๊ณ ์ณ์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ , `age`๋ฅผ `str(age)`๋กœ ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค: -{* ../../docs_src/python_types/tutorial004.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +## ํƒ€์ž… ์„ ์–ธ { #declaring-types } -## ํƒ€์ž… ์„ ์–ธ +๋ฐฉ๊ธˆ ํƒ€์ž… ํžŒํŠธ๋ฅผ ์„ ์–ธํ•˜๋Š” ์ฃผ์š” ์œ„์น˜๋ฅผ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. -๋ฐฉ๊ธˆ ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ์จ ํƒ€์ž… ํžŒํŠธ๋ฅผ ์„ ์–ธํ•˜๋Š” ์ฃผ์š” ์žฅ์†Œ๋ฅผ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. +์ด๊ฒƒ์€ **FastAPI**์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ๋„ ์ฃผ์š” ์œ„์น˜์ž…๋‹ˆ๋‹ค. -์ด ์œ„์น˜๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด **FastAPI**์™€ ํ•จ๊ป˜ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ์š” ์žฅ์†Œ์ž…๋‹ˆ๋‹ค. - -### Simple ํƒ€์ž… +### Simple ํƒ€์ž… { #simple-types } `str`๋ฟ ์•„๋‹ˆ๋ผ ๋ชจ๋“  ํŒŒ์ด์ฌ ํ‘œ์ค€ ํƒ€์ž…์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค๋ฉด: +์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: * `int` * `float` * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +### ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋Š” Generic(์ œ๋„ค๋ฆญ) ํƒ€์ž… { #generic-types-with-type-parameters } -### ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ™œ์šฉํ•œ Generic(์ œ๋„ค๋ฆญ) ํƒ€์ž… +`dict`, `list`, `set`, `tuple`์ฒ˜๋Ÿผ ๋‹ค๋ฅธ ๊ฐ’์„ ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‚ด๋ถ€ ๊ฐ’์—๋„ ๊ฐ์ž์˜ ํƒ€์ž…์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -`dict`, `list`, `set`, `tuple`๊ณผ ๊ฐ™์€ ๊ฐ’์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ์žˆ๊ณ , ๋‚ด๋ถ€์˜ ๊ฐ’์€ ๊ฐ์ž์˜ ํƒ€์ž…์„ ๊ฐ€์งˆ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ๋‚ด๋ถ€ ํƒ€์ž…์„ ๊ฐ€์ง€๋Š” ํƒ€์ž…์„ "**generic**" ํƒ€์ž…์ด๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‚ด๋ถ€ ํƒ€์ž…๊นŒ์ง€ ํฌํ•จํ•ด ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -ํƒ€์ž…๊ณผ ๋‚ด๋ถ€ ํƒ€์ž…์„ ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํŒŒ์ด์ฌ ํ‘œ์ค€ ๋ชจ๋“ˆ์ธ `typing`์„ ์ด์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +์ด๋Ÿฐ ํƒ€์ž…๊ณผ ๋‚ด๋ถ€ ํƒ€์ž…์„ ์„ ์–ธํ•˜๋ ค๋ฉด ํ‘œ์ค€ ํŒŒ์ด์ฌ ๋ชจ๋“ˆ `typing`์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ชจ๋“ˆ์€ ์ด๋Ÿฌํ•œ ํƒ€์ž… ํžŒํŠธ๋ฅผ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. -๊ตฌ์ฒด์ ์œผ๋กœ๋Š” ์•„๋ž˜ ํƒ€์ž… ํžŒํŠธ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +#### ๋” ์ตœ์‹  ๋ฒ„์ „์˜ Python { #newer-versions-of-python } -#### `List` +`typing`์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฌธ๋ฒ•์€ Python 3.6๋ถ€ํ„ฐ ์ตœ์‹  ๋ฒ„์ „๊นŒ์ง€, Python 3.9, Python 3.10 ๋“ฑ์„ ํฌํ•จํ•œ ๋ชจ๋“  ๋ฒ„์ „๊ณผ **ํ˜ธํ™˜**๋ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค๋ฉด, `str`์˜ `list`์ธ ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•ด๋ด…์‹œ๋‹ค. +ํŒŒ์ด์ฌ์ด ๋ฐœ์ „ํ•จ์— ๋”ฐ๋ผ **๋” ์ตœ์‹  ๋ฒ„์ „**์—์„œ๋Š” ์ด๋Ÿฌํ•œ ํƒ€์ž… ์• ๋„ˆํ…Œ์ด์…˜ ์ง€์›์ด ๊ฐœ์„ ๋˜๋ฉฐ, ๋งŽ์€ ๊ฒฝ์šฐ ํƒ€์ž… ์• ๋„ˆํ…Œ์ด์…˜์„ ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•ด `typing` ๋ชจ๋“ˆ์„ importํ•ด์„œ ์‚ฌ์šฉํ•  ํ•„์š”์กฐ์ฐจ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -`typing`์—์„œ `List`(๋Œ€๋ฌธ์ž `L`)๋ฅผ import ํ•ฉ๋‹ˆ๋‹ค. +ํ”„๋กœ์ ํŠธ์—์„œ ๋” ์ตœ์‹  ๋ฒ„์ „์˜ ํŒŒ์ด์ฌ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๊ทธ ์ถ”๊ฐ€์ ์ธ ๋‹จ์ˆœํ•จ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -{* ../../docs_src/python_types/tutorial006.py hl[1] *} +์ด ๋ฌธ์„œ ์ „์ฒด์—๋Š” ๊ฐ ํŒŒ์ด์ฌ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜๋Š” ์˜ˆ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค(์ฐจ์ด๊ฐ€ ์žˆ์„ ๋•Œ). +์˜ˆ๋ฅผ ๋“ค์–ด "**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 { #list } -์ด๋•Œ ๋ฐฐ์—ด์€ ๋‚ด๋ถ€ ํƒ€์ž…์„ ํฌํ•จํ•˜๋Š” ํƒ€์ž…์ด๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€๊ด„ํ˜ธ ์•ˆ์— ๋„ฃ์–ด์ค๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, `str`์˜ `list`์ธ ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•ด๋ด…์‹œ๋‹ค. -{* ../../docs_src/python_types/tutorial006.py hl[4] *} +๊ฐ™์€ ์ฝœ๋ก (`:`) ๋ฌธ๋ฒ•์œผ๋กœ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. +ํƒ€์ž…์œผ๋กœ `list`๋ฅผ ๋„ฃ์Šต๋‹ˆ๋‹ค. -/// tip | ํŒ - -๋Œ€๊ด„ํ˜ธ ์•ˆ์˜ ๋‚ด๋ถ€ ํƒ€์ž…์€ "ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜(type paramters)"๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. - -์ด๋ฒˆ ์˜ˆ์ œ์—์„œ๋Š” `str`์ด `List`์— ๋“ค์–ด๊ฐ„ ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜ ์ž…๋‹ˆ๋‹ค. - -/// - -์ด๋Š” "`items`์€ `list`์ธ๋ฐ, ๋ฐฐ์—ด์— ๋“ค์–ด์žˆ๋Š” ์•„์ดํ…œ ๊ฐ๊ฐ์€ `str`์ด๋‹ค"๋ผ๋Š” ๋œป์ž…๋‹ˆ๋‹ค. - -์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ์จ, ์—๋””ํ„ฐ๋Š” ๋ฐฐ์—ด์— ๋“ค์–ด์žˆ๋Š” ์•„์ดํ…œ์„ ์ฒ˜๋ฆฌํ• ๋•Œ๋„ ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: - -<img src="/img/python-types/image05.png"> - -ํƒ€์ž…์ด ์—†์œผ๋ฉด ์ด๊ฑด ๊ฑฐ์˜ ๋ถˆ๊ฐ€๋Šฅ์ด๋‚˜ ๋‹ค๋ฆ„ ์—†์Šต๋‹ˆ๋‹ค. - -๋ณ€์ˆ˜ `item`์€ `items`์˜ ๊ฐœ๋ณ„ ์š”์†Œ๋ผ๋Š” ์‚ฌ์‹ค์„ ์•Œ์•„๋‘์„ธ์š”. - -๊ทธ๋ฆฌ๊ณ  ์—๋””ํ„ฐ๋Š” ๊ณ„์† `str`๋ผ๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ณ  ๋„์™€์ค๋‹ˆ๋‹ค. - -#### `Tuple`๊ณผ `Set` - -`tuple`๊ณผ `set`๋„ ๋™์ผํ•˜๊ฒŒ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -{* ../../docs_src/python_types/tutorial007.py hl[1,4] *} - - -์ด ๋œป์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค: - -* ๋ณ€์ˆ˜ `items_t`๋Š”, ์ฐจ๋ก€๋Œ€๋กœ `int`, `int`, `str`์ธ `tuple`์ด๋‹ค. -* ๋ณ€์ˆ˜ `items_s`๋Š”, ๊ฐ ์•„์ดํ…œ์ด `bytes`์ธ `set`์ด๋‹ค. - -#### `Dict` - -`dict`๋ฅผ ์„ ์–ธํ•˜๋ ค๋ฉด ์ปด๋งˆ๋กœ ๊ตฌ๋ถ„๋œ 2๊ฐœ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. - -์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” `dict`์˜ ํ‚ค(key)์ด๊ณ , - -๋‘ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” `dict`์˜ ๊ฐ’(value)์ž…๋‹ˆ๋‹ค. - -{* ../../docs_src/python_types/tutorial008.py hl[1,4] *} - - -์ด ๋œป์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค: - -* ๋ณ€์ˆ˜ `prices`๋Š” `dict`์ด๋‹ค: - * `dict`์˜ ํ‚ค(key)๋Š” `str`ํƒ€์ž…์ด๋‹ค. (๊ฐ ์•„์ดํ…œ์˜ ์ด๋ฆ„(name)) - * `dict`์˜ ๊ฐ’(value)๋Š” `float`ํƒ€์ž…์ด๋‹ค. (๊ฐ ์•„์ดํ…œ์˜ ๊ฐ€๊ฒฉ(price)) - -#### `Optional` - -`str`๊ณผ ๊ฐ™์ด ํƒ€์ž…์„ ์„ ์–ธํ•  ๋•Œ `Optional`์„ ์“ธ ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, "์„ ํƒ์ (Optional)"์ด๊ธฐ๋•Œ๋ฌธ์— `None`๋„ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009.py!} -``` - -`Optional[str]`์„ `str` ๋Œ€์‹  ์“ฐ๊ฒŒ ๋˜๋ฉด, ํŠน์ • ๊ฐ’์ด ์‹ค์ œ๋กœ๋Š” `None`์ด ๋  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ ํ•ญ์ƒ `str`์ด๋ผ๊ณ  ๊ฐ€์ •ํ•˜๋Š” ์ƒํ™ฉ์—์„œ ์—๋””ํ„ฐ๊ฐ€ ์—๋Ÿฌ๋ฅผ ์ฐพ๊ฒŒ ๋„์™€์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -#### Generic(์ œ๋„ค๋ฆญ) ํƒ€์ž… - -์ด ํƒ€์ž…์€ ๋Œ€๊ด„ํ˜ธ ์•ˆ์— ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง€๋ฉฐ, ์ข…๋ฅ˜๋Š”: - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Optional` -* ...๋“ฑ๋“ฑ - -์œ„์™€ ๊ฐ™์€ ํƒ€์ž…์€ **Generic(์ œ๋„ค๋ฆญ) ํƒ€์ž…** ํ˜น์€ **Generics(์ œ๋„ค๋ฆญ์Šค)**๋ผ๊ณ  ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. - -### ํƒ€์ž…์œผ๋กœ์„œ์˜ ํด๋ž˜์Šค - -๋ณ€์ˆ˜์˜ ํƒ€์ž…์œผ๋กœ ํด๋ž˜์Šค๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. - -์ด๋ฆ„(name)์„ ๊ฐ€์ง„ `Person` ํด๋ž˜์Šค๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค. - -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} - - -๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ณ€์ˆ˜๋ฅผ `Person`์ด๋ผ๊ณ  ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. - -{* ../../docs_src/python_types/tutorial010.py hl[6] *} - - -๊ทธ๋ฆฌ๊ณ  ์—ญ์‹œ๋‚˜ ๋ชจ๋“  ์—๋””ํ„ฐ ๋„์›€์„ ๋ฐ›๊ฒŒ ๋˜๊ฒ ์ฃ . - -<img src="/img/python-types/image06.png"> - -## Pydantic ๋ชจ๋ธ - -<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>์€ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ(Validation)์„ ์œ„ํ•œ ํŒŒ์ด์ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. - -๋‹น์‹ ์€ ์†์„ฑ๋“ค์„ ํฌํ•จํ•œ ํด๋ž˜์Šค ํ˜•ํƒœ๋กœ "๋ชจ์–‘(shape)"์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -๊ทธ๋ฆฌ๊ณ  ๊ฐ ์†์„ฑ์€ ํƒ€์ž…์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. - -์ด ํด๋ž˜์Šค๋ฅผ ํ™œ์šฉํ•˜์—ฌ์„œ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค๊ฒŒ ๋˜๋ฉด, ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” ์ ๋‹นํ•œ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜๊นŒ์ง€ ์‹œํ‚ค๊ธฐ๋„ ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. - -๊ทธ๋ฆฌ๊ณ  ๊ฒฐ๊ณผ ๊ฐ์ฒด์— ๋Œ€ํ•ด์„œ๋Š” ์—๋””ํ„ฐ์˜ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. - -Pydantic ๊ณต์‹ ๋ฌธ์„œ ์˜ˆ์‹œ: - -{* ../../docs_src/python_types/tutorial011.py *} +`list`๋Š” ๋‚ด๋ถ€ ํƒ€์ž…์„ ํฌํ•จํ•˜๋Š” ํƒ€์ž…์ด๋ฏ€๋กœ, ๊ทธ ํƒ€์ž…๋“ค์„ ๋Œ€๊ด„ํ˜ธ ์•ˆ์— ๋„ฃ์Šต๋‹ˆ๋‹ค: +{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} /// info | ์ •๋ณด -Pydantic<์— ๋Œ€ํ•ด ๋” ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">๊ณต์‹ ๋ฌธ์„œ</a>๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.</a> +๋Œ€๊ด„ํ˜ธ ์•ˆ์˜ ๋‚ด๋ถ€ ํƒ€์ž…์€ "type parameters"๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ `str`์ด `list`์— ์ „๋‹ฌ๋œ ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. /// -**FastAPI**๋Š” ๋ชจ๋‘ Pydantic์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋Š” "๋ณ€์ˆ˜ `items`๋Š” `list`์ด๊ณ , ์ด `list`์˜ ๊ฐ ์•„์ดํ…œ์€ `str`์ด๋‹ค"๋ผ๋Š” ๋œป์ž…๋‹ˆ๋‹ค. -์ด ๋ชจ๋“  ๊ฒƒ์ด ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๋Š”์ง€์— ๋Œ€ํ•ด์„œ๋Š” [์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ](tutorial/index.md){.internal-link target=_blank} ์—์„œ ๋” ๋งŽ์ด ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด, ์—๋””ํ„ฐ๋Š” ๋ฆฌ์ŠคํŠธ์˜ ์•„์ดํ…œ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์•ˆ์—๋„ ์ง€์›์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -## **FastAPI**์—์„œ์˜ ํƒ€์ž… ํžŒํŠธ +<img src="/img/python-types/image05.png"> -**FastAPI**๋Š” ์—ฌ๋Ÿฌ ๋ถ€๋ถ„์—์„œ ํƒ€์ž… ํžŒํŠธ์˜ ์žฅ์ ์„ ์ทจํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +ํƒ€์ž…์ด ์—†์œผ๋ฉด, ์ด๋Š” ๊ฑฐ์˜ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. -**FastAPI**์—์„œ ํƒ€์ž… ํžŒํŠธ์™€ ํ•จ๊ป˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋ฉด ์žฅ์ ์€: +๋ณ€์ˆ˜ `item`์ด ๋ฆฌ์ŠคํŠธ `items`์˜ ์š”์†Œ ์ค‘ ํ•˜๋‚˜๋ผ๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. + +๊ทธ๋ฆฌ๊ณ  ์—๋””ํ„ฐ๋Š” ์—ฌ์ „ํžˆ ์ด๊ฒƒ์ด `str`์ž„์„ ์•Œ๊ณ , ๊ทธ์— ๋Œ€ํ•œ ์ง€์›์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +#### Tuple๊ณผ Set { #tuple-and-set } + +`tuple`๊ณผ `set`๋„ ๋™์ผํ•˜๊ฒŒ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} + +์ด๋Š” ๋‹ค์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค: + +* ๋ณ€์ˆ˜ `items_t`๋Š” 3๊ฐœ์˜ ์•„์ดํ…œ์„ ๊ฐ€์ง„ `tuple`์ด๋ฉฐ, `int`, ๋˜ ๋‹ค๋ฅธ `int`, ๊ทธ๋ฆฌ๊ณ  `str`์ž…๋‹ˆ๋‹ค. +* ๋ณ€์ˆ˜ `items_s`๋Š” `set`์ด๋ฉฐ, ๊ฐ ์•„์ดํ…œ์˜ ํƒ€์ž…์€ `bytes`์ž…๋‹ˆ๋‹ค. + +#### Dict { #dict } + +`dict`๋ฅผ ์ •์˜ํ•˜๋ ค๋ฉด, ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ 2๊ฐœ์˜ ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. + +์ฒซ ๋ฒˆ์งธ ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” `dict`์˜ ํ‚ค๋ฅผ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๋‘ ๋ฒˆ์งธ ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” `dict`์˜ ๊ฐ’์„ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค: + +{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} + +์ด๋Š” ๋‹ค์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค: + +* ๋ณ€์ˆ˜ `prices`๋Š” `dict`์ž…๋‹ˆ๋‹ค: + * ์ด `dict`์˜ ํ‚ค๋Š” `str` ํƒ€์ž…์ž…๋‹ˆ๋‹ค(์˜ˆ: ๊ฐ ์•„์ดํ…œ์˜ ์ด๋ฆ„). + * ์ด `dict`์˜ ๊ฐ’์€ `float` ํƒ€์ž…์ž…๋‹ˆ๋‹ค(์˜ˆ: ๊ฐ ์•„์ดํ…œ์˜ ๊ฐ€๊ฒฉ). + +#### Union { #union } + +๋ณ€์ˆ˜๊ฐ€ **์—ฌ๋Ÿฌ ํƒ€์ž… ์ค‘ ์–ด๋–ค ๊ฒƒ์ด๋“ ** ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด `int` ๋˜๋Š” `str`์ž…๋‹ˆ๋‹ค. + +Python 3.6 ์ด์ƒ(3.10 ํฌํ•จ)์—์„œ๋Š” `typing`์˜ `Union` ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๊ณ , ๋Œ€๊ด„ํ˜ธ ์•ˆ์— ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…๋“ค์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +Python 3.10์—๋Š” ๊ฐ€๋Šฅํ•œ ํƒ€์ž…๋“ค์„ <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>์„ธ๋กœ ๋ง‰๋Œ€(`|`)</abbr>๋กœ ๊ตฌ๋ถ„ํ•ด ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” **์ƒˆ ๋ฌธ๋ฒ•**๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +//// tab | Python 3.10+ + +```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`์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. + +#### `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" +{!> ../../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!} +``` + +//// + +#### `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`์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์•ˆ์œผ๋กœ, ํƒ€์ž… ์œ ๋‹ˆ์˜จ์„ ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•ด <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>์„ธ๋กœ ๋ง‰๋Œ€(`|`)</abbr>๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ํ›จ์”ฌ ๋” ์ข‹๊ณ  ๋‹จ์ˆœํ•ฉ๋‹ˆ๋‹ค. + +//// + +//// tab | Python 3.9+ + +๋Œ€๊ด„ํ˜ธ์™€ ๋‚ด๋ถ€ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด, ๋™์ผํ•œ ๋‚ด์žฅ ํƒ€์ž…๋“ค์„ ์ œ๋„ค๋ฆญ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +* `list` +* `tuple` +* `set` +* `dict` + +๊ทธ๋ฆฌ๊ณ  `typing` ๋ชจ๋“ˆ์˜ ์ œ๋„ค๋ฆญ๋“ค: + +* `Union` +* `Optional` +* ...๊ทธ ๋ฐ–์˜ ๊ฒƒ๋“ค. + +//// + +### ํƒ€์ž…์œผ๋กœ์„œ์˜ ํด๋ž˜์Šค { #classes-as-types } + +๋ณ€์ˆ˜์˜ ํƒ€์ž…์œผ๋กœ ํด๋ž˜์Šค๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ฆ„์„ ๊ฐ€์ง„ `Person` ํด๋ž˜์Šค๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค: + +{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} + +๊ทธ๋Ÿฌ๋ฉด `Person` ํƒ€์ž…์˜ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} + +๊ทธ๋ฆฌ๊ณ  ๋‹ค์‹œ, ์—๋””ํ„ฐ์˜ ๋ชจ๋“  ์ง€์›์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/python-types/image06.png"> + +์ด๋Š” "`one_person`์€ `Person` ํด๋ž˜์Šค์˜ **์ธ์Šคํ„ด์Šค**"๋ผ๋Š” ๋œป์ž…๋‹ˆ๋‹ค. + +"`one_person`์€ `Person`์ด๋ผ๋Š” **ํด๋ž˜์Šค**๋‹ค"๋ผ๋Š” ๋œป์ด ์•„๋‹™๋‹ˆ๋‹ค. + +## Pydantic ๋ชจ๋ธ { #pydantic-models } + +<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>์€ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํŒŒ์ด์ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. + +์†์„ฑ์„ ๊ฐ€์ง„ ํด๋ž˜์Šค ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ์˜ "๋ชจ์–‘(shape)"์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๊ฐ ์†์„ฑ์€ ํƒ€์ž…์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ ๊ทธ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ช‡ ๊ฐ€์ง€ ๊ฐ’์œผ๋กœ ์ƒ์„ฑํ•˜๋ฉด, ๊ฐ’๋“ค์„ ๊ฒ€์ฆํ•˜๊ณ , (๊ทธ๋Ÿฐ ๊ฒฝ์šฐ๋ผ๋ฉด) ์ ์ ˆํ•œ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•œ ๋’ค, ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๊ทธ ๊ฒฐ๊ณผ ๊ฐ์ฒด์— ๋Œ€ํ•ด ์—๋””ํ„ฐ์˜ ๋ชจ๋“  ์ง€์›์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +Pydantic ๊ณต์‹ ๋ฌธ์„œ์˜ ์˜ˆ์‹œ: + +{* ../../docs_src/python_types/tutorial011_py310.py *} + +/// info | ์ •๋ณด + +<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๋ ค๋ฉด ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”</a>. + +/// + +**FastAPI**๋Š” ๋ชจ๋‘ Pydantic์— ๊ธฐ๋ฐ˜์„ ๋‘๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๋ชจ๋“  ๊ฒƒ์€ [์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ](tutorial/index.md){.internal-link target=_blank}์—์„œ ์‹ค์ œ๋กœ ๋งŽ์ด ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// tip | ํŒ + +Pydantic์€ ๊ธฐ๋ณธ๊ฐ’ ์—†์ด `Optional` ๋˜๋Š” `Union[Something, None]`์„ ์‚ฌ์šฉํ•  ๋•Œ ํŠน๋ณ„ํ•œ ๋™์ž‘์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ๋Š” Pydantic ๋ฌธ์„œ์˜ <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>์—์„œ ๋” ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +## ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์• ๋„ˆํ…Œ์ด์…˜์ด ์žˆ๋Š” ํƒ€์ž… ํžŒํŠธ { #type-hints-with-metadata-annotations } + +ํŒŒ์ด์ฌ์—๋Š” `Annotated`๋ฅผ ์‚ฌ์šฉํ•ด ์ด๋Ÿฌํ•œ ํƒ€์ž… ํžŒํŠธ์— **์ถ”๊ฐ€ <abbr title="Data about the data, in this case, information about the type, e.g. a description.">๋ฉ”ํƒ€๋ฐ์ดํ„ฐ</abbr>**๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +Python 3.9๋ถ€ํ„ฐ `Annotated`๋Š” ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์ผ๋ถ€์ด๋ฏ€๋กœ, `typing`์—์„œ importํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} + +ํŒŒ์ด์ฌ ์ž์ฒด๋Š” ์ด `Annotated`๋กœ ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์—๋””ํ„ฐ์™€ ๋‹ค๋ฅธ ๋„๊ตฌ๋“ค์—๊ฒŒ๋Š” ํƒ€์ž…์ด ์—ฌ์ „ํžˆ `str`์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ `Annotated`์˜ ์ด ๊ณต๊ฐ„์„ ์‚ฌ์šฉํ•ด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๊ธธ ์›ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ **FastAPI**์— ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ธฐ์–ตํ•ด์•ผ ํ•  ์ค‘์š”ํ•œ ์ ์€ `Annotated`์— ์ „๋‹ฌํ•˜๋Š” **์ฒซ ๋ฒˆ์งธ *ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜***๊ฐ€ **์‹ค์ œ ํƒ€์ž…**์ด๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚˜๋จธ์ง€๋Š” ๋‹ค๋ฅธ ๋„๊ตฌ๋ฅผ ์œ„ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. + +์ง€๊ธˆ์€ `Annotated`๊ฐ€ ์กด์žฌํ•˜๋ฉฐ, ํ‘œ์ค€ ํŒŒ์ด์ฌ์ด๋ผ๋Š” ๊ฒƒ๋งŒ ์•Œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๐Ÿ˜Ž + +๋‚˜์ค‘์— ์ด๊ฒƒ์ด ์–ผ๋งˆ๋‚˜ **๊ฐ•๋ ฅ**ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์ด๊ฒƒ์ด **ํ‘œ์ค€ ํŒŒ์ด์ฌ**์ด๋ผ๋Š” ์‚ฌ์‹ค์€, ์—๋””ํ„ฐ์—์„œ ๊ฐ€๋Šฅํ•œ **์ตœ๊ณ ์˜ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜**์„ ๊ณ„์† ์–ป์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ๋กœ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ๋ฆฌํŒฉํ„ฐ๋งํ•˜๋Š” ๋“ฑ์—์„œ๋„์š”. โœจ + +๋˜ํ•œ ์ฝ”๋“œ๊ฐ€ ๋งŽ์€ ๋‹ค๋ฅธ ํŒŒ์ด์ฌ ๋„๊ตฌ ๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๋งค์šฐ ํ˜ธํ™˜๋œ๋‹ค๋Š” ๋œป์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿš€ + +/// + +## **FastAPI**์—์„œ์˜ ํƒ€์ž… ํžŒํŠธ { #type-hints-in-fastapi } + +**FastAPI**๋Š” ์ด๋Ÿฌํ•œ ํƒ€์ž… ํžŒํŠธ๋ฅผ ํ™œ์šฉํ•ด ์—ฌ๋Ÿฌ ๊ฐ€์ง€๋ฅผ ํ•ฉ๋‹ˆ๋‹ค. + +**FastAPI**์—์„œ๋Š” ํƒ€์ž… ํžŒํŠธ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋ฉด ๋‹ค์Œ์„ ์–ป์Šต๋‹ˆ๋‹ค: * **์—๋””ํ„ฐ ๋„์›€**. * **ํƒ€์ž… ํ™•์ธ**. -...๊ทธ๋ฆฌ๊ณ  **FastAPI**๋Š” ๊ฐ™์€ ์ •์˜๋ฅผ ์•„๋ž˜์—๋„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค: +...๊ทธ๋ฆฌ๊ณ  **FastAPI**๋Š” ๊ฐ™์€ ์„ ์–ธ์„ ๋‹ค์Œ์—๋„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: -* **์š”๊ตฌ์‚ฌํ•ญ ์ •์˜**: ์š”์ฒญ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜, ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜, ํ—ค๋”, ๋ฐ”๋””, ์˜์กด์„ฑ ๋“ฑ. -* **๋ฐ์ดํ„ฐ ๋ณ€ํ™˜**: ์š”์ฒญ์—์„œ ์š”๊ตฌํ•œ ํƒ€์ž…์œผ๋กœ. -* **๋ฐ์ดํ„ฐ ๊ฒ€์ฆ**: ๊ฐ ์š”์ฒญ๋งˆ๋‹ค: - * ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” **์ž๋™์œผ๋กœ ์—๋Ÿฌ**๋ฅผ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. -* OpenAPI๋ฅผ ํ™œ์šฉํ•œ **API ๋ฌธ์„œํ™”**: - * ์ž๋™์œผ๋กœ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ์œ ์ € ์ธํ„ฐํŽ˜์ด์Šค์— ์“ฐ์ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +* **์š”๊ตฌ์‚ฌํ•ญ ์ •์˜**: ์š”์ฒญ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜, ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜, ํ—ค๋”, ๋ฐ”๋””, ์˜์กด์„ฑ ๋“ฑ์—์„œ. +* **๋ฐ์ดํ„ฐ ๋ณ€ํ™˜**: ์š”์ฒญ์—์„œ ํ•„์š”ํ•œ ํƒ€์ž…์œผ๋กœ. +* **๋ฐ์ดํ„ฐ ๊ฒ€์ฆ**: ๊ฐ ์š”์ฒญ์—์„œ: + * ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์„ ๋•Œ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜๋˜๋Š” **์ž๋™ ์˜ค๋ฅ˜**๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +* OpenAPI๋ฅผ ์‚ฌ์šฉํ•ด API๋ฅผ **๋ฌธ์„œํ™”**: + * ์ž๋™ ์ƒํ˜ธ์ž‘์šฉ ๋ฌธ์„œ UI์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -์œ„ ๋‚ด์šฉ์ด ๋‹ค์†Œ ์ถ”์ƒ์ ์ผ ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ฑฑ์ •๋งˆ์„ธ์š”. [์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ](tutorial/index.md){.internal-link target=_blank}์—์„œ ์ „๋ถ€ ํ™•์ธ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +์ด ๋ชจ๋“  ๊ฒƒ์ด ๋‹ค์†Œ ์ถ”์ƒ์ ์œผ๋กœ ๋“ค๋ฆด ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฑฑ์ •ํ•˜์ง€ ๋งˆ์„ธ์š”. [์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ](tutorial/index.md){.internal-link target=_blank}์—์„œ ์‹ค์ œ๋กœ ํ™•์ธํ•˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฑด, ํ‘œ์ค€ ํŒŒ์ด์ฌ ํƒ€์ž…์„ ํ•œ ๊ณณ์—์„œ(ํด๋ž˜์Šค๋ฅผ ๋”ํ•˜๊ฑฐ๋‚˜, ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ ) ์‚ฌ์šฉํ•จ์œผ๋กœ์จ **FastAPI**๊ฐ€ ๋‹น์‹ ์„ ์œ„ํ•ด ๋งŽ์€ ์ผ์„ ํ•ด์ค€๋‹ค๋Š” ์‚ฌ์‹ค์ด์ฃ . +๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ ์€ ํ‘œ์ค€ ํŒŒ์ด์ฌ ํƒ€์ž…์„ ํ•œ ๊ณณ์—์„œ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ(๋” ๋งŽ์€ ํด๋ž˜์Šค, ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋“ฑ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋Œ€์‹ ) **FastAPI**๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์„ ์œ„ํ•ด ๋งŽ์€ ์ผ์„ ํ•ด์ค€๋‹ค๋Š” ์‚ฌ์‹ค์ž…๋‹ˆ๋‹ค. /// info | ์ •๋ณด -๋งŒ์•ฝ ๋ชจ๋“  ์ž์Šต์„œ๋ฅผ ๋‹ค ๋ณด์•˜์Œ์—๋„ ํƒ€์ž…์— ๋Œ€ํ•ด์„œ ๋” ๋ณด๊ณ ์ž ๋ฐฉ๋ฌธํ•œ ๊ฒฝ์šฐ์—๋Š” <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">`mypy`์—์„œ ์ œ๊ณตํ•˜๋Š” "cheat sheet"</a>์ด ์ข‹์€ ์ž๋ฃŒ๊ฐ€ ๋  ๊ฒ๋‹ˆ๋‹ค. +์ž์Šต์„œ๋ฅผ ๋ชจ๋‘ ๋๋‚ด๊ณ  ํƒ€์ž…์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ๋Œ์•„์™”๋‹ค๋ฉด, ์ข‹์€ ์ž๋ฃŒ๋กœ <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">`mypy`์˜ "cheat sheet"</a>๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. /// diff --git a/docs/ko/docs/resources/index.md b/docs/ko/docs/resources/index.md index e804dd4d56..477b93a584 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/tutorial/background-tasks.md b/docs/ko/docs/tutorial/background-tasks.md index 9c4d57481f..9e868f2fa7 100644 --- a/docs/ko/docs/tutorial/background-tasks.md +++ b/docs/ko/docs/tutorial/background-tasks.md @@ -1,84 +1,86 @@ -# ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… +# ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… { #background-tasks } -FastAPI์—์„œ๋Š” ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ ํ›„์— ์‹คํ–‰ํ•  ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +FastAPI์—์„œ๋Š” ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ *ํ›„์—* ์‹คํ–‰ํ•  ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‘๋‹ต์„ ๋ฐ›๊ธฐ ์œ„ํ•ด ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์š”์ฒญ ํ›„์— ๋ฐœ์ƒํ•ด์•ผํ•˜๋Š” ์ž‘์—…์— ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. +๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์€ ์š”์ฒญ ํ›„์— ๋ฐœ์ƒํ•ด์•ผ ํ•˜์ง€๋งŒ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‘๋‹ต์„ ๋ฐ›๊ธฐ ์ „์— ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†๋Š” ์ž‘์—…์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. -์ด๋Ÿฌํ•œ ์ž‘์—…์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. -* ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ ํ›„ ์ „์†ก๋˜๋Š” ์ด๋ฉ”์ผ ์•Œ๋ฆผ - * ์ด๋ฉ”์ผ ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•˜๊ณ  ์ด๋ฉ”์ผ์„ ์ „์†กํ•˜๋Š” ๊ฒƒ์€ (๋ช‡ ์ดˆ ์ •๋„) "๋А๋ฆฐ" ๊ฒฝํ–ฅ์ด ์žˆ์œผ๋ฏ€๋กœ, ์‘๋‹ต์€ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์ด๋ฉ”์ผ ์•Œ๋ฆผ์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ „์†กํ•˜๋Š” ๊ฒŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +* ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ ํ›„ ์ „์†ก๋˜๋Š” ์ด๋ฉ”์ผ ์•Œ๋ฆผ: + * ์ด๋ฉ”์ผ ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•˜๊ณ  ์ด๋ฉ”์ผ์„ ์ „์†กํ•˜๋Š” ๊ฒƒ์€ (๋ช‡ ์ดˆ ์ •๋„) "๋А๋ฆฐ" ๊ฒฝํ–ฅ์ด ์žˆ์œผ๋ฏ€๋กœ, ์‘๋‹ต์€ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์ด๋ฉ”์ผ ์•Œ๋ฆผ์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ „์†กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ: - * ์˜ˆ๋ฅผ ๋“ค์–ด ์ฒ˜๋ฆฌ์— ์˜ค๋žœ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•˜์„ ๋•Œ "Accepted" (HTTP 202)์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + * ์˜ˆ๋ฅผ ๋“ค์–ด ์ฒ˜๋ฆฌ์— ์˜ค๋žœ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฑฐ์ณ์•ผ ํ•˜๋Š” ํŒŒ์ผ์„ ๋ฐ›์•˜๋‹ค๋ฉด, "Accepted"(HTTP 202) ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ํŒŒ์ผ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…` ์‚ฌ์šฉ +## `BackgroundTasks` ์‚ฌ์šฉ { #using-backgroundtasks } -๋จผ์ € ์•„๋ž˜์™€ ๊ฐ™์ด `BackgroundTasks`๋ฅผ ์ž„ํฌํŠธํ•˜๊ณ , `BackgroundTasks`๋ฅผ _๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜_ ์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๊ฐ€์ ธ์˜ค๊ณ  ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. +๋จผ์ € `BackgroundTasks`๋ฅผ ์ž„ํฌํŠธํ•˜๊ณ , `BackgroundTasks` ํƒ€์ž… ์„ ์–ธ์œผ๋กœ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์— ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} -**FastAPI** ๋Š” `BackgroundTasks` ๊ฐœ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๋งค๊ฐœ ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. +**FastAPI**๊ฐ€ `BackgroundTasks` ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ•ด๋‹น ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. -## ์ž‘์—… ํ•จ์ˆ˜ ์ƒ์„ฑ +## ์ž‘์—… ํ•จ์ˆ˜ ์ƒ์„ฑ { #create-a-task-function } -๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์œผ๋กœ ์‹คํ–‰ํ•  ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. +๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์œผ๋กœ ์‹คํ–‰ํ•  ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -์ด๊ฒƒ์€ ๋‹จ์ˆœํžˆ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ํ‘œ์ค€ ํ•จ์ˆ˜์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. +์ด๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ํ‘œ์ค€ ํ•จ์ˆ˜์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. -**FastAPI**๋Š” ์ด๊ฒƒ์ด `async def` ํ•จ์ˆ˜์ด๋“ , ์ผ๋ฐ˜ `def` ํ•จ์ˆ˜์ด๋“  ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. +`async def` ํ•จ์ˆ˜์ผ ์ˆ˜๋„, ์ผ๋ฐ˜ `def` ํ•จ์ˆ˜์ผ ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, **FastAPI**๊ฐ€ ์ด๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ, ์•„๋ž˜ ์ž‘์—…์€ ํŒŒ์ผ์— ์“ฐ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. (์ด๋ฉ”์ผ ๋ณด๋‚ด๊ธฐ ์‹œ๋ฌผ๋ ˆ์ด์…˜) +์ด ๊ฒฝ์šฐ ์ž‘์—… ํ•จ์ˆ˜๋Š” ํŒŒ์ผ์— ์“ฐ๊ธฐ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค(์ด๋ฉ”์ผ ์ „์†ก์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜). -๊ทธ๋ฆฌ๊ณ  ์ด ์ž‘์—…์€ `async`์™€ `await`๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ผ๋ฐ˜ `def` ํ•จ์ˆ˜๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์“ฐ๊ธฐ ์ž‘์—…์€ `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()` ํ•จ์ˆ˜ ํ†ตํ•ด _๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…_ ๊ฐœ์ฒด์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* ๋‚ด๋ถ€์—์„œ `.add_task()` ๋ฉ”์„œ๋“œ๋กœ ์ž‘์—… ํ•จ์ˆ˜๋ฅผ *๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…* ๊ฐ์ฒด์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} -`.add_task()` ํ•จ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ธ์ž๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค : +`.add_task()`๋Š” ๋‹ค์Œ ์ธ์ž๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค: -- ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋˜๋Š” ์ž‘์—… ํ•จ์ˆ˜ (`write_notification`). -- ์ž‘์—… ํ•จ์ˆ˜์— ์ˆœ์„œ๋Œ€๋กœ ์ „๋‹ฌ๋˜์–ด์•ผ ํ•˜๋Š” ์ผ๋ จ์˜ ์ธ์ž (`email`). -- ์ž‘์—… ํ•จ์ˆ˜์— ์ „๋‹ฌ๋˜์–ด์•ผํ•˜๋Š” ๋ชจ๋“  ํ‚ค์›Œ๋“œ ์ธ์ž (`message="some notification"`). +* ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋  ์ž‘์—… ํ•จ์ˆ˜(`write_notification`). +* ์ž‘์—… ํ•จ์ˆ˜์— ์ˆœ์„œ๋Œ€๋กœ ์ „๋‹ฌ๋˜์–ด์•ผ ํ•˜๋Š” ์ธ์ž ์‹œํ€€์Šค(`email`). +* ์ž‘์—… ํ•จ์ˆ˜์— ์ „๋‹ฌ๋˜์–ด์•ผ ํ•˜๋Š” ํ‚ค์›Œ๋“œ ์ธ์ž(`message="some notification"`). -## ์˜์กด์„ฑ ์ฃผ์ž… +## ์˜์กด์„ฑ ์ฃผ์ž… { #dependency-injection } -`BackgroundTasks`๋ฅผ ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด _๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜_, ์ข…์†์„ฑ, ํ•˜์œ„ ์ข…์†์„ฑ ๋“ฑ ์—ฌ๋Ÿฌ ์ˆ˜์ค€์—์„œ BackgroundTasks ์œ ํ˜•์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`BackgroundTasks`๋Š” ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์—์„œ๋„ ๋™์ž‘ํ•˜๋ฉฐ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*, ์˜์กด์„ฑ(dependable), ํ•˜์œ„ ์˜์กด์„ฑ ๋“ฑ ์—ฌ๋Ÿฌ ์ˆ˜์ค€์—์„œ `BackgroundTasks` ํƒ€์ž…์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” ๊ฐ ๊ฒฝ์šฐ์— ์ˆ˜ํ–‰ํ•  ์ž‘์—…๊ณผ ๋™์ผํ•œ ๊ฐœ์ฒด๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ์—, ๋ชจ๋“  ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ด ํ•จ๊ป˜ ๋ณ‘ํ•ฉ๋˜๊ณ  ๋‚˜์ค‘์— ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๊ฐ ๊ฒฝ์šฐ์— ๋ฌด์—‡์„ ํ•ด์•ผ ํ•˜๋Š”์ง€์™€ ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ ์–ด๋–ป๊ฒŒ ์žฌ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”์ง€๋ฅผ ์•Œ๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ๋ชจ๋“  ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ด ํ•จ๊ป˜ ๋ณ‘ํ•ฉ๋˜์–ด ์ดํ›„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *} -์ด ์˜ˆ์ œ์—์„œ๋Š” ์‘๋‹ต์ด ๋ฐ˜ํ™˜๋œ ํ›„์— `log.txt` ํŒŒ์ผ์— ๋ฉ”์‹œ์ง€๊ฐ€ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. +{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *} -์š”์ฒญ์— ์ฟผ๋ฆฌ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์˜ ๋กœ๊ทธ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  _๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜_ ์—์„œ ์ƒ์„ฑ๋œ ๋˜ ๋‹ค๋ฅธ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์€ ๊ฒฝ๋กœ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. +์ด ์˜ˆ์ œ์—์„œ๋Š” ์‘๋‹ต์ด ์ „์†ก๋œ *ํ›„์—* ๋ฉ”์‹œ์ง€๊ฐ€ `log.txt` ํŒŒ์ผ์— ์ž‘์„ฑ๋ฉ๋‹ˆ๋‹ค. -## ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +์š”์ฒญ์— ์ฟผ๋ฆฌ๊ฐ€ ์žˆ์—ˆ๋‹ค๋ฉด, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์œผ๋กœ ๋กœ๊ทธ์— ์ž‘์„ฑ๋ฉ๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ์ƒ์„ฑ๋œ ๋˜ ๋‹ค๋ฅธ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ด `email` ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. + +## ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ { #technical-details } `BackgroundTasks` ํด๋ž˜์Šค๋Š” <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`starlette.background`</a>์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. -`BackgroundTasks` ํด๋ž˜์Šค๋Š” FastAPI์—์„œ ์ง์ ‘ ์ž„ํฌํŠธํ•˜๊ฑฐ๋‚˜ ํฌํ•จํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ˆ˜๋กœ `BackgroundTask` (๋์— `s`๊ฐ€ ์—†์Œ)์„ ์ž„ํฌํŠธํ•˜๋”๋ผ๋„ starlette.background์—์„œ `BackgroundTask`๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +FastAPI์— ์ง์ ‘ ์ž„ํฌํŠธ/ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ `fastapi`์—์„œ ์ž„ํฌํŠธํ•  ์ˆ˜ ์žˆ๊ณ , ์‹ค์ˆ˜๋กœ `starlette.background`์—์„œ ๋Œ€์•ˆ์ธ `BackgroundTask`(๋์— `s`๊ฐ€ ์—†์Œ)๋ฅผ ์ž„ํฌํŠธํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -(`BackgroundTask`๊ฐ€ ์•„๋‹Œ) `BackgroundTasks`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, _๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜_ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๊ณ  ๋‚˜๋จธ์ง€๋Š” **FastAPI**๊ฐ€ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ `Request` ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. +`BackgroundTask`๊ฐ€ ์•„๋‹Œ `BackgroundTasks`๋งŒ ์‚ฌ์šฉํ•˜๋ฉด, ์ด๋ฅผ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ  ๋‚˜๋จธ์ง€๋Š” **FastAPI**๊ฐ€ `Request` ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ๋•Œ์ฒ˜๋Ÿผ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•ด ์ค๋‹ˆ๋‹ค. -FastAPI์—์„œ `BackgroundTask`๋ฅผ ๋‹จ๋…์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์—ฌ์ „ํžˆ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ์ฒด๋ฅผ ์ฝ”๋“œ์—์„œ ์ƒ์„ฑํ•˜๊ณ , ์ด ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” Starlette `Response`๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +FastAPI์—์„œ `BackgroundTask`๋งŒ ๋‹จ๋…์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์ฝ”๋“œ์—์„œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ ํฌํ•จํ•˜๋Š” Starlette `Response`๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -<a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`Starlette์˜ ๊ณต์‹ ๋ฌธ์„œ`</a>์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋” ์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">Starlette์˜ Background Tasks ๊ณต์‹ ๋ฌธ์„œ</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ๊ฒฝ๊ณ  +## ์ฃผ์˜์‚ฌํ•ญ { #caveat } -๋งŒ์•ฝ ๋ฌด๊ฑฐ์šด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์•ผํ•˜๊ณ  ๋™์ผํ•œ ํ”„๋กœ์„ธ์Šค์—์„œ ์‹คํ–‰ํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ (์˜ˆ: ๋ฉ”๋ชจ๋ฆฌ, ๋ณ€์ˆ˜ ๋“ฑ์„ ๊ณต์œ ํ•  ํ•„์š”๊ฐ€ ์—†์Œ) <a href="https://docs.celeryq.dev" class="external-link" target="_blank">`Celery`</a>์™€ ๊ฐ™์€ ํฐ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ฌด๊ฑฐ์šด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๊ณ , ๋ฐ˜๋“œ์‹œ ๋™์ผํ•œ ํ”„๋กœ์„ธ์Šค์—์„œ ์‹คํ–‰ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด(์˜ˆ: ๋ฉ”๋ชจ๋ฆฌ, ๋ณ€์ˆ˜ ๋“ฑ์„ ๊ณต์œ ํ•  ํ•„์š”๊ฐ€ ์—†์Œ) <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a> ๊ฐ™์€ ๋” ํฐ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -RabbitMQ ๋˜๋Š” Redis์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€/์ž‘์—… ํ ์‹œ์Šคํ…œ ๋ณด๋‹ค ๋ณต์žกํ•œ ๊ตฌ์„ฑ์ด ํ•„์š”ํ•œ ๊ฒฝํ–ฅ์ด ์žˆ์ง€๋งŒ, ์—ฌ๋Ÿฌ ์ž‘์—… ํ”„๋กœ์„ธ์Šค๋ฅผ ํŠนํžˆ ์—ฌ๋Ÿฌ ์„œ๋ฒ„์˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋“ค์€ RabbitMQ๋‚˜ Redis ๊ฐ™์€ ๋ฉ”์‹œ์ง€/์ž‘์—… ํ ๊ด€๋ฆฌ์ž ๋“ฑ ๋” ๋ณต์žกํ•œ ์„ค์ •์„ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์ง€๋งŒ, ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค์—์„œ, ํŠนํžˆ ์—ฌ๋Ÿฌ ์„œ๋ฒ„์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ ๋™์ผํ•œ FastAPI ์•ฑ์—์„œ ๋ณ€์ˆ˜ ๋ฐ ๊ฐœ์ฒด์— ์ ‘๊ทผํ•ด์•ผํ–๋Š” ์ž‘์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ˆ˜ํ–‰์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ (์˜ˆ : ์•Œ๋ฆผ ์ด๋ฉ”์ผ ๋ณด๋‚ด๊ธฐ) ๊ฐ„๋‹จํ•˜๊ฒŒ `BackgroundTasks`๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์„ธ์š”. +ํ•˜์ง€๋งŒ ๋™์ผํ•œ **FastAPI** ์•ฑ์˜ ๋ณ€์ˆ˜์™€ ๊ฐ์ฒด์— ์ ‘๊ทผํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜(๋˜๋Š” ์ด๋ฉ”์ผ ์•Œ๋ฆผ ์ „์†ก์ฒ˜๋Ÿผ) ์ž‘์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•œ๋‹ค๋ฉด, `BackgroundTasks`๋ฅผ ๊ฐ„๋‹จํžˆ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. -## ์š”์•ฝ +## ์š”์•ฝ { #recap } -๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด _๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜_ ์— ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `BackgroundTasks`๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์™€ ์˜์กด์„ฑ์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `BackgroundTasks`๋ฅผ ์ž„ํฌํŠธํ•ด ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/body-fields.md b/docs/ko/docs/tutorial/body-fields.md index 4708e7099f..c98734ab37 100644 --- a/docs/ko/docs/tutorial/body-fields.md +++ b/docs/ko/docs/tutorial/body-fields.md @@ -1,8 +1,8 @@ -# ๋ณธ๋ฌธ - ํ•„๋“œ +# ๋ณธ๋ฌธ - ํ•„๋“œ { #body-fields } -`Query`, `Path`์™€ `Body`๋ฅผ ์‚ฌ์šฉํ•ด *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜* ๋งค๊ฐœ๋ณ€์ˆ˜ ๋‚ด์—์„œ ์ถ”๊ฐ€์ ์ธ ๊ฒ€์ฆ์ด๋‚˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์„ ์–ธํ•œ ๊ฒƒ์ฒ˜๋Ÿผ Pydantic์˜ `Field`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋ธ ๋‚ด์—์„œ ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`Query`, `Path`์™€ `Body`๋ฅผ ์‚ฌ์šฉํ•ด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* ๋งค๊ฐœ๋ณ€์ˆ˜ ๋‚ด์—์„œ ์ถ”๊ฐ€์ ์ธ ๊ฒ€์ฆ์ด๋‚˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์„ ์–ธํ•œ ๊ฒƒ์ฒ˜๋Ÿผ Pydantic์˜ `Field`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋ธ ๋‚ด์—์„œ ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Field` ์ž„ํฌํŠธ +## `Field` ์ž„ํฌํŠธ { #import-field } ๋จผ์ € ์ด๋ฅผ ์ž„ํฌํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: @@ -14,7 +14,7 @@ /// -## ๋ชจ๋ธ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ ์„ ์–ธ +## ๋ชจ๋ธ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ ์„ ์–ธ { #declare-model-attributes } ๊ทธ ๋‹ค์Œ ๋ชจ๋ธ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์™€ ํ•จ๊ป˜ `Field`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -22,7 +22,7 @@ `Field`๋Š” `Query`, `Path`์™€ `Body`์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ, ๋ชจ๋‘ ๊ฐ™์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค ๋“ฑ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. -/// note | ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ ์‹ค์ œ๋กœ `Query`, `Path`๋“ฑ, ์—ฌ๋Ÿฌ๋ถ„์ด ์•ž์œผ๋กœ ๋ณผ ๋‹ค๋ฅธ ๊ฒƒ๋“ค์€ ๊ณตํ†ต ํด๋ž˜์Šค์ธ `Param` ํด๋ž˜์Šค์˜ ์„œ๋ธŒํด๋ž˜์Šค ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋Š”๋ฐ, ๊ทธ ์ž์ฒด๋กœ Pydantic์˜ `FieldInfo` ํด๋ž˜์Šค์˜ ์„œ๋ธŒํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. @@ -36,11 +36,11 @@ /// tip | ํŒ -์ฃผ๋ชฉํ•  ์ ์€ ํƒ€์ž…, ๊ธฐ๋ณธ ๊ฐ’ ๋ฐ `Field`๋กœ ์ด๋ฃจ์–ด์ง„ ๊ฐ ๋ชจ๋ธ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๊ฐ€ `Path`, `Query`์™€ `Body`๋Œ€์‹  `Field`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„๋‹ค๋Š” ์  ์ž…๋‹ˆ๋‹ค. +์ฃผ๋ชฉํ•  ์ ์€ ํƒ€์ž…, ๊ธฐ๋ณธ ๊ฐ’ ๋ฐ `Field`๋กœ ์ด๋ฃจ์–ด์ง„ ๊ฐ ๋ชจ๋ธ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๊ฐ€ `Path`, `Query`์™€ `Body`๋Œ€์‹  `Field`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„๋‹ค๋Š” ์  ์ž…๋‹ˆ๋‹ค. /// -## ๋ณ„๋„ ์ •๋ณด ์ถ”๊ฐ€ +## ๋ณ„๋„ ์ •๋ณด ์ถ”๊ฐ€ { #add-extra-information } `Field`, `Query`, `Body`, ๊ทธ ์™ธ ์•ˆ์— ๋ณ„๋„ ์ •๋ณด๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ƒ์„ฑ๋œ JSON ์Šคํ‚ค๋งˆ์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. @@ -53,7 +53,7 @@ /// -## ์š”์•ฝ +## ์š”์•ฝ { #recap } ๋ชจ๋ธ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์œ„ํ•œ ์ถ”๊ฐ€ ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•ด Pydantic์˜ `Field` ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/body-multiple-params.md b/docs/ko/docs/tutorial/body-multiple-params.md index edf892dfab..701351e637 100644 --- a/docs/ko/docs/tutorial/body-multiple-params.md +++ b/docs/ko/docs/tutorial/body-multiple-params.md @@ -1,26 +1,24 @@ -# ๋ณธ๋ฌธ - ๋‹ค์ค‘ ๋งค๊ฐœ๋ณ€์ˆ˜ +# ๋ณธ๋ฌธ - ๋‹ค์ค‘ ๋งค๊ฐœ๋ณ€์ˆ˜ { #body-multiple-parameters } -์ง€๊ธˆ๋ถ€ํ„ฐ `Path`์™€ `Query`๋ฅผ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. +์ด์ œ `Path`์™€ `Query`๋ฅผ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ํ™•์ธํ–ˆ์œผ๋‹ˆ, ์š”์ฒญ ๋ณธ๋ฌธ ์„ ์–ธ์— ๋Œ€ํ•œ ๋” ๊ณ ๊ธ‰ ์‚ฌ์šฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -์š”์ฒญ ๋ณธ๋ฌธ ์„ ์–ธ์— ๋Œ€ํ•œ ์‹ฌํ™” ์‚ฌ์šฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. +## `Path`, `Query` ๋ฐ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜ ํ˜ผํ•ฉ { #mix-path-query-and-body-parameters } -## `Path`, `Query` ๋ฐ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜ ํ˜ผํ•ฉ +๋จผ์ €, ๋ฌผ๋ก  `Path`, `Query` ๋ฐ ์š”์ฒญ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜ ์„ ์–ธ์„ ์ž์œ ๋กญ๊ฒŒ ํ˜ผํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , **FastAPI**๋Š” ์–ด๋–ค ๋™์ž‘์„ ํ• ์ง€ ์••๋‹ˆ๋‹ค. -๋‹น์—ฐํ•˜๊ฒŒ `Path`, `Query` ๋ฐ ์š”์ฒญ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜ ์„ ์–ธ์„ ์ž์œ ๋กญ๊ฒŒ ํ˜ผํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , **FastAPI**๋Š” ์–ด๋–ค ๋™์ž‘์„ ํ• ์ง€ ์••๋‹ˆ๋‹ค. +๋˜ํ•œ ๊ธฐ๋ณธ ๊ฐ’์„ `None`์œผ๋กœ ์„ค์ •ํ•ด ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ํƒ์‚ฌํ•ญ์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -๋˜ํ•œ, ๊ธฐ๋ณธ ๊ฐ’์„ `None`์œผ๋กœ ์„ค์ •ํ•ด ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ํƒ์‚ฌํ•ญ์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -{* ../../docs_src/body_multiple_params/tutorial001.py hl[19:21] *} +{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} /// note | ์ฐธ๊ณ  -์ด ๊ฒฝ์šฐ์—๋Š” ๋ณธ๋ฌธ์œผ๋กœ ๋ถ€ํ„ฐ ๊ฐ€์ ธ์˜จ ` item`์€ ๊ธฐ๋ณธ๊ฐ’์ด `None`์ด๊ธฐ ๋•Œ๋ฌธ์—, ์„ ํƒ์‚ฌํ•ญ์ด๋ผ๋Š” ์ ์„ ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ์—๋Š” ๋ณธ๋ฌธ์—์„œ ๊ฐ€์ ธ์˜ฌ `item`์ด ์„ ํƒ์‚ฌํ•ญ์ด๋ผ๋Š” ์ ์„ ์œ ์˜ํ•˜์„ธ์š”. ๊ธฐ๋ณธ๊ฐ’์ด `None`์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. /// -## ๋‹ค์ค‘ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜ +## ๋‹ค์ค‘ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜ { #multiple-body-parameters } -์ด์ „ ์˜ˆ์ œ์—์„œ ๋ณด๋“ฏ์ด, *๊ฒฝ๋กœ ์ž‘๋™*์€ ์•„๋ž˜์™€ ๊ฐ™์ด `Item` ์†์„ฑ์„ ๊ฐ€์ง„ JSON ๋ณธ๋ฌธ์„ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค: +์ด์ „ ์˜ˆ์ œ์—์„œ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ `Item`์˜ ์†์„ฑ์„ ๊ฐ€์ง„ JSON ๋ณธ๋ฌธ์„ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค: ```JSON { @@ -33,11 +31,12 @@ ํ•˜์ง€๋งŒ, ๋‹ค์ค‘ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜ ์—ญ์‹œ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ. `item`๊ณผ `user`: -{* ../../docs_src/body_multiple_params/tutorial002.py hl[22] *} +{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} -์ด ๊ฒฝ์šฐ์—, **FastAPI**๋Š” ์ด ํ•จ์ˆ˜ ์•ˆ์— ํ•œ ๊ฐœ ์ด์ƒ์˜ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜(Pydantic ๋ชจ๋ธ์ธ ๋‘ ๋งค๊ฐœ๋ณ€์ˆ˜)๊ฐ€ ์žˆ๋‹ค๊ณ  ์•Œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -๊ทธ๋ž˜์„œ, ๋ณธ๋ฌธ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ํ‚ค(ํ•„๋“œ ๋ช…)๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ณธ๋ฌธ์„ ์˜ˆ์ธกํ•ฉ๋‹ˆ๋‹ค: +์ด ๊ฒฝ์šฐ์—, **FastAPI**๋Š” ์ด ํ•จ์ˆ˜์— ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ 1๊ฐœ๋ณด๋‹ค ๋งŽ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•„์ฑŒ ๊ฒƒ์ž…๋‹ˆ๋‹ค(๋‘ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ Pydantic ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค). + +๊ทธ๋ž˜์„œ, ๋ณธ๋ฌธ์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ํ‚ค(ํ•„๋“œ ์ด๋ฆ„)๋กœ ์‚ฌ์šฉํ•˜๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ณธ๋ฌธ์„ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค: ```JSON { @@ -56,29 +55,28 @@ /// note | ์ฐธ๊ณ  -์ด์ „๊ณผ ๊ฐ™์ด `item`์ด ์„ ์–ธ ๋˜์—ˆ๋”๋ผ๋„, ๋ณธ๋ฌธ ๋‚ด์˜ `item` ํ‚ค๊ฐ€ ์žˆ์„ ๊ฒƒ์ด๋ผ๊ณ  ์˜ˆ์ธกํ•ฉ๋‹ˆ๋‹ค. +`item`์ด ์ด์ „๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์„ ์–ธ๋˜์—ˆ๋”๋ผ๋„, ์ด์ œ๋Š” ๋ณธ๋ฌธ์—์„œ `item` ํ‚ค ์•ˆ์— ์žˆ์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋œ๋‹ค๋Š” ์ ์„ ์œ ์˜ํ•˜์„ธ์š”. /// -FastAPI๋Š” ์š”์ฒญ์„ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜ํ•ด, ๋งค๊ฐœ๋ณ€์ˆ˜์˜ `item`๊ณผ `user`๋ฅผ ํŠน๋ณ„ํ•œ ๋‚ด์šฉ์œผ๋กœ ๋ฐ›๋„๋ก ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**FastAPI**๋Š” ์š”์ฒญ์—์„œ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜์„ ์ˆ˜ํ–‰ํ•˜์—ฌ, ๋งค๊ฐœ๋ณ€์ˆ˜ `item`์ด ํ•ด๋‹นํ•˜๋Š” ๋‚ด์šฉ์„ ๋ฐ›๊ณ  `user`๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ฐ›๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. -๋ณตํ•ฉ ๋ฐ์ดํ„ฐ์˜ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  OpenAPI ์Šคํ‚ค๋งˆ ๋ฐ ์ž๋™ ๋ฌธ์„œ๋ฅผ ๋ฌธ์„œํ™”ํ•ฉ๋‹ˆ๋‹ค. +๋ณตํ•ฉ ๋ฐ์ดํ„ฐ์˜ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•˜๊ณ , OpenAPI ์Šคํ‚ค๋งˆ ๋ฐ ์ž๋™ ๋ฌธ์„œ์—๋„ ๊ทธ์— ๋งž๊ฒŒ ๋ฌธ์„œํ™”ํ•ฉ๋‹ˆ๋‹ค. -## ๋ณธ๋ฌธ ๋‚ด์˜ ๋‹จ์ผ ๊ฐ’ +## ๋ณธ๋ฌธ ๋‚ด์˜ ๋‹จ์ผ ๊ฐ’ { #singular-values-in-body } -์ฟผ๋ฆฌ ๋ฐ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ํ•˜๋Š” `Query`์™€ `Path`์™€ ๊ฐ™์ด, **FastAPI**๋Š” ๋™๋“ฑํ•œ `Body`๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +์ฟผ๋ฆฌ ๋ฐ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ํ•˜๋Š” `Query`์™€ `Path`๊ฐ€ ์žˆ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ, **FastAPI**๋Š” ๋™๋“ฑํ•œ `Body`๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด ์ด์ „์˜ ๋ชจ๋ธ์„ ํ™•์žฅํ•˜๋ฉด, `item`๊ณผ `user`์™€ ๋™์ผํ•œ ๋ณธ๋ฌธ์— ๋˜ ๋‹ค๋ฅธ `importance`๋ผ๋Š” ํ‚ค๋ฅผ ๊ฐ–๋„๋ก ํ•  ์ˆ˜์žˆ์Šต๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด ์ด์ „ ๋ชจ๋ธ์„ ํ™•์žฅํ•ด์„œ, `item`๊ณผ `user` ์™ธ์—๋„ ๊ฐ™์€ ๋ณธ๋ฌธ์— `importance`๋ผ๋Š” ๋‹ค๋ฅธ ํ‚ค๋ฅผ ๋‘๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋‹จ์ผ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์„ ์–ธํ•œ๋‹ค๋ฉด, **FastAPI**๋Š” ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๊ฐ€์ •ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๋‹จ์ผ ๊ฐ’์ด๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ ์„ ์–ธํ•˜๋ฉด, **FastAPI**๋Š” ์ด๋ฅผ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ผ๊ณ  ๊ฐ€์ •ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ, **FastAPI**์˜ `Body`๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค๋ฅธ ๋ณธ๋ฌธ ํ‚ค๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +ํ•˜์ง€๋งŒ `Body`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ๋ณธ๋ฌธ ํ‚ค๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก **FastAPI**์— ์ง€์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} -{* ../../docs_src/body_multiple_params/tutorial003.py hl[23] *} - -์ด ๊ฒฝ์šฐ์—๋Š” **FastAPI**๋Š” ๋ณธ๋ฌธ์„ ์ด์™€ ๊ฐ™์ด ์˜ˆ์ธกํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: - +์ด ๊ฒฝ์šฐ์—๋Š” **FastAPI**๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ณธ๋ฌธ์„ ์˜ˆ์ƒํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: ```JSON { @@ -96,58 +94,55 @@ FastAPI๋Š” ์š”์ฒญ์„ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜ํ•ด, ๋งค๊ฐœ๋ณ€์ˆ˜์˜ `item`๊ณผ `user`๋ฅผ } ``` -๋‹ค์‹œ ๋งํ•ด, ๋ฐ์ดํ„ฐ ํƒ€์ž…, ๊ฒ€์ฆ, ๋ฌธ์„œ ๋“ฑ์„ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +๋‹ค์‹œ ๋งํ•ด, ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๋ณ€ํ™˜ํ•˜๊ณ , ๊ฒ€์ฆํ•˜๊ณ , ๋ฌธ์„œํ™”ํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. -## ๋‹ค์ค‘ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ฟผ๋ฆฌ +## ๋‹ค์ค‘ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ฟผ๋ฆฌ { #multiple-body-params-and-query } -๋‹น์—ฐํžˆ, ํ•„์š”ํ•  ๋•Œ๋งˆ๋‹ค ์ถ”๊ฐ€์ ์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ณ , ์ด๋Š” ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. +๋ฌผ๋ก , ํ•„์š”ํ•  ๋•Œ๋งˆ๋‹ค ์–ด๋–ค ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜์— ์ถ”๊ฐ€๋กœ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹จ์ผ ๊ฐ’์€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ํ•ด์„๋˜๋ฏ€๋กœ, ๋ช…์‹œ์ ์œผ๋กœ `Query`๋ฅผ ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์—†๊ณ , ์•„๋ž˜์ฒ˜๋Ÿผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: - -{* ../../docs_src/body_multiple_params/tutorial004.py hl[27] *} - -์ด๋ ‡๊ฒŒ: +๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹จ์ผ ๊ฐ’์€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ํ•ด์„๋˜๋ฏ€๋กœ, ๋ช…์‹œ์ ์œผ๋กœ `Query`๋ฅผ ์ถ”๊ฐ€ํ•  ํ•„์š” ์—†์ด ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: ```Python -q: Optional[str] = None +q: Union[str, None] = None ``` +๋˜๋Š” Python 3.10 ์ด์ƒ์—์„œ๋Š”: + +```Python +q: str | None = None +``` + +์˜ˆ๋ฅผ ๋“ค์–ด: + +{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} + + /// info | ์ •๋ณด -`Body` ๋˜ํ•œ `Query`, `Path` ๊ทธ๋ฆฌ๊ณ  ์ดํ›„์— ๋ณผ ๋‹ค๋ฅธ ๊ฒƒ๋“ค์ฒ˜๋Ÿผ ๋™์ผํ•œ ์ถ”๊ฐ€ ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +`Body` ๋˜ํ•œ `Query`, `Path` ๊ทธ๋ฆฌ๊ณ  ์ดํ›„์— ๋ณผ ๋‹ค๋ฅธ ๊ฒƒ๋“ค๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋™์ผํ•œ ์ถ”๊ฐ€ ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ชจ๋‘ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. /// -## ๋‹จ์ผ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฝ์ž…ํ•˜๊ธฐ +## ๋‹จ์ผ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฝ์ž…ํ•˜๊ธฐ { #embed-a-single-body-parameter } -Pydantic ๋ชจ๋ธ `Item`์˜ `item`์„ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์˜ค์ง ํ•œ๊ฐœ๋งŒ ๊ฐ–๊ณ ์žˆ๋‹ค๊ณ  ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. +Pydantic ๋ชจ๋ธ `Item`์—์„œ ๊ฐ€์ ธ์˜จ ๋‹จ์ผ `item` ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜๋งŒ ์žˆ๋‹ค๊ณ  ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. -๊ธฐ๋ณธ์ ์œผ๋กœ **FastAPI**๋Š” ์ง์ ‘ ๋ณธ๋ฌธ์œผ๋กœ ์˜ˆ์ธกํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๊ธฐ๋ณธ์ ์œผ๋กœ **FastAPI**๋Š” ๊ทธ ๋ณธ๋ฌธ์„ ์ง์ ‘ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ, ๋งŒ์•ฝ ๋ชจ๋ธ ๋‚ด์šฉ์— `item `ํ‚ค๋ฅผ ๊ฐ€์ง„ JSON์œผ๋กœ ์˜ˆ์ธกํ•˜๊ธธ ์›ํ•œ๋‹ค๋ฉด, ์ถ”๊ฐ€์ ์ธ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•œ ๊ฒƒ์ฒ˜๋Ÿผ `Body`์˜ ํŠน๋ณ„ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜์ธ `embed`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: - -{* ../../docs_src/body_multiple_params/tutorial005.py hl[17] *} - -์•„๋ž˜ ์ฒ˜๋Ÿผ: +ํ•˜์ง€๋งŒ ์ถ”๊ฐ€ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ๋•Œ์ฒ˜๋Ÿผ, `item` ํ‚ค๋ฅผ ๊ฐ€์ง€๊ณ  ๊ทธ ์•ˆ์— ๋ชจ๋ธ ๋‚ด์šฉ์ด ๋“ค์–ด ์žˆ๋Š” JSON์„ ์˜ˆ์ƒํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด, `Body`์˜ ํŠน๋ณ„ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜ `embed`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```Python -item: Item = Body(..., embed=True) +item: Item = Body(embed=True) ``` -์ด ๊ฒฝ์šฐ์— **FastAPI**๋Š” ๋ณธ๋ฌธ์„ ์•„๋ž˜ ๋Œ€์‹ ์—: +๋‹ค์Œ๊ณผ ๊ฐ™์ด์š”: + +{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *} + + +์ด ๊ฒฝ์šฐ **FastAPI**๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ณธ๋ฌธ์„ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค: ```JSON hl_lines="2" -{ - "name": "Foo", - "description": "The pretender", - "price": 42.0, - "tax": 3.2 -} -``` - -์•„๋ž˜ ์ฒ˜๋Ÿผ ์˜ˆ์ธกํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค: - -```JSON { "item": { "name": "Foo", @@ -158,12 +153,23 @@ item: Item = Body(..., embed=True) } ``` -## ์ •๋ฆฌ +๋‹ค์Œ ๋Œ€์‹ ์—: -์š”์ฒญ์ด ๋‹จ ํ•œ๊ฐœ์˜ ๋ณธ๋ฌธ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋”๋ผ๋„, *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๋กœ ๋‹ค์ค‘ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` -ํ•˜์ง€๋งŒ, **FastAPI**๋Š” ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , ํ•จ์ˆ˜์— ์˜ฌ๋ฐ”๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, *๊ฒฝ๋กœ ์ž‘๋™*์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ์Šคํ‚ค๋งˆ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ๋ฌธ์„œํ™” ํ•ฉ๋‹ˆ๋‹ค. +## ์ •๋ฆฌ { #recap } -๋˜ํ•œ, ๋‹จ์ผ ๊ฐ’์„ ๋ณธ๋ฌธ์˜ ์ผ๋ถ€๋กœ ๋ฐ›๋„๋ก ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์š”์ฒญ์€ ๋ณธ๋ฌธ์„ ํ•˜๋‚˜๋งŒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์— ๋‹ค์ค‘ ๋ณธ๋ฌธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  **FastAPI**๋Š” ๋‹จ ํ•œ๊ฐœ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์„ ์–ธ ๋˜๋”๋ผ๋„, ๋ณธ๋ฌธ ๋‚ด์˜ ํ‚ค๋กœ ์‚ฝ์ž… ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ **FastAPI**๋Š” ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , ํ•จ์ˆ˜์— ์˜ฌ๋ฐ”๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ ์˜ฌ๋ฐ”๋ฅธ ์Šคํ‚ค๋งˆ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ๋ฌธ์„œํ™”ํ•ฉ๋‹ˆ๋‹ค. + +๋˜ํ•œ ๋‹จ์ผ ๊ฐ’์„ ๋ณธ๋ฌธ์˜ ์ผ๋ถ€๋กœ ๋ฐ›๋„๋ก ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋‹จ ํ•˜๋‚˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋งŒ ์„ ์–ธ๋˜์–ด ์žˆ๋”๋ผ๋„, **FastAPI**์— ๋ณธ๋ฌธ์„ ํ‚ค ์•ˆ์— ์‚ฝ์ž…ํ•˜๋„๋ก ์ง€์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/body-nested-models.md b/docs/ko/docs/tutorial/body-nested-models.md index ebd7b3ba6a..4a8c1afc13 100644 --- a/docs/ko/docs/tutorial/body-nested-models.md +++ b/docs/ko/docs/tutorial/body-nested-models.md @@ -1,35 +1,26 @@ -# ๋ณธ๋ฌธ - ์ค‘์ฒฉ ๋ชจ๋ธ +# ๋ณธ๋ฌธ - ์ค‘์ฒฉ ๋ชจ๋ธ { #body-nested-models } -**FastAPI**๋ฅผ ์ด์šฉํ•˜๋ฉด (Pydantic ๋•๋ถ„์—) ๋‹จ๋…์œผ๋กœ ๊นŠ์ด ์ค‘์ฒฉ๋œ ๋ชจ๋ธ์„ ์ •์˜, ๊ฒ€์ฆ, ๋ฌธ์„œํ™”ํ•˜๋ฉฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ๋ฆฌ์ŠคํŠธ ํ•„๋“œ +**FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด (Pydantic ๋•๋ถ„์—) ์ž„์˜๋กœ ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ ๋ชจ๋ธ์„ ์ •์˜, ๊ฒ€์ฆ, ๋ฌธ์„œํ™”ํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ๋ฆฌ์ŠคํŠธ ํ•„๋“œ { #list-fields } ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์„œ๋ธŒํƒ€์ž…์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํŒŒ์ด์ฌ `list`๋Š”: -{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *} +{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *} -์ด๋Š” `tags`๋ฅผ ํ•ญ๋ชฉ ๋ฆฌ์ŠคํŠธ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๊ฐ ํ•ญ๋ชฉ์˜ ํƒ€์ž…์„ ์„ ์–ธํ•˜์ง€ ์•Š๋”๋ผ๋„์š”. +์ด๋Š” `tags`๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋งŒ๋“ค์ง€๋งŒ, ๋ฆฌ์ŠคํŠธ ์š”์†Œ์˜ ํƒ€์ž…์„ ์„ ์–ธํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. -## ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋Š” ๋ฆฌ์ŠคํŠธ ํ•„๋“œ +## ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋Š” ๋ฆฌ์ŠคํŠธ ํ•„๋“œ { #list-fields-with-type-parameter } -ํ•˜์ง€๋งŒ ํŒŒ์ด์ฌ์€ ๋‚ด๋ถ€์˜ ํƒ€์ž…์ด๋‚˜ "ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜"๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋Š” ํŠน์ • ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค: +ํ•˜์ง€๋งŒ ํŒŒ์ด์ฌ์—๋Š” ๋‚ด๋ถ€ ํƒ€์ž…, ์ฆ‰ "ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜"๋ฅผ ์‚ฌ์šฉํ•ด ๋ฆฌ์ŠคํŠธ๋ฅผ ์„ ์–ธํ•˜๋Š” ํŠน์ •ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค: -### typing์˜ `List` ์ž„ํฌํŠธ +### ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `list` ์„ ์–ธ { #declare-a-list-with-a-type-parameter } -๋จผ์ €, ํŒŒ์ด์ฌ ํ‘œ์ค€ `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] ``` ์ด ๋ชจ๋“  ๊ฒƒ์€ ํƒ€์ž… ์„ ์–ธ์„ ์œ„ํ•œ ํ‘œ์ค€ ํŒŒ์ด์ฌ ๋ฌธ๋ฒ•์ž…๋‹ˆ๋‹ค. @@ -38,45 +29,45 @@ my_list: List[str] ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์˜ˆ์ œ์—์„œ `tags`๋ฅผ ๊ตฌ์ฒด์ ์œผ๋กœ "๋ฌธ์ž์—ด์˜ ๋ฆฌ์ŠคํŠธ"๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *} +{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} -## ์ง‘ํ•ฉ ํƒ€์ž… +## ์ง‘ํ•ฉ ํƒ€์ž… { #set-types } -๊ทธ๋Ÿฐ๋ฐ ์ƒ๊ฐํ•ด๋ณด๋‹ˆ ํƒœ๊ทธ๋Š” ๋ฐ˜๋ณต๋˜๋ฉด ์•ˆ ๋˜๊ณ , ๊ณ ์œ ํ•œ(Unique) ๋ฌธ์ž์—ด์ด์–ด์•ผ ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ๋ฐ ์ƒ๊ฐํ•ด๋ณด๋‹ˆ ํƒœ๊ทธ๋Š” ๋ฐ˜๋ณต๋˜๋ฉด ์•ˆ ๋˜๊ณ , ์•„๋งˆ ๊ณ ์œ ํ•œ ๋ฌธ์ž์—ด์ด์–ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ํŒŒ์ด์ฌ์€ ์ง‘ํ•ฉ์„ ์œ„ํ•œ ํŠน๋ณ„ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž… `set`์ด ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ํŒŒ์ด์ฌ์—๋Š” ๊ณ ์œ ํ•œ ํ•ญ๋ชฉ๋“ค์˜ ์ง‘ํ•ฉ์„ ์œ„ํ•œ ํŠน๋ณ„ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž… `set`์ด ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ ‡๋‹ค๋ฉด `Set`์„ ์ž„ํฌํŠธ ํ•˜๊ณ  `tags`๋ฅผ `str`์˜ `set`์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ทธ๋ ‡๋‹ค๋ฉด `tags`๋ฅผ ๋ฌธ์ž์—ด์˜ ์ง‘ํ•ฉ์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *} +{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *} -๋•๋ถ„์— ์ค‘๋ณต ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ์š”์ฒญ์„ ์ˆ˜์‹ ํ•˜๋”๋ผ๋„ ๊ณ ์œ ํ•œ ํ•ญ๋ชฉ๋“ค์˜ ์ง‘ํ•ฉ์œผ๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ค‘๋ณต ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ์š”์ฒญ์„ ๋ฐ›๋”๋ผ๋„ ๊ณ ์œ ํ•œ ํ•ญ๋ชฉ๋“ค์˜ ์ง‘ํ•ฉ์œผ๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ถœ๋ ฅ ํ•  ๋•Œ๋งˆ๋‹ค ์†Œ์Šค์— ์ค‘๋ณต์ด ์žˆ๋”๋ผ๋„ ๊ณ ์œ ํ•œ ํ•ญ๋ชฉ๋“ค์˜ ์ง‘ํ•ฉ์œผ๋กœ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ถœ๋ ฅํ•  ๋•Œ๋งˆ๋‹ค, ์†Œ์Šค์— ์ค‘๋ณต์ด ์žˆ๋”๋ผ๋„ ๊ณ ์œ ํ•œ ํ•ญ๋ชฉ๋“ค์˜ ์ง‘ํ•ฉ์œผ๋กœ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ทธ์— ๋”ฐ๋ผ ์ฃผ์„์ด ์ƒ๊ธฐ๊ณ  ๋ฌธ์„œํ™”๋ฉ๋‹ˆ๋‹ค. -## ์ค‘์ฒฉ ๋ชจ๋ธ +## ์ค‘์ฒฉ ๋ชจ๋ธ { #nested-models } Pydantic ๋ชจ๋ธ์˜ ๊ฐ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋Š” ํƒ€์ž…์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฐ๋ฐ ํ•ด๋‹น ํƒ€์ž… ์ž์ฒด๋กœ ๋˜๋‹ค๋ฅธ Pydantic ๋ชจ๋ธ์˜ ํƒ€์ž…์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ๋ฐ ๊ทธ ํƒ€์ž… ์ž์ฒด๊ฐ€ ๋˜ ๋‹ค๋ฅธ Pydantic ๋ชจ๋ธ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋ฏ€๋กœ ํŠน์ •ํ•œ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์˜ ์ด๋ฆ„, ํƒ€์ž…, ๊ฒ€์ฆ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ JSON "๊ฐ์ฒด"๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ ํŠน์ •ํ•œ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ ์ด๋ฆ„, ํƒ€์ž…, ๊ฒ€์ฆ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ JSON "๊ฐ์ฒด"๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋ชจ๋“  ๊ฒƒ์ด ๋‹จ๋…์œผ๋กœ ์ค‘์ฒฉ๋ฉ๋‹ˆ๋‹ค. +๋ชจ๋“  ๊ฒƒ์ด ์ž„์˜์˜ ๊นŠ์ด๋กœ ์ค‘์ฒฉ๋ฉ๋‹ˆ๋‹ค. -### ์„œ๋ธŒ๋ชจ๋ธ ์ •์˜ +### ์„œ๋ธŒ๋ชจ๋ธ ์ •์˜ { #define-a-submodel } -์˜ˆ๋ฅผ ๋“ค์–ด, `Image` ๋ชจ๋ธ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์˜ˆ๋ฅผ ๋“ค์–ด, `Image` ๋ชจ๋ธ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/body_nested_models/tutorial004.py hl[9:11] *} +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} -### ์„œ๋ธŒ๋ชจ๋“ˆ์„ ํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉ +### ์„œ๋ธŒ๋ชจ๋ธ์„ ํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉ { #use-the-submodel-as-a-type } -๊ทธ๋ฆฌ๊ณ  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์˜ ํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์˜ ํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *} +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *} ์ด๋Š” **FastAPI**๊ฐ€ ๋‹ค์Œ๊ณผ ์œ ์‚ฌํ•œ ๋ณธ๋ฌธ์„ ๊ธฐ๋Œ€ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค: @@ -94,32 +85,32 @@ Pydantic ๋ชจ๋ธ์˜ ๊ฐ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋Š” ํƒ€์ž…์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. } ``` -๋‹ค์‹œ ํ•œ๋ฒˆ, **FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ์„ ์–ธ์„ ํ•จ์œผ๋กœ์จ ์–ป๋Š” ๊ฒƒ์€: +๋‹ค์‹œ ํ•œ๋ฒˆ, **FastAPI**๋กœ ๊ทธ ์„ ์–ธ๋งŒ ํ•ด๋„ ์–ป๋Š” ๊ฒƒ์€: * ์ค‘์ฒฉ ๋ชจ๋ธ๋„ ํŽธ์ง‘๊ธฐ ์ง€์›(์ž๋™์™„์„ฑ ๋“ฑ) * ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ * ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ * ์ž๋™ ๋ฌธ์„œํ™” -## ํŠน๋ณ„ํ•œ ํƒ€์ž…๊ณผ ๊ฒ€์ฆ +## ํŠน๋ณ„ํ•œ ํƒ€์ž…๊ณผ ๊ฒ€์ฆ { #special-types-and-validation } -`str`, `int`, `float` ๋“ฑ๊ณผ ๊ฐ™์€ ๋‹จ์ผ ํƒ€์ž…๊ณผ๋Š” ๋ณ„๊ฐœ๋กœ, `str`์„ ์ƒ์†ํ•˜๋Š” ๋” ๋ณต์žกํ•œ ๋‹จ์ผ ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`str`, `int`, `float` ๋“ฑ๊ณผ ๊ฐ™์€ ์ผ๋ฐ˜์ ์ธ ๋‹จ์ผ ํƒ€์ž…๊ณผ๋Š” ๋ณ„๊ฐœ๋กœ, `str`์„ ์ƒ์†ํ•˜๋Š” ๋” ๋ณต์žกํ•œ ๋‹จ์ผ ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋ชจ๋“  ์˜ต์…˜์„ ๋ณด๋ ค๋ฉด, <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">Pydantic's exotic types</a> ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. ๋‹ค์Œ ์žฅ์—์„œ ๋ช‡๊ฐ€์ง€ ์˜ˆ์ œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์˜ต์…˜์„ ๋ณด๋ ค๋ฉด <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">Pydantic์˜ Type Overview</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. ๋‹ค์Œ ์žฅ์—์„œ ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์ œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด `Image` ๋ชจ๋ธ ์•ˆ์— `url` ํ•„๋“œ๋ฅผ `str` ๋Œ€์‹  Pydantic์˜ `HttpUrl`๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์˜ˆ๋ฅผ ๋“ค์–ด `Image` ๋ชจ๋ธ์—๋Š” `url` ํ•„๋“œ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ, ์ด๋ฅผ `str` ๋Œ€์‹  Pydantic์˜ `HttpUrl` ์ธ์Šคํ„ด์Šค๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/body_nested_models/tutorial005.py hl[4,10] *} +{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *} -์ด ๋ฌธ์ž์—ด์ด ์œ ํšจํ•œ URL์ธ์ง€ ๊ฒ€์‚ฌํ•˜๊ณ  JSON ์Šคํ‚ค๋งˆ/OpenAPI๋กœ ๋ฌธ์„œํ™” ๋ฉ๋‹ˆ๋‹ค. +์ด ๋ฌธ์ž์—ด์€ ์œ ํšจํ•œ URL์ธ์ง€ ๊ฒ€์‚ฌ๋˜๋ฉฐ, JSON Schema / OpenAPI์—๋„ ๊ทธ์— ๋งž๊ฒŒ ๋ฌธ์„œํ™”๋ฉ๋‹ˆ๋‹ค. -## ์„œ๋ธŒ๋ชจ๋ธ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ–๋Š” ์–ดํŠธ๋ฆฌ๋ทฐํŠธ +## ์„œ๋ธŒ๋ชจ๋ธ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ–๋Š” ์–ดํŠธ๋ฆฌ๋ทฐํŠธ { #attributes-with-lists-of-submodels } `list`, `set` ๋“ฑ์˜ ์„œ๋ธŒํƒ€์ž…์œผ๋กœ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/body_nested_models/tutorial006.py hl[20] *} +{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *} -์•„๋ž˜์™€ ๊ฐ™์€ JSON ๋ณธ๋ฌธ์œผ๋กœ ์˜ˆ์ƒ(๋ณ€ํ™˜, ๊ฒ€์ฆ, ๋ฌธ์„œํ™” ๋“ฑ์„)ํ•ฉ๋‹ˆ๋‹ค: +์•„๋ž˜์™€ ๊ฐ™์€ JSON ๋ณธ๋ฌธ์„ ์˜ˆ์ƒ(๋ณ€ํ™˜, ๊ฒ€์ฆ, ๋ฌธ์„œํ™” ๋“ฑ)ํ•ฉ๋‹ˆ๋‹ค: ```JSON hl_lines="11" { @@ -147,84 +138,84 @@ Pydantic ๋ชจ๋ธ์˜ ๊ฐ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋Š” ํƒ€์ž…์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. /// info | ์ •๋ณด -`images` ํ‚ค๊ฐ€ ์–ด๋–ป๊ฒŒ ์ด๋ฏธ์ง€ ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ–๋Š”์ง€ ์ฃผ๋ชฉํ•˜์„ธ์š”. +`images` ํ‚ค๊ฐ€ ์ด์ œ ์ด๋ฏธ์ง€ ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ–๋Š”์ง€ ์ฃผ๋ชฉํ•˜์„ธ์š”. /// -## ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ ๋ชจ๋ธ +## ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ ๋ชจ๋ธ { #deeply-nested-models } -๋‹จ๋…์œผ๋กœ ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ ๋ชจ๋ธ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์ž„์˜๋กœ ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ ๋ชจ๋ธ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/body_nested_models/tutorial007.py hl[9,14,20,23,27] *} +{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *} /// info | ์ •๋ณด -`Offer`๊ฐ€ ์„ ํƒ์‚ฌํ•ญ `Image` ๋ฆฌ์ŠคํŠธ๋ฅผ ์ฐจ๋ก€๋กœ ๊ฐ–๋Š” `Item` ๋ฆฌ์ŠคํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ์ฃผ๋ชฉํ•˜์„ธ์š” +`Offer`๊ฐ€ `Item`์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ง€๊ณ , ๊ทธ `Item`์ด ๋‹ค์‹œ ์„ ํƒ ์‚ฌํ•ญ์ธ `Image` ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ–๋Š”์ง€ ์ฃผ๋ชฉํ•˜์„ธ์š” /// -## ์ˆœ์ˆ˜ ๋ฆฌ์ŠคํŠธ์˜ ๋ณธ๋ฌธ +## ์ˆœ์ˆ˜ ๋ฆฌ์ŠคํŠธ์˜ ๋ณธ๋ฌธ { #bodies-of-pure-lists } -์˜ˆ์ƒ๋˜๋Š” JSON ๋ณธ๋ฌธ์˜ ์ตœ์ƒ์œ„ ๊ฐ’์ด JSON `array`(ํŒŒ์ด์ฌ `list`)๋ฉด, Pydantic ๋ชจ๋ธ์—์„œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ ํƒ€์ž…์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์˜ˆ์ƒ๋˜๋Š” JSON ๋ณธ๋ฌธ์˜ ์ตœ์ƒ์œ„ ๊ฐ’์ด JSON `array`(ํŒŒ์ด์ฌ `list`)๋ผ๋ฉด, Pydantic ๋ชจ๋ธ์—์„œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ ํƒ€์ž…์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```Python -images: List[Image] +images: list[Image] ``` ์ด๋ฅผ ์•„๋ž˜์ฒ˜๋Ÿผ: -{* ../../docs_src/body_nested_models/tutorial008.py hl[15] *} +{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} -## ์–ด๋””์„œ๋‚˜ ํŽธ์ง‘๊ธฐ ์ง€์› +## ์–ด๋””์„œ๋‚˜ ํŽธ์ง‘๊ธฐ ์ง€์› { #editor-support-everywhere } -๊ทธ๋ฆฌ๊ณ  ์–ด๋””์„œ๋‚˜ ํŽธ์ง‘๊ธฐ ์ง€์›์„ ๋ฐ›์„์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์–ด๋””์„œ๋‚˜ ํŽธ์ง‘๊ธฐ ์ง€์›์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ์ŠคํŠธ ๋‚ด๋ถ€ ํ•ญ๋ชฉ์˜ ๊ฒฝ์šฐ์—๋„: <img src="/img/tutorial/body-nested-models/image01.png"> -Pydantic ๋ชจ๋ธ ๋Œ€์‹ ์— `dict`๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—…ํ•  ๊ฒฝ์šฐ, ์ด๋Ÿฌํ•œ ํŽธ์ง‘๊ธฐ ์ง€์›์„ ๋ฐ›์„์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. +Pydantic ๋ชจ๋ธ ๋Œ€์‹  `dict`๋กœ ์ง์ ‘ ์ž‘์—…ํ•œ๋‹ค๋ฉด ์ด๋Ÿฐ ์ข…๋ฅ˜์˜ ํŽธ์ง‘๊ธฐ ์ง€์›์„ ๋ฐ›์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์ˆ˜์‹ ํ•œ ๋”•์…”๋„ˆ๋ฆฌ๊ฐ€ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜๋˜๊ณ  ์ถœ๋ ฅ๋„ ์ž๋™์œผ๋กœ JSON์œผ๋กœ ๋ณ€ํ™˜๋˜๋ฏ€๋กœ ๊ฑฑ์ •ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๊ทธ ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ๋„ ๊ฑฑ์ •ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋“ค์–ด์˜ค๋Š” dict๋Š” ์ž๋™์œผ๋กœ ๋ณ€ํ™˜๋˜๊ณ , ์ถœ๋ ฅ๋„ ์ž๋™์œผ๋กœ JSON์œผ๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค. -## ๋‹จ๋… `dict`์˜ ๋ณธ๋ฌธ +## ์ž„์˜์˜ `dict` ๋ณธ๋ฌธ { #bodies-of-arbitrary-dicts } -์ผ๋ถ€ ํƒ€์ž…์˜ ํ‚ค์™€ ๋‹ค๋ฅธ ํƒ€์ž…์˜ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ `dict`๋กœ ๋ณธ๋ฌธ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ ํ‚ค๋Š” ์–ด๋–ค ํƒ€์ž…์ด๊ณ  ๊ฐ’์€ ๋‹ค๋ฅธ ํƒ€์ž…์ธ `dict`๋กœ ๋ณธ๋ฌธ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -(Pydantic์„ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ์ฒ˜๋Ÿผ) ์œ ํšจํ•œ ํ•„๋“œ/์–ดํŠธ๋ฆฌ๋ทฐํŠธ ์ด๋ฆ„์ด ๋ฌด์—‡์ธ์ง€ ์•Œ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด (Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์ฒ˜๋Ÿผ) ์œ ํšจํ•œ ํ•„๋“œ/์–ดํŠธ๋ฆฌ๋ทฐํŠธ ์ด๋ฆ„์ด ๋ฌด์—‡์ธ์ง€ ๋ฏธ๋ฆฌ ์•Œ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. -์•„์ง ๋ชจ๋ฅด๋Š” ํ‚ค๋ฅผ ๋ฐ›์œผ๋ ค๋Š” ๊ฒฝ์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. +์•„์ง ๋ชจ๋ฅด๋Š” ํ‚ค๋ฅผ ๋ฐ›์œผ๋ ค๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. --- -๋‹ค๋ฅธ ์œ ์šฉํ•œ ๊ฒฝ์šฐ๋Š” ๋‹ค๋ฅธ ํƒ€์ž…์˜ ํ‚ค๋ฅผ ๊ฐ€์งˆ ๋•Œ์ž…๋‹ˆ๋‹ค. ์˜ˆ. `int`. +๋˜ ๋‹ค๋ฅธ ์œ ์šฉํ•œ ๊ฒฝ์šฐ๋Š” ๋‹ค๋ฅธ ํƒ€์ž…(์˜ˆ: `int`)์˜ ํ‚ค๋ฅผ ๊ฐ–๊ณ  ์‹ถ์„ ๋•Œ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๊ทธ ๊ฒฝ์šฐ๋ฅผ ๋ณผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ, `float` ๊ฐ’์„ ๊ฐ€์ง„ `int` ํ‚ค๊ฐ€ ์žˆ๋Š” ๋ชจ๋“  `dict`๋ฅผ ๋ฐ›์•„๋“ค์ž…๋‹ˆ๋‹ค: +์ด ๊ฒฝ์šฐ, `int` ํ‚ค์™€ `float` ๊ฐ’์„ ๊ฐ€์ง„ ํ•œ ์–ด๋–ค `dict`๋“  ๋ฐ›์•„๋“ค์ž…๋‹ˆ๋‹ค: -{* ../../docs_src/body_nested_models/tutorial009.py hl[15] *} +{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} /// tip | ํŒ -JSON์€ ์˜ค์ง `str`ํ˜• ํ‚ค๋งŒ ์ง€์›ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์—ผ๋‘์— ๋‘์„ธ์š”. +JSON์€ ํ‚ค๋กœ `str`๋งŒ ์ง€์›ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์—ผ๋‘์— ๋‘์„ธ์š”. -ํ•˜์ง€๋งŒ Pydantic์€ ์ž๋™ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ Pydantic์€ ์ž๋™ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. -์ฆ‰, API ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฌธ์ž์—ด์„ ํ‚ค๋กœ ๋ณด๋‚ด๋”๋ผ๋„ ํ•ด๋‹น ๋ฌธ์ž์—ด์ด ์ˆœ์ˆ˜ํ•œ ์ •์ˆ˜๋ฅผ ํฌํ•จํ•˜๋Š”ํ•œ Pydantic์€ ์ด๋ฅผ ๋ณ€ํ™˜ํ•˜๊ณ  ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. +์ฆ‰, API ํด๋ผ์ด์–ธํŠธ๋Š” ํ‚ค๋กœ ๋ฌธ์ž์—ด๋งŒ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋”๋ผ๋„, ํ•ด๋‹น ๋ฌธ์ž์—ด์ด ์ˆœ์ˆ˜ํ•œ ์ •์ˆ˜๋ฅผ ํฌํ•จํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด Pydantic์ด ์ด๋ฅผ ๋ณ€ํ™˜ํ•˜๊ณ  ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋ฏ€๋กœ `weights`๋กœ ๋ฐ›์€ `dict`๋Š” ์‹ค์ œ๋กœ `int` ํ‚ค์™€ `float` ๊ฐ’์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  `weights`๋กœ ๋ฐ›๋Š” `dict`๋Š” ์‹ค์ œ๋กœ `int` ํ‚ค์™€ `float` ๊ฐ’์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. /// -## ์š”์•ฝ +## ์š”์•ฝ { #recap } -**FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Pydantic ๋ชจ๋ธ์ด ์ œ๊ณตํ•˜๋Š” ์ตœ๋Œ€ ์œ ์—ฐ์„ฑ์„ ํ™•๋ณดํ•˜๋ฉด์„œ ์ฝ”๋“œ๋ฅผ ๊ฐ„๋‹จํ•˜๊ณ  ์งง๊ฒŒ, ๊ทธ๋ฆฌ๊ณ  ์šฐ์•„ํ•˜๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Pydantic ๋ชจ๋ธ์ด ์ œ๊ณตํ•˜๋Š” ์ตœ๋Œ€ ์œ ์—ฐ์„ฑ์„ ํ™•๋ณดํ•˜๋ฉด์„œ ์ฝ”๋“œ๋ฅผ ๊ฐ„๋‹จํ•˜๊ณ  ์งง๊ณ  ์šฐ์•„ํ•˜๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋ฌผ๋ก  ์•„๋ž˜์˜ ์ด์ ๋„ ์žˆ์Šต๋‹ˆ๋‹ค: +ํ•˜์ง€๋งŒ ์•„๋ž˜์˜ ๋ชจ๋“  ์ด์ ๋„ ์žˆ์Šต๋‹ˆ๋‹ค: -* ํŽธ์ง‘๊ธฐ ์ง€์› (์ž๋™์™„์„ฑ์ด ์–ด๋””์„œ๋‚˜!) -* ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ (์ผ๋ช… ํŒŒ์‹ฑ/์ง๋ ฌํ™”) +* ํŽธ์ง‘๊ธฐ ์ง€์›(์–ด๋””์„œ๋‚˜ ์ž๋™์™„์„ฑ!) +* ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜(์ผ๋ช… ํŒŒ์‹ฑ/์ง๋ ฌํ™”) * ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ * ์Šคํ‚ค๋งˆ ๋ฌธ์„œํ™” * ์ž๋™ ๋ฌธ์„œ diff --git a/docs/ko/docs/tutorial/body.md b/docs/ko/docs/tutorial/body.md index b3914fa4b4..1e66c60c22 100644 --- a/docs/ko/docs/tutorial/body.md +++ b/docs/ko/docs/tutorial/body.md @@ -1,10 +1,10 @@ -# ์š”์ฒญ ๋ณธ๋ฌธ +# ์š”์ฒญ ๋ณธ๋ฌธ { #request-body } ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €๋ผ๊ณ  ํ•ด๋ด…์‹œ๋‹ค)๋กœ๋ถ€ํ„ฐ ์—ฌ๋Ÿฌ๋ถ„์˜ API๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์•ผ ํ•  ๋•Œ, **์š”์ฒญ ๋ณธ๋ฌธ**์œผ๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค. **์š”์ฒญ** ๋ณธ๋ฌธ์€ ํด๋ผ์ด์–ธํŠธ์—์„œ API๋กœ ๋ณด๋‚ด์ง€๋Š” ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. **์‘๋‹ต** ๋ณธ๋ฌธ์€ API๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ๋ถ„์˜ API๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ **์‘๋‹ต** ๋ณธ๋ฌธ์„ ๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ๋Š” **์š”์ฒญ** ๋ณธ๋ฌธ์„ ๋งค ๋ฒˆ ๋ณด๋‚ผ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ๋ถ„์˜ API๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ **์‘๋‹ต** ๋ณธ๋ฌธ์„ ๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ๋Š” ํ•ญ์ƒ **์š”์ฒญ ๋ณธ๋ฌธ**์„ ๋ณด๋‚ผ ํ•„์š”๋Š” ์—†๊ณ , ๋•Œ๋กœ๋Š” (์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ํ•จ๊ป˜) ์–ด๋–ค ๊ฒฝ๋กœ๋งŒ ์š”์ฒญํ•˜๊ณ  ๋ณธ๋ฌธ์€ ๋ณด๋‚ด์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. **์š”์ฒญ** ๋ณธ๋ฌธ์„ ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ชจ๋“  ๊ฐ•๋ ฅํ•จ๊ณผ ์ด์ ์„ ๊ฐ–์ถ˜ <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. @@ -18,13 +18,13 @@ /// -## Pydantic์˜ `BaseModel` ์ž„ํฌํŠธ +## Pydantic์˜ `BaseModel` ์ž„ํฌํŠธ { #import-pydantics-basemodel } ๋จผ์ € `pydantic`์—์„œ `BaseModel`๋ฅผ ์ž„ํฌํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: {* ../../docs_src/body/tutorial001_py310.py hl[2] *} -## ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ๋งŒ๋“ค๊ธฐ +## ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ๋งŒ๋“ค๊ธฐ { #create-your-data-model } `BaseModel`๋ฅผ ์ƒ์†๋ฐ›์€ ํด๋ž˜์Šค๋กœ ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. @@ -32,6 +32,7 @@ {* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} + ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ๋•Œ์™€ ๊ฐ™์ด, ๋ชจ๋ธ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๊ฐ€ ๊ธฐ๋ณธ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด๋„ ์ด๋Š” ํ•„์ˆ˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ๊ทธ์™ธ์—๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. ๊ทธ์ € `None`์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ ํƒ์ ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด, ์œ„์˜ ์ด ๋ชจ๋ธ์€ JSON "`object`" (ํ˜น์€ ํŒŒ์ด์ฌ `dict`)์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: @@ -39,7 +40,7 @@ ```JSON { "name": "Foo", - "description": "์„ ํƒ์ ์ธ ์„ค๋ช…๋ž€", + "description": "An optional description", "price": 45.2, "tax": 3.5 } @@ -54,15 +55,15 @@ } ``` -## ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ์„œ ์„ ์–ธํ•˜๊ธฐ +## ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ์„œ ์„ ์–ธํ•˜๊ธฐ { #declare-it-as-a-parameter } -์—ฌ๋Ÿฌ๋ถ„์˜ *๊ฒฝ๋กœ ์ž‘๋™*์— ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด, ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ทธ๋ฆฌ๊ณ  ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ ์„ ์–ธํ–ˆ๋˜ ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์„ ์–ธํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ๋ถ„์˜ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด, ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ทธ๋ฆฌ๊ณ  ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ ์„ ์–ธํ–ˆ๋˜ ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์„ ์–ธํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. {* ../../docs_src/body/tutorial001_py310.py hl[16] *} ...๊ทธ๋ฆฌ๊ณ  ๋งŒ๋“ค์–ด๋‚ธ ๋ชจ๋ธ์ธ `Item`์œผ๋กœ ํƒ€์ž…์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. -## ๊ฒฐ๊ณผ +## ๊ฒฐ๊ณผ { #results } ์œ„์—์„œ์˜ ๋‹จ์ˆœํ•œ ํŒŒ์ด์ฌ ํƒ€์ž… ์„ ์–ธ์œผ๋กœ, **FastAPI**๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค: @@ -72,20 +73,20 @@ * ๋งŒ์•ฝ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด, ์ •ํ™•ํžˆ ์–ด๋–ค ๊ฒƒ์ด ๊ทธ๋ฆฌ๊ณ  ์–ด๋””์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž˜ ๋ชป ๋˜์—ˆ๋Š”์ง€ ์ง€์‹œํ•˜๋Š” ์นœ์ ˆํ•˜๊ณ  ๋ช…๋ฃŒํ•œ ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. * ๋งค๊ฐœ๋ณ€์ˆ˜ `item`์— ํฌํ•จ๋œ ์ˆ˜์‹  ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. * ํ•จ์ˆ˜ ๋‚ด์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `Item` ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ชจ๋“  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์™€ ๊ทธ์— ๋Œ€ํ•œ ํƒ€์ž…์— ๋Œ€ํ•œ ํŽธ์ง‘๊ธฐ ์ง€์›(์™„์„ฑ ๋“ฑ)์„ ๋˜ํ•œ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ชจ๋ธ์„ ์œ„ํ•œ <a href="https://json-schema.org" class="external-link" target="_blank">JSON ์Šคํ‚ค๋งˆ</a> ์ •์˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ์— ์ ํ•ฉํ•˜๋‹ค๋ฉด ์—ฌ๋Ÿฌ๋ถ„์ด ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ๊ณณ ์–ด๋””์—์„œ๋‚˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* ์ด๋Ÿฌํ•œ ์Šคํ‚ค๋งˆ๋Š”, ์ƒ์„ฑ๋œ OpenAPI ์Šคํ‚ค๋งˆ ์ผ๋ถ€๊ฐ€ ๋  ๊ฒƒ์ด๋ฉฐ, ์ž๋™ ๋ฌธ์„œํ™” <abbr title="์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค">UI</abbr>์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. +* ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ชจ๋ธ์„ ์œ„ํ•œ <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> ์ •์˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ์— ์ ํ•ฉํ•˜๋‹ค๋ฉด ์—ฌ๋Ÿฌ๋ถ„์ด ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ๊ณณ ์–ด๋””์—์„œ๋‚˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ์ด๋Ÿฌํ•œ ์Šคํ‚ค๋งˆ๋Š”, ์ƒ์„ฑ๋œ OpenAPI ์Šคํ‚ค๋งˆ ์ผ๋ถ€๊ฐ€ ๋  ๊ฒƒ์ด๋ฉฐ, ์ž๋™ ๋ฌธ์„œํ™” <abbr title="User Interfaces โ€“ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค">UIs</abbr>์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -## ์ž๋™ ๋ฌธ์„œํ™” +## ์ž๋™ ๋ฌธ์„œํ™” { #automatic-docs } ๋ชจ๋ธ์˜ JSON ์Šคํ‚ค๋งˆ๋Š” ์ƒ์„ฑ๋œ OpenAPI ์Šคํ‚ค๋งˆ์— ํฌํ•จ๋˜๋ฉฐ ๋Œ€ํ™”ํ˜• API ๋ฌธ์„œ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค: <img src="/img/tutorial/body/image01.png"> -์ด๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฐ๊ฐ์˜ *๊ฒฝ๋กœ ์ž‘๋™*๋‚ด๋ถ€์˜ API ๋ฌธ์„œ์—๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค: +์ด๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฐ๊ฐ์˜ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ๋‚ด๋ถ€์˜ API ๋ฌธ์„œ์—๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค: <img src="/img/tutorial/body/image02.png"> -## ํŽธ์ง‘๊ธฐ ์ง€์› +## ํŽธ์ง‘๊ธฐ ์ง€์› { #editor-support } ํŽธ์ง‘๊ธฐ์—์„œ, ํ•จ์ˆ˜ ๋‚ด์—์„œ ํƒ€์ž… ํžŒํŠธ์™€ ์™„์„ฑ์„ ์–ด๋””์„œ๋‚˜ (๋งŒ์•ฝ Pydantic model ๋Œ€์‹ ์— `dict`์„ ๋ฐ›์„ ๊ฒฝ์šฐ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค) ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -97,13 +98,13 @@ ๋‹จ์ˆœํ•œ ์šฐ์—ฐ์ด ์•„๋‹™๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ ์ „์ฒด๊ฐ€ ์ด๋Ÿฌํ•œ ๋””์ž์ธ์„ ์ค‘์‹ฌ์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -๊ทธ ์–ด๋–ค ์‹คํ–‰ ์ „์—, ๋ชจ๋“  ํŽธ์ง‘๊ธฐ์—์„œ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„ ๋‹จ๊ณ„์—์„œ ํ˜น๋…ํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +๊ทธ ์–ด๋–ค ๊ตฌํ˜„ ์ „์—, ๋ชจ๋“  ํŽธ์ง‘๊ธฐ์—์„œ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„ ๋‹จ๊ณ„์—์„œ ํ˜น๋…ํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด Pydantic ์ž์ฒด์—์„œ ๋ช‡๋ช‡ ๋ณ€๊ฒฝ์ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ „ ์Šคํฌ๋ฆฐ์ƒท์€ <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>๋ฅผ ์ฐ์€ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ๋˜‘๊ฐ™์€ ํŽธ์ง‘๊ธฐ ์ง€์›์„ <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>์—์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฑฐ๋‚˜, ๋Œ€๋ถ€๋ถ„์˜ ๋‹ค๋ฅธ ํŽธ์ง‘๊ธฐ์—์„œ๋„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +ํ•˜์ง€๋งŒ ๋˜‘๊ฐ™์€ ํŽธ์ง‘๊ธฐ ์ง€์›์„ <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>์™€ ๋Œ€๋ถ€๋ถ„์˜ ๋‹ค๋ฅธ ํŒŒ์ด์ฌ ํŽธ์ง‘๊ธฐ์—์„œ๋„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <img src="/img/tutorial/body/image05.png"> @@ -113,21 +114,21 @@ ๋‹ค์Œ ์‚ฌํ•ญ์„ ํฌํ•จํ•ด Pydantic ๋ชจ๋ธ์— ๋Œ€ํ•œ ํŽธ์ง‘๊ธฐ ์ง€์›์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค: -* ์ž๋™ ์™„์„ฑ -* ํƒ€์ž… ํ™•์ธ -* ๋ฆฌํŒฉํ† ๋ง -* ๊ฒ€์ƒ‰ -* ์ ๊ฒ€ +* auto-completion +* type checks +* refactoring +* searching +* inspections /// -## ๋ชจ๋ธ ์‚ฌ์šฉํ•˜๊ธฐ +## ๋ชจ๋ธ ์‚ฌ์šฉํ•˜๊ธฐ { #use-the-model } ํ•จ์ˆ˜ ์•ˆ์—์„œ ๋ชจ๋ธ ๊ฐ์ฒด์˜ ๋ชจ๋“  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์— ์ง์ ‘ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/body/tutorial002_py310.py hl[19] *} +{* ../../docs_src/body/tutorial002_py310.py *} -## ์š”์ฒญ ๋ณธ๋ฌธ + ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ +## ์š”์ฒญ ๋ณธ๋ฌธ + ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ { #request-body-path-parameters } ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์š”์ฒญ ๋ณธ๋ฌธ์„ ๋™์‹œ์— ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -135,7 +136,8 @@ {* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} -## ์š”์ฒญ ๋ณธ๋ฌธ + ๊ฒฝ๋กœ + ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ + +## ์š”์ฒญ ๋ณธ๋ฌธ + ๊ฒฝ๋กœ + ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ { #request-body-path-query-parameters } **๋ณธ๋ฌธ**, **๊ฒฝ๋กœ** ๊ทธ๋ฆฌ๊ณ  **์ฟผ๋ฆฌ** ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชจ๋‘ ๋™์‹œ์— ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -153,10 +155,12 @@ FastAPI๋Š” `q`์˜ ๊ฐ’์ด ํ•„์š”์—†์Œ์„ ์•Œ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๊ฐ’์ด `= None`์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -`Union[str, None]`์— ์žˆ๋Š” `Union`์€ FastAPI์— ์˜ํ•ด ์‚ฌ์šฉ๋œ ๊ฒƒ์ด ์•„๋‹ˆ์ง€๋งŒ, ํŽธ์ง‘๊ธฐ๋กœ ํ•˜์—ฌ๊ธˆ ๋” ๋‚˜์€ ์ง€์›๊ณผ ์—๋Ÿฌ ํƒ์ง€๋ฅผ ์ง€์›ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +Python 3.10+์˜ `str | None` ๋˜๋Š” Python 3.9+์˜ `Union[str, None]`์— ์žˆ๋Š” `Union`์€ FastAPI๊ฐ€ `q` ๊ฐ’์ด ํ•„์ˆ˜๊ฐ€ ์•„๋‹˜์„ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๊ฐ’์ด `= None`์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•„์ˆ˜๊ฐ€ ์•„๋‹˜์„ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๋ฉด ํŽธ์ง‘๊ธฐ๊ฐ€ ๋” ๋‚˜์€ ์ง€์›์„ ์ œ๊ณตํ•˜๊ณ  ์˜ค๋ฅ˜๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## Pydantic์—†์ด +## Pydantic์—†์ด { #without-pydantic } -๋งŒ์•ฝ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋ฉด, **Body** ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. [Body - ๋‹ค์ค‘ ๋งค๊ฐœ๋ณ€์ˆ˜: ๋ณธ๋ฌธ์— ์žˆ๋Š” ์œ ์ผํ•œ ๊ฐ’](body-multiple-params.md#_2){.internal-link target=_blank} ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. +๋งŒ์•ฝ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋ฉด, **Body** ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank} ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. diff --git a/docs/ko/docs/tutorial/cookie-param-models.md b/docs/ko/docs/tutorial/cookie-param-models.md index e7eef0b1de..00238d1b79 100644 --- a/docs/ko/docs/tutorial/cookie-param-models.md +++ b/docs/ko/docs/tutorial/cookie-param-models.md @@ -1,8 +1,8 @@ -# ์ฟ ํ‚ค ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชจ๋ธ +# ์ฟ ํ‚ค ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชจ๋ธ { #cookie-parameter-models } ๊ด€๋ จ์žˆ๋Š” **์ฟ ํ‚ค**๋“ค์˜ ๊ทธ๋ฃน์ด ์žˆ๋Š” ๊ฒฝ์šฐ, **Pydantic ๋ชจ๋ธ**์„ ์ƒ์„ฑํ•˜์—ฌ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿช -์ด๋ฅผ ํ†ตํ•ด **์—ฌ๋Ÿฌ ์œ„์น˜**์—์„œ **๋ชจ๋ธ์„ ์žฌ์‚ฌ์šฉ** ํ•  ์ˆ˜ ์žˆ๊ณ  ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ +์ด๋ฅผ ํ†ตํ•ด **์—ฌ๋Ÿฌ ์œ„์น˜**์—์„œ **๋ชจ๋ธ์„ ์žฌ์‚ฌ์šฉ** ํ•  ์ˆ˜ ์žˆ๊ณ  ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž /// note | ์ฐธ๊ณ  @@ -16,7 +16,7 @@ /// -## Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•œ ์ฟ ํ‚ค +## Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•œ ์ฟ ํ‚ค { #cookies-with-a-pydantic-model } **Pydantic ๋ชจ๋ธ**์— ํ•„์š”ํ•œ **์ฟ ํ‚ค** ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•œ ๋‹ค์Œ, ํ•ด๋‹น ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `Cookie`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: @@ -24,7 +24,7 @@ **FastAPI**๋Š” ์š”์ฒญ์—์„œ ๋ฐ›์€ **์ฟ ํ‚ค**์—์„œ **๊ฐ ํ•„๋“œ**์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ **์ถ”์ถœ**ํ•˜๊ณ  ์ •์˜ํ•œ Pydantic ๋ชจ๋ธ์„ ์ค๋‹ˆ๋‹ค. -## ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ +## ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ { #check-the-docs } ๋ฌธ์„œ UI `/docs`์—์„œ ์ •์˜ํ•œ ์ฟ ํ‚ค๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -36,27 +36,27 @@ ๋ช…์‹ฌํ•˜์„ธ์š”, ๋‚ด๋ถ€์ ์œผ๋กœ **๋ธŒ๋ผ์šฐ์ €๋Š” ์ฟ ํ‚ค๋ฅผ ํŠน๋ณ„ํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ**ํ•˜๊ธฐ ๋•Œ๋ฌธ์— **์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ**๊ฐ€ ์‰ฝ๊ฒŒ ์ฟ ํ‚ค๋ฅผ ๊ฑด๋“œ๋ฆด ์ˆ˜ **์—†์Šต๋‹ˆ๋‹ค**. -`/docs`์—์„œ **API ๋ฌธ์„œ UI**๋กœ ์ด๋™ํ•˜๋ฉด *๊ฒฝ๋กœ ์ž‘์—…*์— ๋Œ€ํ•œ ์ฟ ํ‚ค์˜ **๋ฌธ์„œ**๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`/docs`์—์„œ **API ๋ฌธ์„œ UI**๋กœ ์ด๋™ํ•˜๋ฉด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ๋Œ€ํ•œ ์ฟ ํ‚ค์˜ **๋ฌธ์„œ**๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์•„๋ฌด๋ฆฌ **๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅ**ํ•˜๊ณ  "์‹คํ–‰(Execute)"์„ ํด๋ฆญํ•ด๋„, ๋ฌธ์„œ UI๋Š” **์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ**๋กœ ์ž‘๋™ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฟ ํ‚ค๋Š” ์ „์†ก๋˜์ง€ ์•Š๊ณ , ์•„๋ฌด ๊ฐ’๋„ ์“ฐ์ง€ ์•Š์€ ๊ฒƒ์ฒ˜๋Ÿผ **์˜ค๋ฅ˜** ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. /// -## ์ถ”๊ฐ€ ์ฟ ํ‚ค ๊ธˆ์ง€ํ•˜๊ธฐ +## ์ถ”๊ฐ€ ์ฟ ํ‚ค ๊ธˆ์ง€ํ•˜๊ธฐ { #forbid-extra-cookies } ์ผ๋ถ€ ํŠน๋ณ„ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€(ํ”ํ•˜์ง€๋Š” ์•Š๊ฒ ์ง€๋งŒ)์—์„œ๋Š” ์ˆ˜์‹ ํ•˜๋ ค๋Š” ์ฟ ํ‚ค๋ฅผ **์ œํ•œ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด์ œ API๋Š” ์ž์‹ ์˜ <abbr title="๋†๋‹ด์ž…๋‹ˆ๋‹ค, ํ˜น์‹œ๋‚˜ ํ•ด์„œ์š”. ์ฟ ํ‚ค ๋™์˜์™€ ๊ด€๋ จํ•ด์„œ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ์—†์ง€๋งŒ, ์ด์ œ API์กฐ์ฐจ๋„ ์ž˜๋ชป๋œ ์ฟ ํ‚ค๋ฅผ ๊ฑฐ๋ถ€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ์žฌ๋ฐŒ์Šต๋‹ˆ๋‹ค. ์ฟ ํ‚ค ๋“œ์„ธ์š”. ๐Ÿช">์ฟ ํ‚ค ๋™์˜</abbr>๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๊ฐ–๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿคช๐Ÿช +์ด์ œ API๋Š” ์ž์‹ ์˜ <abbr title="This is a joke, just in case. It has nothing to do with cookie consents, but it's funny that even the API can now reject the poor cookies. Have a cookie. ๐Ÿช">cookie consent</abbr>๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๊ฐ–๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿคช๐Ÿช Pydantic์˜ ๋ชจ๋ธ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€(`extra`) ํ•„๋“œ๋ฅผ ๊ธˆ์ง€(`forbid`)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} +{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *} ํด๋ผ์ด์–ธํŠธ๊ฐ€ **์ถ”๊ฐ€ ์ฟ ํ‚ค**๋ฅผ ๋ณด๋‚ด๋ ค๊ณ  ์‹œ๋„ํ•˜๋ฉด, **์˜ค๋ฅ˜** ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -<abbr title="์ด๊ฑด ๋˜ ๋‹ค๋ฅธ ๋†๋‹ด์ž…๋‹ˆ๋‹ค. ์ œ ๋ง์— ๊ท€ ๊ธฐ์šธ์ด์ง€ ๋งˆ์„ธ์š”. ์ปคํ”ผ๋ž‘ ์ฟ ํ‚ค ์ข€ ๋“œ์„ธ์š”. โ˜•">API๊ฐ€ ๊ฑฐ๋ถ€</abbr>ํ•˜๋Š”๋ฐ๋„ ๋™์˜๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด ์• ์“ฐ๋Š” ๋ถˆ์Œํ•œ ์ฟ ํ‚ค ๋ฐฐ๋„ˆ(ํŒ์—…)๋“ค. ๐Ÿช +๋™์˜๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด ์• ์“ฐ๋Š” ๋ถˆ์Œํ•œ ์ฟ ํ‚ค ๋ฐฐ๋„ˆ(ํŒ์—…)๋“ค, <abbr title="This is another joke. Don't pay attention to me. Have some coffee for your cookie. โ˜•">API๊ฐ€ ๊ฑฐ๋ถ€</abbr>ํ•˜๋Š”๋ฐ๋„. ๐Ÿช -์˜ˆ๋ฅผ ๋“ค์–ด, ํด๋ผ์ด์–ธํŠธ๊ฐ€ `good-list-please` ๊ฐ’์œผ๋กœ `santa_tracker` ์ฟ ํ‚ค๋ฅผ ๋ณด๋‚ด๋ ค๊ณ  ํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ๋Š” `santa_tracker` <abbr title="์‚ฐํƒ€๋Š” ์ฟ ํ‚ค๊ฐ€ ๋ถ€์กฑํ•œ ๊ฒƒ์„ ๋ชป๋งˆ๋•…ํ•ดํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ… ์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค, ์ฟ ํ‚ค ๋†๋‹ด์€ ์ด์ œ ์—†์Šต๋‹ˆ๋‹ค.">์ฟ ํ‚ค๊ฐ€ ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค</abbr>๋Š” **์˜ค๋ฅ˜** ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: +์˜ˆ๋ฅผ ๋“ค์–ด, ํด๋ผ์ด์–ธํŠธ๊ฐ€ `good-list-please` ๊ฐ’์œผ๋กœ `santa_tracker` ์ฟ ํ‚ค๋ฅผ ๋ณด๋‚ด๋ ค๊ณ  ํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ๋Š” `santa_tracker` <abbr title="Santa disapproves the lack of cookies. ๐ŸŽ… Okay, no more cookie jokes.">์ฟ ํ‚ค๊ฐ€ ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค</abbr>๋Š” **์˜ค๋ฅ˜** ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: ```json { @@ -71,6 +71,6 @@ Pydantic์˜ ๋ชจ๋ธ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€(`extra`) ํ•„๋“œ๋ฅผ ๊ธˆ์ง€(`forb } ``` -## ์š”์•ฝ +## ์š”์•ฝ { #summary } -**Pydantic ๋ชจ๋ธ**์„ ์‚ฌ์šฉํ•˜์—ฌ **FastAPI**์—์„œ <abbr title="๊ฐ€๊ธฐ ์ „์— ๋งˆ์ง€๋ง‰ ์ฟ ํ‚ค๋ฅผ ๋“œ์„ธ์š”. ๐Ÿช">**์ฟ ํ‚ค**</abbr>๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ +**Pydantic ๋ชจ๋ธ**์„ ์‚ฌ์šฉํ•˜์—ฌ **FastAPI**์—์„œ <abbr title="Have a last cookie before you go. ๐Ÿช">**์ฟ ํ‚ค**</abbr>๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž diff --git a/docs/ko/docs/tutorial/cookie-params.md b/docs/ko/docs/tutorial/cookie-params.md index fba756d498..0591a5e96b 100644 --- a/docs/ko/docs/tutorial/cookie-params.md +++ b/docs/ko/docs/tutorial/cookie-params.md @@ -1,14 +1,14 @@ -# ์ฟ ํ‚ค ๋งค๊ฐœ๋ณ€์ˆ˜ +# ์ฟ ํ‚ค ๋งค๊ฐœ๋ณ€์ˆ˜ { #cookie-parameters } ์ฟ ํ‚ค ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `Query`์™€ `Path` ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Cookie` ์ž„ํฌํŠธ +## `Cookie` ์ž„ํฌํŠธ { #import-cookie } ๋จผ์ € `Cookie`๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค: {* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *} -## `Cookie` ๋งค๊ฐœ๋ณ€์ˆ˜ ์„ ์–ธ +## `Cookie` ๋งค๊ฐœ๋ณ€์ˆ˜ ์„ ์–ธ { #declare-cookie-parameters } ๊ทธ๋Ÿฐ ๋‹ค์Œ, `Path`์™€ `Query`์ฒ˜๋Ÿผ ๋™์ผํ•œ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฟ ํ‚ค ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. @@ -20,7 +20,7 @@ `Cookie`๋Š” `Path` ๋ฐ `Query`์˜ "์ž๋งค"ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์ด ์—ญ์‹œ ๋™์ผํ•œ ๊ณตํ†ต `Param` ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•ฉ๋‹ˆ๋‹ค. -`Query`, `Path`, `Cookie` ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๊ฒƒ๋“ค์€ `fastapi`์—์„œ ์ž„ํฌํŠธ ํ•  ๋•Œ, ์‹ค์ œ๋กœ๋Š” ํŠน๋ณ„ํ•œ ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์ž„์„ ๊ธฐ์–ตํ•˜์„ธ์š”. +ํ•˜์ง€๋งŒ `fastapi`์—์„œ `Query`, `Path`, `Cookie` ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๊ฒƒ๋“ค์„ ์ž„ํฌํŠธํ•  ๋•Œ, ์‹ค์ œ๋กœ๋Š” ํŠน๋ณ„ํ•œ ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์ž„์„ ๊ธฐ์–ตํ•˜์„ธ์š”. /// @@ -30,6 +30,16 @@ /// -## ์š”์•ฝ +/// info | ์ •๋ณด -`Cookie`๋Š” `Query`, `Path`์™€ ๋™์ผํ•œ ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. +**๋ธŒ๋ผ์šฐ์ €๋Š” ์ฟ ํ‚ค๋ฅผ** ๋‚ด๋ถ€์ ์œผ๋กœ ํŠน๋ณ„ํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์—, **JavaScript**๊ฐ€ ์‰ฝ๊ฒŒ ์ฟ ํ‚ค๋ฅผ ๋‹ค๋ฃจ๋„๋ก ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์„ ์—ผ๋‘์— ๋‘์„ธ์š”. + +`/docs`์˜ **API docs UI**๋กœ ์ด๋™ํ•˜๋ฉด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ๋Œ€ํ•œ ์ฟ ํ‚ค **๋ฌธ์„œ**๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ **๋ฐ์ดํ„ฐ๋ฅผ ์ฑ„์šฐ๊ณ ** "Execute"๋ฅผ ํด๋ฆญํ•˜๋”๋ผ๋„, docs UI๋Š” **JavaScript**๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฟ ํ‚ค๊ฐ€ ์ „์†ก๋˜์ง€ ์•Š๊ณ , ์•„๋ฌด ๊ฐ’๋„ ์ž…๋ ฅํ•˜์ง€ ์•Š์€ ๊ฒƒ์ฒ˜๋Ÿผ **์˜ค๋ฅ˜** ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// + +## ์š”์•ฝ { #recap } + +`Query`์™€ `Path`์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๊ณตํ†ต ํŒจํ„ด์œผ๋กœ, `Cookie`๋ฅผ ์‚ฌ์šฉํ•ด ์ฟ ํ‚ค๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/cors.md b/docs/ko/docs/tutorial/cors.md index 1ef5a74803..0f3948a3dc 100644 --- a/docs/ko/docs/tutorial/cors.md +++ b/docs/ko/docs/tutorial/cors.md @@ -1,10 +1,10 @@ -# ๊ต์ฐจ ์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ  +# CORS (๊ต์ฐจ-์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ ) { #cors-cross-origin-resource-sharing } -<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS ๋˜๋Š” "๊ต์ฐจ-์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ "</a>๋ž€, ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋™์ž‘ํ•˜๋Š” ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ์ฝ”๋“œ๋กœ ๋ฐฑ์—”๋“œ์™€ ํ†ต์‹ ํ•˜๊ณ , ๋ฐฑ์—”๋“œ๋Š” ํ•ด๋‹น ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋‹ค๋ฅธ "์ถœ์ฒ˜"์— ์กด์žฌํ•˜๋Š” ์ƒํ™ฉ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS ๋˜๋Š” "Cross-Origin Resource Sharing"</a>๋ž€, ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰๋˜๋Š” ํ”„๋ก ํŠธ์—”๋“œ์— ๋ฐฑ์—”๋“œ์™€ ํ†ต์‹ ํ•˜๋Š” JavaScript ์ฝ”๋“œ๊ฐ€ ์žˆ๊ณ , ๋ฐฑ์—”๋“œ๊ฐ€ ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋‹ค๋ฅธ "์ถœ์ฒ˜(origin)"์— ์žˆ๋Š” ์ƒํ™ฉ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -## ์ถœ์ฒ˜ +## ์ถœ์ฒ˜ { #origin } -์ถœ์ฒ˜๋ž€ ํ”„๋กœํ† ์ฝœ(`http` , `https`), ๋„๋ฉ”์ธ(`myapp.com`, `localhost`, `localhost.tiangolo.com` ), ๊ทธ๋ฆฌ๊ณ  ํฌํŠธ(`80`, `443`, `8080` )์˜ ์กฐํ•ฉ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +์ถœ์ฒ˜๋ž€ ํ”„๋กœํ† ์ฝœ(`http`, `https`), ๋„๋ฉ”์ธ(`myapp.com`, `localhost`, `localhost.tiangolo.com`), ๊ทธ๋ฆฌ๊ณ  ํฌํŠธ(`80`, `443`, `8080`)์˜ ์กฐํ•ฉ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์•„๋ž˜๋Š” ๋ชจ๋‘ ์ƒ์ดํ•œ ์ถœ์ฒ˜์ž…๋‹ˆ๋‹ค: @@ -12,74 +12,78 @@ * `https://localhost` * `http://localhost:8080` -๋ชจ๋‘ `localhost` ์— ์žˆ์ง€๋งŒ, ์„œ๋กœ ๋‹ค๋ฅธ ํ”„๋กœํ† ์ฝœ๊ณผ ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ๋‹ค๋ฅธ "์ถœ์ฒ˜"์ž…๋‹ˆ๋‹ค. +๋ชจ๋‘ `localhost`์— ์žˆ๋”๋ผ๋„, ์„œ๋กœ ๋‹ค๋ฅธ ํ”„๋กœํ† ์ฝœ์ด๋‚˜ ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์„œ๋กœ ๋‹ค๋ฅธ "์ถœ์ฒ˜"์ž…๋‹ˆ๋‹ค. -## ๋‹จ๊ณ„ +## ๋‹จ๊ณ„ { #steps } -๋ธŒ๋ผ์šฐ์ € ๋‚ด `http://localhost:8080`์—์„œ ๋™์ž‘ํ•˜๋Š” ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ์žˆ๊ณ , ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” `http://localhost`๋ฅผ ํ†ตํ•ด ๋ฐฑ์—”๋“œ์™€ ํ†ต์‹ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค(ํฌํŠธ๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ๋ธŒ๋ผ์šฐ์ €๋Š” `80` ์„ ๊ธฐ๋ณธ ํฌํŠธ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค). +๊ทธ๋Ÿฌ๋ฉด ๋ธŒ๋ผ์šฐ์ €์—์„œ `http://localhost:8080`์œผ๋กœ ์‹คํ–‰๋˜๋Š” ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ์žˆ๊ณ , ๊ทธ JavaScript๊ฐ€ `http://localhost`์—์„œ ์‹คํ–‰๋˜๋Š” ๋ฐฑ์—”๋“œ์™€ ํ†ต์‹ ํ•˜๋ ค๊ณ  ํ•œ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค(ํฌํŠธ๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ธŒ๋ผ์šฐ์ €๋Š” ๊ธฐ๋ณธ ํฌํŠธ `80`์„ ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค). -๊ทธ๋Ÿฌ๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ๋ฐฑ์—”๋“œ์— HTTP `OPTIONS` ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ๋ฐฑ์—”๋“œ์—์„œ ์ด ๋‹ค๋ฅธ ์ถœ์ฒ˜(`http://localhost:8080`)์™€์˜ ํ†ต์‹ ์„ ํ—ˆ๊ฐ€ํ•˜๋Š” ์ ์ ˆํ•œ ํ—ค๋”๋ฅผ ๋ณด๋‚ด๋ฉด, ๋ธŒ๋ผ์šฐ์ €๋Š” ํ”„๋ก ํŠธ์—”๋“œ์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋ฐฑ์—”๋“œ์— ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋Ÿฌ๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” `:80`-๋ฐฑ์—”๋“œ์— HTTP `OPTIONS` ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ๋ฐฑ์—”๋“œ๊ฐ€ ์ด ๋‹ค๋ฅธ ์ถœ์ฒ˜(`http://localhost:8080`)๋กœ๋ถ€ํ„ฐ์˜ ํ†ต์‹ ์„ ํ—ˆ๊ฐ€ํ•˜๋Š” ์ ์ ˆํ•œ ํ—ค๋”๋ฅผ ๋ณด๋‚ด๋ฉด, `:8080`-๋ธŒ๋ผ์šฐ์ €๋Š” ํ”„๋ก ํŠธ์—”๋“œ์˜ JavaScript๊ฐ€ `:80`-๋ฐฑ์—”๋“œ์— ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. -์ด๋ฅผ ์œ„ํ•ด, ๋ฐฑ์—”๋“œ๋Š” "ํ—ˆ์šฉ๋œ ์ถœ์ฒ˜(allowed origins)" ๋ชฉ๋ก์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค. +์ด๋ฅผ ์œ„ํ•ด, `:80`-๋ฐฑ์—”๋“œ๋Š” "ํ—ˆ์šฉ๋œ ์ถœ์ฒ˜(allowed origins)" ๋ชฉ๋ก์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ, ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด `http://localhost:8080`์„ ๋ชฉ๋ก์— ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ, `:8080`-ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋ ค๋ฉด ๋ชฉ๋ก์— `http://localhost:8080`์ด ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -## ์™€์ผ๋“œ์นด๋“œ +## ์™€์ผ๋“œ์นด๋“œ { #wildcards } -๋ชจ๋“  ์ถœ์ฒ˜๋ฅผ ํ—ˆ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ชฉ๋ก์„ `"*"` ("์™€์ผ๋“œ์นด๋“œ")๋กœ ์„ ์–ธํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +๋˜ํ•œ ๋ชฉ๋ก์„ `"*"`("์™€์ผ๋“œ์นด๋“œ")๋กœ ์„ ์–ธํ•ด ๋ชจ๋‘ ํ—ˆ์šฉ๋œ๋‹ค๊ณ  ๋งํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์ด๊ฒƒ์€ ํŠน์ •ํ•œ ์œ ํ˜•์˜ ํ†ต์‹ ๋งŒ์„ ํ—ˆ์šฉํ•˜๋ฉฐ, ์ฟ ํ‚ค ๋ฐ ์•ก์„ธ์Šค ํ† ํฐ๊ณผ ์‚ฌ์šฉ๋˜๋Š” ์ธ์ฆ ํ—ค๋”(Authoriztion header) ๋“ฑ์ด ํฌํ•จ๋œ ๊ฒฝ์šฐ์™€ ๊ฐ™์ด ์ž๊ฒฉ ์ฆ๋ช…(credentials)์ด ํฌํ•จ๋œ ํ†ต์‹ ์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๊ทธ๋Ÿฌ๋ฉด ์ž๊ฒฉ ์ฆ๋ช…(credentials)์ด ํฌํ•จ๋œ ๋ชจ๋“  ๊ฒƒ์„ ์ œ์™ธํ•˜๊ณ  ํŠน์ • ์œ ํ˜•์˜ ํ†ต์‹ ๋งŒ ํ—ˆ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ: ์ฟ ํ‚ค, Bearer Token์— ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ Authorization ํ—ค๋” ๋“ฑ. -๋”ฐ๋ผ์„œ ๋ชจ๋“  ์ž‘์—…์„ ์˜๋„ํ•œ๋Œ€๋กœ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด, ํ—ˆ์šฉ๋˜๋Š” ์ถœ์ฒ˜๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ ๋ชจ๋“  ๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด, ํ—ˆ์šฉ๋œ ์ถœ์ฒ˜๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค. -## `CORSMiddleware` ์‚ฌ์šฉ +## `CORSMiddleware` ์‚ฌ์šฉ { #use-corsmiddleware } -`CORSMiddleware` ์„ ์‚ฌ์šฉํ•˜์—ฌ **FastAPI** ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ ๊ต์ฐจ ์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ  ํ™˜๊ฒฝ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`CORSMiddleware`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ด๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* `CORSMiddleware` ์ž„ํฌํŠธ. -* ํ—ˆ์šฉ๋˜๋Š” ์ถœ์ฒ˜(๋ฌธ์ž์—ด ํ˜•์‹)์˜ ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ. -* FastAPI ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— "๋ฏธ๋“ค์›จ์–ด(middleware)"๋กœ ์ถ”๊ฐ€. +* `CORSMiddleware`๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค. +* ํ—ˆ์šฉ๋œ ์ถœ์ฒ˜(๋ฌธ์ž์—ด)์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +* **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— "๋ฏธ๋“ค์›จ์–ด(middleware)"๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. -๋ฐฑ์—”๋“œ์—์„œ ๋‹ค์Œ์˜ ์‚ฌํ•ญ์„ ํ—ˆ์šฉํ• ์ง€์— ๋Œ€ํ•ด ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: +๋˜ํ•œ ๋ฐฑ์—”๋“œ๊ฐ€ ๋‹ค์Œ์„ ํ—ˆ์šฉํ• ์ง€ ์—ฌ๋ถ€๋„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -* ์ž๊ฒฉ์ฆ๋ช… (์ธ์ฆ ํ—ค๋”, ์ฟ ํ‚ค ๋“ฑ). -* ํŠน์ •ํ•œ HTTP ๋ฉ”์†Œ๋“œ(`POST`, `PUT`) ๋˜๋Š” ์™€์ผ๋“œ์นด๋“œ `"*"` ๋ฅผ ์‚ฌ์šฉํ•œ ๋ชจ๋“  HTTP ๋ฉ”์†Œ๋“œ. -* ํŠน์ •ํ•œ HTTP ํ—ค๋” ๋˜๋Š” ์™€์ผ๋“œ์นด๋“œ `"*"` ๋ฅผ ์‚ฌ์šฉํ•œ ๋ชจ๋“  HTTP ํ—ค๋”. +* ์ž๊ฒฉ ์ฆ๋ช…(Authorization ํ—ค๋”, ์ฟ ํ‚ค ๋“ฑ). +* ํŠน์ • HTTP ๋ฉ”์„œ๋“œ(`POST`, `PUT`) ๋˜๋Š” ์™€์ผ๋“œ์นด๋“œ `"*"`๋ฅผ ์‚ฌ์šฉํ•œ ๋ชจ๋“  ๋ฉ”์„œ๋“œ. +* ํŠน์ • 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` ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์ œํ•œ์ ์ด๋ฏ€๋กœ, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ต์ฐจ-๋„๋ฉ”์ธ ์ƒํ™ฉ์—์„œ ํŠน์ •ํ•œ ์ถœ์ฒ˜, ๋ฉ”์†Œ๋“œ, ํ—ค๋” ๋“ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ ค๋ฉด ์ด๋“ค์„ ๋ช…์‹œ์ ์œผ๋กœ ํ—ˆ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -๋‹ค์Œ์˜ ์ธ์ž๋“ค์ด ์ง€์›๋ฉ๋‹ˆ๋‹ค: +`CORSMiddleware` ๊ตฌํ˜„์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œํ•œ์ ์ด๋ฏ€๋กœ, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ Cross-Domain ์ปจํ…์ŠคํŠธ์—์„œ ํŠน์ • ์ถœ์ฒ˜, ๋ฉ”์„œ๋“œ ๋˜๋Š” ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ ค๋ฉด ์ด๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -* `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` - ๊ต์ฐจ-์ถœ์ฒ˜ ์š”์ฒญ์‹œ ์ฟ ํ‚ค ์ง€์› ์—ฌ๋ถ€๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ `False` ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ ํ•ด๋‹น ํ•ญ๋ชฉ์„ ํ—ˆ์šฉํ•  ๊ฒฝ์šฐ `allow_origins` ๋Š” `['*']` ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์—†์œผ๋ฉฐ, ์ถœ์ฒ˜๋ฅผ ๋ฐ˜๋“œ์‹œ ํŠน์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -* `expose_headers` - ๋ธŒ๋ผ์šฐ์ €์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๋Š” ๋ชจ๋“  ์‘๋‹ต ํ—ค๋”๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ `[]` ์ž…๋‹ˆ๋‹ค. -* `max_age` - ๋ธŒ๋ผ์šฐ์ €๊ฐ€ CORS ์‘๋‹ต์„ ์บ์‹œ์— ์ €์žฅํ•˜๋Š” ์ตœ๋Œ€ ์‹œ๊ฐ„์„ ์ดˆ ๋‹จ์œ„๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ `600` ์ž…๋‹ˆ๋‹ค. +๋‹ค์Œ ์ธ์ž๋“ค์ด ์ง€์›๋ฉ๋‹ˆ๋‹ค: -๋ฏธ๋“ค์›จ์–ด๋Š” ๋‘๊ฐ€์ง€ ํŠน์ •ํ•œ ์ข…๋ฅ˜์˜ HTTP ์š”์ฒญ์— ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค... +* `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` ํ—ค๋”๋Š” <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">๋‹จ์ˆœ CORS ์š”์ฒญ</a>์— ๋Œ€ํ•ด ํ•ญ์ƒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค. +* `allow_credentials` - ๊ต์ฐจ-์ถœ์ฒ˜ ์š”์ฒญ์— ๋Œ€ํ•ด ์ฟ ํ‚ค๋ฅผ ์ง€์›ํ•ด์•ผ ํ•จ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ `False`์ž…๋‹ˆ๋‹ค. -### CORS ์‚ฌ์ „ ์š”์ฒญ + `allow_credentials`๊ฐ€ `True`๋กœ ์„ค์ •๋œ ๊ฒฝ์šฐ `allow_origins`, `allow_methods`, `allow_headers` ์ค‘ ์–ด๋А ๊ฒƒ๋„ `['*']`๋กœ ์„ค์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋‘ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#credentialed_requests_and_wildcards" class="external-link" rel="noopener" target="_blank">๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •</a>๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -`Origin` ๋ฐ `Access-Control-Request-Method` ํ—ค๋”์™€ ํ•จ๊ป˜ ์ „์†กํ•˜๋Š” ๋ชจ๋“  `OPTIONS` ์š”์ฒญ์ž…๋‹ˆ๋‹ค. +* `expose_headers` - ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๋Š” ๋ชจ๋“  ์‘๋‹ต ํ—ค๋”๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ `[]`์ž…๋‹ˆ๋‹ค. +* `max_age` - ๋ธŒ๋ผ์šฐ์ €๊ฐ€ CORS ์‘๋‹ต์„ ์บ์‹œํ•˜๋Š” ์ตœ๋Œ€ ์‹œ๊ฐ„์„ ์ดˆ ๋‹จ์œ„๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ `600`์ž…๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ ๋ฏธ๋“ค์›จ์–ด๋Š” ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„ ์ ์ ˆํ•œ CORS ํ—ค๋”์™€, ์ •๋ณด ์ œ๊ณต์„ ์œ„ํ•œ `200` ๋˜๋Š” `400` ์‘๋‹ต์œผ๋กœ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค. +๋ฏธ๋“ค์›จ์–ด๋Š” ๋‘ ๊ฐ€์ง€ ํŠน์ •ํ•œ ์ข…๋ฅ˜์˜ HTTP ์š”์ฒญ์— ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค... -### ๋‹จ์ˆœํ•œ ์š”์ฒญ +### CORS ์‚ฌ์ „ ์š”์ฒญ { #cors-preflight-requests } -`Origin` ํ—ค๋”๋ฅผ ๊ฐ€์ง„ ๋ชจ๋“  ์š”์ฒญ. ์ด ๊ฒฝ์šฐ ๋ฏธ๋“ค์›จ์–ด๋Š” ์š”์ฒญ์„ ์ •์ƒ์ ์œผ๋กœ ์ „๋‹ฌํ•˜์ง€๋งŒ, ์ ์ ˆํ•œ CORS ํ—ค๋”๋ฅผ ์‘๋‹ต์— ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค. +`Origin` ๋ฐ `Access-Control-Request-Method` ํ—ค๋”๊ฐ€ ์žˆ๋Š” ๋ชจ๋“  `OPTIONS` ์š”์ฒญ์ž…๋‹ˆ๋‹ค. -## ๋” ๋งŽ์€ ์ •๋ณด +์ด ๊ฒฝ์šฐ ๋ฏธ๋“ค์›จ์–ด๋Š” ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„ ์ ์ ˆํ•œ CORS ํ—ค๋”์™€ ํ•จ๊ป˜, ์ •๋ณด ์ œ๊ณต ๋ชฉ์ ์œผ๋กœ `200` ๋˜๋Š” `400` ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -<abbr title="๊ต์ฐจ-์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ ">CORS</abbr>์— ๋Œ€ํ•œ ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ์•Œ๊ณ ์‹ถ๋‹ค๋ฉด, <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS ๋ฌธ์„œ</a>๋ฅผ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. +### ๋‹จ์ˆœํ•œ ์š”์ฒญ { #simple-requests } -/// note | ๊ธฐ์ˆ ์  ์„ธ๋ถ€ ์‚ฌํ•ญ +`Origin` ํ—ค๋”๊ฐ€ ์žˆ๋Š” ๋ชจ๋“  ์š”์ฒญ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๋ฏธ๋“ค์›จ์–ด๋Š” ์š”์ฒญ์„ ์ •์ƒ์ ์œผ๋กœ ํ†ต๊ณผ์‹œํ‚ค์ง€๋งŒ, ์‘๋‹ต์— ์ ์ ˆํ•œ CORS ํ—ค๋”๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. -`from starlette.middleware.cors import CORSMiddleware` ์—ญ์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +## ๋” ๋งŽ์€ ์ •๋ณด { #more-info } -**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์ธ ๋‹น์‹ ์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `fastapi.middleware` ์—์„œ ๋ช‡๊ฐ€์ง€์˜ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์˜ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ Stralette์œผ๋กœ๋ถ€ํ„ฐ ์ง์ ‘ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. +<abbr title="Cross-Origin Resource Sharing โ€“ ๊ต์ฐจ-์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ ">CORS</abbr>์— ๋Œ€ํ•œ ๋” ๋งŽ์€ ์ •๋ณด๋Š” <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS ๋ฌธ์„œ</a>๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +`from starlette.middleware.cors import CORSMiddleware`๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์ธ ์—ฌ๋Ÿฌ๋ถ„์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `fastapi.middleware`์— ์—ฌ๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฏธ๋“ค์›จ์–ด ๋Œ€๋ถ€๋ถ„์€ Starlette์—์„œ ์ง์ ‘ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. /// diff --git a/docs/ko/docs/tutorial/debugging.md b/docs/ko/docs/tutorial/debugging.md index e42f1ba880..ca20acff64 100644 --- a/docs/ko/docs/tutorial/debugging.md +++ b/docs/ko/docs/tutorial/debugging.md @@ -1,14 +1,14 @@ -# ๋””๋ฒ„๊น… +# ๋””๋ฒ„๊น… { #debugging } ์˜ˆ๋ฅผ ๋“ค๋ฉด Visual Studio Code ๋˜๋Š” PyCharm์„ ์‚ฌ์šฉํ•˜์—ฌ ํŽธ์ง‘๊ธฐ์—์„œ ๋””๋ฒ„๊ฑฐ๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `uvicorn` ํ˜ธ์ถœ +## `uvicorn` ํ˜ธ์ถœ { #call-uvicorn } -FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ `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`๋ผ๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. @@ -62,7 +62,7 @@ from myapp import app # Some more code ``` -์ด ๊ฒฝ์šฐ `myapp.py` ๋‚ด๋ถ€์˜ ์ž๋™ ๋ณ€์ˆ˜์—๋Š” ๊ฐ’์ด `"__main__"`์ธ ๋ณ€์ˆ˜ `__name__`์ด ์—†์Šต๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ `myapp.py` ๋‚ด๋ถ€์˜ ์ž๋™ ๋ณ€์ˆ˜ `__name__`์—๋Š” ๊ฐ’์ด `"__main__"`์ด ๋“ค์–ด๊ฐ€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์Œ ํ–‰ @@ -74,11 +74,11 @@ from myapp import app /// info | ์ •๋ณด -์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">๊ณต์‹ Python ๋ฌธ์„œ</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š” +์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">๊ณต์‹ Python ๋ฌธ์„œ</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. /// -## ๋””๋ฒ„๊ฑฐ๋กœ ์ฝ”๋“œ ์‹คํ–‰ +## ๋””๋ฒ„๊ฑฐ๋กœ ์ฝ”๋“œ ์‹คํ–‰ { #run-your-code-with-your-debugger } ์ฝ”๋“œ์—์„œ ์ง์ ‘ Uvicorn ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋””๋ฒ„๊ฑฐ์—์„œ ์ง์ ‘ Python ํ”„๋กœ๊ทธ๋žจ(FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜)์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -101,7 +101,7 @@ from myapp import app Pycharm์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค -* "Run" ๋ฉ”๋‰ด๋ฅผ ์—ฝ๋‹ˆ๋‹ค +* "Run" ๋ฉ”๋‰ด๋ฅผ ์—ฝ๋‹ˆ๋‹ค. * "Debug..." ์˜ต์…˜์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. * ๊ทธ๋Ÿฌ๋ฉด ์ƒํ™ฉ์— ๋งž๋Š” ๋ฉ”๋‰ด๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. * ๋””๋ฒ„๊ทธํ•  ํŒŒ์ผ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค(์ด ๊ฒฝ์šฐ `main.py`). diff --git a/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md index 3e5cdcc8c8..68bba669ae 100644 --- a/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md @@ -1,30 +1,30 @@ -# ์˜์กด์„ฑ์œผ๋กœ์„œ์˜ ํด๋ž˜์Šค +# ์˜์กด์„ฑ์œผ๋กœ์„œ์˜ ํด๋ž˜์Šค { #classes-as-dependencies } -**์˜์กด์„ฑ ์ฃผ์ž…** ์‹œ์Šคํ…œ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ธฐ ์ „์— ์ด์ „ ์˜ˆ์ œ๋ฅผ ์—…๊ทธ๋ ˆ์ด๋“œ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. +**์˜์กด์„ฑ ์ฃผ์ž…** ์‹œ์Šคํ…œ์— ๋Œ€ํ•ด ๋” ๊นŠ์ด ์‚ดํŽด๋ณด๊ธฐ ์ „์—, ์ด์ „ ์˜ˆ์ œ๋ฅผ ์—…๊ทธ๋ ˆ์ด๋“œํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -## ์ด์ „ ์˜ˆ์ œ์˜ `๋”•์…”๋„ˆ๋ฆฌ` +## ์ด์ „ ์˜ˆ์ œ์˜ `dict` { #a-dict-from-the-previous-example } -์ด์ „ ์˜ˆ์ œ์—์„œ, ์šฐ๋ฆฌ๋Š” ์˜์กด์„ฑ(์˜์กด ๊ฐ€๋Šฅํ•œ) ํ•จ์ˆ˜์—์„œ `๋”•์…”๋„ˆ๋ฆฌ`๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค: +์ด์ „ ์˜ˆ์ œ์—์„œ๋Š” ์˜์กด์„ฑ("dependable")์—์„œ `dict`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/dependencies/tutorial001.py hl[9] *} +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *} -์šฐ๋ฆฌ๋Š” *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `commons`์—์„œ `๋”•์…”๋„ˆ๋ฆฌ` ๊ฐ์ฒด๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๊ทธ๋Ÿฌ๋ฉด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `commons`์—์„œ `dict`๋ฅผ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ๋Š” ์—๋””ํ„ฐ๋“ค์ด `๋”•์…”๋„ˆ๋ฆฌ` ๊ฐ์ฒด์˜ ํ‚ค๋‚˜ ๋ฐธ๋ฅ˜์˜ ์ž๋ฃŒํ˜•์„ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ž๋™ ์™„์„ฑ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด ์ค„ ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์—๋””ํ„ฐ๋Š” `dict`์˜ ํ‚ค์™€ ๊ฐ’ ํƒ€์ž…์„ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— `dict`์— ๋Œ€ํ•ด์„œ๋Š” (์™„์„ฑ ๊ธฐ๋Šฅ ๊ฐ™์€) ๋งŽ์€ ์ง€์›์„ ์ œ๊ณตํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์ด ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค... +๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค... -## ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒƒ +## ์˜์กด์„ฑ์ด ๋˜๊ธฐ ์œ„ํ•œ ์กฐ๊ฑด { #what-makes-a-dependency } -์ง€๊ธˆ๊นŒ์ง€ ํ•จ์ˆ˜๋กœ ์„ ์–ธ๋œ ์˜์กด์„ฑ์„ ๋ด์™”์Šต๋‹ˆ๋‹ค. +์ง€๊ธˆ๊นŒ์ง€๋Š” ํ•จ์ˆ˜๋กœ ์„ ์–ธ๋œ ์˜์กด์„ฑ์„ ๋ดค์Šต๋‹ˆ๋‹ค. -์•„๋งˆ๋„ ๋” ์ผ๋ฐ˜์ ์ด๊ธฐ๋Š” ํ•˜๊ฒ ์ง€๋งŒ ์˜์กด์„ฑ์„ ์„ ์–ธํ•˜๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ ์•„๋‹™๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๊ทธ๊ฒƒ๋งŒ์ด ์˜์กด์„ฑ์„ ์„ ์–ธํ•˜๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ ์•„๋‹™๋‹ˆ๋‹ค(์•„๋งˆ๋„ ๋” ์ผ๋ฐ˜์ ์ด๊ธด ํ•˜๊ฒ ์ง€๋งŒ์š”). -ํ•ต์‹ฌ ์š”์†Œ๋Š” ์˜์กด์„ฑ์ด "ํ˜ธ์ถœ ๊ฐ€๋Šฅ"ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค +ํ•ต์‹ฌ ์š”์†Œ๋Š” ์˜์กด์„ฑ์ด "ํ˜ธ์ถœ ๊ฐ€๋Šฅ(callable)"ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. -ํŒŒ์ด์ฌ์—์„œ์˜ "**ํ˜ธ์ถœ ๊ฐ€๋Šฅ**"์€ ํŒŒ์ด์ฌ์ด ํ•จ์ˆ˜์ฒ˜๋Ÿผ "ํ˜ธ์ถœ"ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +ํŒŒ์ด์ฌ์—์„œ "**ํ˜ธ์ถœ ๊ฐ€๋Šฅ(callable)**"์ด๋ž€ ํŒŒ์ด์ฌ์ด ํ•จ์ˆ˜์ฒ˜๋Ÿผ "ํ˜ธ์ถœ"ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ, ๋งŒ์•ฝ ๋‹น์‹ ์ด `something`(ํ•จ์ˆ˜๊ฐ€ ์•„๋‹ ์ˆ˜๋„ ์žˆ์Œ) ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , +๋”ฐ๋ผ์„œ `something`(ํ•จ์ˆ˜๊ฐ€ _์•„๋‹_ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค)์ด๋ผ๋Š” ๊ฐ์ฒด๊ฐ€ ์žˆ๊ณ , ๋‹ค์Œ์ฒ˜๋Ÿผ "ํ˜ธ์ถœ"(์‹คํ–‰)ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด: ```Python something() @@ -36,11 +36,11 @@ something() something(some_argument, some_keyword_argument="foo") ``` -์ƒ๊ธฐ์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ "ํ˜ธ์ถœ(์‹คํ–‰)" ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด "ํ˜ธ์ถœ ๊ฐ€๋Šฅ"์ด ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๊ฒƒ์€ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ(callable)"์ž…๋‹ˆ๋‹ค. -## ์˜์กด์„ฑ์œผ๋กœ์„œ์˜ ํด๋ž˜์Šค +## ์˜์กด์„ฑ์œผ๋กœ์„œ์˜ ํด๋ž˜์Šค { #classes-as-dependencies_1 } -ํŒŒ์ด์ฌ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํŒŒ์ด์ฌ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค ๋•Œ๋„ ๊ฐ™์€ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: @@ -53,125 +53,236 @@ class Cat: fluffy = Cat(name="Mr Fluffy") ``` -์ด ๊ฒฝ์šฐ์— `fluffy`๋Š” ํด๋ž˜์Šค `Cat`์˜ ์ธ์Šคํ„ด์Šค์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ๋Š” `fluffy`๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ `Cat`์„ "ํ˜ธ์ถœ"ํ–ˆ์Šต๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ `fluffy`๋Š” ํด๋ž˜์Šค `Cat`์˜ ์ธ์Šคํ„ด์Šค์ž…๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ, ํŒŒ์ด์ฌ ํด๋ž˜์Šค๋Š” **ํ˜ธ์ถœ ๊ฐ€๋Šฅ**ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  `fluffy`๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด `Cat`์„ "ํ˜ธ์ถœ"ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ž˜์„œ **FastAPI**์—์„œ๋Š” ํŒŒ์ด์ฌ ํด๋ž˜์Šค๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ ํŒŒ์ด์ฌ ํด๋ž˜์Šค๋„ **ํ˜ธ์ถœ ๊ฐ€๋Šฅ(callable)**ํ•ฉ๋‹ˆ๋‹ค. -FastAPI๊ฐ€ ์‹ค์งˆ์ ์œผ๋กœ ํ™•์ธํ•˜๋Š” ๊ฒƒ์€ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ์„ฑ"(ํ•จ์ˆ˜, ํด๋ž˜์Šค ๋˜๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  ๊ฒƒ)๊ณผ ์ •์˜๋œ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์ž…๋‹ˆ๋‹ค. +๊ทธ๋Ÿฌ๋ฉด **FastAPI**์—์„œ๋Š” ํŒŒ์ด์ฌ ํด๋ž˜์Šค๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -"ํ˜ธ์ถœ ๊ฐ€๋Šฅ"ํ•œ ๊ฒƒ์„ ์˜์กด์„ฑ์œผ๋กœ์„œ **FastAPI**์— ์ „๋‹ฌํ•˜๋ฉด, ๊ทธ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ"ํ•œ ๊ฒƒ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์„ ๋ถ„์„ํ•œ ํ›„ ์ด๋ฅผ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๋ฅผ ์œ„ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ํ•˜์œ„-์˜์กด์„ฑ ๋˜ํ•œ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. +FastAPI๊ฐ€ ์‹ค์ œ๋กœ ํ™•์ธํ•˜๋Š” ๊ฒƒ์€ ๊ทธ๊ฒƒ์ด "ํ˜ธ์ถœ ๊ฐ€๋Šฅ(callable)"(ํ•จ์ˆ˜, ํด๋ž˜์Šค, ๋˜๋Š” ๋‹ค๋ฅธ ๋ฌด์—‡์ด๋“ )ํ•œ์ง€์™€ ์ •์˜๋œ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์ž…๋‹ˆ๋‹ค. -๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์—†๋Š” "ํ˜ธ์ถœ ๊ฐ€๋Šฅ"ํ•œ ๊ฒƒ ์—ญ์‹œ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์—†๋Š” *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. +**FastAPI**์—์„œ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ(callable)"ํ•œ ๊ฒƒ์„ ์˜์กด์„ฑ์œผ๋กœ ๋„˜๊ธฐ๋ฉด, ๊ทธ "ํ˜ธ์ถœ ๊ฐ€๋Šฅ(callable)"ํ•œ ๊ฒƒ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์„ ๋ถ„์„ํ•˜๊ณ  *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ํ•˜์œ„ ์˜์กด์„ฑ๋„ ํฌํ•จํ•ด์„œ์š”. -๊ทธ๋ž˜์„œ, ์šฐ๋ฆฌ๋Š” ์œ„ ์˜ˆ์ œ์—์„œ์˜ `common_paramenters` ์˜์กด์„ฑ์„ ํด๋ž˜์Šค `CommonQueryParams`๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๊ฒƒ์€ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์ „ํ˜€ ์—†๋Š” callable์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์—†๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์— ์ ์šฉ๋˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/dependencies/tutorial002.py hl[11:15] *} +๊ทธ๋Ÿฌ๋ฉด ์œ„์˜ ์˜์กด์„ฑ("dependable") `common_parameters`๋ฅผ ํด๋ž˜์Šค `CommonQueryParams`๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” `__init__` ๋ฉ”์„œ๋“œ์— ์ฃผ๋ชฉํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค: +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *} -{* ../../docs_src/dependencies/tutorial002.py hl[12] *} +ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉํ•˜๋Š” `__init__` ๋ฉ”์„œ๋“œ์— ์ฃผ๋ชฉํ•˜์„ธ์š”: -...์ด์ „ `common_parameters`์™€ ๋™์ผํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค: +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *} -{* ../../docs_src/dependencies/tutorial001.py hl[9] *} +...์ด์ „์˜ `common_parameters`์™€ ๋™์ผํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค: -์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์€ **FastAPI**๊ฐ€ ์˜์กด์„ฑ์„ "ํ•ด๊ฒฐ"ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *} -ํ•จ์ˆ˜์™€ ํด๋ž˜์Šค ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹ ๋ชจ๋‘, ์•„๋ž˜ ์š”์†Œ๋ฅผ ๊ฐ–์Šต๋‹ˆ๋‹ค: +์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์ด **FastAPI**๊ฐ€ ์˜์กด์„ฑ์„ "ํ•ด๊ฒฐ"ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ๊ฒƒ๋“ค์ž…๋‹ˆ๋‹ค. -* `๋ฌธ์ž์—ด`์ด๋ฉด์„œ ์„ ํƒ์‚ฌํ•ญ์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`. -* ๊ธฐ๋ณธ๊ฐ’์ด `0`์ด๋ฉด์„œ `์ •์ˆ˜ํ˜•`์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `skip` -* ๊ธฐ๋ณธ๊ฐ’์ด `100`์ด๋ฉด์„œ `์ •์ˆ˜ํ˜•`์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `limit` +๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ ๋‹ค์Œ์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: -๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹ ๋ชจ๋‘, ๋ฐ์ดํ„ฐ๋Š” ๋ณ€ํ™˜, ๊ฒ€์ฆ๋˜๊ณ  OpenAPI ์Šคํ‚ค๋งˆ์— ๋ฌธ์„œํ™”๋ฉ๋‹ˆ๋‹ค. +* `str`์ธ ์„ ํƒ์  ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`. +* ๊ธฐ๋ณธ๊ฐ’์ด `0`์ธ `int` ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `skip`. +* ๊ธฐ๋ณธ๊ฐ’์ด `100`์ธ `int` ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `limit`. -## ์‚ฌ์šฉํ•ด๋ด…์‹œ๋‹ค! +๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ ๋ฐ์ดํ„ฐ๋Š” ๋ณ€ํ™˜๋˜๊ณ , ๊ฒ€์ฆ๋˜๋ฉฐ, OpenAPI ์Šคํ‚ค๋งˆ์— ๋ฌธ์„œํ™”๋˜๋Š” ๋“ฑ ์—ฌ๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. -์ด์ œ ์•„๋ž˜์˜ ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•ด์„œ ์˜์กด์„ฑ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +## ์‚ฌ์šฉํ•˜๊ธฐ { #use-it } -{* ../../docs_src/dependencies/tutorial002.py hl[19] *} +์ด์ œ ์ด ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•ด ์˜์กด์„ฑ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” `CommonQueryParams` ํด๋ž˜์Šค๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํ•ด๋‹น ํด๋ž˜์Šค์˜ "์ธ์Šคํ„ด์Šค"๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ทธ ์ธ์Šคํ„ด์Šค๋Š” ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `commons`๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *} -## ํƒ€์ž… ํžŒํŒ… vs `Depends` +**FastAPI**๋Š” `CommonQueryParams` ํด๋ž˜์Šค๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ํ•ด๋‹น ํด๋ž˜์Šค์˜ "์ธ์Šคํ„ด์Šค"๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ๊ทธ ์ธ์Šคํ„ด์Šค๊ฐ€ ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `commons`๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. -์œ„ ์ฝ”๋“œ์—์„œ `CommonQueryParams`๋ฅผ ๋‘ ๋ฒˆ ์ž‘์„ฑํ•œ ๋ฐฉ์‹์— ์ฃผ๋ชฉํ•˜์‹ญ์‹œ์˜ค: +## ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜ vs `Depends` { #type-annotation-vs-depends } + +์œ„ ์ฝ”๋“œ์—์„œ `CommonQueryParams`๋ฅผ ๋‘ ๋ฒˆ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ์‹์— ์ฃผ๋ชฉํ•˜์„ธ์š”: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip | ํŒ + +๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด `Annotated` ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. + +/// ```Python commons: CommonQueryParams = Depends(CommonQueryParams) ``` -๋งˆ์ง€๋ง‰ `CommonQueryParams` ๋ณ€์ˆ˜๋ฅผ ๋ณด๋ฉด: +//// + +๋งˆ์ง€๋ง‰ `CommonQueryParams`๋Š”, ๋‹ค์Œ์—์„œ: ```Python -... = Depends(CommonQueryParams) +... Depends(CommonQueryParams) ``` -... **FastAPI**๊ฐ€ ์‹ค์ œ๋กœ ์–ด๋–ค ๊ฒƒ์ด ์˜์กด์„ฑ์ธ์ง€ ์•Œ๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. -FastAPI๋Š” ์„ ์–ธ๋œ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์„ ์ถ”์ถœํ•  ๊ฒƒ์ด๊ณ  ์‹ค์ œ๋กœ ์ด ๋ณ€์ˆ˜๋“ค์„ ํ˜ธ์ถœํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +...**FastAPI**๊ฐ€ ์‹ค์ œ๋กœ ๋ฌด์—‡์ด ์˜์กด์„ฑ์ธ์ง€ ์•Œ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +FastAPI๋Š” ์—ฌ๊ธฐ์—์„œ ์„ ์–ธ๋œ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์„ ์ถ”์ถœํ•˜๊ณ , ์‹ค์ œ๋กœ ์ด๊ฒƒ์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. --- -์ด ๊ฒฝ์šฐ์—, ์ฒซ๋ฒˆ์งธ `CommonQueryParams` ๋ณ€์ˆ˜๋ฅผ ๋ณด๋ฉด: +์ด ๊ฒฝ์šฐ ์ฒซ ๋ฒˆ์งธ `CommonQueryParams`๋Š” ๋‹ค์Œ์—์„œ: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, ... +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip | ํŒ + +๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด `Annotated` ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. + +/// ```Python commons: CommonQueryParams ... ``` -... **FastAPI**๋Š” `CommonQueryParams` ๋ณ€์ˆ˜์— ์–ด๋– ํ•œ ํŠน๋ณ„ํ•œ ์˜๋ฏธ๋„ ๋ถ€์—ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. FastAPI๋Š” ์ด ๋ณ€์ˆ˜๋ฅผ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜, ๊ฒ€์ฆ ๋“ฑ์— ํ™œ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. (ํ™œ์šฉํ•˜๋ ค๋ฉด `= Depends(CommonQueryParams)`๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.) +//// -์‚ฌ์‹ค ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด๋„ ๋ฌด๊ด€ํ•ฉ๋‹ˆ๋‹ค: +...**FastAPI**์— ํŠน๋ณ„ํ•œ ์˜๋ฏธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. FastAPI๋Š” ์ด๋ฅผ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜, ๊ฒ€์ฆ ๋“ฑ์— ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(๊ทธ๋Ÿฐ ์šฉ๋„๋กœ๋Š” `Depends(CommonQueryParams)`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค). + +์‹ค์ œ๋กœ๋Š” ์ด๋ ‡๊ฒŒ๋งŒ ์ž‘์„ฑํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[Any, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip | ํŒ + +๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด `Annotated` ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. + +/// ```Python commons = Depends(CommonQueryParams) ``` -..์ „์ฒด์ ์ธ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค: +//// -{* ../../docs_src/dependencies/tutorial003.py hl[19] *} +...๋‹ค์Œ๊ณผ ๊ฐ™์ด์š”: -๊ทธ๋Ÿฌ๋‚˜ ์ž๋ฃŒํ˜•์„ ์„ ์–ธํ•˜๋ฉด ์—๋””ํ„ฐ๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜ `commons`๋กœ ์ „๋‹ฌ๋  ๊ฒƒ์ด ๋ฌด์—‡์ธ์ง€ ์•Œ๊ฒŒ ๋˜๊ณ , ์ด๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ ์™„์„ฑ, ์ž๋ฃŒํ˜• ํ™•์ธ ๋“ฑ์— ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. +{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *} -<!-- <img src="/img/tutorial/dependencies/image02.png"> --> +ํ•˜์ง€๋งŒ ํƒ€์ž…์„ ์„ ์–ธํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์—๋””ํ„ฐ๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜ `commons`์— ๋ฌด์—‡์ด ์ „๋‹ฌ๋˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๊ณ , ์ฝ”๋“œ ์™„์„ฑ, ํƒ€์ž… ์ฒดํฌ ๋“ฑ์—์„œ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -## ์ฝ”๋“œ ๋‹จ์ถ• +<img src="/img/tutorial/dependencies/image02.png"> -๊ทธ๋Ÿฌ๋‚˜ ์—ฌ๊ธฐ `CommonQueryParams`๋ฅผ ๋‘ ๋ฒˆ์ด๋‚˜ ์ž‘์„ฑํ•˜๋Š”, ์ฝ”๋“œ ๋ฐ˜๋ณต์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +## ๋‹จ์ถ• { #shortcut } + +ํ•˜์ง€๋งŒ `CommonQueryParams`๋ฅผ ๋‘ ๋ฒˆ ์ž‘์„ฑํ•˜๋Š” ์ฝ”๋“œ ๋ฐ˜๋ณต์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip | ํŒ + +๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด `Annotated` ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. + +/// ```Python commons: CommonQueryParams = Depends(CommonQueryParams) ``` -**FastAPI**๋Š” *ํŠนํžˆ* ์˜์กด์„ฑ์ด **FastAPI**๊ฐ€ ํด๋ž˜์Šค ์ž์ฒด์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด "ํ˜ธ์ถœ"ํ•˜๋Š” ํด๋ž˜์Šค์ธ ๊ฒฝ์šฐ, ์กฐ๊ธˆ ๋” ์‰ฌ์šด ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +//// -์ด๋Ÿฌํ•œ ํŠน์ •ํ•œ ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +**FastAPI**๋Š” ์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•œ ๋‹จ์ถ• ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ์˜์กด์„ฑ์€ *ํŠนํžˆ* **FastAPI**๊ฐ€ "ํ˜ธ์ถœ"ํ•ด์„œ ํด๋ž˜์Šค ์ž์ฒด์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค๋„๋ก ํ•˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. -์ด๋ ‡๊ฒŒ ์“ฐ๋Š” ๊ฒƒ ๋Œ€์‹ : +์ด ํŠน์ •ํ•œ ๊ฒฝ์šฐ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +๋‹ค์Œ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹ : + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip | ํŒ + +๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด `Annotated` ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. + +/// ```Python commons: CommonQueryParams = Depends(CommonQueryParams) ``` -...์ด๋ ‡๊ฒŒ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.: +//// + +...์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends()] +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip | ํŒ + +๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด `Annotated` ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. + +/// ```Python commons: CommonQueryParams = Depends() ``` -์˜์กด์„ฑ์„ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•˜๋Š” ๊ฒฝ์šฐ `Depends(CommonQueryParams)`์ฒ˜๋Ÿผ ํด๋ž˜์Šค ์ด๋ฆ„ ์ „์ฒด๋ฅผ *๋‹ค์‹œ* ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹ , ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋„ฃ์ง€ ์•Š์€ `Depends()`์˜ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +//// -์•„๋ž˜์— ๊ฐ™์€ ์˜ˆ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: +์˜์กด์„ฑ์„ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•˜๊ณ , `Depends(CommonQueryParams)` ์•ˆ์— ํด๋ž˜์Šค ์ „์ฒด๋ฅผ *๋‹ค์‹œ* ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹  ๋งค๊ฐœ๋ณ€์ˆ˜ ์—†์ด `Depends()`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/dependencies/tutorial004.py hl[19] *} +๊ทธ๋Ÿฌ๋ฉด ๊ฐ™์€ ์˜ˆ์ œ๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ณด์ผ ๊ฒ๋‹ˆ๋‹ค: -...์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ๋‹จ์ถ•ํ•˜์—ฌ๋„ **FastAPI**๋Š” ๋ฌด์—‡์„ ํ•ด์•ผํ•˜๋Š”์ง€ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *} + +...๊ทธ๋ฆฌ๊ณ  **FastAPI**๋Š” ๋ฌด์—‡์„ ํ•ด์•ผ ํ•˜๋Š”์ง€ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. /// tip | ํŒ -๋งŒ์•ฝ ์ด๊ฒƒ์ด ๋„์›€์ด ๋˜๊ธฐ๋ณด๋‹ค ๋” ํ—ท๊ฐˆ๋ฆฌ๊ฒŒ ๋งŒ๋“ ๋‹ค๋ฉด, ์žŠ์–ด๋ฒ„๋ฆฌ์‹ญ์‹œ์˜ค. ์ด๊ฒƒ์ด ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. +๋„์›€์ด ๋˜๊ธฐ๋ณด๋‹ค ๋” ํ—ท๊ฐˆ๋ฆฐ๋‹ค๋ฉด, ๋ฌด์‹œํ•˜์„ธ์š”. ์ด๊ฑด *ํ•„์ˆ˜*๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. -์ด๊ฒƒ์€ ๋‹จ์ง€ ์†์‰ฌ์šด ๋ฐฉ๋ฒ•์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด **FastAPI**๋Š” ์ฝ”๋“œ ๋ฐ˜๋ณต์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +๊ทธ์ € ๋‹จ์ถ• ๋ฐฉ๋ฒ•์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. **FastAPI**๋Š” ์ฝ”๋“œ ๋ฐ˜๋ณต์„ ์ตœ์†Œํ™”ํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” ๊ฒƒ์„ ์ค‘์š”ํ•˜๊ฒŒ ์ƒ๊ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. /// 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 4a3854cefa..39c78c0786 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,4 +1,4 @@ -# ๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์—์„œ์˜ ์˜์กด์„ฑ +# ๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์—์„œ์˜ ์˜์กด์„ฑ { #dependencies-in-path-operation-decorators } ๋ช‡๋ช‡ ๊ฒฝ์šฐ์—๋Š”, *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜* ์•ˆ์—์„œ ์˜์กด์„ฑ์˜ ๋ฐ˜ํ™˜ ๊ฐ’์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. @@ -8,7 +8,7 @@ ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์—, `Depends`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค *๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— `dependencies`์˜ `list`๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## *๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— `dependencies` ์ถ”๊ฐ€ํ•˜๊ธฐ +## *๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— `dependencies` ์ถ”๊ฐ€ํ•˜๊ธฐ { #add-dependencies-to-the-path-operation-decorator } *๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*๋Š” `dependencies`๋ผ๋Š” ์„ ํƒ์ ์ธ ์ธ์ž๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. @@ -22,7 +22,7 @@ ์ผ๋ถ€ ํŽธ์ง‘๊ธฐ์—์„œ๋Š” ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ  ์˜ค๋ฅ˜๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. -*๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ `dependencies`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŽธ์ง‘๊ธฐ/๋„๊ตฌ ์˜ค๋ฅ˜๋ฅผ ํ”ผํ•˜๋ฉฐ ์‹คํ–‰๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ ์ด๋Ÿฌํ•œ `dependencies`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŽธ์ง‘๊ธฐ/๋„๊ตฌ ์˜ค๋ฅ˜๋ฅผ ํ”ผํ•˜๋ฉด์„œ๋„ ์‹คํ–‰๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ณด๊ณ  ๋ถˆํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋Š” ์ƒˆ๋กœ์šด ๊ฐœ๋ฐœ์ž์˜ ํ˜ผ๋ž€์„ ๋ฐฉ์ง€ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -36,23 +36,23 @@ /// -## ์˜์กด์„ฑ ์˜ค๋ฅ˜์™€ ๊ฐ’ ๋ฐ˜ํ™˜ํ•˜๊ธฐ +## ์˜์กด์„ฑ ์˜ค๋ฅ˜์™€ ๊ฐ’ ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #dependencies-errors-and-return-values } ํ‰์†Œ์— ์‚ฌ์šฉํ•˜๋˜๋Œ€๋กœ ๊ฐ™์€ ์˜์กด์„ฑ *ํ•จ์ˆ˜*๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### ์˜์กด์„ฑ ์š”๊ตฌ์‚ฌํ•ญ +### ์˜์กด์„ฑ ์š”๊ตฌ์‚ฌํ•ญ { #dependency-requirements } (ํ—ค๋”๊ฐ™์€) ์š”์ฒญ ์š”๊ตฌ์‚ฌํ•ญ์ด๋‚˜ ํ•˜์œ„-์˜์กด์„ฑ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: {* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} -### ์˜ค๋ฅ˜ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ +### ์˜ค๋ฅ˜ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ { #raise-exceptions } -๋‹ค์Œ ์˜์กด์„ฑ์€ ๊ธฐ์กด ์˜์กด์„ฑ๊ณผ ๋™์ผํ•˜๊ฒŒ ์˜ˆ์™ธ๋ฅผ `raise`๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋‹ค์Œ ์˜์กด์„ฑ์€ ๊ธฐ์กด ์˜์กด์„ฑ๊ณผ ๋™์ผํ•˜๊ฒŒ ์˜ˆ์™ธ๋ฅผ `raise`ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: {* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} -### ๊ฐ’ ๋ฐ˜ํ™˜ํ•˜๊ธฐ +### ๊ฐ’ ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #return-values } ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜, ๊ทธ๋Ÿฌ์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ฐ’์€ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. @@ -60,10 +60,10 @@ {* ../../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` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋Š” ๋ฒ•์— ๋Œ€ํ•ด์„œ ๋ฐฐ์šฐ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -## ์ „์—ญ ์˜์กด์„ฑ +## ์ „์—ญ ์˜์กด์„ฑ { #global-dependencies } ๋‹ค์Œ์œผ๋กœ ๊ฐ *๊ฒฝ๋กœ ์ž‘๋™*์— ์ ์šฉ๋˜๋„๋ก `FastAPI` ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์— ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฒ•์„ ๋ณผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md index ff174937de..9bf6c0693a 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๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ +# `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ { #dependencies-with-yield } -FastAPI๋Š” <abbr title='๋•Œ๋กœ๋Š” "์ข…๋ฃŒ ์ฝ”๋“œ", "์ •๋ฆฌ ์ฝ”๋“œ", "์ข…๋ฃŒ ์ฒ˜๋ฆฌ ์ฝ”๋“œ", "๋‹ซ๊ธฐ ์ฝ”๋“œ", "์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž ์ข…๋ฃŒ ์ฝ”๋“œ" ๋“ฑ์œผ๋กœ๋„ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค'>์ž‘์—… ์™„๋ฃŒ ํ›„ ์ถ”๊ฐ€ ๋‹จ๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š”</abbr> ์˜์กด์„ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +FastAPI๋Š” <abbr title='sometimes also called "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code", etc. โ€“ ๋•Œ๋กœ๋Š” "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code" ๋“ฑ์œผ๋กœ๋„ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค'>์ž‘์—… ์™„๋ฃŒ ํ›„ ์ถ”๊ฐ€ ๋‹จ๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š”</abbr> ์˜์กด์„ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด `return` ๋Œ€์‹  `yield`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์ถ”๊ฐ€๋กœ ์‹คํ–‰ํ•  ๋‹จ๊ณ„ (์ฝ”๋“œ)๋ฅผ ๊ทธ ๋’ค์— ์ž‘์„ฑํ•˜์„ธ์š”. @@ -23,21 +23,21 @@ FastAPI๋Š” <abbr title='๋•Œ๋กœ๋Š” "์ข…๋ฃŒ ์ฝ”๋“œ", "์ •๋ฆฌ ์ฝ”๋“œ", "์ข…๋ฃŒ ์ฒ˜ /// -## `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์˜์กด์„ฑ +## `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์˜์กด์„ฑ { #a-database-dependency-with-yield } ์˜ˆ๋ฅผ ๋“ค์–ด, ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธ์…˜์„ ์ƒ์„ฑํ•˜๊ณ  ์ž‘์—…์ด ๋๋‚œ ํ›„์— ์„ธ์…˜์„ ์ข…๋ฃŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๊ธฐ ์ „์—๋Š” `yield`๋ฌธ์„ ํฌํ•จํ•˜์—ฌ ๊ทธ ์ด์ „์˜ ์ฝ”๋“œ๋งŒ์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} -yield๋œ ๊ฐ’์€ *๊ฒฝ๋กœ ์ž‘์—…* ๋ฐ ๋‹ค๋ฅธ ์˜์กด์„ฑ๋“ค์— ์ฃผ์ž…๋˜๋Š” ๊ฐ’ ์ž…๋‹ˆ๋‹ค: +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 | ํŒ @@ -47,19 +47,19 @@ yield๋œ ๊ฐ’์€ *๊ฒฝ๋กœ ์ž‘์—…* ๋ฐ ๋‹ค๋ฅธ ์˜์กด์„ฑ๋“ค์— ์ฃผ์ž…๋˜๋Š” ๊ฐ’ ์ž… /// -## `yield`์™€ `try`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ +## `yield`์™€ `try`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ { #a-dependency-with-yield-and-try } `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ `try` ๋ธ”๋ก์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๋„์ค‘ ๋ฐœ์ƒํ•œ ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค๋ฅธ ์˜์กด์„ฑ์ด๋‚˜ *๊ฒฝ๋กœ ์ž‘์—…*์˜ ์ค‘๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŠธ๋žœ์žญ์…˜ "๋กค๋ฐฑ"์ด ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ์˜์กด์„ฑ์—์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค๋ฅธ ์˜์กด์„ฑ์ด๋‚˜ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ ์ค‘๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŠธ๋žœ์žญ์…˜ "๋กค๋ฐฑ"์ด ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ์˜์กด์„ฑ์—์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์˜์กด์„ฑ ๋‚ด์—์„œ `except SomeException`์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, `finally`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ ๋ฐœ์ƒ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„ ์—†์ด ์ข…๋ฃŒ ๋‹จ๊ณ„๊นŒ ์‹คํ–‰๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} -## `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ•˜์œ„ ์˜์กด์„ฑ +## `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ•˜์œ„ ์˜์กด์„ฑ { #sub-dependencies-with-yield } ๋ชจ๋“  ํฌ๊ธฐ์™€ ํ˜•ํƒœ์˜ ํ•˜์œ„ ์˜์กด์„ฑ๊ณผ ํ•˜์œ„ ์˜์กด์„ฑ์˜ "ํŠธ๋ฆฌ"๋„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋“ค ๋ชจ๋‘๊ฐ€ `yield`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -87,21 +87,23 @@ yield๋œ ๊ฐ’์€ *๊ฒฝ๋กœ ์ž‘์—…* ๋ฐ ๋‹ค๋ฅธ ์˜์กด์„ฑ๋“ค์— ์ฃผ์ž…๋˜๋Š” ๊ฐ’ ์ž… /// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ -ํŒŒ์ด์ฌ์˜ <a href=โ€œhttps://docs.python.org/3/library/contextlib.htmlโ€ class=โ€œexternal-linkโ€ target=โ€œ_blankโ€>Context Managers</a> ๋•๋ถ„์— ์ด ๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. +ํŒŒ์ด์ฌ์˜ <a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">Context Managers</a> ๋•๋ถ„์— ์ด ๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. -**FastAPI**๋Š” ์ด๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. +**FastAPI**๋Š” ์ด๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฅผ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. /// -## `yield`์™€ `HTTPException`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ +## `yield`์™€ `HTTPException`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ { #dependencies-with-yield-and-httpexception } -`yield`์™€ `try` ๋ธ”๋ก์ด ์žˆ๋Š” ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +`yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ `try` ๋ธ”๋ก์„ ์‚ฌ์šฉํ•ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , ๊ทธ ๋‹ค์Œ `finally` ๋’ค์— ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. -๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ, `yield` ์ดํ›„์˜ ์ข…๋ฃŒ ์ฝ”๋“œ์—์„œ `HTTPException`์ด๋‚˜ ์œ ์‚ฌํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ `except`๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ ์žก๊ณ  ๊ทธ์— ๋Œ€ํ•ด ๋ฌด์–ธ๊ฐ€๋ฅผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, `HTTPException` ๊ฐ™์€ ๋‹ค๋ฅธ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// tip | ํŒ -์ด๋Š” ๋‹ค์†Œ ๊ณ ๊ธ‰ ๊ธฐ์ˆ ์ด๋ฉฐ, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๊ฒฝ๋กœ ์—ฐ์‚ฐ ํ•จ์ˆ˜ ๋“ฑ ๋‚˜๋จธ์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ๋‚ด๋ถ€์—์„œ ์˜ˆ์™ธ (`HTTPException` ํฌํ•จ)๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์‹ค์ œ๋กœ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ด๋Š” ๋‹ค์†Œ ๊ณ ๊ธ‰ ๊ธฐ์ˆ ์ด๋ฉฐ, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์‹ค์ œ๋กœ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* ๋“ฑ ๋‚˜๋จธ์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ๋‚ด๋ถ€์—์„œ ์˜ˆ์™ธ (`HTTPException` ํฌํ•จ)๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ @@ -109,27 +111,27 @@ yield๋œ ๊ฐ’์€ *๊ฒฝ๋กœ ์ž‘์—…* ๋ฐ ๋‹ค๋ฅธ ์˜์กด์„ฑ๋“ค์— ์ฃผ์ž…๋˜๋Š” ๊ฐ’ ์ž… {* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} -์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ (๋˜๋Š” ์ถ”๊ฐ€๋กœ ๋‹ค๋ฅธ `HTTPException`์„ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด) ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์€ [์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค. +์˜ˆ์™ธ๋ฅผ ์žก๊ณ  ๊ทธ์— ๊ธฐ๋ฐ˜ํ•ด ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋ ค๋ฉด, [์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”. -## `yield`์™€ `except`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ +## `yield`์™€ `except`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ { #dependencies-with-yield-and-except } -`yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ `except`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•˜๊ณ  ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ฑฐ๋‚˜ (๋˜๋Š” ์ƒˆ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š์œผ๋ฉด), FastAPI๋Š” ํ•ด๋‹น ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ผ๋ฐ˜์ ์ธ Python ๋ฐฉ์‹๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค: +`yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ `except`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•˜๊ณ  ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ฑฐ๋‚˜ (๋˜๋Š” ์ƒˆ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š์œผ๋ฉด), FastAPI๋Š” ์ผ๋ฐ˜์ ์ธ Python์—์„œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์˜ˆ์™ธ๊ฐ€ ์žˆ์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•„์ฐจ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} -์ด ๊ฒฝ์šฐ, `HTTPException`์ด๋‚˜ ์œ ์‚ฌํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ๋Š” HTTP 500 Internal Server Error ์‘๋‹ต์„ ๋ณด๊ฒŒ ๋˜์ง€๋งŒ, ์„œ๋ฒ„๋Š” ์–ด๋–ค ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€์— ๋Œ€ํ•œ **๋กœ๊ทธ**๋‚˜ ๋‹ค๋ฅธ ํ‘œ์‹œ๋ฅผ ์ „ํ˜€ ๊ฐ€์ง€์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๐Ÿ˜ฑ +์ด ๊ฒฝ์šฐ, `HTTPException`์ด๋‚˜ ์œ ์‚ฌํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ๋Š” ๋งˆ๋•…ํžˆ *HTTP 500 Internal Server Error* ์‘๋‹ต์„ ๋ณด๊ฒŒ ๋˜์ง€๋งŒ, ์„œ๋ฒ„์—๋Š” ์–ด๋–ค ์˜ค๋ฅ˜์˜€๋Š”์ง€์— ๋Œ€ํ•œ **๋กœ๊ทธ**๋‚˜ ๋‹ค๋ฅธ ํ‘œ์‹œ๊ฐ€ **์ „ํ˜€ ๋‚จ์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค**. ๐Ÿ˜ฑ -### `yield`์™€ `except`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ ํ•ญ์ƒ `raise` ํ•˜๊ธฐ +### `yield`์™€ `except`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ ํ•ญ์ƒ `raise` ํ•˜๊ธฐ { #always-raise-in-dependencies-with-yield-and-except } -`yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์—์„œ ์˜ˆ์™ธ๋ฅผ ์žก์•˜์„ ๋•Œ๋Š” `HTTPException`์ด๋‚˜ ์œ ์‚ฌํ•œ ์˜ˆ์™ธ๋ฅผ ์ƒˆ๋กœ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๋Š” ํ•œ, ๋ฐ˜๋“œ์‹œ ์›๋ž˜์˜ ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค. +`yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์—์„œ ์˜ˆ์™ธ๋ฅผ ์žก์•˜์„ ๋•Œ, ๋‹ค๋ฅธ `HTTPException`์ด๋‚˜ ์œ ์‚ฌํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ๋ฉด, **์›๋ž˜ ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค**. `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 } ์‹คํ–‰ ์ˆœ์„œ๋Š” ์•„๋ž˜ ๋‹ค์ด์–ด๊ทธ๋žจ๊ณผ ๊ฑฐ์˜ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. ์‹œ๊ฐ„์€ ์œ„์—์„œ ์•„๋ž˜๋กœ ํ๋ฆ…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ ์—ด์€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๊ฑฐ๋‚˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ถ€๋ถ„ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. @@ -170,7 +172,7 @@ participant tasks as Background tasks /// info | ์ •๋ณด -ํด๋ผ์ด์–ธํŠธ์— **ํ•˜๋‚˜์˜ ์‘๋‹ต** ๋งŒ ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์˜ค๋ฅ˜ ์‘๋‹ต ์ค‘ ํ•˜๋‚˜์ผ ์ˆ˜๋„ ์žˆ๊ณ ,*๊ฒฝ๋กœ ์ž‘์—…*์—์„œ ์ƒ์„ฑ๋œ ์‘๋‹ต์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +ํด๋ผ์ด์–ธํŠธ์—๋Š” **ํ•˜๋‚˜์˜ ์‘๋‹ต**๋งŒ ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์˜ค๋ฅ˜ ์‘๋‹ต ์ค‘ ํ•˜๋‚˜์ผ ์ˆ˜๋„ ์žˆ๊ณ , *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ ์ƒ์„ฑ๋œ ์‘๋‹ต์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์‘๋‹ต ์ค‘ ํ•˜๋‚˜๊ฐ€ ์ „์†ก๋œ ํ›„์—๋Š” ๋‹ค๋ฅธ ์‘๋‹ต์„ ๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. @@ -178,55 +180,67 @@ participant tasks as Background tasks /// tip | ํŒ -์ด ๋‹ค์ด์–ด๊ทธ๋žจ์€ `HTTPException`์„ ๋ณด์—ฌ์ฃผ์ง€๋งŒ, `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ ์ฒ˜๋ฆฌํ•œ ์˜ˆ์™ธ๋‚˜ [์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•œ ๋‹ค๋ฅธ ์˜ˆ์™ธ๋„ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -์–ด๋–ค ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋“ , `HTTPException`์„ ํฌํ•จํ•˜์—ฌ yield๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์œผ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œํ‚ค๊ฑฐ๋‚˜ ์ƒˆ๋กœ์šด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ์ฝ”๋“œ์—์„œ ์–ด๋–ค ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ฉด `HTTPException`์„ ํฌํ•จํ•ด `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์œผ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ํ•ด๋‹น ์˜ˆ์™ธ(๋˜๋Š” ์ƒˆ ์˜ˆ์™ธ)๋ฅผ `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"`์ž…๋‹ˆ๋‹ค. -์ด๋Š” ์ฒ˜๋ฆฌ๊ธฐ ์—†์ด ์ „๋‹ฌ๋œ ์˜ˆ์™ธ(๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜)์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ๋ฉ”๋ชจ๋ฆฌ ์†Œ๋น„๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ์ผ๋ฐ˜ ํŒŒ์ด์ฌ ์ฝ”๋“œ์˜ ๋™์ž‘๊ณผ ์ผ์น˜ํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด 0.110.0 ๋ฒ„์ „์—์„œ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +### ํ•˜์œ„ ์˜์กด์„ฑ์„ ์œ„ํ•œ `scope` { #scope-for-sub-dependencies } -### ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…๊ณผ `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ, ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ +`scope="request"`(๊ธฐ๋ณธ๊ฐ’)๋กœ ์˜์กด์„ฑ์„ ์„ ์–ธํ•˜๋ฉด, ๋ชจ๋“  ํ•˜์œ„ ์˜์กด์„ฑ๋„ `scope`๊ฐ€ `"request"`์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. -FastAPI 0.106.0 ์ด์ „์—๋Š” `yield` ์ดํ›„์— ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค. `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ ์ข…๋ฃŒ ์ฝ”๋“œ๋Š” ์‘๋‹ต์ด ์ „์†ก๋œ ์ดํ›„์— ์‹คํ–‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, [์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}๊ฐ€ ์ด๋ฏธ ์‹คํ–‰๋œ ์ƒํƒœ์˜€์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ `scope`๊ฐ€ `"function"`์ธ ์˜์กด์„ฑ์€ `scope`๊ฐ€ `"function"`์ธ ์˜์กด์„ฑ๊ณผ `"request"`์ธ ์˜์กด์„ฑ์„ ๋ชจ๋‘ ์˜์กด์„ฑ์œผ๋กœ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๋Š” ์ฃผ๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ๋‚ด์—์„œ ์˜์กด์„ฑ์—์„œ "yield๋œ" ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ข…๋ฃŒ ์ฝ”๋“œ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ด ์™„๋ฃŒ๋œ ํ›„์— ์‹คํ–‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค +์ด๋Š” ์–ด๋–ค ์˜์กด์„ฑ์ด๋“ , ์ข…๋ฃŒ ์ฝ”๋“œ์—์„œ ํ•˜์œ„ ์˜์กด์„ฑ์„ ๊ณ„์† ์‚ฌ์šฉํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์œผ๋ฏ€๋กœ, ํ•˜์œ„ ์˜์กด์„ฑ๋ณด๋‹ค ๋จผ์ € ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฆฌ์†Œ์Šค๋ฅผ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์–‘๋ณดํ•œ ์˜์กด์„ฑ(์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ)์—์„œ ๋ณด์œ ํ•˜๋ฉด์„œ ์‘๋‹ต์ด ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ์ด๋™ํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜๊ธฐ ๋•Œ๋ฌธ์— FastAPI 0.106.0์—์„œ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +```mermaid +sequenceDiagram -/// tip | ํŒ +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`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์˜ ๋ฆฌ์†Œ์Šค์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. +FastAPI์˜ ์—ฌ๋Ÿฌ ๋ฒ„์ „์—์„œ ๋ฌด์—‡์ด ๋ฐ”๋€Œ์—ˆ๋Š”์ง€ ๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด, ๊ณ ๊ธ‰ ๊ฐ€์ด๋“œ์˜ [๊ณ ๊ธ‰ ์˜์กด์„ฑ - `yield`, `HTTPException`, `except` ๋ฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}์—์„œ ๋” ์ž์„ธํžˆ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +## ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž { #context-managers } -์˜ˆ๋ฅผ ๋“ค์–ด, ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ๋‚ด๋ถ€์—์„œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธ์…˜์„ ์ƒ์„ฑํ•˜๊ณ  ์ด ์ƒˆ๋กœ์šด ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ์ฒด๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ง์ ‘ ์ „๋‹ฌํ•˜๋Š” ๋Œ€์‹ , ํ•ด๋‹น ๊ฐ์ฒด์˜ ID๋ฅผ ์ „๋‹ฌํ•œ ๋‹ค์Œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ๊ฐ์ฒด๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค - -## ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž - -### "์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž"๋ž€? +### "์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž"๋ž€ { #what-are-context-managers } "์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž"๋Š” Python์—์„œ `with` ๋ฌธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank"> `with`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค</a>: +์˜ˆ๋ฅผ ๋“ค์–ด, <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank">`with`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค</a>: ```Python with open("./somefile.txt") as f: @@ -240,7 +254,7 @@ with open("./somefile.txt") as f: `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์„ ์ƒ์„ฑํ•˜๋ฉด **FastAPI**๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ฅผ ์œ„ํ•œ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค๋ฅธ ๊ด€๋ จ ๋„๊ตฌ๋“ค๊ณผ ๊ฒฐํ•ฉํ•ฉ๋‹ˆ๋‹ค. -### `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž ์‚ฌ์šฉํ•˜๊ธฐ +### `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž ์‚ฌ์šฉํ•˜๊ธฐ { #using-context-managers-in-dependencies-with-yield } /// warning | ๊ฒฝ๊ณ  @@ -255,7 +269,7 @@ Python์—์„œ๋Š” ๋‹ค์Œ์„ ํ†ตํ•ด ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต **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 | ํŒ diff --git a/docs/ko/docs/tutorial/dependencies/global-dependencies.md b/docs/ko/docs/tutorial/dependencies/global-dependencies.md index 0d0e7684db..68e4295da2 100644 --- a/docs/ko/docs/tutorial/dependencies/global-dependencies.md +++ b/docs/ko/docs/tutorial/dependencies/global-dependencies.md @@ -1,15 +1,16 @@ -# ์ „์—ญ ์˜์กด์„ฑ +# ์ „์—ญ ์˜์กด์„ฑ { #global-dependencies } -๋ช‡๋ช‡ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์— ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ช‡๋ช‡ ์œ ํ˜•์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์— ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -[*๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— `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_an_py39.py hl[16] *} +{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *} -๊ทธ๋ฆฌ๊ณ  [*๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— `dependencies` ์ถ”๊ฐ€ํ•˜๊ธฐ](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}์— ๋Œ€ํ•œ ์•„์ด๋””์–ด๋Š” ์—ฌ์ „ํžˆ ์ ์šฉ๋˜์ง€๋งŒ ์—ฌ๊ธฐ์—์„œ๋Š” ์•ฑ์— ์žˆ๋Š” ๋ชจ๋“  *๊ฒฝ๋กœ ์ž‘๋™*์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. -## *๊ฒฝ๋กœ ์ž‘๋™* ๋ชจ์Œ์— ๋Œ€ํ•œ ์˜์กด์„ฑ +๊ทธ๋ฆฌ๊ณ  [*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— `dependencies` ์ถ”๊ฐ€ํ•˜๊ธฐ](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} ์„น์…˜์˜ ๋ชจ๋“  ์•„์ด๋””์–ด๋Š” ์—ฌ์ „ํžˆ ์ ์šฉ๋˜์ง€๋งŒ, ์ด ๊ฒฝ์šฐ์—๋Š” ์•ฑ์˜ ๋ชจ๋“  *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. -์ดํ›„์— ์—ฌ๋Ÿฌ ํŒŒ์ผ๋“ค์„ ๊ฐ€์ง€๋Š” ๋” ํฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์กฐํ™”ํ•˜๋Š” ๋ฒ•([๋” ํฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ - ์—ฌ๋Ÿฌ ํŒŒ์ผ๋“ค](../../tutorial/bigger-applications.md){.internal-link target=_blank})์„ ์ฝ์„ ๋•Œ, *๊ฒฝ๋กœ ์ž‘๋™* ๋ชจ์Œ์— ๋Œ€ํ•œ ๋‹จ์ผ `dependencies` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋Š” ๋ฒ•์— ๋Œ€ํ•ด์„œ ๋ฐฐ์šฐ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +## *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ๊ทธ๋ฃน์— ๋Œ€ํ•œ ์˜์กด์„ฑ { #dependencies-for-groups-of-path-operations } + +๋‚˜์ค‘์— ์—ฌ๋Ÿฌ ํŒŒ์ผ์„ ํฌํ•จํ•  ์ˆ˜๋„ ์žˆ๋Š” ๋” ํฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์กฐํ™”ํ•˜๋Š” ๋ฒ•([๋” ํฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ - ์—ฌ๋Ÿฌ ํŒŒ์ผ๋“ค](../../tutorial/bigger-applications.md){.internal-link target=_blank})์„ ์ฝ์„ ๋•Œ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ๊ทธ๋ฃน์— ๋Œ€ํ•œ ๋‹จ์ผ `dependencies` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋Š” ๋ฒ•์„ ๋ฐฐ์šฐ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/dependencies/index.md b/docs/ko/docs/tutorial/dependencies/index.md index b35a41e37a..167e6a4784 100644 --- a/docs/ko/docs/tutorial/dependencies/index.md +++ b/docs/ko/docs/tutorial/dependencies/index.md @@ -1,14 +1,14 @@ -# ์˜์กด์„ฑ +# ์˜์กด์„ฑ { #dependencies } -**FastAPI**๋Š” ์•„์ฃผ ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ์ง๊ด€์ ์ธ **<abbr title="์ปดํฌ๋„ŒํŠธ, ์ž์›, ์ œ๊ณต์ž, ์„œ๋น„์Šค, ์ธ์ ํ„ฐ๋ธ”๋กœ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค">์˜์กด์„ฑ ์ฃผ์ž…</abbr>** ์‹œ์Šคํ…œ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +**FastAPI**๋Š” ์•„์ฃผ ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ์ง๊ด€์ ์ธ **<abbr title="also known as components, resources, providers, services, injectables">์˜์กด์„ฑ ์ฃผ์ž…</abbr>** ์‹œ์Šคํ…œ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ์•„์ฃผ ์‰ฝ๊ฒŒ ์„ค๊ณ„ํ–ˆ์œผ๋ฉฐ, ์–ด๋А ๊ฐœ๋ฐœ์ž๋‚˜ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์™€ **FastAPI**๋ฅผ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. -## "์˜์กด์„ฑ ์ฃผ์ž…"์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? +## "์˜์กด์„ฑ ์ฃผ์ž…"์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? { #what-is-dependency-injection } -**"์˜์กด์„ฑ ์ฃผ์ž…"**์€ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ(์ด ๊ฒฝ์šฐ, ๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜)๊ฐ€ ์ž‘๋™ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฒƒ, ์ฆ‰ "์˜์กด์„ฑ"์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +**"์˜์กด์„ฑ ์ฃผ์ž…"**์€ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ(์ด ๊ฒฝ์šฐ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*)๊ฐ€ ์ž‘๋™ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฒƒ, ์ฆ‰ "์˜์กด์„ฑ"์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -๊ทธ ํ›„์—, ์‹œ์Šคํ…œ(์ด ๊ฒฝ์šฐ FastAPI)์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ์˜์กด์„ฑ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๋ชจ๋“  ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.(์˜์กด์„ฑ์„ "์ฃผ์ž…"ํ•ฉ๋‹ˆ๋‹ค) +๊ทธ ํ›„์—, ์‹œ์Šคํ…œ(์ด ๊ฒฝ์šฐ **FastAPI**)์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ์˜์กด์„ฑ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๋ชจ๋“  ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.(์˜์กด์„ฑ์„ "์ฃผ์ž…"ํ•ฉ๋‹ˆ๋‹ค) ์ด๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‚ฌํ•ญ์„ ํ•„์š”๋กœ ํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค: @@ -19,17 +19,17 @@ ์ด ๋ชจ๋“  ์‚ฌํ•ญ์„ ํ•  ๋•Œ ์ฝ”๋“œ ๋ฐ˜๋ณต์„ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. -## ์ฒซ๋ฒˆ์งธ ๋‹จ๊ณ„ +## ์ฒซ๋ฒˆ์งธ ๋‹จ๊ณ„ { #first-steps } ์•„์ฃผ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ๋ด…์‹œ๋‹ค. ๋„ˆ๋ฌด ๊ฐ„๋‹จํ•  ๊ฒƒ์ด๊ธฐ์— ์ง€๊ธˆ ๋‹น์žฅ์€ ์œ ์šฉํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ฅผ ํ†ตํ•ด **์˜์กด์„ฑ ์ฃผ์ž…** ์‹œ์Šคํ…œ์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€์— ์ค‘์ ์„ ๋‘˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -### ์˜์กด์„ฑ ํ˜น์€ "๋””ํŽœ๋”๋ธ”" ๋งŒ๋“ค๊ธฐ +### ์˜์กด์„ฑ ํ˜น์€ "๋””ํŽœ๋”๋ธ”" ๋งŒ๋“ค๊ธฐ { #create-a-dependency-or-dependable } ์˜์กด์„ฑ์— ์ง‘์ค‘ํ•ด ๋ด…์‹œ๋‹ค. -*๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ–๋Š” ๋‹จ์ˆœํ•œ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค: +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๊ฐ€ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ–๋Š” ๋‹จ์ˆœํ•œ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค: {* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *} @@ -37,9 +37,9 @@ **๋‹จ ๋‘ ์ค„์ž…๋‹ˆ๋‹ค**. -๊ทธ๋ฆฌ๊ณ , ์ด ํ•จ์ˆ˜๋Š” ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ชจ๋“  *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ์™€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ , ์ด ํ•จ์ˆ˜๋Š” ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ชจ๋“  *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ์™€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ๋ถ„์€ ์ด๋ฅผ "๋ฐ์ฝ”๋ ˆ์ดํ„ฐ"๊ฐ€ ์—†๋Š” (`@app.get("/some-path")`๊ฐ€ ์—†๋Š”) *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ๋ถ„์€ ์ด๋ฅผ "๋ฐ์ฝ”๋ ˆ์ดํ„ฐ"๊ฐ€ ์—†๋Š” (`@app.get("/some-path")`๊ฐ€ ์—†๋Š”) *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ๋ถ„์ด ์›ํ•˜๋Š” ๋ฌด์—‡์ด๋“  ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -47,7 +47,7 @@ * ์„ ํƒ์ ์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`, `str`์„ ์ž๋ฃŒํ˜•์œผ๋กœ ๊ฐ€์ง‘๋‹ˆ๋‹ค. * ์„ ํƒ์ ์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `skip`, `int`๋ฅผ ์ž๋ฃŒํ˜•์œผ๋กœ ๊ฐ€์ง€๋ฉฐ ๊ธฐ๋ณธ ๊ฐ’์€ `0`์ž…๋‹ˆ๋‹ค. -* ์„ ํƒ์ ์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `limit`,`int`๋ฅผ ์ž๋ฃŒํ˜•์œผ๋กœ ๊ฐ€์ง€๋ฉฐ ๊ธฐ๋ณธ ๊ฐ’์€ `100`์ž…๋‹ˆ๋‹ค. +* ์„ ํƒ์ ์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `limit` that is an `int`, and by default is `100`. ๊ทธ ํ›„ ์œ„์˜ ๊ฐ’์„ ํฌํ•จํ•œ `dict` ์ž๋ฃŒํ˜•์œผ๋กœ ๋ฐ˜ํ™˜ํ•  ๋ฟ์ž…๋‹ˆ๋‹ค. @@ -57,17 +57,17 @@ FastAPI๋Š” 0.95.0 ๋ฒ„์ „๋ถ€ํ„ฐ `Annotated`์— ๋Œ€ํ•œ ์ง€์›์„ (๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์˜›๋‚  ๋ฒ„์ „์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ, `Annotated`๋ฅผ ์‚ฌ์šฉํ•˜๋ ค ํ•˜๋ฉด ์—๋Ÿฌ๋ฅผ ๋งž์ดํ•˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -`Annotated`๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์ตœ์†Œ 0.95.1๋กœ [FastAPI ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ](../../deployment/versions.md#fastapi_2){.internal-link target=_blank}๋ฅผ ํ™•์‹คํ•˜๊ฒŒ ํ•˜์„ธ์š”. +`Annotated`๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์ตœ์†Œ 0.95.1๋กœ [FastAPI ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}๋ฅผ ํ™•์‹คํ•˜๊ฒŒ ํ•˜์„ธ์š”. /// -### `Depends` ๋ถˆ๋Ÿฌ์˜ค๊ธฐ +### `Depends` ๋ถˆ๋Ÿฌ์˜ค๊ธฐ { #import-depends } {* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *} -### "์˜์กด์ž"์— ์˜์กด์„ฑ ๋ช…์‹œํ•˜๊ธฐ +### "์˜์กด์ž"์— ์˜์กด์„ฑ ๋ช…์‹œํ•˜๊ธฐ { #declare-the-dependency-in-the-dependant } -*๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `Body`, `Query` ๋“ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹๊ณผ ๊ฐ™์ด ์ƒˆ๋กœ์šด ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `Depends`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `Body`, `Query` ๋“ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹๊ณผ ๊ฐ™์ด ์ƒˆ๋กœ์šด ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `Depends`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: {* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *} @@ -79,7 +79,7 @@ FastAPI๋Š” 0.95.0 ๋ฒ„์ „๋ถ€ํ„ฐ `Annotated`์— ๋Œ€ํ•œ ์ง€์›์„ (๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์—ฌ๋Ÿฌ๋ถ„์€ ์ง์ ‘ **ํ˜ธ์ถœํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค** (๋์— ๊ด„ํ˜ธ๋ฅผ ์น˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค), ๋‹จ์ง€ `Depends()`์— ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„˜๊ฒจ ์คฌ์„ ๋ฟ์ž…๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ๊ทธ ํ•จ์ˆ˜๋Š” *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๊ทธ ํ•จ์ˆ˜๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. /// tip | ํŒ @@ -91,7 +91,7 @@ FastAPI๋Š” 0.95.0 ๋ฒ„์ „๋ถ€ํ„ฐ `Annotated`์— ๋Œ€ํ•œ ์ง€์›์„ (๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ * ์˜ฌ๋ฐ”๋ฅธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„ ์˜์กด์„ฑ("๋””ํŽœ๋”๋ธ”") ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. * ํ•จ์ˆ˜์—์„œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค. -* *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์— ์žˆ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜์— ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค +* *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์— ์žˆ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜์— ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค ```mermaid graph TB @@ -104,7 +104,7 @@ common_parameters --> read_items common_parameters --> read_users ``` -์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ณต์šฉ ์ฝ”๋“œ๋ฅผ ํ•œ๋ฒˆ๋งŒ ์ ์–ด๋„ ๋˜๋ฉฐ, **FastAPI**๋Š” *๊ฒฝ๋กœ ์ž‘๋™*์„ ์œ„ํ•ด ์ด์— ๋Œ€ํ•œ ํ˜ธ์ถœ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ณต์šฉ ์ฝ”๋“œ๋ฅผ ํ•œ๋ฒˆ๋งŒ ์ ์–ด๋„ ๋˜๋ฉฐ, **FastAPI**๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์„ ์œ„ํ•ด ์ด์— ๋Œ€ํ•œ ํ˜ธ์ถœ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. /// check | ํ™•์ธ @@ -114,7 +114,7 @@ common_parameters --> read_users /// -## `Annotated`์ธ ์˜์กด์„ฑ ๊ณต์œ ํ•˜๊ธฐ +## `Annotated`์ธ ์˜์กด์„ฑ ๊ณต์œ ํ•˜๊ธฐ { #share-annotated-dependencies } ์œ„์˜ ์˜ˆ์ œ์—์„œ ๋ช‡๋ช‡ ์ž‘์€ **์ฝ”๋“œ ์ค‘๋ณต**์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์•˜์„ ๊ฒ๋‹ˆ๋‹ค. @@ -138,25 +138,25 @@ commons: Annotated[dict, Depends(common_parameters)] ์ด ์˜์กด์„ฑ์€ ๊ณ„์†ํ•ด์„œ ์˜ˆ์ƒํ•œ๋Œ€๋กœ ์ž‘๋™ํ•  ๊ฒƒ์ด๋ฉฐ, **์ œ์ผ ์ข‹์€ ๋ถ€๋ถ„**์€ **ํƒ€์ž… ์ •๋ณด๊ฐ€ ๋ณด์กด๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค**. ์ฆ‰ ์—ฌ๋Ÿฌ๋ถ„์˜ ํŽธ์ง‘๊ธฐ๊ฐ€ **์ž๋™ ์™„์„ฑ**, **์ธ๋ผ์ธ ์—๋Ÿฌ** ๋“ฑ์„ ๊ณ„์†ํ•ด์„œ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. `mypy`๊ฐ™์€ ๋‹ค๋ฅธ ๋„๊ตฌ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. -์ด๋Š” ํŠนํžˆ **๋งŽ์€ *๊ฒฝ๋กœ ์ž‘๋™***์—์„œ **๊ฐ™์€ ์˜์กด์„ฑ**์„ ๊ณ„์†ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” **๊ฑฐ๋Œ€ ์ฝ”๋“œ ๊ธฐ๋ฐ˜**์•ˆ์—์„œ ์‚ฌ์šฉํ•˜๋ฉด ์œ ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ด๋Š” ํŠนํžˆ **๋งŽ์€ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ***์—์„œ **๊ฐ™์€ ์˜์กด์„ฑ**์„ ๊ณ„์†ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” **๊ฑฐ๋Œ€ ์ฝ”๋“œ ๊ธฐ๋ฐ˜**์•ˆ์—์„œ ์‚ฌ์šฉํ•˜๋ฉด ์œ ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -## `async`ํ•˜๊ฒŒ, ํ˜น์€ `async`ํ•˜์ง€ ์•Š๊ฒŒ +## `async`ํ•˜๊ฒŒ, ํ˜น์€ `async`ํ•˜์ง€ ์•Š๊ฒŒ { #to-async-or-not-to-async } -์˜์กด์„ฑ์ด (*๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์—์„œ ์ฒ˜๋Ÿผ ๋˜‘๊ฐ™์ด) **FastAPI**์— ์˜ํ•ด ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•  ๋•Œ ๋™์ผํ•œ ๊ทœ์น™์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. +์˜์กด์„ฑ์ด (*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ์ฒ˜๋Ÿผ ๋˜‘๊ฐ™์ด) **FastAPI**์— ์˜ํ•ด ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•  ๋•Œ ๋™์ผํ•œ ๊ทœ์น™์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. `async def`์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ํ˜น์€ ์ผ๋ฐ˜์ ์ธ `def`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ์ผ๋ฐ˜์ ์ธ `def` *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜* ์•ˆ์— `async def`๋กœ ์˜์กด์„ฑ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, `async def` *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜* ์•ˆ์— `def`๋กœ ์˜์กด์„ฑ์„ ์„ ์–ธํ•˜๋Š” ๋“ฑ์˜ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์ผ๋ฐ˜์ ์ธ `def` *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* ์•ˆ์— `async def`๋กœ ์˜์กด์„ฑ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, `async def` *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* ์•ˆ์— `def`๋กœ ์˜์กด์„ฑ์„ ์„ ์–ธํ•˜๋Š” ๋“ฑ์˜ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ฌด ๋ฌธ์ œ ์—†์Šต๋‹ˆ๋‹ค. **FastAPI**๋Š” ๋ฌด์—‡์„ ํ• ์ง€ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. /// note | ์ฐธ๊ณ  -์ž˜ ๋ชจ๋ฅด์‹œ๊ฒ ๋‹ค๋ฉด, [Async: *"In a hurry?"*](../../async.md){.internal-link target=_blank} ๋ฌธ์„œ์—์„œ `async`์™€ `await`์— ๋Œ€ํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ž˜ ๋ชจ๋ฅด์‹œ๊ฒ ๋‹ค๋ฉด, [Async: *"In a hurry?"*](../../async.md#in-a-hurry){.internal-link target=_blank} ๋ฌธ์„œ์—์„œ `async`์™€ `await`์— ๋Œ€ํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## OpenAPI์™€ ํ†ตํ•ฉ +## OpenAPI์™€ ํ†ตํ•ฉ { #integrated-with-openapi } ๋ชจ๋“  ์š”์ฒญ ์„ ์–ธ, ๊ฒ€์ฆ๊ณผ ์˜์กด์„ฑ(๋ฐ ํ•˜์œ„ ์˜์กด์„ฑ)์— ๋Œ€ํ•œ ์š”๊ตฌ ์‚ฌํ•ญ์€ ๋™์ผํ•œ OpenAPI ์Šคํ‚ค๋งˆ์— ํ†ตํ•ฉ๋ฉ๋‹ˆ๋‹ค. @@ -164,15 +164,15 @@ commons: Annotated[dict, Depends(common_parameters)] <img src="/img/tutorial/dependencies/image01.png"> -## ๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ๋ฒ• +## ๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ๋ฒ• { #simple-usage } -์ด๋ฅผ ๋ณด๋ฉด, *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๋Š” *๊ฒฝ๋กœ*์™€ *์ž‘๋™*์ด ๋งค์นญ๋˜๋ฉด ์–ธ์ œ๋“ ์ง€ ์‚ฌ์šฉ๋˜๋„๋ก ์ •์˜๋˜์—ˆ์œผ๋ฉฐ, **FastAPI**๋Š” ์˜ฌ๋ฐ”๋ฅธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ํ•ด๋‹น ์š”์ฒญ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. +์ด๋ฅผ ๋ณด๋ฉด, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๋Š” *๊ฒฝ๋กœ*์™€ *์ž‘๋™*์ด ๋งค์นญ๋˜๋ฉด ์–ธ์ œ๋“ ์ง€ ์‚ฌ์šฉ๋˜๋„๋ก ์ •์˜๋˜์—ˆ์œผ๋ฉฐ, **FastAPI**๋Š” ์˜ฌ๋ฐ”๋ฅธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ํ•ด๋‹น ์š”์ฒญ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์‹ค, ๋ชจ๋“  (ํ˜น์€ ๋Œ€๋ถ€๋ถ„์˜) ์›น ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์ด์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์€ ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜๋“ค์„ ์ ˆ๋Œ€ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ(์ด ๊ฒฝ์šฐ **FastAPI**)์— ์˜ํ•ด ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. -์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ๊ณผ ํ•จ๊ป˜๋ผ๋ฉด **FastAPI**์—๊ฒŒ ์—ฌ๋Ÿฌ๋ถ„์˜ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋ฌด์–ธ๊ฐ€์— ์—ฌ๋Ÿฌ๋ถ„์˜ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜* ๋˜ํ•œ "์˜์กด"ํ•˜๊ณ  ์žˆ์Œ์„ ์•Œ๋ฆด ์ˆ˜ ์žˆ์œผ๋ฉฐ, **FastAPI**๋Š” ์ด๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ "์ฃผ์ž…"ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ๊ณผ ํ•จ๊ป˜๋ผ๋ฉด **FastAPI**์—๊ฒŒ ์—ฌ๋Ÿฌ๋ถ„์˜ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋ฌด์–ธ๊ฐ€์— ์—ฌ๋Ÿฌ๋ถ„์˜ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* ๋˜ํ•œ "์˜์กด"ํ•˜๊ณ  ์žˆ์Œ์„ ์•Œ๋ฆด ์ˆ˜ ์žˆ์œผ๋ฉฐ, **FastAPI**๋Š” ์ด๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ "์ฃผ์ž…"ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. "์˜์กด์„ฑ ์ฃผ์ž…"์ด๋ผ๋Š” ๋™์ผํ•œ ์•„์ด๋””์–ด์— ๋Œ€ํ•œ ๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ ์šฉ์–ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: @@ -182,15 +182,15 @@ commons: Annotated[dict, Depends(common_parameters)] * ์ธ์ ํ„ฐ๋ธ” * ์ปดํฌ๋„ŒํŠธ -## **FastAPI** ํ”Œ๋Ÿฌ๊ทธ์ธ +## **FastAPI** ํ”Œ๋Ÿฌ๊ทธ์ธ { #fastapi-plug-ins } -ํ†ตํ•ฉ๊ณผ "ํ”Œ๋Ÿฌ๊ทธ์ธ"์€ **์˜์กด์„ฑ ์ฃผ์ž…** ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ **"ํ”Œ๋Ÿฌ๊ทธ์ธ"์„ ๋งŒ๋“ค ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค**, ์™œ๋ƒํ•˜๋ฉด ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์—ฌ๋Ÿฌ๋ถ„์˜ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์— ํ†ตํ•ฉ๊ณผ ์ƒํ˜ธ ์ž‘์šฉ์„ ๋ฌดํ•œ๋Œ€๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +ํ†ตํ•ฉ๊ณผ "ํ”Œ๋Ÿฌ๊ทธ์ธ"์€ **์˜์กด์„ฑ ์ฃผ์ž…** ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ **"ํ”Œ๋Ÿฌ๊ทธ์ธ"์„ ๋งŒ๋“ค ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค**, ์™œ๋ƒํ•˜๋ฉด ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์—ฌ๋Ÿฌ๋ถ„์˜ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์— ํ†ตํ•ฉ๊ณผ ์ƒํ˜ธ ์ž‘์šฉ์„ ๋ฌดํ•œ๋Œ€๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  "๋ง ๊ทธ๋Œ€๋กœ", ๊ทธ์ € ํ•„์š”๋กœ ํ•˜๋Š” ํŒŒ์ด์ฌ ํŒจํ‚ค์ง€๋ฅผ ์ž„ํฌํŠธํ•˜๊ณ  ๋‹จ ๋ช‡ ์ค„์˜ ์ฝ”๋“œ๋กœ ์—ฌ๋Ÿฌ๋ถ„์˜ API ํ•จ์ˆ˜์™€ ํ†ตํ•ฉํ•จ์œผ๋กœ์จ, ์˜์กด์„ฑ์„ ์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ด€๊ณ„ํ˜• ๋ฐ NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๋ณด์•ˆ ๋“ฑ, ์ด์— ๋Œ€ํ•œ ์˜ˆ์‹œ๋ฅผ ๋‹ค์Œ ์žฅ์—์„œ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## **FastAPI** ํ˜ธํ™˜์„ฑ +## **FastAPI** ํ˜ธํ™˜์„ฑ { #fastapi-compatibility } ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์˜ ๋‹จ์ˆœํ•จ์€ **FastAPI**๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์š”์†Œ๋“ค๊ณผ ํ˜ธํ™˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค: @@ -203,7 +203,7 @@ commons: Annotated[dict, Depends(common_parameters)] * ์‘๋‹ต ๋ฐ์ดํ„ฐ ์ฃผ์ž… ์‹œ์Šคํ…œ * ๊ธฐํƒ€ ๋“ฑ๋“ฑ. -## ๊ฐ„ํŽธํ•˜๊ณ  ๊ฐ•๋ ฅํ•˜๋‹ค +## ๊ฐ„ํŽธํ•˜๊ณ  ๊ฐ•๋ ฅํ•˜๋‹ค { #simple-and-powerful } ๊ณ„์ธต์ ์ธ ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์€ ์ •์˜ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฝ์ง€๋งŒ, ์—ฌ์ „ํžˆ ๋งค์šฐ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. @@ -211,7 +211,7 @@ commons: Annotated[dict, Depends(common_parameters)] ๋์—๋Š”, ๊ณ„์ธต์ ์ธ ๋‚˜๋ฌด๋กœ ๋œ ์˜์กด์„ฑ์ด ๋งŒ๋“ค์–ด์ง€๋ฉฐ, ๊ทธ๋ฆฌ๊ณ  **์˜์กด์„ฑ ์ฃผ์ž…** ์‹œ์Šคํ…œ์€ (ํ•˜์œ„ ์˜์กด์„ฑ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ) ์ด๋Ÿฌํ•œ ์˜์กด์„ฑ๋“ค์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ๊ฐ ๋‹จ๊ณ„๋งˆ๋‹ค ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค(์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค). -์˜ˆ๋ฅผ ๋“ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์ด 4๊ฐœ์˜ API ์—”๋“œํฌ์ธํŠธ(*๊ฒฝ๋กœ ์ž‘๋™*)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค: +์˜ˆ๋ฅผ ๋“ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์ด 4๊ฐœ์˜ API ์—”๋“œํฌ์ธํŠธ(*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค: * `/items/public/` * `/items/private/` @@ -243,8 +243,8 @@ admin_user --> activate_user paying_user --> pro_items ``` -## **OpenAPI**์™€์˜ ํ†ตํ•ฉ +## **OpenAPI**์™€์˜ ํ†ตํ•ฉ { #integrated-with-openapi_1 } -์ด ๋ชจ๋“  ์˜์กด์„ฑ์€ ๊ฐ๊ฐ์˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ์„ ์–ธํ•˜๋Š” ๋™์‹œ์—, *๊ฒฝ๋กœ ์ž‘๋™*์— ๋งค๊ฐœ๋ณ€์ˆ˜, ๊ฒ€์ฆ ๋“ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. +์ด ๋ชจ๋“  ์˜์กด์„ฑ์€ ๊ฐ๊ฐ์˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ์„ ์–ธํ•˜๋Š” ๋™์‹œ์—, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ๋งค๊ฐœ๋ณ€์ˆ˜, ๊ฒ€์ฆ ๋“ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. **FastAPI**๋Š” ์ด ๋ชจ๋“  ๊ฒƒ์„ OpenAPI ์Šคํ‚ค๋งˆ์— ์ถ”๊ฐ€ํ•  ๊ฒƒ์ด๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ ์‹œ์Šคํ…œ์— ๋‚˜ํƒ€๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/encoder.md b/docs/ko/docs/tutorial/encoder.md index 4323957f41..804f00e35d 100644 --- a/docs/ko/docs/tutorial/encoder.md +++ b/docs/ko/docs/tutorial/encoder.md @@ -1,35 +1,35 @@ -# JSON ํ˜ธํ™˜ ๊ฐ€๋Šฅ ์ธ์ฝ”๋” +# JSON ํ˜ธํ™˜ ๊ฐ€๋Šฅ ์ธ์ฝ”๋” { #json-compatible-encoder } -๋ฐ์ดํ„ฐ ์œ ํ˜•(์˜ˆ: Pydantic ๋ชจ๋ธ)์„ JSON๊ณผ ํ˜ธํ™˜๋œ ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: `dict`, `list` ๋“ฑ) +๋ฐ์ดํ„ฐ ์œ ํ˜•(์˜ˆ: Pydantic ๋ชจ๋ธ)์„ JSON๊ณผ ํ˜ธํ™˜๋˜๋Š” ํ˜•ํƒœ(์˜ˆ: `dict`, `list` ๋“ฑ)๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค๋ฉด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค๋ฉด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. -์ด๋ฅผ ์œ„ํ•ด, **FastAPI** ์—์„œ๋Š” `jsonable_encoder()` ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +์ด๋ฅผ ์œ„ํ•ด, **FastAPI**์—์„œ๋Š” `jsonable_encoder()` ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -## `jsonable_encoder` ์‚ฌ์šฉ +## `jsonable_encoder` ์‚ฌ์šฉ { #using-the-jsonable-encoder } JSON ํ˜ธํ™˜ ๊ฐ€๋Šฅ ๋ฐ์ดํ„ฐ๋งŒ ์ˆ˜์‹ ํ•˜๋Š” `fake_db` ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์กด์žฌํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค๋ฉด, `datetime` ๊ฐ์ฒด๋Š” JSON๊ณผ ํ˜ธํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ ์ด ๋ฐ์ดํ„ฐ๋Š” ๋ฐ›์•„๋“ค์—ฌ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค๋ฉด, `datetime` ๊ฐ์ฒด๋Š” JSON๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์ด๋ฅผ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ `datetime` ๊ฐ์ฒด๋Š” <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ISO format</a> ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜๋Š” `str`๋กœ ๋ณ€ํ™˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ `datetime` ๊ฐ์ฒด๋Š” <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ISO format</a>์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜๋Š” `str`๋กœ ๋ณ€ํ™˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” Pydantic ๋ชจ๋ธ(์†์„ฑ์ด ์žˆ๋Š” ๊ฐ์ฒด)์„ ๋ฐ›์ง€ ์•Š๊ณ , `dict` ๋งŒ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. +๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” Pydantic ๋ชจ๋ธ(์†์„ฑ์ด ์žˆ๋Š” ๊ฐ์ฒด)์„ ๋ฐ›์ง€ ์•Š๊ณ , `dict`๋งŒ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. -์ด๋ฅผ ์œ„ํ•ด `jsonable_encoder` ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ฅผ ์œ„ํ•ด `jsonable_encoder`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -Pydantic ๋ชจ๋ธ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ๋ฐ›๊ณ  JSON ํ˜ธํ™˜ ๊ฐ€๋Šฅํ•œ ๋ฒ„์ „์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค: +Pydantic ๋ชจ๋ธ ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ๋ฐ›๊ณ  JSON ํ˜ธํ™˜ ๊ฐ€๋Šฅํ•œ ๋ฒ„์ „์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/encoder/tutorial001.py hl[5,22] *} +{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *} -์ด ์˜ˆ์‹œ๋Š” Pydantic ๋ชจ๋ธ์„ `dict`๋กœ, `datetime` ํ˜•์‹์„ `str`๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +์ด ์˜ˆ์‹œ์—์„œ๋Š” Pydantic ๋ชจ๋ธ์„ `dict`๋กœ, `datetime`์„ `str`๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -์ด๋ ‡๊ฒŒ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ๋Š” ํŒŒ์ด์ฌ ํ‘œ์ค€์ธ <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>๋กœ ์ธ์ฝ”๋”ฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ๋Š” ํŒŒ์ด์ฌ ํ‘œ์ค€์ธ <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>๋กœ ์ธ์ฝ”๋”ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ธธ์ด๊ฐ€ ๊ธด ๋ฌธ์ž์—ด ํ˜•ํƒœ์˜ JSON ํ˜•์‹(๋ฌธ์ž์—ด)์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์žˆ๋Š” ์ƒํ™ฉ์—์„œ๋Š” `str`๋กœ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. JSON๊ณผ ๋ชจ๋‘ ํ˜ธํ™˜๋˜๋Š” ๊ฐ’๊ณผ ํ•˜์œ„ ๊ฐ’์ด ์žˆ๋Š” Python ํ‘œ์ค€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ (์˜ˆ: `dict`)๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +JSON ํ˜•์‹(๋ฌธ์ž์—ด)์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์žˆ๋Š” ํฐ `str`์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. JSON๊ณผ ๋ชจ๋‘ ํ˜ธํ™˜๋˜๋Š” ๊ฐ’๊ณผ ํ•˜์œ„ ๊ฐ’์ด ์žˆ๋Š” ํŒŒ์ด์ฌ ํ‘œ์ค€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ(์˜ˆ: `dict`)๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. /// note | ์ฐธ๊ณ  -์‹ค์ œ๋กœ `jsonable_encoder`๋Š” **FastAPI** ์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ๋‹ค๋ฅธ ๋งŽ์€ ๊ณณ์—์„œ๋„ ์ด๋Š” ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. +`jsonable_encoder`๋Š” ์‹ค์ œ๋กœ **FastAPI**์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ๋‹ค๋ฅธ ๋งŽ์€ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋„ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. /// diff --git a/docs/ko/docs/tutorial/extra-data-types.md b/docs/ko/docs/tutorial/extra-data-types.md index 4a41ba0dc6..4a51e92861 100644 --- a/docs/ko/docs/tutorial/extra-data-types.md +++ b/docs/ko/docs/tutorial/extra-data-types.md @@ -1,4 +1,4 @@ -# ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜• +# ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜• { #extra-data-types } ์ง€๊ธˆ๊นŒ์ง€ ์ผ๋ฐ˜์ ์ธ ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜•์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: @@ -17,7 +17,7 @@ * ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ. * ์ž๋™ ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ๋ฌธ์„œํ™”. -## ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜• +## ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜• { #other-data-types } ์•„๋ž˜์˜ ์ถ”๊ฐ€์ ์ธ ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜•์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -36,7 +36,7 @@ * `datetime.timedelta`: * ํŒŒ์ด์ฌ์˜ `datetime.timedelta`. * ์š”์ฒญ๊ณผ ์‘๋‹ต์—์„œ ์ „์ฒด ์ดˆ(seconds)์˜ `float`๋กœ ํ‘œํ˜„๋ฉ๋‹ˆ๋‹ค. - * Pydantic์€ "ISO 8601 ์‹œ์ฐจ ์ธ์ฝ”๋”ฉ"์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ ๋˜ํ•œ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. <a href="https://docs.pydantic.dev/latest/concepts/serialization/#json_encoders" class="external-link" target="_blank">๋” ๋งŽ์€ ์ •๋ณด๋Š” ์ด ๋ฌธ์„œ์—์„œ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค.</a>. + * Pydantic์€ "ISO 8601 time diff encoding"์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ ๋˜ํ•œ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. <a href="https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers" class="external-link" target="_blank">๋” ๋งŽ์€ ์ •๋ณด๋Š” ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”</a>. * `frozenset`: * ์š”์ฒญ๊ณผ ์‘๋‹ต์—์„œ `set`์™€ ๋™์ผํ•˜๊ฒŒ ์ทจ๊ธ‰๋ฉ๋‹ˆ๋‹ค: * ์š”์ฒญ ์‹œ, ๋ฆฌ์ŠคํŠธ๋ฅผ ์ฝ์–ด ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๊ณ  `set`๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. @@ -49,11 +49,11 @@ * `Decimal`: * ํ‘œ์ค€ ํŒŒ์ด์ฌ์˜ `Decimal`. * ์š”์ฒญ๊ณผ ์‘๋‹ต์—์„œ `float`์™€ ๋™์ผํ•˜๊ฒŒ ๋‹ค๋ค„์ง‘๋‹ˆ๋‹ค. -* ์—ฌ๊ธฐ์—์„œ ๋ชจ๋“  ์œ ํšจํ•œ pydantic ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <a href="https://docs.pydantic.dev/latest/usage/types/types/" class="external-link" target="_blank">Pydantic ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜•</a>. +* ์—ฌ๊ธฐ์—์„œ ๋ชจ๋“  ์œ ํšจํ•œ Pydantic ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <a href="https://docs.pydantic.dev/latest/usage/types/types/" class="external-link" target="_blank">Pydantic ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜•</a>. -## ์˜ˆ์‹œ +## ์˜ˆ์‹œ { #example } -์œ„์˜ ๋ช‡๋ช‡ ์ž๋ฃŒํ˜•์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•˜๋Š” *๊ฒฝ๋กœ ์ž‘๋™* ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. +์œ„์˜ ๋ช‡๋ช‡ ์ž๋ฃŒํ˜•์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•˜๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. {* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *} diff --git a/docs/ko/docs/tutorial/extra-models.md b/docs/ko/docs/tutorial/extra-models.md index 8e45590613..bb5daddea0 100644 --- a/docs/ko/docs/tutorial/extra-models.md +++ b/docs/ko/docs/tutorial/extra-models.md @@ -1,43 +1,34 @@ -# ์ถ”๊ฐ€ ๋ชจ๋ธ +# ์ถ”๊ฐ€ ๋ชจ๋ธ { #extra-models } -์ง€๋‚œ ์˜ˆ์ œ์— ์ด์–ด์„œ, ์—ฐ๊ด€๋œ ๋ชจ๋ธ์„ ์—ฌ๋Ÿฌ๊ฐœ ๊ฐ–๋Š” ๊ฒƒ์€ ํ”ํ•œ ์ผ์ž…๋‹ˆ๋‹ค. +์ง€๋‚œ ์˜ˆ์ œ์— ์ด์–ด์„œ, ์—ฐ๊ด€๋œ ๋ชจ๋ธ์„ ์—ฌ๋Ÿฌ ๊ฐœ ๊ฐ–๋Š” ๊ฒƒ์€ ํ”ํ•œ ์ผ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ ์‚ฌ์šฉ์ž ๋ชจ๋ธ์˜ ๊ฒฝ์šฐ์— ๊ทธ๋Ÿฌํ•œ๋ฐ, ์™œ๋ƒํ•˜๋ฉด: -* **์ž…๋ ฅ ๋ชจ๋ธ** ์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. -* **์ถœ๋ ฅ ๋ชจ๋ธ** ์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์ง€๋ฉด ์•ˆ๋ฉ๋‹ˆ๋‹ค. -* **๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ** ์€ ํ•ด์‹œ์ฒ˜๋ฆฌ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +* **์ž…๋ ฅ ๋ชจ๋ธ**์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +* **์ถœ๋ ฅ ๋ชจ๋ธ**์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์ง€๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. +* **๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ**์€ ์•„๋งˆ๋„ ํ•ด์‹œ ์ฒ˜๋ฆฌ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์งˆ ํ•„์š”๊ฐ€ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. /// danger | ์œ„ํ—˜ ์ ˆ๋Œ€ ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ‰๋ฌธ์œผ๋กœ ์ €์žฅํ•˜์ง€ ๋งˆ์„ธ์š”. ํ•ญ์ƒ ์ดํ›„์— ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•œ "์•ˆ์ „ํ•œ ํ•ด์‹œ(secure hash)"๋กœ ์ €์žฅํ•˜์„ธ์š”. -๋งŒ์•ฝ ์ด๊ฒŒ ๋ฌด์—‡์ธ์ง€ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด, [security chapters](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}.์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ์— ๋Œ€ํ•ด ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋งŒ์•ฝ ์ด๊ฒŒ ๋ฌด์—‡์ธ์ง€ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด, [security chapters](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}์—์„œ "password hash"๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## ๋‹ค์ค‘ ๋ชจ๋ธ +## ๋‹ค์ค‘ ๋ชจ๋ธ { #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 } -/// info | ์ •๋ณด - -Pydantic v1์—์„œ๋Š” ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ `.dict()`๋กœ ๋ถˆ๋ ธ์œผ๋ฉฐ, Pydantic v2์—์„œ๋Š” `.model_dump()`๋กœ ์ด๋ฆ„์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. `.dict()`๋Š” ์—ฌ์ „ํžˆ ์ง€์›๋˜์ง€๋งŒ ๋” ์ด์ƒ ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. - -์—ฌ๊ธฐ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์ œ๋Š” Pydantic v1๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด `.dict()`๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, Pydantic v2๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด `.model_dump()`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. - -/// - -### `**user_in.dict()` ์— ๋Œ€ํ•˜์—ฌ - -#### Pydantic์˜ `.dict()` +#### Pydantic์˜ `.model_dump()` { #pydantics-model-dump } `user_in`์€ Pydantic ๋ชจ๋ธ ํด๋ž˜์Šค์ธ `UserIn`์ž…๋‹ˆ๋‹ค. -Pydantic ๋ชจ๋ธ์€ ๋ชจ๋ธ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•œ `dict`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” `.dict()` ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +Pydantic ๋ชจ๋ธ์€ ๋ชจ๋ธ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•œ `dict`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” `.model_dump()` ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด Pydantic ๊ฐ์ฒด `user_in`์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -48,7 +39,7 @@ 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`์— ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋œ `dict`๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค(์ด๋Š” Pydantic ๋ชจ๋ธ ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ `dict`์ž…๋‹ˆ๋‹ค). @@ -70,7 +61,7 @@ Python์˜ `dict`๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค: } ``` -#### `dict` ์–ธํŒจํ‚น(Unpacking) +#### `dict` ์–ธํŒจํ‚น { #unpacking-a-dict } `user_dict`์™€ ๊ฐ™์€ `dict`๋ฅผ ํ•จ์ˆ˜(๋˜๋Š” ํด๋ž˜์Šค)์— `**user_dict`๋กœ ์ „๋‹ฌํ•˜๋ฉด, Python์€ ์ด๋ฅผ "์–ธํŒฉ(unpack)"ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ `user_dict`์˜ ํ‚ค์™€ ๊ฐ’์„ ๊ฐ๊ฐ ํ‚ค-๊ฐ’ ์ธ์ž๋กœ ์ง์ ‘ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. @@ -102,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()`๋Š” `dict`์ด๋ฉฐ, ์ด๋ฅผ `**`๋กœ Python์ด "์–ธํŒฉ(unpack)"ํ•˜๋„๋ก ํ•˜์—ฌ `UserInDB`์— ์ „๋‹ฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +...์™œ๋ƒํ•˜๋ฉด `user_in.model_dump()`๋Š” `dict`์ด๋ฉฐ, ์ด๋ฅผ `**`๋กœ Python์ด "์–ธํŒฉ(unpack)"ํ•˜๋„๋ก ํ•˜์—ฌ `UserInDB`์— ์ „๋‹ฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ๋‹ค๋ฅธ Pydantic ๋ชจ๋ธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด Pydantic ๋ชจ๋ธ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -#### `dict` ์–ธํŒจํ‚น(Unpacking)๊ณผ ์ถ”๊ฐ€ ํ‚ค์›Œ๋“œ +#### `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) ``` ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: @@ -147,7 +138,7 @@ UserInDB( /// -## ์ค‘๋ณต ์ค„์ด๊ธฐ +## ์ค‘๋ณต ์ค„์ด๊ธฐ { #reduce-duplication } ์ฝ”๋“œ ์ค‘๋ณต์„ ์ค„์ด๋Š” ๊ฒƒ์€ **FastAPI**์˜ ํ•ต์‹ฌ ์•„์ด๋””์–ด ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. @@ -161,11 +152,11 @@ UserInDB( ๋ชจ๋“  ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜, ๊ฒ€์ฆ, ๋ฌธ์„œํ™” ๋“ฑ์€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ๋ชจ๋ธ ๊ฐ„์˜ ์ฐจ์ด์ ๋งŒ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(ํ‰๋ฌธ `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`์œผ๋กœ ์‘๋‹ต์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์‘๋‹ต์ด ๊ทธ ์ค‘ ํ•˜๋‚˜์˜ ํƒ€์ž…์ผ ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. @@ -175,18 +166,17 @@ OpenAPI์—์„œ๋Š” ์ด๋ฅผ `anyOf`๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. /// note | ์ฐธ๊ณ  -<a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a>์„ ์ •์˜ํ• ๋•Œ๋Š” ๋” ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์„ ๋จผ์ € ํฌํ•จํ•˜๊ณ , ๋œ ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์„ ๊ทธ ๋’ค์— ๋‚˜์—ดํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์ œ์—์„œ๋Š” `Union[PlaneItem, CarItem]` ๋ฅผ ๋ณด๋ฉด, ๋” ๊ตฌ์ฒด์ ์ธ `PlaneItem`์ด `CarItem`๋ณด๋‹ค ์•ž์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค. +<a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a>์„ ์ •์˜ํ•  ๋•Œ๋Š” ๋” ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์„ ๋จผ์ € ํฌํ•จํ•˜๊ณ , ๋œ ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์„ ๊ทธ ๋’ค์— ๋‚˜์—ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์ œ์—์„œ๋Š” `Union[PlaneItem, CarItem]`์—์„œ ๋” ๊ตฌ์ฒด์ ์ธ `PlaneItem`์ด `CarItem`๋ณด๋‹ค ์•ž์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค. /// {* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} - -### Python 3.10์—์„œ `Union` +### Python 3.10์—์„œ `Union` { #union-in-python-3-10 } ์œ„์˜ ์˜ˆ์ œ์—์„œ๋Š” `response_model` ์ธ์ž ๊ฐ’์œผ๋กœ `Union[PlaneItem, CarItem]`์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ, ์ด๋ฅผ **ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜(type annotation)** ์ด ์•„๋‹Œ **์ธ์ž ๊ฐ’(argument value)** ์œผ๋กœ ์ „๋‹ฌํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Python 3.10์—์„œ๋„ `Union`์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ, ์ด๋ฅผ **ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜(type annotation)**์ด ์•„๋‹Œ **์ธ์ž ๊ฐ’(argument value)**์œผ๋กœ ์ „๋‹ฌํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Python 3.10์—์„œ๋„ `Union`์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์— ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ง ๋ง‰๋Œ€(|)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -194,9 +184,9 @@ OpenAPI์—์„œ๋Š” ์ด๋ฅผ `anyOf`๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. some_variable: PlaneItem | CarItem ``` -ํ•˜์ง€๋งŒ ์ด๋ฅผ `response_model=PlaneItem | CarItem`๊ณผ ๊ฐ™์ด ํ• ๋‹นํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Python์ด ์ด๋ฅผ ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ํ•ด์„ํ•˜์ง€ ์•Š๊ณ , `PlaneItem`๊ณผ `CarItem` ์‚ฌ์ด์˜ **์ž˜๋ชป๋œ ์—ฐ์‚ฐ(invalid operation)**์„ ์‹œ๋„ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค +ํ•˜์ง€๋งŒ ์ด๋ฅผ `response_model=PlaneItem | CarItem`๊ณผ ๊ฐ™์ด ํ• ๋‹นํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Python์ด ์ด๋ฅผ ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ํ•ด์„ํ•˜์ง€ ์•Š๊ณ , `PlaneItem`๊ณผ `CarItem` ์‚ฌ์ด์˜ **์ž˜๋ชป๋œ ์—ฐ์‚ฐ(invalid operation)**์„ ์‹œ๋„ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -## ๋ชจ๋ธ ๋ฆฌ์ŠคํŠธ +## ๋ชจ๋ธ ๋ฆฌ์ŠคํŠธ { #list-of-models } ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ ํ˜•ํƒœ์˜ ์‘๋‹ต์„ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -204,8 +194,7 @@ some_variable: PlaneItem | CarItem {* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} - -## ์ž„์˜์˜ `dict` ์‘๋‹ต +## ์ž„์˜์˜ `dict` ์‘๋‹ต { #response-with-arbitrary-dict } Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ํ‚ค์™€ ๊ฐ’์˜ ํƒ€์ž…๋งŒ ์„ ์–ธํ•˜์—ฌ ํ‰๋ฒ”ํ•œ ์ž„์˜์˜ `dict`๋กœ ์‘๋‹ต์„ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -215,9 +204,8 @@ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ํ‚ค์™€ ๊ฐ’์˜ ํƒ€์ž…๋งŒ ์„ ์–ธํ•˜์—ฌ ํ‰ {* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} - -## ์š”์•ฝ +## ์š”์•ฝ { #recap } ์—ฌ๋Ÿฌ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ณ , ๊ฐ ๊ฒฝ์šฐ์— ๋งž๊ฒŒ ์ž์œ ๋กญ๊ฒŒ ์ƒ์†ํ•˜์„ธ์š”. -์—”ํ„ฐํ‹ฐ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ "์ƒํƒœ"๋ฅผ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ์—”ํ„ฐํ‹ฐ๋‹น ๋‹จ์ผ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž "์—”ํ„ฐํ‹ฐ"๊ฐ€ `password`, `password_hash`, ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์—†๋Š” ์ƒํƒœ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์ฒ˜๋Ÿผ ๋ง์ž…๋‹ˆ๋‹ค. +์—”ํ„ฐํ‹ฐ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ "์ƒํƒœ"๋ฅผ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ์—”ํ„ฐํ‹ฐ๋‹น ๋‹จ์ผ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž "์—”ํ„ฐํ‹ฐ"๊ฐ€ `password`, `password_hash`, ๊ทธ๋ฆฌ๊ณ  ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์—†๋Š” ์ƒํƒœ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์ฒ˜๋Ÿผ ๋ง์ž…๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/first-steps.md b/docs/ko/docs/tutorial/first-steps.md index 20ce0d6469..db78f16783 100644 --- a/docs/ko/docs/tutorial/first-steps.md +++ b/docs/ko/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,36 +11,52 @@ <div class="termy"> ```console -$ uvicorn main:app --reload +$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u> -<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -<span style="color: green;">INFO</span>: Started reloader process [28720] -<span style="color: green;">INFO</span>: Started server process [28722] -<span style="color: green;">INFO</span>: Waiting for application startup. -<span style="color: green;">INFO</span>: Application startup complete. + <span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server ๐Ÿš€ + + Searching for package file structure from directories + with <font color="#3465A4">__init__.py</font> files + Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> ๐Ÿ main.py + + <span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with + the following code: + + <u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u> + + <span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font> + + <span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use: + <b>fastapi run</b> + + Logs: + + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories: + <b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C + to quit<b>)</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. ``` </div> -/// note | ์ฐธ๊ณ  - -`uvicorn main:app` ๋ช…๋ น์€ ๋‹ค์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค: - -* `main`: ํŒŒ์ผ `main.py` (ํŒŒ์ด์ฌ "๋ชจ๋“ˆ"). -* `app`: `main.py` ๋‚ด๋ถ€์˜ `app = FastAPI()` ์ค„์—์„œ ์ƒ์„ฑํ•œ ์˜ค๋ธŒ์ ํŠธ. -* `--reload`: ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‹œ ์ž๋™์œผ๋กœ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘. ๊ฐœ๋ฐœ ์‹œ์—๋งŒ ์‚ฌ์šฉ. - -/// - ์ถœ๋ ฅ๋˜๋Š” ์ค„๋“ค ์ค‘์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋‚ด์šฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค: ```hl_lines="4" INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` -ํ•ด๋‹น ์ค„์€ ๋กœ์ปฌ์—์„œ ์•ฑ์ด ์„œ๋น„์Šค๋˜๋Š” URL์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. +ํ•ด๋‹น ์ค„์€ ๋กœ์ปฌ ๋จธ์‹ ์—์„œ ์•ฑ์ด ์„œ๋น„์Šค๋˜๋Š” URL์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. -### ํ™•์ธํ•˜๊ธฐ +### ํ™•์ธํ•˜๊ธฐ { #check-it } ๋ธŒ๋ผ์šฐ์ €๋กœ <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>๋ฅผ ์—ฌ์„ธ์š”. @@ -50,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 } ์ด์ œ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>๋กœ ๊ฐ€๋ด…๋‹ˆ๋‹ค. @@ -58,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 } ๊ทธ๋ฆฌ๊ณ  ์ด์ œ, <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>๋กœ ๊ฐ€๋ด…๋‹ˆ๋‹ค. @@ -66,41 +82,41 @@ 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 } <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a>๋Š” API์˜ ์Šคํ‚ค๋งˆ๋ฅผ ์–ด๋–ป๊ฒŒ ์ •์˜ํ•˜๋Š”์ง€ ์ง€์‹œํ•˜๋Š” ๊ทœ๊ฒฉ์ž…๋‹ˆ๋‹ค. ์ด ์Šคํ‚ค๋งˆ ์ •์˜๋Š” API ๊ฒฝ๋กœ, ๊ฐ€๋Šฅํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋“ฑ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. -#### ๋ฐ์ดํ„ฐ "์Šคํ‚ค๋งˆ" +#### ๋ฐ์ดํ„ฐ "์Šคํ‚ค๋งˆ" { #data-schema } "์Šคํ‚ค๋งˆ"๋ผ๋Š” ์šฉ์–ด๋Š” JSON์ฒ˜๋Ÿผ ์–ด๋–ค ๋ฐ์ดํ„ฐ์˜ ํ˜•ํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ JSON ์†์„ฑ, ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž… ๋“ฑ์„ ๋œปํ•ฉ๋‹ˆ๋‹ค. -#### OpenAPI์™€ JSON ์Šคํ‚ค๋งˆ +#### OpenAPI์™€ JSON ์Šคํ‚ค๋งˆ { #openapi-and-json-schema } OpenAPI๋Š” ๋‹น์‹ ์˜ API์— ๋Œ€ํ•œ API ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด ์Šคํ‚ค๋งˆ๋Š” JSON ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ์˜ ํ‘œ์ค€์ธ **JSON ์Šคํ‚ค๋งˆ**๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹น์‹ ์˜ API๊ฐ€ ๋ณด๋‚ด๊ณ  ๋ฐ›๋Š” ๋ฐ์ดํ„ฐ์˜ ์ •์˜(๋˜๋Š” "์Šคํ‚ค๋งˆ")๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. -#### `openapi.json` ํ™•์ธ +#### `openapi.json` ํ™•์ธ { #check-the-openapi-json } -FastAPI๋Š” ์ž๋™์œผ๋กœ API์˜ ์„ค๋ช…๊ณผ ํ•จ๊ป˜ JSON (์Šคํ‚ค๋งˆ)๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +๊ฐ€๊ณต๋˜์ง€ ์•Š์€ OpenAPI ์Šคํ‚ค๋งˆ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋Š”์ง€ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด, FastAPI๋Š” ์ž๋™์œผ๋กœ ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ชจ๋“  API์— ๋Œ€ํ•œ ์„ค๋ช…๊ณผ ํ•จ๊ป˜ JSON (์Šคํ‚ค๋งˆ)๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -๊ฐ€๊ณต๋˜์ง€ ์•Š์€ OpenAPI ์Šคํ‚ค๋งˆ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋Š”์ง€ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด, ์—ฌ๊ธฐ์—์„œ ์ง์ ‘ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>. +์—ฌ๊ธฐ์—์„œ ์ง์ ‘ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹œ์ž‘ํ•˜๋Š” JSON์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```JSON { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": { "title": "FastAPI", "version": "0.1.0" @@ -119,7 +135,7 @@ FastAPI๋Š” ์ž๋™์œผ๋กœ API์˜ ์„ค๋ช…๊ณผ ํ•จ๊ป˜ JSON (์Šคํ‚ค๋งˆ)๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ ... ``` -#### OpenAPI์˜ ์šฉ๋„ +#### OpenAPI์˜ ์šฉ๋„ { #what-is-openapi-for } OpenAPI ์Šคํ‚ค๋งˆ๋Š” ํฌํ•จ๋œ ๋‘ ๊ฐœ์˜ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. @@ -127,11 +143,47 @@ OpenAPI ์Šคํ‚ค๋งˆ๋Š” ํฌํ•จ๋œ ๋‘ ๊ฐœ์˜ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ ์‹œ์Šคํ…œ์„ ์ œ๊ณต API์™€ ํ†ต์‹ ํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ(ํ”„๋ก ํŠธ์—”๋“œ, ๋ชจ๋ฐ”์ผ, IoT ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋“ฑ)๋ฅผ ์œ„ํ•ด ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๋ฐ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ๋‹จ๊ณ„๋ณ„ ์š”์•ฝ +### ์•ฑ ๋ฐฐํฌํ•˜๊ธฐ(์„ ํƒ ์‚ฌํ•ญ) { #deploy-your-app-optional } -### 1 ๋‹จ๊ณ„: `FastAPI` ์ž„ํฌํŠธ +์„ ํƒ์ ์œผ๋กœ FastAPI ์•ฑ์„ <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>์— ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์ง ๋Œ€๊ธฐ์ž ๋ช…๋‹จ์— ๋“ฑ๋กํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ๋“ฑ๋กํ•˜๋Ÿฌ ๊ฐ€์„ธ์š”. ๐Ÿš€ -{* ../../docs_src/first_steps/tutorial001.py hl[1] *} +์ด๋ฏธ **FastAPI Cloud** ๊ณ„์ •์ด ์žˆ๋‹ค๋ฉด(๋Œ€๊ธฐ์ž ๋ช…๋‹จ์—์„œ ์ดˆ๋Œ€ํ•ด ๋“œ๋ ธ์Šต๋‹ˆ๋‹ค ๐Ÿ˜‰), ํ•œ ๋ฒˆ์˜ ๋ช…๋ น์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋ฐฐํฌํ•˜๊ธฐ ์ „์—, ๋กœ๊ทธ์ธ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”: + +<div class="termy"> + +```console +$ fastapi login + +You are logged in to FastAPI Cloud ๐Ÿš€ +``` + +</div> + +๊ทธ ๋‹ค์Œ ์•ฑ์„ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค: + +<div class="termy"> + +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +โœ… Deployment successful! + +๐Ÿ” Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +</div> + +์ด๊ฒŒ ์ „๋ถ€์ž…๋‹ˆ๋‹ค! ์ด์ œ ํ•ด๋‹น URL์—์„œ ์•ฑ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โœจ + +## ๋‹จ๊ณ„๋ณ„ ์š”์•ฝ { #recap-step-by-step } + +### 1 ๋‹จ๊ณ„: `FastAPI` ์ž„ํฌํŠธ { #step-1-import-fastapi } + +{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} `FastAPI`๋Š” ๋‹น์‹ ์˜ API๋ฅผ ์œ„ํ•œ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ํŒŒ์ด์ฌ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. @@ -143,45 +195,17 @@ API์™€ ํ†ต์‹ ํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ(ํ”„๋ก ํŠธ์—”๋“œ, ๋ชจ๋ฐ”์ผ, IoT ์• ํ”Œ๋ฆฌ์ผ€ /// -### 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๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์ƒํ˜ธ์ž‘์šฉ์˜ ์ฃผ์š” ์ง€์ ์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -์ด `app`์€ ๋‹ค์Œ ๋ช…๋ น์—์„œ `uvicorn`์ด ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค: +### 3 ๋‹จ๊ณ„: *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์ƒ์„ฑ { #step-3-create-a-path-operation } -<div class="termy"> - -```console -$ uvicorn main:app --reload - -<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -</div> - -์•„๋ž˜์ฒ˜๋Ÿผ ์•ฑ์„ ๋งŒ๋“ ๋‹ค๋ฉด: - -{* ../../docs_src/first_steps/tutorial002.py hl[3] *} - -์ด๋ฅผ `main.py` ํŒŒ์ผ์— ๋„ฃ๊ณ , `uvicorn`์„ ์•„๋ž˜์ฒ˜๋Ÿผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: - -<div class="termy"> - -```console -$ uvicorn main:my_awesome_api --reload - -<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -</div> - -### 3 ๋‹จ๊ณ„: *๊ฒฝ๋กœ ์ž‘๋™* ์ƒ์„ฑ - -#### ๊ฒฝ๋กœ +#### ๊ฒฝ๋กœ { #path } ์—ฌ๊ธฐ์„œ "๊ฒฝ๋กœ"๋Š” ์ฒซ ๋ฒˆ์งธ `/`๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋Š” URL์˜ ๋’ท๋ถ€๋ถ„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. @@ -205,7 +229,7 @@ https://example.com/items/foo API๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ "๊ฒฝ๋กœ"๋Š” "๊ด€์‹ฌ์‚ฌ"์™€ "๋ฆฌ์†Œ์Šค"๋ฅผ ๋ถ„๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ฃผ์š”ํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. -#### ์ž‘๋™ +#### ์ž‘๋™ { #operation } "์ž‘๋™(Operation)"์€ HTTP "๋ฉ”์†Œ๋“œ" ์ค‘ ํ•˜๋‚˜๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. @@ -240,14 +264,14 @@ API๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ ์ผ๋ฐ˜์ ์œผ๋กœ ํŠน์ • ํ–‰๋™์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ํŠน์ • ์šฐ๋ฆฌ ์—ญ์‹œ ์ด์ œ๋ถ€ํ„ฐ ๋ฉ”์†Œ๋“œ๋ฅผ "**์ž‘๋™**"์ด๋ผ๊ณ  ๋ถ€๋ฅผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -#### *๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ* ์ •์˜ +#### *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ* ์ •์˜ { #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**์—๊ฒŒ ๋ฐ”๋กœ ์•„๋ž˜์— ์žˆ๋Š” ํ•จ์ˆ˜๊ฐ€ ๋‹ค์Œ์œผ๋กœ ์ด๋™ํ•˜๋Š” ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. +`@app.get("/")`์€ **FastAPI**์—๊ฒŒ ๋ฐ”๋กœ ์•„๋ž˜์— ์žˆ๋Š” ํ•จ์ˆ˜๊ฐ€ ๋‹ค์Œ์œผ๋กœ ์ด๋™ํ•˜๋Š” ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค: * ๊ฒฝ๋กœ `/` -* <abbr title="HTTP GET ๋ฉ”์†Œ๋“œ"><code>get</code> ์ž‘๋™</abbr> ์‚ฌ์šฉ +* <abbr title="an HTTP GET method"><code>get</code> operation</abbr> ์‚ฌ์šฉ /// info | `@decorator` ์ •๋ณด @@ -259,7 +283,7 @@ API๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ ์ผ๋ฐ˜์ ์œผ๋กœ ํŠน์ • ํ–‰๋™์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ํŠน์ • ์šฐ๋ฆฌ์˜ ๊ฒฝ์šฐ, ์ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” **FastAPI**์—๊ฒŒ ์•„๋ž˜ ํ•จ์ˆ˜๊ฐ€ **๊ฒฝ๋กœ** `/`์˜ `get` **์ž‘๋™**์— ํ•ด๋‹นํ•œ๋‹ค๊ณ  ์•Œ๋ ค์ค๋‹ˆ๋‹ค. -์ด๊ฒƒ์ด "**๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ**"์ž…๋‹ˆ๋‹ค. +์ด๊ฒƒ์ด "**๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ**"์ž…๋‹ˆ๋‹ค. /// @@ -288,15 +312,15 @@ API๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ ์ผ๋ฐ˜์ ์œผ๋กœ ํŠน์ • ํ–‰๋™์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ํŠน์ • /// -### 4 ๋‹จ๊ณ„: **๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜** ์ •์˜ +### 4 ๋‹จ๊ณ„: **๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜** ์ •์˜ { #step-4-define-the-path-operation-function } -๋‹ค์Œ์€ ์šฐ๋ฆฌ์˜ "**๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜**"์ž…๋‹ˆ๋‹ค: +๋‹ค์Œ์€ ์šฐ๋ฆฌ์˜ "**๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜**"์ž…๋‹ˆ๋‹ค: * **๊ฒฝ๋กœ**: ๋Š” `/`์ž…๋‹ˆ๋‹ค. * **์ž‘๋™**: ์€ `get`์ž…๋‹ˆ๋‹ค. * **ํ•จ์ˆ˜**: ๋Š” "๋ฐ์ฝ”๋ ˆ์ดํ„ฐ" ์•„๋ž˜์— ์žˆ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค (`@app.get("/")` ์•„๋ž˜). -{* ../../docs_src/first_steps/tutorial001.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} ์ด๊ฒƒ์€ ํŒŒ์ด์ฌ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. @@ -308,17 +332,17 @@ URL "`/`"์— ๋Œ€ํ•œ `GET` ์ž‘๋™์„ ์‚ฌ์šฉํ•˜๋Š” ์š”์ฒญ์„ ๋ฐ›์„ ๋•Œ๋งˆ๋‹ค **Fa `async def`์„ ์ด์šฉํ•˜๋Š” ๋Œ€์‹  ์ผ๋ฐ˜ ํ•จ์ˆ˜๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/first_steps/tutorial003.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} /// note | ์ฐธ๊ณ  -์ฐจ์ด์ ์„ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด [Async: *"๋ฐ”์˜์‹  ๊ฒฝ์šฐ"*](../async.md#_1){.internal-link target=_blank}์„ ํ™•์ธํ•˜์„ธ์š”. +์ฐจ์ด์ ์„ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด [Async: *"๋ฐ”์˜์‹  ๊ฒฝ์šฐ"*](../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` ๋“ฑ์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -326,10 +350,31 @@ Pydantic ๋ชจ๋ธ์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค(๋‚˜์ค‘์— ๋” ์ž์„ธํžˆ ์‚ดํŽด JSON์œผ๋กœ ์ž๋™ ๋ณ€ํ™˜๋˜๋Š” ๊ฐ์ฒด๋“ค๊ณผ ๋ชจ๋ธ๋“ค(ORM ๋“ฑ์„ ํฌํ•จํ•ด์„œ)์ด ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋งˆ์Œ์— ๋“œ๋Š” ๊ฒƒ์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค, ์ด๋ฏธ ์ง€์›๋˜๊ณ  ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -## ์š”์•ฝ +### 6 ๋‹จ๊ณ„: ๋ฐฐํฌํ•˜๊ธฐ { #step-6-deploy-it } + +ํ•œ ๋ฒˆ์˜ ๋ช…๋ น์œผ๋กœ **<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>**์— ์•ฑ์„ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค: `fastapi deploy`. ๐ŸŽ‰ + +#### FastAPI Cloud ์†Œ๊ฐœ { #about-fastapi-cloud } + +**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>**๋Š” **FastAPI** ๋’ค์— ์žˆ๋Š” ๋™์ผํ•œ ์ž‘์„ฑ์ž์™€ ํŒ€์ด ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. + +์ตœ์†Œํ•œ์˜ ๋…ธ๋ ฅ์œผ๋กœ API๋ฅผ **๋นŒ๋“œ**, **๋ฐฐํฌ**, **์ ‘๊ทผ**ํ•˜๋Š” ๊ณผ์ •์„ ๊ฐ„์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. + +FastAPI๋กœ ์•ฑ์„ ๋นŒ๋“œํ•  ๋•Œ์˜ ๋™์ผํ•œ **๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜**์„ ํด๋ผ์šฐ๋“œ์— **๋ฐฐํฌ**ํ•  ๋•Œ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰ + +FastAPI Cloud๋Š” *FastAPI์™€ ์นœ๊ตฌ๋“ค* ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ์˜ ์ฃผ์š” ์Šคํฐ์„œ์ด์ž ์ž๊ธˆ ์ œ๊ณต์ž์ž…๋‹ˆ๋‹ค. โœจ + +#### ๋‹ค๋ฅธ ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด์— ๋ฐฐํฌํ•˜๊ธฐ { #deploy-to-other-cloud-providers } + +FastAPI๋Š” ์˜คํ”ˆ ์†Œ์Šค์ด๋ฉฐ ํ‘œ์ค€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์„ ํƒํ•œ ์–ด๋–ค ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด์—๋„ FastAPI ์•ฑ์„ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด์˜ ๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ผ FastAPI ์•ฑ์„ ๋ฐฐํฌํ•˜์„ธ์š”. ๐Ÿค“ + +## ์š”์•ฝ { #recap } * `FastAPI` ์ž„ํฌํŠธ. * `app` ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ. -* (`@app.get("/")`์ฒ˜๋Ÿผ) **๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ** ์ž‘์„ฑ. -* (์œ„์— ์žˆ๋Š” `def root(): ...`์ฒ˜๋Ÿผ) **๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜** ์ž‘์„ฑ. -* (`uvicorn main:app --reload`์ฒ˜๋Ÿผ) ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰. +* (`@app.get("/")`์ฒ˜๋Ÿผ) **๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ** ์ž‘์„ฑ. +* (์œ„์— ์žˆ๋Š” `def root(): ...`์ฒ˜๋Ÿผ) **๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜** ์ž‘์„ฑ. +* `fastapi dev` ๋ช…๋ น์œผ๋กœ ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰. +* ์„ ํƒ์ ์œผ๋กœ `fastapi deploy`๋กœ ์•ฑ ๋ฐฐํฌ. diff --git a/docs/ko/docs/tutorial/header-param-models.md b/docs/ko/docs/tutorial/header-param-models.md index bab7291e3c..ad872e1534 100644 --- a/docs/ko/docs/tutorial/header-param-models.md +++ b/docs/ko/docs/tutorial/header-param-models.md @@ -1,4 +1,4 @@ -# ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชจ๋ธ +# ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชจ๋ธ { #header-parameter-models } ๊ด€๋ จ ์žˆ๋Š” **ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜** ๊ทธ๋ฃน์ด ์žˆ๋Š” ๊ฒฝ์šฐ, **Pydantic ๋ชจ๋ธ**์„ ์ƒ์„ฑํ•˜์—ฌ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -10,7 +10,7 @@ /// -## Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•œ ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜ +## Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•œ ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜ { #header-parameters-with-a-pydantic-model } **Pydantic ๋ชจ๋ธ**์— ํ•„์š”ํ•œ **ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜**๋ฅผ ์„ ์–ธํ•œ ๋‹ค์Œ, ํ•ด๋‹น ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `Header`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: @@ -18,7 +18,7 @@ **FastAPI**๋Š” ์š”์ฒญ์—์„œ ๋ฐ›์€ **ํ—ค๋”**์—์„œ **๊ฐ ํ•„๋“œ**์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ **์ถ”์ถœ**ํ•˜๊ณ  ์ •์˜ํ•œ Pydantic ๋ชจ๋ธ์„ ์ค๋‹ˆ๋‹ค. -## ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ +## ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ { #check-the-docs } ๋ฌธ์„œ UI `/docs`์—์„œ ํ•„์š”ํ•œ ํ—ค๋”๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -26,7 +26,7 @@ <img src="/img/tutorial/header-param-models/image01.png"> </div> -## ์ถ”๊ฐ€ ํ—ค๋” ๊ธˆ์ง€ํ•˜๊ธฐ +## ์ถ”๊ฐ€ ํ—ค๋” ๊ธˆ์ง€ํ•˜๊ธฐ { #forbid-extra-headers } ์ผ๋ถ€ ํŠน๋ณ„ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€(ํ”ํ•˜์ง€๋Š” ์•Š๊ฒ ์ง€๋งŒ)์—์„œ๋Š” ์ˆ˜์‹ ํ•˜๋ ค๋Š” ํ—ค๋”๋ฅผ **์ œํ•œ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -51,6 +51,22 @@ Pydantic์˜ ๋ชจ๋ธ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€(`extra`) ํ•„๋“œ๋ฅผ ๊ธˆ์ง€(`forb } ``` -## ์š”์•ฝ +## ๋ฐ‘์ค„ ๋ณ€ํ™˜ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ธฐ { #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/ko/docs/tutorial/header-params.md b/docs/ko/docs/tutorial/header-params.md index 7379eb2a07..4c47644d86 100644 --- a/docs/ko/docs/tutorial/header-params.md +++ b/docs/ko/docs/tutorial/header-params.md @@ -1,20 +1,20 @@ -# ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜ +# ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜ { #header-parameters } ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `Query`, `Path` ๊ทธ๋ฆฌ๊ณ  `Cookie` ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `Header` ์ž„ํฌํŠธ +## `Header` ์ž„ํฌํŠธ { #import-header } ๋จผ์ € `Header`๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/header_params/tutorial001.py hl[3] *} +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *} -## `Header` ๋งค๊ฐœ๋ณ€์ˆ˜ ์„ ์–ธ +## `Header` ๋งค๊ฐœ๋ณ€์ˆ˜ ์„ ์–ธ { #declare-header-parameters } `Path`, `Query` ๊ทธ๋ฆฌ๊ณ  `Cookie`๋ฅผ ์‚ฌ์šฉํ•œ ๋™์ผํ•œ ๊ตฌ์กฐ๋ฅผ ์ด์šฉํ•˜์—ฌ ํ—ค๋” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๊ฐ’์€ ๊ธฐ๋ณธ๊ฐ’์ด๋ฉฐ, ์ถ”๊ฐ€ ๊ฒ€์ฆ์ด๋‚˜ ์–ด๋…ธํ…Œ์ด์…˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชจ๋‘ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/header_params/tutorial001.py hl[9] *} +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[9] *} /// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ @@ -30,7 +30,7 @@ /// -## ์ž๋™ ๋ณ€ํ™˜ +## ์ž๋™ ๋ณ€ํ™˜ { #automatic-conversion } `Header`๋Š” `Path`, `Query` ๊ทธ๋ฆฌ๊ณ  `Cookie`๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ ์™ธ์— ๊ธฐ๋Šฅ์ด ์กฐ๊ธˆ ๋” ์žˆ์Šต๋‹ˆ๋‹ค. @@ -46,15 +46,15 @@ ๋งŒ์•ฝ ์–ธ๋”์Šค์ฝ”์–ด๋ฅผ ํ•˜์ดํ”ˆ์œผ๋กœ ์ž๋™ ๋ณ€ํ™˜์„ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•  ์–ด๋–ค ์ด์œ ๊ฐ€ ์žˆ๋‹ค๋ฉด, `Header`์˜ `convert_underscores` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `False`๋กœ ์„ค์ •ํ•˜์‹ญ์‹œ์˜ค: -{* ../../docs_src/header_params/tutorial002.py hl[10] *} +{* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *} /// warning | ๊ฒฝ๊ณ  -`convert_underscore`๋ฅผ `False`๋กœ ์„ค์ •ํ•˜๊ธฐ ์ „์—, ์–ด๋–ค HTTP ํ”„๋ก์‹œ๋“ค๊ณผ ์„œ๋ฒ„๋“ค์€ ์–ธ๋”์Šค์ฝ”์–ด๊ฐ€ ํฌํ•จ๋œ ํ—ค๋” ์‚ฌ์šฉ์„ ํ—ˆ๋ฝํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ๋ช…์‹ฌํ•˜์‹ญ์‹œ์˜ค. +`convert_underscores`๋ฅผ `False`๋กœ ์„ค์ •ํ•˜๊ธฐ ์ „์—, ์–ด๋–ค HTTP ํ”„๋ก์‹œ๋“ค๊ณผ ์„œ๋ฒ„๋“ค์€ ์–ธ๋”์Šค์ฝ”์–ด๊ฐ€ ํฌํ•จ๋œ ํ—ค๋” ์‚ฌ์šฉ์„ ํ—ˆ๋ฝํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ๋ช…์‹ฌํ•˜์‹ญ์‹œ์˜ค. /// -## ์ค‘๋ณต ํ—ค๋” +## ์ค‘๋ณต ํ—ค๋” { #duplicate-headers } ์ค‘๋ณต ํ—ค๋”๋“ค์„ ์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๋‹ค์ค‘๊ฐ’์„ ๊ฐ–๋Š” ๋™์ผํ•œ ํ—ค๋”๋ฅผ ๋œปํ•ฉ๋‹ˆ๋‹ค. @@ -64,9 +64,9 @@ ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‘ ๋ฒˆ ์ด์ƒ ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ๋Š” `X-Token`ํ—ค๋”๋ฅผ ์„ ์–ธํ•˜๋ ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/header_params/tutorial003.py hl[9] *} +{* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *} -๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‘ ๊ฐœ์˜ HTTP ํ—ค๋”๋ฅผ ์ „์†กํ•˜์—ฌ ํ•ด๋‹น *๊ฒฝ๋กœ* ์™€ ํ†ต์‹ ํ•  ๊ฒฝ์šฐ: +๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‘ ๊ฐœ์˜ HTTP ํ—ค๋”๋ฅผ ์ „์†กํ•˜์—ฌ ํ•ด๋‹น *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์™€ ํ†ต์‹ ํ•  ๊ฒฝ์šฐ: ``` X-Token: foo @@ -84,7 +84,7 @@ X-Token: bar } ``` -## ์š”์•ฝ +## ์š”์•ฝ { #recap } `Header`๋Š” `Query`, `Path`, `Cookie`์™€ ๋™์ผํ•œ ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/index.md b/docs/ko/docs/tutorial/index.md index 9f5328992f..c44aac2d4d 100644 --- a/docs/ko/docs/tutorial/index.md +++ b/docs/ko/docs/tutorial/index.md @@ -1,84 +1,95 @@ -# ์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ +# ์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ { #tutorial-user-guide } -์ด ์ž์Šต์„œ๋Š” ๋‹จ๊ณ„๋ณ„๋กœ **FastAPI**์˜ ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. +์ด ์ž์Šต์„œ๋Š” **FastAPI**์˜ ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ์„ ๋‹จ๊ณ„๋ณ„๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. -๊ฐ ์„น์…˜์€ ์ด์ „ ์„น์…˜์— ๊ธฐ๋ฐ˜ํ•˜๋Š” ์ˆœ์ฐจ์ ์ธ ๊ตฌ์กฐ๋กœ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, ๊ฐ ์ฃผ์ œ๋กœ ๊ตฌ๋ถ„๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”์— ๋”ฐ๋ผ ํŠน์ • ์„น์…˜์œผ๋กœ ๋ฐ”๋กœ ์ด๋™ํ•˜์—ฌ ํ•„์š”ํ•œ ๋‚ด์šฉ์„ ๋ฐ”๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ฐ ์„น์…˜์€ ์ด์ „ ์„น์…˜์„ ๋ฐ”ํƒ•์œผ๋กœ ์ ์ง„์ ์œผ๋กœ ๊ตฌ์„ฑ๋˜์ง€๋งŒ, ์ฃผ์ œ๋ฅผ ๋ถ„๋ฆฌํ•œ ๊ตฌ์กฐ๋กœ ๋˜์–ด ์žˆ์–ด ํŠน์ • API ์š”๊ตฌ์‚ฌํ•ญ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์›ํ•˜๋Š” ์„น์…˜์œผ๋กœ ๋ฐ”๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋˜ํ•œ ํ–ฅํ›„์—๋„ ์ฐธ์กฐ ์ž๋ฃŒ๋กœ ์“ฐ์ผ ์ˆ˜ ์žˆ๋„๋ก ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ ๋‚˜์ค‘์— ์ฐธ๊ณ  ์ž๋ฃŒ๋กœ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด์กŒ์œผ๋ฏ€๋กœ, ํ•„์š”ํ•  ๋•Œ ๋‹ค์‹œ ๋Œ์•„์™€ ์ •ํ™•ํžˆ ํ•„์š”ํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋ฏ€๋กœ ํ•„์š”ํ•  ๋•Œ์— ๋‹ค์‹œ ๋Œ์•„์™€์„œ ์›ํ•˜๋Š” ๊ฒƒ์„ ์ •ํ™•ํžˆ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +## ์ฝ”๋“œ ์‹คํ–‰ํ•˜๊ธฐ { #run-the-code } -## ์ฝ”๋“œ ์‹คํ–‰ํ•˜๊ธฐ +๋ชจ๋“  ์ฝ”๋“œ ๋ธ”๋ก์€ ๋ณต์‚ฌํ•ด์„œ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์‹ค์ œ๋กœ ํ…Œ์ŠคํŠธ๋œ Python ํŒŒ์ผ์ž…๋‹ˆ๋‹ค). -๋ชจ๋“  ์ฝ”๋“œ ๋ธ”๋ก์€ ๋ณต์‚ฌํ•˜์—ฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์‹ค์ œ๋กœ ํ…Œ์ŠคํŠธ๋œ ํŒŒ์ด์ฌ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค). - -์˜ˆ์ œ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ์ฝ”๋“œ๋ฅผ `main.py` ํŒŒ์ผ์— ๋ณต์‚ฌํ•˜๊ณ  ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ `uvicorn`์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค: +์˜ˆ์ œ ์ค‘ ์–ด๋–ค ๊ฒƒ์ด๋“  ์‹คํ–‰ํ•˜๋ ค๋ฉด, ์ฝ”๋“œ๋ฅผ `main.py` ํŒŒ์ผ์— ๋ณต์‚ฌํ•˜๊ณ  ๋‹ค์Œ์œผ๋กœ `fastapi dev`๋ฅผ ์‹œ์ž‘ํ•˜์„ธ์š”: <div class="termy"> ```console -$ uvicorn main:app --reload +$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u> -<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -<span style="color: green;">INFO</span>: Started reloader process [28720] -<span style="color: green;">INFO</span>: Started server process [28722] -<span style="color: green;">INFO</span>: Waiting for application startup. -<span style="color: green;">INFO</span>: Application startup complete. + <span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server ๐Ÿš€ + + Searching for package file structure from directories + with <font color="#3465A4">__init__.py</font> files + Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> ๐Ÿ main.py + + <span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with + the following code: + + <u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u> + + <span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font> + + <span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use: + <b>fastapi run</b> + + Logs: + + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories: + <b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C + to quit<b>)</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. ``` </div> -์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ ๋ณต์‚ฌ, ํŽธ์ง‘ํ•  ๋•Œ, ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ **๊ฐ•๋ ฅํžˆ ๊ถŒ์žฅ**ํ•ฉ๋‹ˆ๋‹ค. - -๋กœ์ปฌ ํŽธ์ง‘๊ธฐ์—์„œ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋ชจ๋“  ํƒ€์ž… ๊ฒ€์‚ฌ์™€ ์ž๋™์™„์„ฑ ๋“ฑ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์–ผ๋งˆ๋‚˜ ์ ์€์ง€ ๋ณด๋ฉด์„œ FastAPI์˜ ์ด์ ์„ ๋น„๋กœ์†Œ ๊ฒฝํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ ๋ณต์‚ฌํ•œ ๋’ค ํŽธ์ง‘ํ•˜๊ณ , ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ **๊ฐ•๋ ฅํžˆ ๊ถŒ์žฅ**ํ•ฉ๋‹ˆ๋‹ค. +์—๋””ํ„ฐ์—์„œ ์‚ฌ์šฉํ•ด ๋ณด๋ฉด, ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์–ผ๋งˆ๋‚˜ ์ ์€์ง€, ๋ชจ๋“  ํƒ€์ž… ๊ฒ€์‚ฌ์™€ ์ž๋™์™„์„ฑ ๋“ฑ FastAPI์˜ ์ด์ ์„ ์ œ๋Œ€๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. --- -## FastAPI ์„ค์น˜ +## FastAPI ์„ค์น˜ { #install-fastapi } -์ฒซ ๋ฒˆ์งธ ๋‹จ๊ณ„๋Š” FastAPI๋ฅผ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ฒซ ๋‹จ๊ณ„๋Š” FastAPI๋ฅผ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. -์ž์Šต์‹œ์—๋Š” ๋ชจ๋“  ์„ ํƒ์ ์ธ ์˜์กด์„ฑ ๋ฐ ๊ธฐ๋Šฅ์„ ํ•จ๊ป˜ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค: +[๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ์ƒ์„ฑํ•˜๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, **FastAPI๋ฅผ ์„ค์น˜**ํ•˜์„ธ์š”: <div class="termy"> ```console -$ pip install "fastapi[all]" +$ pip install "fastapi[standard]" ---> 100% ``` </div> -...์ด๋Š” ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์„œ๋ฒ„๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” `uvicorn` ๋˜ํ•œ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. - /// note | ์ฐธ๊ณ  -๋ถ€๋ถ„์ ์œผ๋กœ ์„ค์น˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +`pip install "fastapi[standard]"`๋กœ ์„ค์น˜ํ•˜๋ฉด `fastapi-cloud-cli`๋ฅผ ํฌํ•จํ•œ ๋ช‡ ๊ฐ€์ง€ ๊ธฐ๋ณธ ์„ ํƒ์  standard ์˜์กด์„ฑ์ด ํ•จ๊ป˜ ์„ค์น˜๋˜๋ฉฐ, ์ด๋ฅผ ์‚ฌ์šฉํ•ด <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>์— ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์šด์˜ ํ™˜๊ฒฝ์— ๋ฐฐํฌํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•ฉ๋‹ˆ๋‹ค: +์ด๋Ÿฌํ•œ ์„ ํƒ์  ์˜์กด์„ฑ์ด ํ•„์š” ์—†๋‹ค๋ฉด `pip install fastapi`๋กœ ๋Œ€์‹  ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -``` -pip install fastapi -``` - -์ถ”๊ฐ€๋กœ ์„œ๋ฒ„ ์—ญํ• ์„ ํ•˜๋Š” `uvicorn`์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค: - -``` -pip install uvicorn -``` - -์‚ฌ์šฉํ•˜๋ ค๋Š” ๊ฐ ์„ ํƒ์ ์ธ ์˜์กด์„ฑ์— ๋Œ€ํ•ด์„œ๋„ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. +standard ์˜์กด์„ฑ์€ ์„ค์น˜ํ•˜๋˜ `fastapi-cloud-cli` ์—†์ด ์„ค์น˜ํ•˜๋ ค๋ฉด `pip install "fastapi[standard-no-fastapi-cloud-cli]"`๋กœ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## ๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ +## ๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ { #advanced-user-guide } -์ด **์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ** ๋‹ค์Œ์— ์ฝ์„ ์ˆ˜ ์žˆ๋Š” **๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด **์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**๋ฅผ ์ฝ์€ ๋’ค์— ๋‚˜์ค‘์— ์ฝ์„ ์ˆ˜ ์žˆ๋Š” **๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -**๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**๋Š” ํ˜„์žฌ ๋ฌธ์„œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ , ๋™์ผํ•œ ๊ฐœ๋…์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ์ถ”๊ฐ€์ ์ธ ๊ธฐ๋Šฅ๋“ค์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. +**๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**๋Š” ์ด ๋ฌธ์„œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋™์ผํ•œ ๊ฐœ๋…์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ (์ง€๊ธˆ ์ฝ๊ณ  ์žˆ๋Š”) **์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**๋ฅผ ๋จผ์ € ์ฝ๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๋จผ์ € **์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**(์ง€๊ธˆ ์ฝ๊ณ  ์žˆ๋Š” ๋‚ด์šฉ)๋ฅผ ์ฝ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -**์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**๋งŒ์œผ๋กœ๋„ ์™„์ „ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฉฐ, ํ•„์š”์— ๋”ฐ๋ผ **๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**์˜ ์ถ”๊ฐ€์ ์ธ ์•„์ด๋””์–ด๋ฅผ ์ ์šฉํ•˜์—ฌ ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**์ž์Šต์„œ - ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**๋งŒ์œผ๋กœ ์™„์ „ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์—ˆ๊ณ , ํ•„์š”์— ๋”ฐ๋ผ **๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**์˜ ์ถ”๊ฐ€ ์•„์ด๋””์–ด๋ฅผ ํ™œ์šฉํ•ด ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/metadata.md b/docs/ko/docs/tutorial/metadata.md index a50dfa2e77..1d1df49259 100644 --- a/docs/ko/docs/tutorial/metadata.md +++ b/docs/ko/docs/tutorial/metadata.md @@ -1,26 +1,26 @@ -# ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฐ ๋ฌธ์„œํ™” URL +# ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฐ ๋ฌธ์„œํ™” URL { #metadata-and-docs-urls } -**FastAPI** ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ๋‹ค์–‘ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉ์ž ๋งž์ถค ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋‹ค์–‘ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉ์ž ๋งž์ถค ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## API์— ๋Œ€ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ +## API์— ๋Œ€ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ { #metadata-for-api } OpenAPI ๋ช…์„ธ ๋ฐ ์ž๋™ํ™”๋œ API ๋ฌธ์„œ UI์— ์‚ฌ์šฉ๋˜๋Š” ๋‹ค์Œ ํ•„๋“œ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: | ๋งค๊ฐœ๋ณ€์ˆ˜ | ํƒ€์ž… | ์„ค๋ช… | |----------|------|-------| | `title` | `str` | API์˜ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค. | -| `summary` | `str` | API์— ๋Œ€ํ•œ ์งง์€ ์š”์•ฝ์ž…๋‹ˆ๋‹ค. <small>OpenAPI 3.1.0, FastAPI 0.99.0๋ถ€ํ„ฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅ</small> | +| `summary` | `str` | API์— ๋Œ€ํ•œ ์งง์€ ์š”์•ฝ์ž…๋‹ˆ๋‹ค. <small>OpenAPI 3.1.0, FastAPI 0.99.0๋ถ€ํ„ฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅ.</small> | | `description` | `str` | API์— ๋Œ€ํ•œ ์งง์€ ์„ค๋ช…์ž…๋‹ˆ๋‹ค. ๋งˆํฌ๋‹ค์šด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. | -| `version` | `string` | API์˜ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค. OpenAPI์˜ ๋ฒ„์ „์ด ์•„๋‹Œ, ์—ฌ๋Ÿฌ๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฒ„์ „์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์˜ˆ: `2.5.0` | +| `version` | `string` | API์˜ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค. OpenAPI์˜ ๋ฒ„์ „์ด ์•„๋‹Œ, ์—ฌ๋Ÿฌ๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฒ„์ „์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์˜ˆ: `2.5.0`. | | `terms_of_service` | `str` | API ์ด์šฉ ์•ฝ๊ด€์˜ URL์ž…๋‹ˆ๋‹ค. ์ œ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ URL ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. | | `contact` | `dict` | ๋…ธ์ถœ๋œ API์— ๋Œ€ํ•œ ์—ฐ๋ฝ์ฒ˜ ์ •๋ณด์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <details><summary><code>contact</code> ํ•„๋“œ</summary><table><thead><tr><th>๋งค๊ฐœ๋ณ€์ˆ˜</th><th>ํƒ€์ž…</th><th>์„ค๋ช…</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>์—ฐ๋ฝ์ฒ˜ ์ธ๋ฌผ/์กฐ์ง์˜ ์‹๋ณ„๋ช…์ž…๋‹ˆ๋‹ค.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>์—ฐ๋ฝ์ฒ˜ ์ •๋ณด๊ฐ€ ๋‹ด๊ธด URL์ž…๋‹ˆ๋‹ค. URL ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>์—ฐ๋ฝ์ฒ˜ ์ธ๋ฌผ/์กฐ์ง์˜ ์ด๋ฉ”์ผ ์ฃผ์†Œ์ž…๋‹ˆ๋‹ค. ์ด๋ฉ”์ผ ์ฃผ์†Œ ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.</td></tr></tbody></table></details> | -| `license_info` | `dict` | ๋…ธ์ถœ๋œ API์˜ ๋ผ์ด์„ ์Šค ์ •๋ณด์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <details><summary><code>license_info</code> ํ•„๋“œ</summary><table><thead><tr><th>๋งค๊ฐœ๋ณ€์ˆ˜</th><th>ํƒ€์ž…</th><th>์„ค๋ช…</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ํ•„์ˆ˜</strong> (<code>license_info</code>๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ). API์— ์‚ฌ์šฉ๋œ ๋ผ์ด์„ ์Šค ์ด๋ฆ„์ž…๋‹ˆ๋‹ค.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>API์— ๋Œ€ํ•œ <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> ๋ผ์ด์„ ์Šค ํ‘œํ˜„์ž…๋‹ˆ๋‹ค. <code>identifier</code> ํ•„๋“œ๋Š” <code>url</code> ํ•„๋“œ์™€ ์ƒํ˜ธ ๋ฐฐํƒ€์ ์ž…๋‹ˆ๋‹ค. <small>OpenAPI 3.1.0, FastAPI 0.99.0๋ถ€ํ„ฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅ</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>API์— ์‚ฌ์šฉ๋œ ๋ผ์ด์„ ์Šค์˜ URL์ž…๋‹ˆ๋‹ค. URL ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.</td></tr></tbody></table></details> | +| `license_info` | `dict` | ๋…ธ์ถœ๋œ API์˜ ๋ผ์ด์„ ์Šค ์ •๋ณด์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <details><summary><code>license_info</code> ํ•„๋“œ</summary><table><thead><tr><th>๋งค๊ฐœ๋ณ€์ˆ˜</th><th>ํƒ€์ž…</th><th>์„ค๋ช…</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ํ•„์ˆ˜</strong> (<code>license_info</code>๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ). API์— ์‚ฌ์šฉ๋œ ๋ผ์ด์„ ์Šค ์ด๋ฆ„์ž…๋‹ˆ๋‹ค.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>API์— ๋Œ€ํ•œ <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> ๋ผ์ด์„ ์Šค ํ‘œํ˜„์ž…๋‹ˆ๋‹ค. <code>identifier</code> ํ•„๋“œ๋Š” <code>url</code> ํ•„๋“œ์™€ ์ƒํ˜ธ ๋ฐฐํƒ€์ ์ž…๋‹ˆ๋‹ค. <small>OpenAPI 3.1.0, FastAPI 0.99.0๋ถ€ํ„ฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅ.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>API์— ์‚ฌ์šฉ๋œ ๋ผ์ด์„ ์Šค์˜ URL์ž…๋‹ˆ๋‹ค. URL ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.</td></tr></tbody></table></details> | ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/metadata/tutorial001.py hl[3:16,19:32] *} +{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} -/// tip +/// tip | ํŒ `description` ํ•„๋“œ์— ๋งˆํฌ๋‹ค์šด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ถœ๋ ฅ์—์„œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. @@ -30,79 +30,81 @@ OpenAPI ๋ช…์„ธ ๋ฐ ์ž๋™ํ™”๋œ API ๋ฌธ์„œ UI์— ์‚ฌ์šฉ๋˜๋Š” ๋‹ค์Œ ํ•„๋“œ๋ฅผ <img src="/img/tutorial/metadata/image01.png"> -## ๋ผ์ด์„ ์Šค ์‹๋ณ„์ž +## ๋ผ์ด์„ ์Šค ์‹๋ณ„์ž { #license-identifier } -OpenAPI 3.1.0 ๋ฐ FastAPI 0.99.0๋ถ€ํ„ฐ `license_info`์— `identifier`๋ฅผ URL ๋Œ€์‹  ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +OpenAPI 3.1.0 ๋ฐ FastAPI 0.99.0๋ถ€ํ„ฐ `license_info`์— `url` ๋Œ€์‹  `identifier`๋ฅผ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ: -{* ../../docs_src/metadata/tutorial001_1.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} -## ํƒœ๊ทธ์— ๋Œ€ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ +## ํƒœ๊ทธ์— ๋Œ€ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ { #metadata-for-tags } -`openapi_tags` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฝ๋กœ ์ž‘๋™์„ ๊ทธ๋ฃนํ™”ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ํƒœ๊ทธ์— ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`openapi_tags` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ์„ ๊ทธ๋ฃนํ™”ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์—ฌ๋Ÿฌ ํƒœ๊ทธ์— ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -๋ฆฌ์ŠคํŠธ๋Š” ๊ฐ ํƒœ๊ทธ์— ๋Œ€ํ•ด ํ•˜๋‚˜์˜ ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +๋ฆฌ์ŠคํŠธ๋Š” ๊ฐ ํƒœ๊ทธ์— ๋Œ€ํ•ด ํ•˜๋‚˜์˜ ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋”•์…”๋„ˆ๋ฆฌ์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -* `name` (**ํ•„์ˆ˜**): `tags` ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ *๊ฒฝ๋กœ ์ž‘๋™*๊ณผ `APIRouter`์— ์‚ฌ์šฉ๋œ ํƒœ๊ทธ ์ด๋ฆ„๊ณผ ๋™์ผํ•œ `str`์ž…๋‹ˆ๋‹ค. -* `description`: ํƒœ๊ทธ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์„ค๋ช…์„ ๋‹ด์€ `str`์ž…๋‹ˆ๋‹ค. ๋งˆํฌ๋‹ค์šด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋ฌธ์„œ UI์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. +* `name` (**ํ•„์ˆ˜**): *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ๋ฐ `APIRouter`์˜ `tags` ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํƒœ๊ทธ ์ด๋ฆ„๊ณผ ๋™์ผํ•œ `str`์ž…๋‹ˆ๋‹ค. +* `description`: ํƒœ๊ทธ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์„ค๋ช…์„ ๋‹ด์€ `str`์ž…๋‹ˆ๋‹ค. ๋งˆํฌ๋‹ค์šด์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋ฌธ์„œ UI์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. * `externalDocs`: ์™ธ๋ถ€ ๋ฌธ์„œ๋ฅผ ์„ค๋ช…ํ•˜๋Š” `dict`์ด๋ฉฐ: * `description`: ์™ธ๋ถ€ ๋ฌธ์„œ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์„ค๋ช…์„ ๋‹ด์€ `str`์ž…๋‹ˆ๋‹ค. * `url` (**ํ•„์ˆ˜**): ์™ธ๋ถ€ ๋ฌธ์„œ์˜ URL์„ ๋‹ด์€ `str`์ž…๋‹ˆ๋‹ค. -### ํƒœ๊ทธ์— ๋Œ€ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ +### ํƒœ๊ทธ์— ๋Œ€ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ { #create-metadata-for-tags } -`users` ๋ฐ `items`์— ๋Œ€ํ•œ ํƒœ๊ทธ ์˜ˆ์‹œ์™€ ํ•จ๊ป˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ `openapi_tags` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: +`users` ๋ฐ `items`์— ๋Œ€ํ•œ ํƒœ๊ทธ ์˜ˆ์‹œ๋กœ ์‹œ๋„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *} +ํƒœ๊ทธ์— ๋Œ€ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ `openapi_tags` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•˜์„ธ์š”: -์„ค๋ช… ์•ˆ์— ๋งˆํฌ๋‹ค์šด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด "login"์€ ๊ตต๊ฒŒ(**login**) ํ‘œ์‹œ๋˜๊ณ , "fancy"๋Š” ๊ธฐ์šธ์ž„๊ผด(_fancy_)๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. +{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} -/// tip +์„ค๋ช… ์•ˆ์— ๋งˆํฌ๋‹ค์šด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด "login"์€ ๊ตต๊ฒŒ(**login**) ํ‘œ์‹œ๋˜๊ณ , "fancy"๋Š” ๊ธฐ์šธ์ž„๊ผด(_fancy_)๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. + +/// tip | ํŒ ์‚ฌ์šฉ ์ค‘์ธ ๋ชจ๋“  ํƒœ๊ทธ์— ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. /// -### ํƒœ๊ทธ ์‚ฌ์šฉ +### ํƒœ๊ทธ ์‚ฌ์šฉ { #use-your-tags } -`tags` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ *๊ฒฝ๋กœ ์ž‘๋™* ๋ฐ `APIRouter`์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ํƒœ๊ทธ์— ํ• ๋‹นํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +`tags` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* (๋ฐ `APIRouter`)์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฅผ ์„œ๋กœ ๋‹ค๋ฅธ ํƒœ๊ทธ์— ํ• ๋‹นํ•˜์„ธ์š”: -{* ../../docs_src/metadata/tutorial004.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} -/// info +/// info | ์ •๋ณด -ํƒœ๊ทธ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ [๊ฒฝ๋กœ ์ž‘๋™ ๊ตฌ์„ฑ](path-operation-configuration.md#tags){.internal-link target=_blank}์—์„œ ์ฝ์–ด๋ณด์„ธ์š”. +ํƒœ๊ทธ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ [๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๊ตฌ์„ฑ](path-operation-configuration.md#tags){.internal-link target=_blank}์—์„œ ์ฝ์–ด๋ณด์„ธ์š”. /// -### ๋ฌธ์„œ ํ™•์ธ +### ๋ฌธ์„œ ํ™•์ธ { #check-the-docs } ์ด์ œ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜๋ฉด ๋ชจ๋“  ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค: <img src="/img/tutorial/metadata/image02.png"> -### ํƒœ๊ทธ ์ˆœ์„œ +### ํƒœ๊ทธ ์ˆœ์„œ { #order-of-tags } -๊ฐ ํƒœ๊ทธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ์˜ ์ˆœ์„œ๋Š” ๋ฌธ์„œ UI์— ํ‘œ์‹œ๋˜๋Š” ์ˆœ์„œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. +๊ฐ ํƒœ๊ทธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ์˜ ์ˆœ์„œ๋Š” ๋ฌธ์„œ UI์— ํ‘œ์‹œ๋˜๋Š” ์ˆœ์„œ๋„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ์•ŒํŒŒ๋ฒณ ์ˆœ์„œ์ƒ `users`๋Š” `items` ๋’ค์— ์˜ค์ง€๋งŒ, ์šฐ๋ฆฌ๋Š” `users` ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌ์ŠคํŠธ์˜ ์ฒซ ๋ฒˆ์งธ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ์ถ”๊ฐ€ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋จผ์ € ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, ์•ŒํŒŒ๋ฒณ ์ˆœ์„œ์ƒ `users`๋Š” `items` ๋’ค์— ์˜ค์ง€๋งŒ, ์šฐ๋ฆฌ๋Š” ํ•ด๋‹น ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌ์ŠคํŠธ์˜ ์ฒซ ๋ฒˆ์งธ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ์ถ”๊ฐ€ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋จผ์ € ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. -## OpenAPI URL +## OpenAPI URL { #openapi-url } -OpenAPI ๊ตฌ์กฐ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ `/openapi.json`์—์„œ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. +๊ธฐ๋ณธ์ ์œผ๋กœ OpenAPI ์Šคํ‚ค๋งˆ๋Š” `/openapi.json`์—์„œ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. -`openapi_url` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ์ด๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ `openapi_url` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ด๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ์ด๋ฅผ `/api/v1/openapi.json`์— ์ œ๊ณตํ•˜๋„๋ก ์„ค์ •ํ•˜๋ ค๋ฉด: +์˜ˆ๋ฅผ ๋“ค์–ด, ์ด๋ฅผ `/api/v1/openapi.json`์—์„œ ์ œ๊ณตํ•˜๋„๋ก ์„ค์ •ํ•˜๋ ค๋ฉด: -{* ../../docs_src/metadata/tutorial002.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} -OpenAPI ๊ตฌ์กฐ๋ฅผ ์™„์ „ํžˆ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด `openapi_url=None`์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์„œํ™” ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋„ ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. +OpenAPI ์Šคํ‚ค๋งˆ๋ฅผ ์™„์ „ํžˆ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด `openapi_url=None`์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์„œํ™” ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋„ ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. -## ๋ฌธ์„œํ™” URL +## ๋ฌธ์„œํ™” URL { #docs-urls } ํฌํ•จ๋œ ๋‘ ๊ฐ€์ง€ ๋ฌธ์„œํ™” ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -115,4 +117,4 @@ OpenAPI ๊ตฌ์กฐ๋ฅผ ์™„์ „ํžˆ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด `openapi_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/ko/docs/tutorial/middleware.md b/docs/ko/docs/tutorial/middleware.md index e0daa3c99b..c213c50749 100644 --- a/docs/ko/docs/tutorial/middleware.md +++ b/docs/ko/docs/tutorial/middleware.md @@ -1,66 +1,95 @@ -# ๋ฏธ๋“ค์›จ์–ด +# ๋ฏธ๋“ค์›จ์–ด { #middleware } ๋ฏธ๋“ค์›จ์–ด๋ฅผ **FastAPI** ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -"๋ฏธ๋“ค์›จ์–ด"๋Š” ํŠน์ • *๊ฒฝ๋กœ ์ž‘๋™*์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋˜๊ธฐ ์ „, ๋ชจ๋“  **์š”์ฒญ**์— ๋Œ€ํ•ด์„œ ๋™์ž‘ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ชจ๋“  **์‘๋‹ต**์ด ๋ฐ˜ํ™˜๋˜๊ธฐ ์ „์—๋„ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. +"๋ฏธ๋“ค์›จ์–ด"๋Š” ํŠน์ • *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋˜๊ธฐ ์ „, ๋ชจ๋“  **์š”์ฒญ**์— ๋Œ€ํ•ด์„œ ๋™์ž‘ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ชจ๋“  **์‘๋‹ต**์ด ๋ฐ˜ํ™˜๋˜๊ธฐ ์ „์—๋„ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. -* ๋ฏธ๋“ค์›จ์–ด๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ์˜ค๋Š” **์š”์ฒญ**๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. -* **์š”์ฒญ** ๋˜๋Š” ๋‹ค๋ฅธ ํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* **์š”์ฒญ**์„ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ *๊ฒฝ๋กœ ์ž‘๋™*์œผ๋กœ ์ „๋‹ฌํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. -* ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ *๊ฒฝ๋กœ ์ž‘์—…*์—์„œ ์ƒ์„ฑํ•œ **์‘๋‹ต**๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. -* **์‘๋‹ต** ๋˜๋Š” ๋‹ค๋ฅธ ํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋Š” ๋™์ž‘์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* **์‘๋‹ต**๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +* ๋ฏธ๋“ค์›จ์–ด๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ์˜ค๋Š” ๊ฐ **์š”์ฒญ**์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. +* ๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•ด๋‹น **์š”์ฒญ**์— ๋Œ€ํ•ด ๋ฌด์–ธ๊ฐ€๋ฅผ ํ•˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ๊ทธ๋Ÿฐ ๋‹ค์Œ **์š”์ฒญ**์„ ๋‚˜๋จธ์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์–ด๋–ค *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€)์„ ํ†ตํ•ด ์ฒ˜๋ฆฌ๋˜๋„๋ก ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. +* ๊ทธ๋Ÿฐ ๋‹ค์Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์–ด๋–ค *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€)์ด ์ƒ์„ฑํ•œ **์‘๋‹ต**์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. +* ๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•ด๋‹น **์‘๋‹ต**์— ๋Œ€ํ•ด ๋ฌด์–ธ๊ฐ€๋ฅผ ํ•˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ๊ทธ๋Ÿฐ ๋‹ค์Œ **์‘๋‹ต**์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. /// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ -๋งŒ์•ฝ `yield`๋ฅผ ์‚ฌ์šฉํ•œ ์˜์กด์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์‹คํ–‰๋˜๊ณ  ๋‚œ ํ›„์— exit์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +`yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์ด ์žˆ๋‹ค๋ฉด, exit ์ฝ”๋“œ๋Š” ๋ฏธ๋“ค์›จ์–ด *ํ›„์—* ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. -๋งŒ์•ฝ (๋‚˜์ค‘์— ๋ฌธ์„œ์—์„œ ๋‹ค๋ฃฐ) ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ด ์žˆ๋‹ค๋ฉด, ๋ชจ๋“  ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์‹คํ–‰๋˜๊ณ  *๋‚œ ํ›„์—* ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…(๋’ค์—์„œ ๋ณด๊ฒŒ ๋  [Background Tasks](background-tasks.md){.internal-link target=_blank} ์„น์…˜์—์„œ ๋‹ค๋ฃน๋‹ˆ๋‹ค)์ด ์žˆ๋‹ค๋ฉด, ๋ชจ๋“  ๋ฏธ๋“ค์›จ์–ด *ํ›„์—* ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. /// -## ๋ฏธ๋“ค์›จ์–ด ๋งŒ๋“ค๊ธฐ +## ๋ฏธ๋“ค์›จ์–ด ๋งŒ๋“ค๊ธฐ { #create-a-middleware } -๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ•จ์ˆ˜ ์ƒ๋‹จ์— `@app.middleware("http")` ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ํ•จ์ˆ˜ ์ƒ๋‹จ์— ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ `@app.middleware("http")`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜๋Š” ๋‹ค์Œ ํ•ญ๋ชฉ๋“ค์„ ๋ฐ›์Šต๋‹ˆ๋‹ค: +๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜๋Š” ๋‹ค์Œ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค: * `request`. * `request`๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š” `call_next` ํ•จ์ˆ˜. - * ์ด ํ•จ์ˆ˜๋Š” `request`๋ฅผ ํ•ด๋‹นํ•˜๋Š” *๊ฒฝ๋กœ ์ž‘์—…*์œผ๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. - * ๊ทธ๋Ÿฐ ๋‹ค์Œ, *๊ฒฝ๋กœ ์ž‘์—…*์— ์˜ํ•ด ์ƒ์„ฑ๋œ `response` ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -* `response`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์ „์— ์ถ”๊ฐ€๋กœ `response`๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + * ์ด ํ•จ์ˆ˜๋Š” `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 | ํŒ -์‚ฌ์šฉ์ž ์ •์˜ ํ—ค๋”๋Š” <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">'X-' ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉ</a>ํ•˜์—ฌ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์‚ฌ์šฉ์ž ์ •์˜ ๋…์  ํ—ค๋”๋Š” <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">`X-` ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉ</a>ํ•˜์—ฌ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. -๊ทธ๋Ÿฌ๋‚˜ ๋งŒ์•ฝ ํด๋ผ์ด์–ธํŠธ์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ํ—ค๋”๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, ๊ทธ๊ฒƒ๋“ค์„ CORS ์„ค์ •([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})์— <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette CORS ๋ฌธ์„œ</a>์— ๋ช…์‹œ๋œ `expose_headers` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•˜์—ฌ ํ—ค๋”๋“ค์„ ์ถ”๊ฐ€ํ•˜์—ฌ์•ผํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €์—์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋ ค๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ํ—ค๋”๊ฐ€ ์žˆ๋‹ค๋ฉด, <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette์˜ CORS ๋ฌธ์„œ</a>์— ๋ฌธ์„œํ™”๋œ `expose_headers` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด CORS ์„ค์ •([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. /// -/// note | ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ -`from starlette.requests import request`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +`from starlette.requests import Request`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์—๊ฒŒ ํŽธ์˜๋ฅผ ์œ„ํ•ด ์ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Starlette์—์„œ ์ง์ ‘ ํŒŒ์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์ธ ์—ฌ๋Ÿฌ๋ถ„์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด ์ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” Starlette์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. /// -### `response`์˜ ์ „๊ณผ ํ›„ +### `response`์˜ ์ „๊ณผ ํ›„ { #before-and-after-the-response } -*๊ฒฝ๋กœ ์ž‘๋™*์„ ๋ฐ›๊ธฐ ์ „ `request`์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์–ด๋–ค *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ ๋ฐ›๊ธฐ ์ „์—, `request`์™€ ํ•จ๊ป˜ ์‹คํ–‰๋  ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  `response` ๋˜ํ•œ ์ƒ์„ฑ๋œ ํ›„ ๋ฐ˜ํ™˜๋˜๊ธฐ ์ „์— ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ `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/middleware.md){.internal-link target=\_blank}์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์„œ๋Š” ์ด๋Ÿฌํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€์—์„œ ๋” ์ •ํ™•ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— `time.time()` ๋Œ€์‹  <a href="https://docs.python.org/3/library/time.html#time.perf_counter" class="external-link" target="_blank">`time.perf_counter()`</a>๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿค“ -๋‹ค์Œ ๋ถ€๋ถ„์—์„œ ๋ฏธ๋“ค์›จ์–ด์™€ ํ•จ๊ป˜ <abbr title="๊ต์ฐจ-์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ ">CORS</abbr>๋ฅผ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฃจ๋Š”์ง€์— ๋Œ€ํ•ด ํ™•์ธํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +/// + +## ์—ฌ๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด ์‹คํ–‰ ์ˆœ์„œ { #multiple-middleware-execution-order } + +`@app.middleware()` ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋˜๋Š” `app.add_middleware()` ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ์—ฌ๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด, ์ƒˆ๋กœ ์ถ”๊ฐ€๋œ ๊ฐ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐ์‹ธ ์Šคํƒ์„ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์— ์ถ”๊ฐ€๋œ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ *๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ*์ด๊ณ , ์ฒ˜์Œ์— ์ถ”๊ฐ€๋œ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ *๊ฐ€์žฅ ์•ˆ์ชฝ*์ž…๋‹ˆ๋‹ค. + +์š”์ฒญ ๊ฒฝ๋กœ์—์„œ๋Š” *๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ* ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ๋จผ์ € ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + +์‘๋‹ต ๊ฒฝ๋กœ์—์„œ๋Š” ๋งˆ์ง€๋ง‰์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด: + +```Python +app.add_middleware(MiddlewareA) +app.add_middleware(MiddlewareB) +``` + +์ด ๊ฒฝ์šฐ ์‹คํ–‰ ์ˆœ์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* **์š”์ฒญ**: MiddlewareB โ†’ MiddlewareA โ†’ route + +* **์‘๋‹ต**: route โ†’ MiddlewareA โ†’ MiddlewareB + +์ด๋Ÿฌํ•œ ์Šคํƒœํ‚น ๋™์ž‘์€ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ณ  ์ œ์–ด ๊ฐ€๋Šฅํ•œ ์ˆœ์„œ๋กœ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + +## ๋‹ค๋ฅธ ๋ฏธ๋“ค์›จ์–ด { #other-middlewares } + +๋‹ค๋ฅธ ๋ฏธ๋“ค์›จ์–ด์— ๋Œ€ํ•œ ๋” ๋งŽ์€ ์ •๋ณด๋Š” ๋‚˜์ค‘์— [์ˆ™๋ จ๋œ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ: ํ–ฅ์ƒ๋œ ๋ฏธ๋“ค์›จ์–ด](../advanced/middleware.md){.internal-link target=_blank}์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋‹ค์Œ ์„น์…˜์—์„œ ๋ฏธ๋“ค์›จ์–ด๋กœ <abbr title="Cross-Origin Resource Sharing">CORS</abbr>๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/path-operation-configuration.md b/docs/ko/docs/tutorial/path-operation-configuration.md index 81914182af..b8a87667a0 100644 --- a/docs/ko/docs/tutorial/path-operation-configuration.md +++ b/docs/ko/docs/tutorial/path-operation-configuration.md @@ -1,97 +1,107 @@ -# ๊ฒฝ๋กœ ์ž‘๋™ ์„ค์ • +# ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ์„ค์ • { #path-operation-configuration } -*๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ „๋‹ฌํ• ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. /// warning | ๊ฒฝ๊ณ  -์•„๋ž˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์€ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ์•„๋‹Œ *๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— ์ง์ ‘ ์ „๋‹ฌ๋œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๊ธฐ์–ตํ•˜์‹ญ์‹œ์˜ค. +์•„๋ž˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์€ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๊ฐ€ ์•„๋‹Œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— ์ง์ ‘ ์ „๋‹ฌ๋œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๊ธฐ์–ตํ•˜์„ธ์š”. /// -## ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ +## ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ { #response-status-code } -*๊ฒฝ๋กœ ์ž‘๋™*์˜ ์‘๋‹ต์— ์‚ฌ์šฉ๋  (HTTP) `status_code`๋ฅผ ์ •์˜ํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ ์‘๋‹ต์— ์‚ฌ์šฉ๋  (HTTP) `status_code`๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -`404`์™€ ๊ฐ™์€ `int`ํ˜• ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ „๋‹ฌํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`404`์™€ ๊ฐ™์€ `int`ํ˜• ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ๊ฐ ์ฝ”๋“œ์˜ ์˜๋ฏธ๋ฅผ ๋ชจ๋ฅธ๋‹ค๋ฉด, `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 | ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž„ํฌํŠธํ•˜์…”๋„ ์ข‹์Šต๋‹ˆ๋‹ค. `from starlette import status`. -**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž ์—ฌ๋Ÿฌ๋ถ„์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด์„œ `starlette.status`์™€ ๋™์ผํ•œ `fastapi.status`๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Starlette์—์„œ ์ง์ ‘ ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž ์—ฌ๋Ÿฌ๋ถ„์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `starlette.status`์™€ ๋™์ผํ•œ `fastapi.status`๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” Starlette์—์„œ ์ง์ ‘ ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. /// -## ํƒœ๊ทธ +## ํƒœ๊ทธ { #tags } -(๋ณดํ†ต ๋‹จ์ผ `str`์ธ) `str`๋กœ ๊ตฌ์„ฑ๋œ `list`์™€ ํ•จ๊ป˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `tags`๋ฅผ ์ „๋‹ฌํ•˜์—ฌ, `๊ฒฝ๋กœ ์ž‘๋™`์— ํƒœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +(๋ณดํ†ต ๋‹จ์ผ `str`์ธ) `str`๋กœ ๊ตฌ์„ฑ๋œ `list`์™€ ํ•จ๊ป˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `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์˜ ์Šคํ‚ค๋งˆ์— ์ถ”๊ฐ€๋˜๋ฉฐ, ์ž๋™ ๋ฌธ์„œ ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค: <img src="/img/tutorial/path-operation-configuration/image01.png"> -## ์š”์•ฝ๊ณผ ๊ธฐ์ˆ  +### Enum์„ ์‚ฌ์šฉํ•œ ํƒœ๊ทธ { #tags-with-enums } + +ํฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์žˆ๋‹ค๋ฉด, **์—ฌ๋Ÿฌ ํƒœ๊ทธ**๊ฐ€ ์Œ“์ด๊ฒŒ ๋  ์ˆ˜ ์žˆ๊ณ , ๊ด€๋ จ๋œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ํ•ญ์ƒ **๊ฐ™์€ ํƒœ๊ทธ**๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ํƒœ๊ทธ๋ฅผ `Enum`์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ํ•ฉ๋ฆฌ์ ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +**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.py hl[20:21] *} +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} -## ๋…์ŠคํŠธ๋ง์œผ๋กœ ๋งŒ๋“  ๊ธฐ์ˆ  +## ๋…์ŠคํŠธ๋ง์œผ๋กœ ๋งŒ๋“  ์„ค๋ช… { #description-from-docstring } -์„ค๋ช…์€ ๋ณดํ†ต ๊ธธ์–ด์ง€๊ณ  ์—ฌ๋Ÿฌ ์ค„์— ๊ฑธ์ณ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, *๊ฒฝ๋กœ ์ž‘๋™* ๊ธฐ์ˆ ์„ ํ•จ์ˆ˜ <abbr title="ํ•จ์ˆ˜์•ˆ์— ์žˆ๋Š” ์ฒซ๋ฒˆ์งธ ํ‘œํ˜„์‹์œผ๋กœ, ๋ฌธ์„œ๋กœ ์‚ฌ์šฉ๋  ์—ฌ๋Ÿฌ ์ค„์— ๊ฑธ์นœ (๋ณ€์ˆ˜์— ํ• ๋‹น๋˜์ง€ ์•Š์€) ๋ฌธ์ž์—ด"> ๋…์ŠคํŠธ๋ง</abbr> ์— ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค, ์ด๋ฅผ **FastAPI**๊ฐ€ ๋…์ŠคํŠธ๋ง์œผ๋กœ๋ถ€ํ„ฐ ์ฝ์Šต๋‹ˆ๋‹ค. +์„ค๋ช…์€ ๋ณดํ†ต ๊ธธ์–ด์ง€๊ณ  ์—ฌ๋Ÿฌ ์ค„์— ๊ฑธ์ณ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์„ค๋ช…์„ ํ•จ์ˆ˜ <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation โ€“ ๋ฌธ์„œํ™”์— ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜ ๋‚ด๋ถ€ ์ฒซ ํ‘œํ˜„์‹์˜ ์—ฌ๋Ÿฌ ์ค„ ๋ฌธ์ž์—ด(์–ด๋–ค ๋ณ€์ˆ˜์—๋„ ํ• ๋‹น๋˜์ง€ ์•Š์Œ)">docstring</abbr>์— ์„ ์–ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, **FastAPI**๋Š” ๊ทธ๊ณณ์—์„œ ์ด๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค. -<a href="https://ko.wikipedia.org/wiki/%EB%A7%88%ED%81%AC%EB%8B%A4%EC%9A%B4" class="external-link" target="_blank">๋งˆํฌ๋‹ค์šด</a> ๋ฌธ๋ฒ•์œผ๋กœ ๋…์ŠคํŠธ๋ง์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค, ์ž‘์„ฑ๋œ ๋งˆํฌ๋‹ค์šด ํ˜•์‹์˜ ๋…์ŠคํŠธ๋ง์€ (๋งˆํฌ๋‹ค์šด์˜ ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ ๊ณ ๋ คํ•˜์—ฌ) ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํ™”๋ฉด์— ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. +๋…์ŠคํŠธ๋ง์—๋Š” <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a>์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, (๋…์ŠคํŠธ๋ง์˜ ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ ๊ณ ๋ คํ•˜์—ฌ) ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํ•ด์„๋˜๊ณ  ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *} +{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *} ์ด๋Š” ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค: <img src="/img/tutorial/path-operation-configuration/image02.png"> -## ์‘๋‹ต ๊ธฐ์ˆ  +## ์‘๋‹ต ์„ค๋ช… { #response-description } `response_description` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‘๋‹ต์— ๊ด€ํ•œ ์„ค๋ช…์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *} +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *} /// info | ์ •๋ณด -`response_description`์€ ๊ตฌ์ฒด์ ์œผ๋กœ ์‘๋‹ต์„ ์ง€์นญํ•˜๋ฉฐ, `description`์€ ์ผ๋ฐ˜์ ์ธ *๊ฒฝ๋กœ ์ž‘๋™*์„ ์ง€์นญํ•ฉ๋‹ˆ๋‹ค. +`response_description`์€ ๊ตฌ์ฒด์ ์œผ๋กœ ์‘๋‹ต์„ ์ง€์นญํ•˜๋ฉฐ, `description`์€ ์ผ๋ฐ˜์ ์ธ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์ง€์นญํ•ฉ๋‹ˆ๋‹ค. /// /// check | ํ™•์ธ -OpenAPI๋Š” ๊ฐ *๊ฒฝ๋กœ ์ž‘๋™*์ด ์‘๋‹ต์— ๊ด€ํ•œ ์„ค๋ช…์„ ์š”๊ตฌํ•  ๊ฒƒ์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. +OpenAPI๋Š” ๊ฐ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ ์‘๋‹ต์— ๊ด€ํ•œ ์„ค๋ช…์„ ์š”๊ตฌํ•  ๊ฒƒ์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ, ์‘๋‹ต์— ๊ด€ํ•œ ์„ค๋ช…์ด ์—†์„๊ฒฝ์šฐ, **FastAPI**๊ฐ€ ์ž๋™์œผ๋กœ "์„ฑ๊ณต ์‘๋‹ต" ์ค‘ ํ•˜๋‚˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ, ์‘๋‹ต์— ๊ด€ํ•œ ์„ค๋ช…์„ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉด, **FastAPI**๊ฐ€ "Successful response" ์ค‘ ํ•˜๋‚˜๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. /// <img src="/img/tutorial/path-operation-configuration/image03.png"> -## ๋‹จ์ผ *๊ฒฝ๋กœ ์ž‘๋™* ์ง€์›์ค‘๋‹จ +## *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์ง€์›์ค‘๋‹จํ•˜๊ธฐ { #deprecate-a-path-operation } -๋‹จ์ผ *๊ฒฝ๋กœ ์ž‘๋™*์„ ์—†์• ์ง€ ์•Š๊ณ  <abbr title="๊ตฌ์‹, ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๊ฒƒ์ด ๊ถŒ์žฅ๋จ">์ง€์›์ค‘๋‹จ</abbr>์„ ํ•ด์•ผํ•œ๋‹ค๋ฉด, `deprecated` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์ œ๊ฑฐํ•˜์ง€ ์•Š๊ณ  <abbr title="obsolete, recommended not to use it โ€“ ๊ตฌ์‹์ด๋ฉฐ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋จ">deprecated</abbr>๋กœ ํ‘œ์‹œํ•ด์•ผ ํ•œ๋‹ค๋ฉด, `deprecated` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} -๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์— ์ง€์›์ค‘๋‹จ์ด๋ผ๊ณ  ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. +๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ ์ง€์›์ค‘๋‹จ์œผ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค: <img src="/img/tutorial/path-operation-configuration/image04.png"> -์ง€์›์ค‘๋‹จ๋œ ๊ฒฝ์šฐ์™€ ์ง€์›์ค‘๋‹จ ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ *๊ฒฝ๋กœ ์ž‘๋™*์ด ์–ด๋–ป๊ฒŒ ๋ณด์ด๋Š” ์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. +์ง€์›์ค‘๋‹จ๋œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์™€ ์ง€์›์ค‘๋‹จ๋˜์ง€ ์•Š์€ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ณด์ด๋Š”์ง€ ํ™•์ธํ•ด ๋ณด์„ธ์š”: <img src="/img/tutorial/path-operation-configuration/image05.png"> -## ์ •๋ฆฌ +## ์ •๋ฆฌ { #recap } -*๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— ๋งค๊ฐœ๋ณ€์ˆ˜(๋“ค)๋ฅผ ์ „๋‹ฌํ•จ์œผ๋กœ *๊ฒฝ๋กœ ์ž‘๋™*์„ ์„ค์ •ํ•˜๊ณ  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์— ๋งค๊ฐœ๋ณ€์ˆ˜(๋“ค)๋ฅผ ์ „๋‹ฌํ•˜์—ฌ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/path-params-numeric-validations.md b/docs/ko/docs/tutorial/path-params-numeric-validations.md index f21c9290ed..f2c52d4aac 100644 --- a/docs/ko/docs/tutorial/path-params-numeric-validations.md +++ b/docs/ko/docs/tutorial/path-params-numeric-validations.md @@ -1,116 +1,153 @@ -# ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ˆซ์ž ๊ฒ€์ฆ +# ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ˆซ์ž ๊ฒ€์ฆ { #path-parameters-and-numeric-validations } `Query`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋” ๋งŽ์€ ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์„ ์–ธํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ๋™์ผํ•˜๊ฒŒ `Path`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ™์€ ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ๊ฒฝ๋กœ ์ž„ํฌํŠธ +## `Path` ์ž„ํฌํŠธ { #import-path } -๋จผ์ € `fastapi`์—์„œ `Path`๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค: +๋จผ์ € `fastapi`์—์„œ `Path`๋ฅผ ์ž„ํฌํŠธํ•˜๊ณ , `Annotated`๋„ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[3] *} +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *} -## ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ ์–ธ +/// info | ์ •๋ณด -`Query`์— ๋™์ผํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +FastAPI๋Š” 0.95.0 ๋ฒ„์ „์—์„œ `Annotated` ์ง€์›์„ ์ถ”๊ฐ€ํ–ˆ๊ณ (๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ๊ถŒ์žฅํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค). -์˜ˆ๋ฅผ ๋“ค์–ด, `title` ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ฐ’์„ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ `item_id`์— ์„ ์–ธํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋” ์˜ค๋ž˜๋œ ๋ฒ„์ „์ด ์žˆ๋‹ค๋ฉด `Annotated`๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•  ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[10] *} - -/// note | ์ฐธ๊ณ  - -๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๊ฒฝ๋กœ์˜ ์ผ๋ถ€์—ฌ์•ผ ํ•˜๋ฏ€๋กœ ์–ธ์ œ๋‚˜ ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. - -์ฆ‰, `...`๋กœ ์„ ์–ธํ•ด์„œ ํ•„์ˆ˜์ž„์„ ๋‚˜ํƒ€๋‚ด๋Š”๊ฒŒ ์ข‹์Šต๋‹ˆ๋‹ค. - -๊ทธ๋Ÿผ์—๋„ `None`์œผ๋กœ ์„ ์–ธํ•˜๊ฑฐ๋‚˜ ๊ธฐ๋ณธ๊ฐ’์„ ์ง€์ •ํ• ์ง€๋ผ๋„ ์•„๋ฌด ์˜ํ–ฅ์„ ๋ผ์น˜์ง€ ์•Š์œผ๋ฉฐ ์–ธ์ œ๋‚˜ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. +`Annotated`๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์ตœ์†Œ 0.95.1๊นŒ์ง€ [FastAPI ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}๋ฅผ ๊ผญ ํ•˜์„ธ์š”. /// -## ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ •๋ ฌํ•˜๊ธฐ +## ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ ์–ธ { #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`๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ด๊ฒƒ์€ ์•„๋งˆ ๊ทธ๋ ‡๊ฒŒ ์ค‘์š”ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// `str` ํ˜•์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`๋ฅผ ํ•„์ˆ˜๋กœ ์„ ์–ธํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค. -ํ•ด๋‹น ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•ด ์•„๋ฌด๋Ÿฐ ์„ ์–ธ์„ ํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฏ€๋กœ `Query`๋ฅผ ์ •๋ง๋กœ ์จ์•ผํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. +ํ•ด๋‹น ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•ด ์•„๋ฌด๋Ÿฐ ์„ ์–ธ์„ ํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฏ€๋กœ `Query`๋ฅผ ์ •๋ง๋กœ ์จ์•ผ ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ `item_id` ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์—ฌ์ „ํžˆ `Path`๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ `item_id` ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์—ฌ์ „ํžˆ `Path`๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ค ์ด์œ ๋กœ `Annotated`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค. -ํŒŒ์ด์ฌ์€ "๊ธฐ๋ณธ๊ฐ’"์ด ์—†๋Š” ๊ฐ’ ์•ž์— "๊ธฐ๋ณธ๊ฐ’"์ด ์žˆ๋Š” ๊ฐ’์„ ์ž…๋ ฅํ•˜๋ฉด ๋ถˆํ‰ํ•ฉ๋‹ˆ๋‹ค. +ํŒŒ์ด์ฌ์€ "๊ธฐ๋ณธ๊ฐ’"์ด ์žˆ๋Š” ๊ฐ’์„ "๊ธฐ๋ณธ๊ฐ’"์ด ์—†๋Š” ๊ฐ’ ์•ž์— ๋‘๋ฉด ๋ถˆํ‰ํ•ฉ๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์„ ์žฌ์ •๋ ฌํ•จ์œผ๋กœ์จ ๊ธฐ๋ณธ๊ฐ’(์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`)์ด ์—†๋Š” ๊ฐ’์„ ์ฒ˜์Œ ๋ถ€๋ถ„์— ์œ„์น˜ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์ˆœ์„œ๋ฅผ ์žฌ์ •๋ ฌํ•ด์„œ ๊ธฐ๋ณธ๊ฐ’์ด ์—†๋Š” ๊ฐ’(์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`)์„ ์•ž์— ๋‘˜ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**์—์„œ๋Š” ์ค‘์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋ฆ„, ํƒ€์ž… ๊ทธ๋ฆฌ๊ณ  ์„ ์–ธ๊ตฌ(`Query`, `Path` ๋“ฑ)๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ์ง€ํ•˜๋ฉฐ ์ˆœ์„œ๋Š” ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +**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` ๋‹ค๋ฅธ ์ˆœ์„œ๋กœ ์„ ์–ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ํŒŒ์ด์ฌ์€ ์ด๋ฅผ ์œ„ํ•œ ์ž‘๊ณ  ํŠน๋ณ„ํ•œ ๋ฌธ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} -`*`๋ฅผ ํ•จ์ˆ˜์˜ ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•˜์„ธ์š”. +## ํ•„์š”ํ•œ ๋Œ€๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ •๋ ฌํ•˜๊ธฐ, ํŠธ๋ฆญ { #order-the-parameters-as-you-need-tricks } -ํŒŒ์ด์ฌ์€ `*`์œผ๋กœ ์•„๋ฌด๋Ÿฐ ํ–‰๋™๋„ ํ•˜์ง€ ์•Š์ง€๋งŒ, ๋”ฐ๋ฅด๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์€ <abbr title="์œ ๋ž˜: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>๋กœ๋„ ์•Œ๋ ค์ง„ ํ‚ค์›Œ๋“œ ์ธ์ž(ํ‚ค-๊ฐ’ ์Œ)์—ฌ์•ผ ํ•จ์„ ์ธ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š๋”๋ผ๋„ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. +/// tip | ํŒ -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} +`Annotated`๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ด๊ฒƒ์€ ์•„๋งˆ ๊ทธ๋ ‡๊ฒŒ ์ค‘์š”ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ์ˆซ์ž ๊ฒ€์ฆ: ํฌ๊ฑฐ๋‚˜ ๊ฐ™์Œ +/// -`Query`์™€ `Path`(๋‚˜์ค‘์— ๋ณผ ๋‹ค๋ฅธ ๊ฒƒ๋“ค๋„)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ž์—ด ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ˆซ์ž์˜ ์ œ์•ฝ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์œ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” **์ž‘์€ ํŠธ๋ฆญ**์ด ํ•˜๋‚˜ ์žˆ์ง€๋งŒ, ์ž์ฃผ ํ•„์š”ํ•˜์ง„ ์•Š์„ ๊ฒ๋‹ˆ๋‹ค. -์—ฌ๊ธฐ์„œ `ge=1`์ธ ๊ฒฝ์šฐ, `item_id`๋Š” `1`๋ณด๋‹ค "ํฌ๊ฑฐ๋‚˜(`g`reater) ๊ฐ™์€(`e`qual)" ์ •์ˆ˜ํ˜• ์ˆซ์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. +๋งŒ์•ฝ ๋‹ค์Œ์„ ์›ํ•œ๋‹ค๋ฉด: -{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *} +* `Query`๋‚˜ ์–ด๋–ค ๊ธฐ๋ณธ๊ฐ’ ์—†์ด ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`๋ฅผ ์„ ์–ธํ•˜๊ธฐ +* `Path`๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ `item_id`๋ฅผ ์„ ์–ธํ•˜๊ธฐ +* ์ด๋“ค์„ ๋‹ค๋ฅธ ์ˆœ์„œ๋กœ ๋‘๊ธฐ +* `Annotated`๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ -## ์ˆซ์ž ๊ฒ€์ฆ: ํฌ๊ฑฐ๋‚˜ ๊ฐ™์Œ ๋ฐ ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์Œ +...์ด๋ฅผ ์œ„ํ•ด ํŒŒ์ด์ฌ์—๋Š” ์ž‘์€ ํŠน๋ณ„ํ•œ ๋ฌธ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•จ์ˆ˜์˜ ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `*`๋ฅผ ์ „๋‹ฌํ•˜์„ธ์š”. + +ํŒŒ์ด์ฌ์€ `*`์œผ๋กœ ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์ง€๋งŒ, ๋’ค๋”ฐ๋ฅด๋Š” ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ํ‚ค์›Œ๋“œ ์ธ์ž(ํ‚ค-๊ฐ’ ์Œ)๋กœ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•จ์„ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>๋กœ๋„ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์ด ์—†๋”๋ผ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. + +{* ../../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`๋Š” `1`๋ณด๋‹ค "`g`reater than or `e`qual"(ํฌ๊ฑฐ๋‚˜ ๊ฐ™์€) ์ •์ˆ˜ํ˜• ์ˆซ์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +{* ../../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) +* `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` ๊ฐ’์—๋„ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. -์—ฌ๊ธฐ์—์„œ <abbr title="greater than or equal"><code>ge</code></abbr>๋ฟ๋งŒ ์•„๋‹ˆ๋ผ <abbr title="greater than"><code>gt</code></abbr>๋ฅผ ์„ ์–ธ ํ•  ์ˆ˜์žˆ๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์ง‘๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ๊ฐ’์ด `1`๋ณด๋‹ค ์ž‘๋”๋ผ๋„ ๋ฐ˜๋“œ์‹œ `0`๋ณด๋‹ค ์ปค์•ผํ•ฉ๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์—์„œ <abbr title="greater than"><code>gt</code></abbr>๋ฅผ, <abbr title="greater than or equal"><code>ge</code></abbr>๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ์ค‘์š”ํ•ด์ง‘๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ฐ’์ด `1`๋ณด๋‹ค ์ž‘๋”๋ผ๋„, ๋ฐ˜๋“œ์‹œ `0`๋ณด๋‹ค ์ปค์•ผ ํ•œ๋‹ค๊ณ  ์š”๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, `0.5`๋Š” ์œ ํšจํ•œ ๊ฐ’์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ `0.0` ๋˜๋Š” `0`์€ ๊ทธ๋ ‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค. <abbr title="less than"><code>lt</code></abbr> ์—ญ์‹œ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. -{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.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) +* `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` ํด๋ž˜์Šค์˜ ์„œ๋ธŒ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. +`Query`, `Path`, ๊ทธ๋ฆฌ๊ณ  ๋‚˜์ค‘์— ๋ณด๊ฒŒ ๋  ๋‹ค๋ฅธ ํด๋ž˜์Šค๋“ค์€ ๊ณตํ†ต `Param` ํด๋ž˜์Šค์˜ ์„œ๋ธŒํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ์ด๋“ค ๋ชจ๋‘๋Š” ์—ฌํƒœ๊นŒ์ง€ ๋ณธ ์ถ”๊ฐ€ ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์˜ ๋™์ผํ•œ ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. +์ด๋“ค ๋ชจ๋‘๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ๋ณธ ์ถ”๊ฐ€ ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ๋™์ผํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. /// /// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ -`fastapi`์—์„œ `Query`, `Path` ๋“ฑ์„ ์ž„ํฌํŠธ ํ•  ๋•Œ, ์ด๊ฒƒ๋“ค์€ ์‹ค์ œ๋กœ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. +`fastapi`์—์„œ `Query`, `Path` ๋“ฑ์„ ์ž„ํฌํŠธํ•  ๋•Œ, ์ด๊ฒƒ๋“ค์€ ์‹ค์ œ๋กœ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ํ˜ธ์ถœ๋˜๋ฉด ๋™์ผํ•œ ์ด๋ฆ„์˜ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ํ•จ์ˆ˜์ธ `Query`๋ฅผ ์ž„ํฌํŠธํ•œ ๊ฒ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ˜ธ์ถœํ•˜๋ฉด `Query`๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง„ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -ํŽธ์ง‘๊ธฐ์—์„œ ํƒ€์ž…์— ๋Œ€ํ•œ ์˜ค๋ฅ˜๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด (ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ ) ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ํ•จ์ˆ˜๋“ค์ด ์žˆ๋Š” ์ด์œ ๋Š”(ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ ) ํŽธ์ง‘๊ธฐ์—์„œ ํƒ€์ž…์— ๋Œ€ํ•œ ์˜ค๋ฅ˜๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์˜ค๋ฅ˜๋ฅผ ๋ฌด์‹œํ•˜๊ธฐ ์œ„ํ•œ ์‚ฌ์šฉ์ž ์„ค์ •์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ ๋„ ์ผ๋ฐ˜ ํŽธ์ง‘๊ธฐ์™€ ์ฝ”๋”ฉ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/path-params.md b/docs/ko/docs/tutorial/path-params.md index b72787e0b2..ea5170eccb 100644 --- a/docs/ko/docs/tutorial/path-params.md +++ b/docs/ko/docs/tutorial/path-params.md @@ -1,8 +1,8 @@ -# ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ +# ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ { #path-parameters } ํŒŒ์ด์ฌ์˜ ํฌ๋งท ๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ฌธ๋ฒ•์„ ์ด์šฉํ•˜์—ฌ ๊ฒฝ๋กœ "๋งค๊ฐœ๋ณ€์ˆ˜" ๋˜๋Š” "๋ณ€์ˆ˜"๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ `item_id`์˜ ๊ฐ’์€ ํ•จ์ˆ˜์˜ `item_id` ์ธ์ž๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. @@ -12,11 +12,11 @@ {"item_id":"foo"} ``` -## ํƒ€์ž…์ด ์žˆ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜ +## ํƒ€์ž…์ด ์žˆ๋Š” ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ { #path-parameters-with-types } ํŒŒ์ด์ฌ ํ‘œ์ค€ ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•จ์ˆ˜์— ์žˆ๋Š” ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ํƒ€์ž…์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/path_params/tutorial002.py hl[7] *} +{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *} ์œ„์˜ ์˜ˆ์‹œ์—์„œ, `item_id`๋Š” `int`๋กœ ์„ ์–ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. @@ -26,7 +26,7 @@ /// -## ๋ฐ์ดํ„ฐ <abbr title="๋‹ค์Œ์œผ๋กœ๋„ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค: ์ง๋ ฌํ™”, ํŒŒ์‹ฑ, ๋งˆ์ƒฌ๋ง">๋ณ€ํ™˜</abbr> +## ๋ฐ์ดํ„ฐ <abbr title="๋‹ค์Œ์œผ๋กœ๋„ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค: ์ง๋ ฌํ™”, ํŒŒ์‹ฑ, ๋งˆ์ƒฌ๋ง">๋ณ€ํ™˜</abbr> { #data-conversion } ์ด ์˜ˆ์ œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>์„ ์—ด๋ฉด, ๋‹ค์Œ ์‘๋‹ต์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -42,40 +42,41 @@ /// -## ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ +## ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ { #data-validation } -ํ•˜์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €์—์„œ <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>๋กœ ์ด๋™ํ•˜๋ฉด, HTTP ์˜ค๋ฅ˜๊ฐ€ ์ž˜ ๋œจ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +ํ•˜์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €์—์„œ <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>๋กœ ์ด๋™ํ•˜๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์€ 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`๋Š” `int`๊ฐ€ ์•„๋‹Œ `"foo"` ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ `item_id`๊ฐ€ `int`๊ฐ€ ์•„๋‹Œ `"foo"` ๊ฐ’์„ ๊ฐ€์กŒ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -`int`๊ฐ€ ์•„๋‹Œ `float`์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ๋™์ผํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a> +`int` ๋Œ€์‹  `float`์„ ์ œ๊ณตํ•˜๋ฉด(์˜ˆ: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>) ๋™์ผํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. /// check | ํ™•์ธ ์ฆ‰, ํŒŒ์ด์ฌ ํƒ€์ž… ์„ ์–ธ์„ ํ•˜๋ฉด **FastAPI**๋Š” ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ์„ ํ•ฉ๋‹ˆ๋‹ค. -์˜ค๋ฅ˜์—๋Š” ์ •ํ™•ํžˆ ์–ด๋А ์ง€์ ์—์„œ ๊ฒ€์ฆ์„ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ–ˆ๋Š”์ง€ ๋ช…์‹œ๋ฉ๋‹ˆ๋‹ค. +๋˜ํ•œ ์˜ค๋ฅ˜์—๋Š” ๊ฒ€์ฆ์„ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•œ ์ง€์ ์ด ์ •ํ™•ํžˆ ๋ช…์‹œ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” API์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๊ฐœ๋ฐœํ•˜๊ณ  ๋””๋ฒ„๊น…ํ•˜๋Š” ๋ฐ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. /// -## ๋ฌธ์„œํ™” +## ๋ฌธ์„œํ™” { #documentation } ๊ทธ๋ฆฌ๊ณ  ๋ธŒ๋ผ์šฐ์ €์—์„œ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>๋ฅผ ์—ด๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž๋™ ๋Œ€ํ™”์‹ API ๋ฌธ์„œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -83,23 +84,23 @@ /// check | ํ™•์ธ -๊ทธ์ € ํŒŒ์ด์ฌ ํƒ€์ž… ์„ ์–ธ์„ ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด **FastAPI**๋Š” ์ž๋™ ๋Œ€ํ™”ํ˜• API ๋ฌธ์„œ(Swagger UI)๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +๋‹ค์‹œ ํ•œ ๋ฒˆ, ๋™์ผํ•œ ํŒŒ์ด์ฌ ํƒ€์ž… ์„ ์–ธ๋งŒ์œผ๋กœ **FastAPI**๋Š” ์ž๋™ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ(Swagger UI ํ†ตํ•ฉ)๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์ •์ˆ˜ํ˜•์œผ๋กœ ๋ช…์‹œ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์ •์ˆ˜ํ˜•์œผ๋กœ ์„ ์–ธ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## ํ‘œ์ค€ ๊ธฐ๋ฐ˜์˜ ์ด์ , ๋Œ€์ฒด ๋ฌธ์„œ +## ํ‘œ์ค€ ๊ธฐ๋ฐ˜์˜ ์ด์ , ๋Œ€์ฒด ๋ฌธ์„œ { #standards-based-benefits-alternative-documentation } -๊ทธ๋ฆฌ๊ณ  ์ƒ์„ฑ๋œ ์Šคํ‚ค๋งˆ๋Š” <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a> ํ‘œ์ค€์—์„œ ๋‚˜์˜จ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ˜ธํ™˜๋˜๋Š” ๋„๊ตฌ๊ฐ€ ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์ƒ์„ฑ๋œ ์Šคํ‚ค๋งˆ๋Š” <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" class="external-link" target="_blank">OpenAPI</a> ํ‘œ์ค€์—์„œ ๋‚˜์˜จ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ˜ธํ™˜๋˜๋Š” ๋„๊ตฌ๊ฐ€ ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. -์ด ๋•๋ถ„์— **FastAPI**๋Š” <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>๋กœ ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋Š” (ReDoc์„ ์‚ฌ์šฉํ•˜๋Š”) ๋Œ€์ฒด API ๋ฌธ์„œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: +์ด ๋•๋ถ„์— **FastAPI** ์ž์ฒด์—์„œ <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>๋กœ ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋Š” (ReDoc์„ ์‚ฌ์šฉํ•˜๋Š”) ๋Œ€์ฒด API ๋ฌธ์„œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: <img src="/img/tutorial/path-params/image02.png"> ์ด์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋‹ค์–‘ํ•œ ์–ธ์–ด์— ๋Œ€ํ•œ ์ฝ”๋“œ ์ƒ์„ฑ ๋„๊ตฌ๋ฅผ ํฌํ•จํ•˜์—ฌ ์—ฌ๋Ÿฌ ํ˜ธํ™˜๋˜๋Š” ๋„๊ตฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. -## Pydantic +## Pydantic { #pydantic } ๋ชจ๋“  ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ์€ <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>์— ์˜ํ•ด ๋‚ด๋ถ€์ ์œผ๋กœ ์ˆ˜ํ–‰๋˜๋ฏ€๋กœ ์ด๋กœ ์ธํ•œ ์ด์ ์„ ๋ชจ๋‘ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์€ ๊ด€๋ฆฌ๋ฅผ ์ž˜ ๋ฐ›๊ณ  ์žˆ์Œ์„ ๋А๋‚„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -107,25 +108,31 @@ ์ด ์ค‘ ๋ช‡ ๊ฐ€์ง€๋Š” ์ž์Šต์„œ์˜ ๋‹ค์Œ ์žฅ์— ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. -## ์ˆœ์„œ ๋ฌธ์ œ +## ์ˆœ์„œ ๋ฌธ์ œ { #order-matters } -*๊ฒฝ๋กœ ์ž‘๋™*์„ ๋งŒ๋“ค๋•Œ ๊ณ ์ • ๊ฒฝ๋กœ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” ์ƒํ™ฉ๋“ค์„ ๋งž๋‹ฅ๋œจ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ๋งŒ๋“ค ๋•Œ ๊ณ ์ • ๊ฒฝ๋กœ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” ์ƒํ™ฉ๋“ค์„ ๋งž๋‹ฅ๋œจ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. `/users/me`์ฒ˜๋Ÿผ, ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค๊ณ  ํ•ฉ์‹œ๋‹ค. ์‚ฌ์šฉ์ž ID๋ฅผ ์ด์šฉํ•ด ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒฝ๋กœ `/users/{user_id}`๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -*๊ฒฝ๋กœ ์ž‘๋™*์€ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— `/users/{user_id}` ์ด์ „์— `/users/me`๋ฅผ ๋จผ์ € ์„ ์–ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋Š” ์ˆœ์ฐจ์ ์œผ๋กœ ํ‰๊ฐ€๋˜๊ธฐ ๋•Œ๋ฌธ์— `/users/{user_id}` ์ด์ „์— `/users/me`์— ๋Œ€ํ•œ ๊ฒฝ๋กœ๊ฐ€ ๋จผ์ € ์„ ์–ธ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} -๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด `/users/{user_id}`๋Š” `/users/me` ์š”์ฒญ ๋˜ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜ `user_id`์˜ ๊ฐ’์ด `"me"`์ธ ๊ฒƒ์œผ๋กœ "์ƒ๊ฐํ•˜๊ฒŒ" ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด `/users/{user_id}`์— ๋Œ€ํ•œ ๊ฒฝ๋กœ๊ฐ€ `/users/me`์—๋„ ๋งค์นญ๋˜์–ด, ๋งค๊ฐœ๋ณ€์ˆ˜ `user_id`์— `"me"` ๊ฐ’์ด ๋“ค์–ด์™”๋‹ค๊ณ  "์ƒ๊ฐํ•˜๊ฒŒ" ๋ฉ๋‹ˆ๋‹ค. -## ์‚ฌ์ „์ •์˜ ๊ฐ’ +๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ์žฌ์ •์˜ํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค: -๋งŒ์•ฝ *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜*๋ฅผ ๋ฐ›๋Š” *๊ฒฝ๋กœ ์ž‘๋™*์ด ์žˆ์ง€๋งŒ, *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜*๋กœ ๊ฐ€๋Šฅํ•œ ๊ฐ’๋“ค์„ ๋ฏธ๋ฆฌ ์ •์˜ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํŒŒ์ด์ฌ ํ‘œ์ค€ <abbr title="์—ด๊ฑฐํ˜•(Enumeration)">`Enum`</abbr>์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} -### `Enum` ํด๋ž˜์Šค ์ƒ์„ฑ +๊ฒฝ๋กœ๊ฐ€ ๋จผ์ € ๋งค์นญ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ฒซ ๋ฒˆ์งธ ๊ฒƒ์ด ํ•ญ์ƒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +## ์‚ฌ์ „์ •์˜ ๊ฐ’ { #predefined-values } + +๋งŒ์•ฝ *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜*๋ฅผ ๋ฐ›๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ ์žˆ์ง€๋งŒ, ๊ฐ€๋Šฅํ•œ ์œ ํšจํ•œ *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜* ๊ฐ’๋“ค์„ ๋ฏธ๋ฆฌ ์ •์˜ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํŒŒ์ด์ฌ ํ‘œ์ค€ <abbr title="์—ด๊ฑฐํ˜•(Enumeration)">`Enum`</abbr>์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### `Enum` ํด๋ž˜์Šค ์ƒ์„ฑ { #create-an-enum-class } `Enum`์„ ์ž„ํฌํŠธํ•˜๊ณ  `str`๊ณผ `Enum`์„ ์ƒ์†ํ•˜๋Š” ์„œ๋ธŒ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. @@ -133,47 +140,41 @@ ๊ฐ€๋Šฅํ•œ ๊ฐ’๋“ค์— ํ•ด๋‹นํ•˜๋Š” ๊ณ ์ •๋œ ๊ฐ’์˜ ํด๋ž˜์Šค ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋“ค์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค: -{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} - -/// info | ์ •๋ณด - -<a href="https://docs.python.org/3/library/enum.html" class="external-link" target="_blank">์—ด๊ฑฐํ˜•(๋˜๋Š” enums)</a>์€ ํŒŒ์ด์ฌ ๋ฒ„์ „ 3.4 ์ดํ›„๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. - -/// +{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} /// tip | ํŒ -ํ˜น์‹œ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด, "AlexNet", "ResNet", ๊ทธ๋ฆฌ๊ณ  "LeNet"์€ ๊ทธ์ € ๊ธฐ๊ณ„ ํ•™์Šต <abbr title="๊ธฐ์ˆ ์ ์œผ๋กœ ์ •ํ™•ํžˆ๋Š” ๋”ฅ ๋Ÿฌ๋‹ ๋ชจ๋ธ ๊ตฌ์กฐ">๋ชจ๋ธ</abbr>๋“ค์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. +ํ˜น์‹œ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด, "AlexNet", "ResNet", ๊ทธ๋ฆฌ๊ณ  "LeNet"์€ ๊ทธ์ € ๋จธ์‹  ๋Ÿฌ๋‹ <abbr title="๊ธฐ์ˆ ์ ์œผ๋กœ๋Š” ๋”ฅ ๋Ÿฌ๋‹ ๋ชจ๋ธ ์•„ํ‚คํ…์ฒ˜">๋ชจ๋ธ</abbr>๋“ค์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. /// -### *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜* ์„ ์–ธ +### *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜* ์„ ์–ธ { #declare-a-path-parameter } ์ƒ์„ฑํ•œ ์—ด๊ฑฐํ˜• ํด๋ž˜์Šค(`ModelName`)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜*๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค: -{* ../../docs_src/path_params/tutorial005.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} -### ๋ฌธ์„œ ํ™•์ธ +### ๋ฌธ์„œ ํ™•์ธ { #check-the-docs } *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜*์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์€ ๋ฏธ๋ฆฌ ์ •์˜๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ ์ž˜ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค: <img src="/img/tutorial/path-params/image03.png"> -### ํŒŒ์ด์ฌ *์—ด๊ฑฐํ˜•*์œผ๋กœ ์ž‘์—…ํ•˜๊ธฐ +### ํŒŒ์ด์ฌ *์—ด๊ฑฐํ˜•*์œผ๋กœ ์ž‘์—…ํ•˜๊ธฐ { #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`๋ฅผ ์ด์šฉํ•˜์—ฌ ์‹ค์ œ ๊ฐ’(์œ„ ์˜ˆ์‹œ์˜ ๊ฒฝ์šฐ `str`)์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/path_params/tutorial005.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} /// tip | ํŒ @@ -181,15 +182,15 @@ /// -#### *์—ด๊ฑฐํ˜• ๋ฉค๋ฒ„* ๋ฐ˜ํ™˜ +#### *์—ด๊ฑฐํ˜• ๋ฉค๋ฒ„* ๋ฐ˜ํ™˜ { #return-enumeration-members } -*๊ฒฝ๋กœ ์ž‘๋™*์—์„œ *์—ด๊ฑฐํ˜• ๋ฉค๋ฒ„*๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ค‘์ฒฉ JSON ๋ณธ๋ฌธ(์˜ˆ: `dict`)๋‚ด์˜ ๊ฐ’์œผ๋กœ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ *enum ๋ฉค๋ฒ„*๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” JSON ๋ณธ๋ฌธ(์˜ˆ: `dict`) ๋‚ด์— ์ค‘์ฒฉ๋œ ํ˜•ํƒœ๋กœ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์ „์— ํ•ด๋‹น ๊ฐ’(์ด ๊ฒฝ์šฐ ๋ฌธ์ž์—ด)์œผ๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} -ํด๋ผ์ด์–ธํŠธ๋Š” ์•„๋ž˜์˜ JSON ์‘๋‹ต์„ ์–ป์Šต๋‹ˆ๋‹ค: +ํด๋ผ์ด์–ธํŠธ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ JSON ์‘๋‹ต์„ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: ```JSON { @@ -198,53 +199,53 @@ } ``` -## ๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ +## ๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ { #path-parameters-containing-paths } -๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•˜๋Š” *๊ฒฝ๋กœ ์ž‘๋™* `/files/{file_path}`์ด ์žˆ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค. +๊ฒฝ๋กœ `/files/{file_path}`๋ฅผ ๊ฐ€์ง„ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค. -๊ทธ๋Ÿฐ๋ฐ ์ด ๊ฒฝ์šฐ `file_path` ์ž์ฒด๊ฐ€ `home/johndoe/myfile.txt`์™€ ๊ฐ™์€ ๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ `file_path` ์ž์ฒด๊ฐ€ `home/johndoe/myfile.txt`์™€ ๊ฐ™์€ *๊ฒฝ๋กœ*๋ฅผ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ํ•ด๋‹น ํŒŒ์ผ์˜ URL์€ ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ฉ๋‹ˆ๋‹ค: `/files/home/johndoe/myfile.txt`. -### OpenAPI ์ง€์› +### OpenAPI ์ง€์› { #openapi-support } ํ…Œ์ŠคํŠธ์™€ ์ •์˜๊ฐ€ ์–ด๋ ค์šด ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ OpenAPI๋Š” *๊ฒฝ๋กœ*๋ฅผ ํฌํ•จํ•˜๋Š” *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜*๋ฅผ ๋‚ด๋ถ€์— ์„ ์–ธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿผ์—๋„ Starlette์˜ ๋‚ด๋ถ€ ๋„๊ตฌ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ **FastAPI**์—์„œ๋Š” ์ด๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋Ÿผ์—๋„ Starlette์˜ ๋‚ด๋ถ€ ๋„๊ตฌ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ **FastAPI**์—์„œ๋Š” ์ด๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. -๋ฌธ์„œ์— ๋งค๊ฐœ๋ณ€์ˆ˜์— ๊ฒฝ๋กœ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ์ •๋ณด๊ฐ€ ๋ช…์‹œ๋˜์ง€๋Š” ์•Š์ง€๋งŒ ์—ฌ์ „ํžˆ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. +๋˜ํ•œ ๋ฌธ์„œ๊ฐ€ ์—ฌ์ „ํžˆ ๋™์ž‘ํ•˜๊ธด ํ•˜์ง€๋งŒ, ๋งค๊ฐœ๋ณ€์ˆ˜์— ๊ฒฝ๋กœ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ๋‚ด์šฉ์„ ์ถ”๊ฐ€๋กœ ๋ฌธ์„œํ™”ํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. -### ๊ฒฝ๋กœ ๋ณ€ํ™˜๊ธฐ +### ๊ฒฝ๋กœ ๋ณ€ํ™˜๊ธฐ { #path-convertor } -Starlette์˜ ์˜ต์…˜์„ ์ง์ ‘ ์ด์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ URL์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ *path*๋ฅผ ํฌํ•จํ•˜๋Š” *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜*๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +Starlette์˜ ์˜ต์…˜์„ ์ง์ ‘ ์ด์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ URL์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ *๊ฒฝ๋กœ*๋ฅผ ํฌํ•จํ•˜๋Š” *๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜*๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ``` /files/{file_path:path} ``` -์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ์ด๋ฆ„์€ `file_path`์ด๋ฉฐ, ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„ `:path`๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ *๊ฒฝ๋กœ*์™€ ์ผ์น˜ํ•ด์•ผ ํ•จ์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. +์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ์ด๋ฆ„์€ `file_path`์ด๋ฉฐ, ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„ `:path`๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์–ด๋–ค *๊ฒฝ๋กœ*์™€๋„ ๋งค์นญ๋˜์–ด์•ผ ํ•จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/path_params/tutorial004.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} /// tip | ํŒ -๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ๊ฐ’์ด `/home/johndoe/myfile.txt`์™€ ๊ฐ™์ด ์Šฌ๋ž˜์‹œ๋กœ ์‹œ์ž‘(`/`)ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์„ ํ–‰ ์Šฌ๋ž˜์‹œ(`/`)๊ฐ€ ์žˆ๋Š” `/home/johndoe/myfile.txt`๋ฅผ ํฌํ•จํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ URL์€: `/files//home/johndoe/myfile.txt`์ด๋ฉฐ `files`๊ณผ `home` ์‚ฌ์ด์— ์ด์ค‘ ์Šฌ๋ž˜์‹œ(`//`)๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. +๊ทธ ๊ฒฝ์šฐ URL์€: `/files//home/johndoe/myfile.txt`์ด๋ฉฐ `files`์™€ `home` ์‚ฌ์ด์— ์ด์ค‘ ์Šฌ๋ž˜์‹œ(`//`)๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. /// -## ์š”์•ฝ +## ์š”์•ฝ { #recap } **FastAPI**๋ฅผ ์ด์šฉํ•˜๋ฉด ์งง๊ณ  ์ง๊ด€์ ์ธ ํ‘œ์ค€ ํŒŒ์ด์ฌ ํƒ€์ž… ์„ ์–ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: * ํŽธ์ง‘๊ธฐ ์ง€์›: ์˜ค๋ฅ˜ ๊ฒ€์‚ฌ, ์ž๋™์™„์„ฑ ๋“ฑ -* ๋ฐ์ดํ„ฐ "<abbr title="HTTP ์š”์ฒญ์—์„œ ์ „๋‹ฌ๋˜๋Š” ๋ฌธ์ž์—ด์„ ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜">ํŒŒ์‹ฑ</abbr>" +* ๋ฐ์ดํ„ฐ "<abbr title="HTTP ์š”์ฒญ์—์„œ ์ „๋‹ฌ๋˜๋Š” ๋ฌธ์ž์—ด์„ ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜">parsing</abbr>" * ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ * API ์ฃผ์„(Annotation)๊ณผ ์ž๋™ ๋ฌธ์„œ -๋‹จ ํ•œ๋ฒˆ์˜ ์„ ์–ธ๋งŒ์œผ๋กœ ์œ„ ์‚ฌํ•ญ๋“ค์„ ๋ชจ๋‘ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ํ•œ ๋ฒˆ๋งŒ ์„ ์–ธํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋Œ€์ฒด ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๋น„๊ตํ–ˆ์„ ๋•Œ (์—„์ฒญ๋‚˜๊ฒŒ ๋น ๋ฅธ ์„ฑ๋Šฅ ์™ธ์—๋„) **FastAPI**์˜ ์ฃผ์š”ํ•œ ์žฅ์ ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/query-param-models.md b/docs/ko/docs/tutorial/query-param-models.md index 2ca65a3319..d354c56c3d 100644 --- a/docs/ko/docs/tutorial/query-param-models.md +++ b/docs/ko/docs/tutorial/query-param-models.md @@ -1,48 +1,48 @@ -# ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชจ๋ธ +# ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชจ๋ธ { #query-parameter-models } -์—ฐ๊ด€๋œ ์ฟผ๋ฆฌ **๋งค๊ฐœ๋ณ€์ˆ˜** ๊ทธ๋ฃน์ด ์žˆ๋‹ค๋ฉด **Pydantic ๋ชจ๋ธ** ์„ ์‚ฌ์šฉํ•ด ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฐ๊ด€๋œ **์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜** ๊ทธ๋ฃน์ด ์žˆ๋‹ค๋ฉด ์ด๋ฅผ ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•ด **Pydantic ๋ชจ๋ธ**์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด **์—ฌ๋Ÿฌ ๊ณณ**์—์„œ **๋ชจ๋ธ์„ ์žฌ์‚ฌ์šฉ**ํ•  ์ˆ˜ ์žˆ์„ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ๊ฒ€์ฆ ๋ฐ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋„ ํ•œ ๋ฒˆ์— ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž /// note | ์ฐธ๊ณ  -์ด ๊ธฐ๋Šฅ์€ FastAPI ๋ฒ„์ „ `0.115.0`๋ถ€ํ„ฐ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ๐Ÿค“ +์ด ๊ธฐ๋Šฅ์€ FastAPI ๋ฒ„์ „ `0.115.0`๋ถ€ํ„ฐ ์ง€์›๋ฉ๋‹ˆ๋‹ค. ๐Ÿค“ /// -## ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ Pydantic ๋ชจ๋ธ +## Pydantic ๋ชจ๋ธ๊ณผ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ { #query-parameters-with-a-pydantic-model } -ํ•„์š”ํ•œ **์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜**๋ฅผ **Pydantic ๋ชจ๋ธ** ์•ˆ์— ์„ ์–ธํ•œ ๋‹ค์Œ, ๋ชจ๋ธ์„ `Query`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. +ํ•„์š”ํ•œ **์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜**๋ฅผ **Pydantic ๋ชจ๋ธ** ์•ˆ์— ์„ ์–ธํ•œ ๋‹ค์Œ, ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `Query`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: {* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} -**FastAPI**๋Š” ์š”์ฒญ์˜ **์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜**์—์„œ **๊ฐ ํ•„๋“œ**์˜ ๋ฐ์ดํ„ฐ๋ฅผ **์ถ”์ถœ**ํ•ด ์ •์˜ํ•œ Pydantic ๋ชจ๋ธ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +**FastAPI**๋Š” ์š”์ฒญ์˜ **์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜**์—์„œ **๊ฐ ํ•„๋“œ**์˜ ๋ฐ์ดํ„ฐ๋ฅผ **์ถ”์ถœ**ํ•ด ์ •์˜ํ•œ Pydantic ๋ชจ๋ธ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -## ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ +## ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ { #check-the-docs } -`/docs` ๊ฒฝ๋กœ์˜ API ๋ฌธ์„œ์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`/docs`์˜ ๋ฌธ์„œ UI์—์„œ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <div class="screenshot"> <img src="/img/tutorial/query-param-models/image01.png"> </div> -## ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ธˆ์ง€ +## ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ธˆ์ง€ { #forbid-extra-query-parameters } -๋ช‡๋ช‡์˜ ํŠน์ดํ•œ ๊ฒฝ์šฐ์— (ํ”์น˜ ์•Š์ง€๋งŒ), ํ—ˆ์šฉํ•  ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ **์ œํ•œ**ํ•ด์•ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ช‡๋ช‡์˜ ํŠน์ดํ•œ ๊ฒฝ์šฐ์— (ํ”์น˜ ์•Š์ง€๋งŒ), ๋ฐ›์œผ๋ ค๋Š” ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ **์ œํ•œ**ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -Pydantic ๋ชจ๋ธ ์„ค์ •์—์„œ `extra` ํ•„๋“œ๋ฅผ `forbid` ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +Pydantic์˜ ๋ชจ๋ธ ์„ค์ •์„ ์‚ฌ์šฉํ•ด ์–ด๋–ค `extra` ํ•„๋“œ๋„ `forbid`ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: {* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} -๋งŒ์•ฝ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ **์ถ”๊ฐ€์ ์ธ** ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋ ค๊ณ  ํ•˜๋ฉด, ํด๋ผ์ด์–ธํŠธ๋Š” **์—๋Ÿฌ** ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +ํด๋ผ์ด์–ธํŠธ๊ฐ€ **์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜**๋กœ **์ถ”๊ฐ€์ ์ธ** ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋ ค๊ณ  ํ•˜๋ฉด **์—๋Ÿฌ** ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์ด ๋งŒ์•ฝ ํด๋ผ์ด์–ธํŠธ๊ฐ€ `tool` ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์— `plumbus` ๋ผ๋Š” ๊ฐ’์„ ์ถ”๊ฐ€ํ•ด์„œ ๋ณด๋‚ด๋ ค๊ณ  ํ•˜๋ฉด, +์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์ด ํด๋ผ์ด์–ธํŠธ๊ฐ€ `tool` ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์— `plumbus` ๊ฐ’์„ ๋ณด๋‚ด๋ ค๊ณ  ํ•˜๋ฉด: ```http https://example.com/items/?limit=10&tool=plumbus ``` -ํด๋ผ์ด์–ธํŠธ๋Š” ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `tool` ์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” **์—๋Ÿฌ** ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `tool`์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” **์—๋Ÿฌ** ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: ```json { @@ -57,12 +57,12 @@ https://example.com/items/?limit=10&tool=plumbus } ``` -## ์š”์•ฝ +## ์š”์•ฝ { #summary } -**FastAPI** ์—์„œ **์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜** ๋ฅผ ์„ ์–ธํ•  ๋•Œ **Pydantic ๋ชจ๋ธ** ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž +**FastAPI**์—์„œ **์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜**๋ฅผ ์„ ์–ธํ•  ๋•Œ **Pydantic ๋ชจ๋ธ**์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž /// tip | ํŒ -์Šคํฌ์ผ๋Ÿฌ ๊ฒฝ๊ณ : Pydantic ๋ชจ๋ธ์„ ์ฟ ํ‚ค์™€ ํ—ค๋”์—๋„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ๋Š” ์ดํ›„ ํŠœํ† ๋ฆฌ์–ผ์—์„œ ๋‹ค๋ฃฐ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. ๐Ÿคซ +์Šคํฌ์ผ๋Ÿฌ ๊ฒฝ๊ณ : Pydantic ๋ชจ๋ธ์„ ์ฟ ํ‚ค์™€ ํ—ค๋”์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด์— ๋Œ€ํ•ด์„œ๋Š” ์ดํ›„ ํŠœํ† ๋ฆฌ์–ผ์—์„œ ์ฝ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿคซ /// diff --git a/docs/ko/docs/tutorial/query-params-str-validations.md b/docs/ko/docs/tutorial/query-params-str-validations.md index f2ca453ac5..68824932ee 100644 --- a/docs/ko/docs/tutorial/query-params-str-validations.md +++ b/docs/ko/docs/tutorial/query-params-str-validations.md @@ -1,118 +1,226 @@ -# ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ๋ฌธ์ž์—ด ๊ฒ€์ฆ +# ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ๋ฌธ์ž์—ด ๊ฒ€์ฆ { #query-parameters-and-string-validations } **FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ •๋ณด ๋ฐ ๊ฒ€์ฆ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์˜ˆ๋กœ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} -์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`๋Š” `Optional[str]` ์ž๋ฃŒํ˜•์ž…๋‹ˆ๋‹ค. ์ฆ‰, `str` ์ž๋ฃŒํ˜•์ด์ง€๋งŒ `None` ์—ญ์‹œ ๋  ์ˆ˜ ์žˆ์Œ์„ ๋œปํ•˜๊ณ , ์‹ค์ œ๋กœ ๊ธฐ๋ณธ๊ฐ’์€ `None`์ด๊ธฐ ๋•Œ๋ฌธ์— FastAPI๋Š” ์ด ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ํ•„์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์„ ์••๋‹ˆ๋‹ค. +์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`๋Š” `str | None` ์ž๋ฃŒํ˜•์ž…๋‹ˆ๋‹ค. ์ฆ‰, `str` ์ž๋ฃŒํ˜•์ด์ง€๋งŒ `None` ์—ญ์‹œ ๋  ์ˆ˜ ์žˆ์Œ์„ ๋œปํ•˜๊ณ , ์‹ค์ œ๋กœ ๊ธฐ๋ณธ๊ฐ’์€ `None`์ด๊ธฐ ๋•Œ๋ฌธ์— FastAPI๋Š” ์ด ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ํ•„์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์„ ์••๋‹ˆ๋‹ค. /// note | ์ฐธ๊ณ  FastAPI๋Š” `q`์˜ ๊ธฐ๋ณธ๊ฐ’์ด `= None`์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•„์ˆ˜๊ฐ€ ์•„๋‹˜์„ ์••๋‹ˆ๋‹ค. -`Optional[str]`์— ์žˆ๋Š” `Optional`์€ FastAPI๊ฐ€ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ์ง€๋งŒ, ํŽธ์ง‘๊ธฐ์—๊ฒŒ ๋” ๋‚˜์€ ์ง€์›๊ณผ ์˜ค๋ฅ˜ ํƒ์ง€๋ฅผ ์ œ๊ณตํ•˜๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. +`str | None`์„ ์‚ฌ์šฉํ•˜๋ฉด ํŽธ์ง‘๊ธฐ๊ฐ€ ๋” ๋‚˜์€ ์ง€์›๊ณผ ์˜ค๋ฅ˜ ํƒ์ง€๋ฅผ ์ œ๊ณตํ•˜๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. /// -## ์ถ”๊ฐ€ ๊ฒ€์ฆ +## ์ถ”๊ฐ€ ๊ฒ€์ฆ { #additional-validation } -`q`๊ฐ€ ์„ ํƒ์ ์ด์ง€๋งŒ ๊ฐ’์ด ์ฃผ์–ด์งˆ ๋•Œ๋งˆ๋‹ค **๊ฐ’์ด 50 ๊ธ€์ž๋ฅผ ์ดˆ๊ณผํ•˜์ง€ ์•Š๊ฒŒ** ๊ฐ•์ œํ•˜๋ ค ํ•ฉ๋‹ˆ๋‹ค. +`q`๊ฐ€ ์„ ํƒ์ ์ด์ง€๋งŒ ๊ฐ’์ด ์ฃผ์–ด์งˆ ๋•Œ๋งˆ๋‹ค **๊ธธ์ด๊ฐ€ 50์ž๋ฅผ ์ดˆ๊ณผํ•˜์ง€ ์•Š๊ฒŒ** ๊ฐ•์ œํ•˜๋ ค ํ•ฉ๋‹ˆ๋‹ค. -### `Query` ์ž„ํฌํŠธ +### `Query`์™€ `Annotated` ์ž„ํฌํŠธ { #import-query-and-annotated } -์ด๋ฅผ ์œ„ํ•ด ๋จผ์ € `fastapi`์—์„œ `Query`๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค: +์ด๋ฅผ ์œ„ํ•ด ๋จผ์ € ๋‹ค์Œ์„ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *} +* `fastapi`์—์„œ `Query` +* `typing`์—์„œ `Annotated` -## ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ `Query` ์‚ฌ์šฉ +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} -์ด์ œ `Query`๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•˜์—ฌ `max_length` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ 50์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค: +/// info | ์ •๋ณด -{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *} +FastAPI๋Š” 0.95.0 ๋ฒ„์ „์—์„œ `Annotated` ์ง€์›์„ ์ถ”๊ฐ€ํ–ˆ๊ณ (๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ๊ถŒ์žฅํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค). -๊ธฐ๋ณธ๊ฐ’ `None`์„ `Query(None)`์œผ๋กœ ๋ฐ”๊ฟ”์•ผ ํ•˜๋ฏ€๋กœ, `Query`์˜ ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๊ธฐ๋ณธ๊ฐ’์„ ์ •์˜ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. +์ด์ „ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋ฉด `Annotated`๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•  ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. + +`Annotated`๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์ตœ์†Œ 0.95.1 ๋ฒ„์ „์œผ๋กœ [FastAPI ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}๋ฅผ ์ง„ํ–‰ํ•˜์„ธ์š”. + +/// + +## `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`๋ฅผ ์‚ฌ์šฉํ•ด ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋ง์”€๋“œ๋ฆฐ ๊ฒƒ์„ ๊ธฐ์–ตํ•˜์‹œ๋‚˜์š”? + +์ด์ œ 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`์ž…๋‹ˆ๋‹ค. + +์ด์ œ ์žฌ๋ฏธ์žˆ๋Š” ๋ถ€๋ถ„์œผ๋กœ ๋„˜์–ด๊ฐ€ ๋ด…์‹œ๋‹ค. ๐ŸŽ‰ + +## `q` ๋งค๊ฐœ๋ณ€์ˆ˜์˜ `Annotated`์— `Query` ์ถ”๊ฐ€ํ•˜๊ธฐ { #add-query-to-annotated-in-the-q-parameter } + +์ด์ œ ์ด `Annotated`์— ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ(์ด ๊ฒฝ์šฐ์—๋Š” ์ถ”๊ฐ€ ๊ฒ€์ฆ), `Annotated` ์•ˆ์— `Query`๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  `max_length` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `50`์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *} + +๊ธฐ๋ณธ๊ฐ’์€ ์—ฌ์ „ํžˆ `None`์ด๋ฏ€๋กœ, ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์—ฌ์ „ํžˆ ์„ ํƒ์ ์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ `Annotated` ์•ˆ์— `Query(max_length=50)`๋ฅผ ๋„ฃ์Œ์œผ๋กœ์จ, ์ด ๊ฐ’์— ๋Œ€ํ•ด **์ถ”๊ฐ€ ๊ฒ€์ฆ**์„ ์ ์šฉํ•˜๊ณ  ์ตœ๋Œ€ 50์ž๊นŒ์ง€๋งŒ ํ—ˆ์šฉํ•˜๋„๋ก FastAPI์— ์•Œ๋ ค์ค๋‹ˆ๋‹ค. ๐Ÿ˜Ž + +/// tip | ํŒ + +์—ฌ๊ธฐ์„œ๋Š” **์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜**์ด๊ธฐ ๋•Œ๋ฌธ์— `Query()`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‚˜์ค‘์— `Path()`, `Body()`, `Header()`, `Cookie()`์™€ ๊ฐ™์ด `Query()`์™€ ๋™์ผํ•œ ์ธ์ž๋ฅผ ๋ฐ›๋Š” ๊ฒƒ๋“ค๋„ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// + +์ด์ œ FastAPI๋Š” ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค: + +* ์ตœ๋Œ€ ๊ธธ์ด๊ฐ€ 50์ž์ธ์ง€ ํ™•์ธํ•˜๋„๋ก ๋ฐ์ดํ„ฐ๋ฅผ **๊ฒ€์ฆ**ํ•ฉ๋‹ˆ๋‹ค +* ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์„ ๋•Œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ **๋ช…ํ™•ํ•œ ์˜ค๋ฅ˜**๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค +* OpenAPI ์Šคํ‚ค๋งˆ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ **๋ฌธ์„œํ™”**ํ•ฉ๋‹ˆ๋‹ค(๋”ฐ๋ผ์„œ **์ž๋™ ๋ฌธ์„œ UI**์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค) + +## ๋Œ€์•ˆ(์ด์ „ ๋ฐฉ์‹): ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ `Query` ์‚ฌ์šฉ { #alternative-old-query-as-the-default-value } + +์ด์ „ FastAPI ๋ฒ„์ „(<abbr title="before 2023-03">0.95.0</abbr> ์ด์ „)์—์„œ๋Š” `Annotated`์— ๋„ฃ๋Š” ๋Œ€์‹ , ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ `Query`๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฃผ๋ณ€์—์„œ ์ด ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ณผ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๊ธฐ ๋•Œ๋ฌธ์— ์„ค๋ช…ํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. + +/// 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: Optional[str] = Query(None) +q: str | None = Query(default=None) ``` -...์œ„ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๋™์ผํ•˜๊ฒŒ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ํƒ์ ์œผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค: +...์œ„ ์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ๊ฐ’์ด `None`์ธ ์„ ํƒ์  ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค๋ฉฐ, ์•„๋ž˜์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค: + ```Python -q: Optional[str] = None +q: str | None = None ``` -ํ•˜์ง€๋งŒ ๋ช…์‹œ์ ์œผ๋กœ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ `Query` ๋ฒ„์ „์€ ์ด๊ฒƒ์ด ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์ž„์„ ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. -/// info | ์ •๋ณด - -FastAPI๋Š” ๋‹ค์Œ ๋ถ€๋ถ„์— ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค: +๊ทธ ๋‹ค์Œ, `Query`๋กœ ๋” ๋งŽ์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ์˜ ๊ฒฝ์šฐ ๋ฌธ์ž์—ด์— ์ ์šฉ๋˜๋Š” `max_length` ๋งค๊ฐœ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค: ```Python -= None +q: str | None = Query(default=None, max_length=50) ``` -๋˜๋Š”: +์ด๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•  ๊ฒƒ์ด๊ณ , ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด ๋ช…๋ฐฑํ•œ ์˜ค๋ฅ˜๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉฐ, OpenAPI ์Šคํ‚ค๋งˆ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฌธ์„œํ™” ํ•ฉ๋‹ˆ๋‹ค. + +### ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ `Query` ์‚ฌ์šฉ ๋˜๋Š” `Annotated`์— ๋„ฃ๊ธฐ { #query-as-the-default-value-or-in-annotated } + +`Annotated` ์•ˆ์—์„œ `Query`๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” `Query`์— `default` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. + +๋Œ€์‹  ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ์‹ค์ œ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์ผ๊ด€์„ฑ์ด ๊นจ์ง‘๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: ```Python -= Query(None) +q: Annotated[str, Query(default="rick")] = "morty" ``` -๊ทธ๋ฆฌ๊ณ  `None`์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ผ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ํ•„์ˆ˜์ ์ด์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์„ ํŒŒ์•…ํ•ฉ๋‹ˆ๋‹ค. +...์™œ๋ƒํ•˜๋ฉด ๊ธฐ๋ณธ๊ฐ’์ด `"rick"`์ธ์ง€ `"morty"`์ธ์ง€ ๋ช…ํ™•ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -`Optional` ๋ถ€๋ถ„์€ ํŽธ์ง‘๊ธฐ์—๊ฒŒ ๋” ๋‚˜์€ ์ง€์›์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด์„œ๋งŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. - -/// - -๋˜ํ•œ `Query`๋กœ ๋” ๋งŽ์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ์˜ ๊ฒฝ์šฐ ๋ฌธ์ž์—ด์— ์ ์šฉ๋˜๋Š” `max_length` ๋งค๊ฐœ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค: +๋”ฐ๋ผ์„œ (๊ฐ€๋Šฅํ•˜๋ฉด) ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: ```Python -q: str = Query(None, max_length=50) +q: Annotated[str, Query()] = "rick" ``` -์ด๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•  ๊ฒƒ์ด๊ณ , ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด ๋ช…๋ฐฑํ•œ ์˜ค๋ฅ˜๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉฐ, OpenAPI ์Šคํ‚ค๋งˆ *๊ฒฝ๋กœ ์ž‘๋™*์— ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฌธ์„œํ™” ํ•ฉ๋‹ˆ๋‹ค. +...๋˜๋Š” ์˜ค๋ž˜๋œ ์ฝ”๋“œ๋ฒ ์ด์Šค์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ฐพ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค: -## ๊ฒ€์ฆ ์ถ”๊ฐ€ +```Python +q: str = Query(default="rick") +``` -๋งค๊ฐœ๋ณ€์ˆ˜ `min_length` ๋˜ํ•œ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +### `Annotated`์˜ ์žฅ์  { #advantages-of-annotated } -{* ../../docs_src/query_params_str_validations/tutorial003.py hl[9] *} +ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๊ธฐ๋ณธ๊ฐ’ ๋ฐฉ์‹ ๋Œ€์‹  **`Annotated`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅ**ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์ด์œ ๋กœ **๋” ์ข‹๊ธฐ** ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๐Ÿค“ -## ์ •๊ทœ์‹ ์ถ”๊ฐ€ +**ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜**์˜ **๊ธฐ๋ณธ๊ฐ’**์ด **์‹ค์ œ ๊ธฐ๋ณธ๊ฐ’**์ด ๋˜๋ฏ€๋กœ, ์ „๋ฐ˜์ ์œผ๋กœ Python์— ๋” ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜Œ -๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ผ์น˜ํ•ด์•ผ ํ•˜๋Š” <abbr title="์ •๊ทœํ‘œํ˜„์‹(regular expression), regex ๋˜๋Š” regexp๋Š” ๋ฌธ์ž์—ด ์กฐํšŒ ํŒจํ„ด์„ ์ •์˜ํ•˜๋Š” ๋ฌธ์ž๋“ค์˜ ์ˆœ์—ด์ž…๋‹ˆ๋‹ค">์ •๊ทœํ‘œํ˜„์‹</abbr>์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +FastAPI ์—†์ด๋„ **๋‹ค๋ฅธ ๊ณณ์—์„œ** ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ **ํ˜ธ์ถœ**ํ•  ์ˆ˜ ์žˆ๊ณ , **์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘**ํ•ฉ๋‹ˆ๋‹ค. **ํ•„์ˆ˜** ๋งค๊ฐœ๋ณ€์ˆ˜(๊ธฐ๋ณธ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ)๊ฐ€ ์žˆ๋‹ค๋ฉด **ํŽธ์ง‘๊ธฐ**๊ฐ€ ์˜ค๋ฅ˜๋กœ ์•Œ๋ ค์ค„ ๊ฒƒ์ด๊ณ , ํ•„์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š๊ณ  ์‹คํ–‰ํ•˜๋ฉด **Python**๋„ ์˜ค๋ฅ˜๋ฅผ ๋ƒ…๋‹ˆ๋‹ค. -{* ../../docs_src/query_params_str_validations/tutorial004.py hl[10] *} +`Annotated`๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  **(์ด์ „) ๊ธฐ๋ณธ๊ฐ’ ์Šคํƒ€์ผ**์„ ์‚ฌ์šฉํ•˜๋ฉด, FastAPI ์—†์ด **๋‹ค๋ฅธ ๊ณณ์—์„œ** ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋„ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋„๋ก ํ•จ์ˆ˜์— ์ธ์ž๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ **๊ธฐ์–ต**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ฐ’์ด ๊ธฐ๋Œ€์™€ ๋‹ค๋ฅด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค(์˜ˆ: `str` ๋Œ€์‹  `QueryInfo` ๊ฐ™์€ ๊ฒƒ). ๊ทธ๋ฆฌ๊ณ  ํŽธ์ง‘๊ธฐ๋„ ๊ฒฝ๊ณ ํ•˜์ง€ ์•Š๊ณ  Python๋„ ๊ทธ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋Š” ๋ถˆํ‰ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์˜ค์ง ๋‚ด๋ถ€ ๋™์ž‘์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๋งŒ ๋ฌธ์ œ๊ฐ€ ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค. -์ด ํŠน์ • ์ •๊ทœํ‘œํ˜„์‹์€ ์ „๋‹ฌ ๋ฐ›์€ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค: +`Annotated`๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์–ด๋…ธํ…Œ์ด์…˜์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด์ œ <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a> ๊ฐ™์€ ๋‹ค๋ฅธ ๋„๊ตฌ์—์„œ๋„ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿš€ -* `^`: ์ด์ „์— ๋ฌธ์ž๊ฐ€ ์—†๊ณ  ๋’ค๋”ฐ๋ฅด๋Š” ๋ฌธ์ž๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. -* `fixedquery`: ์ •ํ™•ํžˆ `fixedquery` ๊ฐ’์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. -* `$`: ์—ฌ๊ธฐ์„œ ๋๋‚˜๊ณ  `fixedquery` ์ดํ›„๋กœ ์•„๋ฌด ๋ฌธ์ž๋„ ๊ฐ–์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +## ๊ฒ€์ฆ ๋” ์ถ”๊ฐ€ํ•˜๊ธฐ { #add-more-validations } -**"์ •๊ทœํ‘œํ˜„์‹"** ๊ฐœ๋…์— ๋Œ€ํ•ด ์ƒ์‹ค๊ฐ์„ ๋А๊ผˆ๋‹ค๋ฉด ๊ฑฑ์ •ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. ๋งŽ์€ ์‚ฌ๋žŒ์—๊ฒŒ ์–ด๋ ค์šด ์ฃผ์ œ์ž…๋‹ˆ๋‹ค. ์•„์ง์€ ์ •๊ทœํ‘œํ˜„์‹ ์—†์ด๋„ ๋งŽ์€ ์ž‘์—…๋“ค์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`min_length` ๋งค๊ฐœ๋ณ€์ˆ˜๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -ํ•˜์ง€๋งŒ ์–ธ์ œ๋“ ์ง€ ๊ฐ€์„œ ๋ฐฐ์šธ์ˆ˜ ์žˆ๊ณ , **FastAPI**์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} -## ๊ธฐ๋ณธ๊ฐ’ +## ์ •๊ทœ์‹ ์ถ”๊ฐ€ { #add-regular-expressions } -๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ `None`์„ ์ „๋‹ฌํ•˜๋“ฏ์ด, ๋‹ค๋ฅธ ๊ฐ’์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ผ์น˜ํ•ด์•ผ ํ•˜๋Š” <abbr title="๋ฌธ์ž์—ด์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰ ํŒจํ„ด์„ ์ •์˜ํ•˜๋Š” ๋ฌธ์ž๋“ค์˜ ์ˆœ์—ด์ธ ์ •๊ทœ ํ‘œํ˜„์‹(regular expression), regex ๋˜๋Š” regexp์ž…๋‹ˆ๋‹ค.">์ •๊ทœ ํ‘œํ˜„์‹</abbr> `pattern`์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -`min_length`๊ฐ€ `3`์ด๊ณ , ๊ธฐ๋ณธ๊ฐ’์ด `"fixedquery"`์ธ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`๋ฅผ ์„ ์–ธํ•ด๋ด…์‹œ๋‹ค: +{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} -{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *} +์ด ํŠน์ • ์ •๊ทœํ‘œํ˜„์‹ ํŒจํ„ด์€ ์ „๋‹ฌ ๋ฐ›์€ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’์ด ๋‹ค์Œ์„ ๋งŒ์กฑํ•˜๋Š”์ง€ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค: + +* `^`: ๋’ค๋”ฐ๋ฅด๋Š” ๋ฌธ์ž๋กœ ์‹œ์ž‘ํ•˜๋ฉฐ, ์•ž์—๋Š” ๋ฌธ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. +* `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`๋ฅผ ํ•„์ˆ˜๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -123,42 +231,42 @@ q: str ์•„๋ž˜ ๋Œ€์‹ : ```Python -q: Optional[str] = None +q: str | None = None ``` -๊ทธ๋Ÿฌ๋‚˜ ์ด์ œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด `Query`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: +ํ•˜์ง€๋งŒ ์ด์ œ๋Š” ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด `Query`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: ```Python -q: Optional[str] = Query(None, min_length=3) +q: Annotated[str | None, Query(min_length=3)] = None ``` -๊ทธ๋ž˜์„œ `Query`๋ฅผ ํ•„์ˆ˜๊ฐ’์œผ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•  ๋•Œ๋ฉด, ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ `...`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋”ฐ๋ผ์„œ `Query`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ฐ’์„ ํ•„์ˆ˜๋กœ ์„ ์–ธํ•ด์•ผ ํ•  ๋•Œ๋Š”, ๊ธฐ๋ณธ๊ฐ’์„ ์„ ์–ธํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -/// info | ์ •๋ณด +### ํ•„์ˆ˜์ง€๋งŒ `None` ๊ฐ€๋Šฅ { #required-can-be-none } -์ด์ „์— `...`๋ฅผ ๋ณธ์ ์ด ์—†๋‹ค๋ฉด: ํŠน๋ณ„ํ•œ ๋‹จ์ผ๊ฐ’์œผ๋กœ, <a href="https://docs.python.org/3/library/constants.html#Ellipsis" class="external-link" target="_blank">ํŒŒ์ด์ฌ์˜ ์ผ๋ถ€์ด๋ฉฐ "Ellipsis"๋ผ ๋ถ€๋ฆ…๋‹ˆ๋‹ค</a>. +๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ `None`์„ ํ—ˆ์šฉํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ํ•„์ˆ˜๋ผ๊ณ  ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ’์ด `None`์ด๋”๋ผ๋„ ํด๋ผ์ด์–ธํŠธ๋Š” ๊ฐ’์„ ๋ฐ˜๋“œ์‹œ ์ „์†กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -/// +์ด๋ฅผ ์œ„ํ•ด `None`์ด ์œ ํšจํ•œ ํƒ€์ž…์ด๋ผ๊ณ  ์„ ์–ธํ•˜๋˜, ๊ธฐ๋ณธ๊ฐ’์€ ์„ ์–ธํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค: -์ด๋ ‡๊ฒŒ ํ•˜๋ฉด **FastAPI**๊ฐ€ ์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ํ•„์ˆ˜์ž„์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} -## ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฆฌ์ŠคํŠธ / ๋‹ค์ค‘๊ฐ’ +## ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฆฌ์ŠคํŠธ / ๋‹ค์ค‘๊ฐ’ { #query-parameter-list-multiple-values } -์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `Query`์™€ ํ•จ๊ป˜ ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•  ๋•Œ, ๊ฐ’๋“ค์˜ ๋ฆฌ์ŠคํŠธ๋‚˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ์—ฌ๋Ÿฌ ๊ฐ’์„ ๋ฐ›๋„๋ก ์„ ์–ธ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +`Query`๋กœ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜ํ•  ๋•Œ ๊ฐ’๋“ค์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ›๋„๋ก ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ๊ณ , ๋‹ค๋ฅธ ๋ง๋กœ ํ•˜๋ฉด ์—ฌ๋Ÿฌ ๊ฐ’์„ ๋ฐ›๋„๋ก ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, URL์—์„œ ์—ฌ๋Ÿฌ๋ฒˆ ๋‚˜์˜ค๋Š” `q` ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์˜ˆ๋ฅผ ๋“ค์–ด, 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์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: +๊ทธ ๋‹ค์Œ, ์•„๋ž˜์™€ ๊ฐ™์€ URL๋กœ: ``` http://localhost:8000/items/?q=foo&q=bar ``` -์—ฌ๋Ÿฌ `q` *์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜* ๊ฐ’๋“ค์„ (`foo` ๋ฐ `bar`) ํŒŒ์ด์ฌ `list`๋กœ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜* ๋‚ด *ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜* `q`๋กœ ์ „๋‹ฌ ๋ฐ›์Šต๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ `q` *์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜* ๊ฐ’๋“ค(`foo` ๋ฐ `bar`)์„ ํŒŒ์ด์ฌ `list`๋กœ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ *ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜* `q`์—์„œ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•ด๋‹น URL์— ๋Œ€ํ•œ ์‘๋‹ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: @@ -173,7 +281,7 @@ http://localhost:8000/items/?q=foo&q=bar /// tip | ํŒ -์œ„์˜ ์˜ˆ์™€ ๊ฐ™์ด `list` ์ž๋ฃŒํ˜•์œผ๋กœ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋ ค๋ฉด `Query`๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์š”์ฒญ ๋ณธ๋ฌธ์œผ๋กœ ํ•ด์„๋ฉ๋‹ˆ๋‹ค. +์œ„์˜ ์˜ˆ์™€ ๊ฐ™์ด `list` ํƒ€์ž…์œผ๋กœ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋ ค๋ฉด `Query`๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์š”์ฒญ ๋ณธ๋ฌธ์œผ๋กœ ํ•ด์„๋ฉ๋‹ˆ๋‹ค. /// @@ -181,19 +289,19 @@ http://localhost:8000/items/?q=foo&q=bar <img src="/img/tutorial/query-params-str-validations/image02.png"> -### ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฆฌ์ŠคํŠธ / ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค์ค‘๊ฐ’ +### ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฆฌ์ŠคํŠธ / ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ๋Š” ๋‹ค์ค‘๊ฐ’ { #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] *} -์•„๋ž˜๋กœ ์ด๋™ํ•œ๋‹ค๋ฉด: +๋‹ค์Œ์œผ๋กœ ์ด๋™ํ•˜๋ฉด: ``` http://localhost:8000/items/ ``` -`q`์˜ ๊ธฐ๋ณธ๊ฐ’์€: `["foo", "bar"]`์ด๋ฉฐ ์‘๋‹ต์€ ๋‹ค์Œ์ด ๋ฉ๋‹ˆ๋‹ค: +`q`์˜ ๊ธฐ๋ณธ๊ฐ’์€ `["foo", "bar"]`๊ฐ€ ๋˜๊ณ , ์‘๋‹ต์€ ๋‹ค์Œ์ด ๋ฉ๋‹ˆ๋‹ค: ```JSON { @@ -204,21 +312,21 @@ http://localhost:8000/items/ } ``` -#### `list` ์‚ฌ์šฉํ•˜๊ธฐ +#### `list`๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ { #using-just-list } -`List[str]` ๋Œ€์‹  `list`๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: +`list[str]` ๋Œ€์‹  `list`๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} /// note | ์ฐธ๊ณ  -์ด ๊ฒฝ์šฐ FastAPI๋Š” ๋ฆฌ์ŠคํŠธ์˜ ๋‚ด์šฉ์„ ๊ฒ€์‚ฌํ•˜์ง€ ์•Š์Œ์„ ๋ช…์‹ฌํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ FastAPI๋Š” ๋ฆฌ์ŠคํŠธ์˜ ๋‚ด์šฉ์„ ๊ฒ€์‚ฌํ•˜์ง€ ์•Š์Œ์„ ๋ช…์‹ฌํ•˜์„ธ์š”. -์˜ˆ๋ฅผ ๋“ค์–ด, `List[int]`๋Š” ๋ฆฌ์ŠคํŠธ ๋‚ด์šฉ์ด ์ •์ˆ˜์ธ์ง€ ๊ฒ€์‚ฌ(๋ฐ ๋ฌธ์„œํ™”)ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ `list` ๋‹จ๋…์ผ ๊ฒฝ์šฐ๋Š” ์•„๋‹™๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, `list[int]`๋Š” ๋ฆฌ์ŠคํŠธ ๋‚ด์šฉ์ด ์ •์ˆ˜์ธ์ง€ ๊ฒ€์‚ฌ(๋ฐ ๋ฌธ์„œํ™”)ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ `list` ๋‹จ๋…์ผ ๊ฒฝ์šฐ๋Š” ์•„๋‹™๋‹ˆ๋‹ค. /// -## ๋” ๋งŽ์€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ ์–ธ +## ๋” ๋งŽ์€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ ์–ธ { #declare-more-metadata } ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -226,7 +334,7 @@ http://localhost:8000/items/ /// note | ์ฐธ๊ณ  -๋„๊ตฌ์— ๋”ฐ๋ผ OpenAPI ์ง€์› ์ˆ˜์ค€์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ์„ ๋ช…์‹ฌํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. +๋„๊ตฌ์— ๋”ฐ๋ผ OpenAPI ์ง€์› ์ˆ˜์ค€์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ์„ ๋ช…์‹ฌํ•˜์„ธ์š”. ์ผ๋ถ€๋Š” ์•„์ง ์„ ์–ธ๋œ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ํ‘œ์‹œํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๋ˆ„๋ฝ๋œ ๊ธฐ๋Šฅ์€ ์ด๋ฏธ ๊ฐœ๋ฐœ ๊ณ„ํš์ด ์žˆ์Šต๋‹ˆ๋‹ค. @@ -234,13 +342,13 @@ http://localhost:8000/items/ `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`์ด๊ธธ ์›ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. @@ -250,31 +358,99 @@ http://localhost:8000/items/ http://127.0.0.1:8000/items/?item-query=foobaritems ``` -๊ทธ๋Ÿฌ๋‚˜ `item-query`์€ ์œ ํšจํ•œ ํŒŒ์ด์ฌ ๋ณ€์ˆ˜ ์ด๋ฆ„์ด ์•„๋‹™๋‹ˆ๋‹ค. +๊ทธ๋Ÿฌ๋‚˜ `item-query`๋Š” ์œ ํšจํ•œ ํŒŒ์ด์ฌ ๋ณ€์ˆ˜ ์ด๋ฆ„์ด ์•„๋‹™๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฒƒ์€ `item_query`์ผ ๊ฒ๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์ •ํ™•ํžˆ`item-query`์ด๊ธธ ์›ํ•ฉ๋‹ˆ๋‹ค... +ํ•˜์ง€๋งŒ ์ •ํ™•ํžˆ `item-query`์ด๊ธธ ์›ํ•ฉ๋‹ˆ๋‹ค... ์ด๋Ÿด ๊ฒฝ์šฐ `alias`๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•ด๋‹น ๋ณ„์นญ์€ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’์„ ์ฐพ๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *} -## ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŒ ํ•˜๊ธฐ +## ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉ ์ค‘๋‹จํ•˜๊ธฐ { #deprecating-parameters } -์ด์ œ๋Š” ๋”์ด์ƒ ์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋งˆ์Œ์— ๋“ค์–ดํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. +์ด์ œ๋Š” ๋” ์ด์ƒ ์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋งˆ์Œ์— ๋“ค์–ดํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. -์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•œ๋™์•ˆ์€ ๋‚จ๊ฒจ๋‘ฌ์•ผ ํ•˜์ง€๋งŒ, <abbr title="๊ตฌ์‹์ด๋ฉฐ, ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ์ถ”์ฒœ">์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค(deprecated)</abbr>๊ณ  ํ™•์‹คํ•˜๊ฒŒ ๋ฌธ์„œ์—์„œ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. +์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•œ๋™์•ˆ์€ ๋‚จ๊ฒจ๋‘ฌ์•ผ ํ•˜์ง€๋งŒ, ๋ฌธ์„œ์—์„œ <abbr title="obsolete, recommended not to use it โ€“ ๊ตฌ์‹์ด๋ฉฐ, ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ์ถ”์ฒœ">deprecated</abbr>๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด `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] *} ๋ฌธ์„œ๊ฐ€ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณด์ผ๊ฒ๋‹ˆ๋‹ค: <img src="/img/tutorial/query-params-str-validations/image01.png"> -## ์š”์•ฝ +## 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` ์•ˆ์—์„œ <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-after-validator" class="external-link" target="_blank">Pydantic์˜ `AfterValidator`</a>๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// tip | ํŒ + +Pydantic์—๋Š” <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-before-validator" class="external-link" target="_blank">`BeforeValidator`</a>์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ๊ฒƒ๋“ค๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ + +/// + +์˜ˆ๋ฅผ ๋“ค์–ด, ์ด ์ปค์Šคํ…€ validator๋Š” <abbr title="ISBN means International Standard Book Number โ€“ ๊ตญ์ œ ํ‘œ์ค€ ๋„์„œ ๋ฒˆํ˜ธ">ISBN</abbr> ๋„์„œ ๋ฒˆํ˜ธ์˜ ๊ฒฝ์šฐ ์•„์ดํ…œ ID๊ฐ€ `isbn-`์œผ๋กœ ์‹œ์ž‘ํ•˜๊ณ , <abbr title="IMDB (Internet Movie Database) is a website with information about movies โ€“ ์˜ํ™”์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ์›น์‚ฌ์ดํŠธ">IMDB</abbr> ์˜ํ™” URL ID์˜ ๊ฒฝ์šฐ `imdb-`๋กœ ์‹œ์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} + +/// info | ์ •๋ณด + +์ด๋Š” Pydantic 2 ์ด์ƒ ๋ฒ„์ „์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž + +/// + +/// tip | ํŒ + +๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ๋‹ค๋ฅธ API ๊ฐ™์€ **์™ธ๋ถ€ ๊ตฌ์„ฑ์š”์†Œ**์™€ ํ†ต์‹ ์ด ํ•„์š”ํ•œ ์–ด๋–ค ์ข…๋ฅ˜์˜ ๊ฒ€์ฆ์ด๋“  ํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ๋Œ€์‹  **FastAPI Dependencies**๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ๋Š” ๋‚˜์ค‘์— ๋ฐฐ์šฐ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +์ด ์ปค์Šคํ…€ validator๋Š” ์š”์ฒญ์—์„œ ์ œ๊ณต๋œ **๊ฐ™์€ ๋ฐ์ดํ„ฐ๋งŒ**์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋“ค์„ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// + +### ์ฝ”๋“œ ์ดํ•ดํ•˜๊ธฐ { #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()`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ๋”•์…”๋„ˆ๋ฆฌ ํ•ญ๋ชฉ์˜ ํ‚ค์™€ ๊ฐ’์„ ๋‹ด์€ ํŠœํ”Œ๋กœ ๊ตฌ์„ฑ๋œ <abbr title="๋ฆฌ์ŠคํŠธ, ์„ธํŠธ ๋“ฑ์ฒ˜๋Ÿผ for ๋ฃจํ”„๋กœ ์ˆœํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ">iterable object</abbr>๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค. + +์ด iterable object๋ฅผ `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 } ๋งค๊ฐœ๋ณ€์ˆ˜์— ๊ฒ€์ฆ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -285,12 +461,14 @@ http://127.0.0.1:8000/items/?item-query=foobaritems * `description` * `deprecated` -ํŠน์ • ๋ฌธ์ž์—ด ๊ฒ€์ฆ: +๋ฌธ์ž์—ด์— ํŠนํ™”๋œ ๊ฒ€์ฆ: * `min_length` * `max_length` -* `regex` +* `pattern` + +`AfterValidator`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปค์Šคํ…€ ๊ฒ€์ฆ. ์˜ˆ์ œ์—์„œ `str` ๊ฐ’์˜ ๊ฒ€์ฆ์„ ์–ด๋–ป๊ฒŒ ์ถ”๊ฐ€ํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. -์ˆซ์ž์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ์ž๋ฃŒํ˜•์— ๋Œ€ํ•œ ๊ฒ€์ฆ์„ ์–ด๋–ป๊ฒŒ ์„ ์–ธํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด ๋‹ค์Œ ์žฅ์„ ํ™•์ธํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. +์ˆซ์ž์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ํƒ€์ž…์— ๋Œ€ํ•œ ๊ฒ€์ฆ์„ ์–ด๋–ป๊ฒŒ ์„ ์–ธํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด ๋‹ค์Œ ์žฅ์„ ํ™•์ธํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/query-params.md b/docs/ko/docs/tutorial/query-params.md index d5b9837c4e..5124f73bf5 100644 --- a/docs/ko/docs/tutorial/query-params.md +++ b/docs/ko/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์—์„œ `?` ํ›„์— ๋‚˜์˜ค๊ณ  `&`์œผ๋กœ ๊ตฌ๋ถ„๋˜๋Š” ํ‚ค-๊ฐ’ ์Œ์˜ ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค. @@ -24,11 +24,11 @@ URL์˜ ์ผ๋ถ€์ด๋ฏ€๋กœ "์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ" ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์— ์ ์šฉ๋œ ๋™์ผํ•œ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค: * (๋‹น์—ฐํžˆ) ํŽธ์ง‘๊ธฐ ์ง€์› -* ๋ฐ์ดํ„ฐ <abbr title="HTTP ์š”์ฒญ์—์„œ ์ „๋‹ฌ๋˜๋Š” ๋ฌธ์ž์—ด์„ ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜">"ํŒŒ์‹ฑ"</abbr> +* ๋ฐ์ดํ„ฐ <abbr title="converting the string that comes from an HTTP request into Python data">"ํŒŒ์‹ฑ"</abbr> * ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ * ์ž๋™ ๋ฌธ์„œํ™” -## ๊ธฐ๋ณธ๊ฐ’ +## ๊ธฐ๋ณธ๊ฐ’ { #defaults } ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๊ฒฝ๋กœ์—์„œ ๊ณ ์ •๋œ ๋ถ€๋ถ„์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์„ ํƒ์ ์ผ ์ˆ˜ ์žˆ๊ณ  ๊ธฐ๋ณธ๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -57,33 +57,25 @@ http://127.0.0.1:8000/items/?skip=20 * `skip=20`: URL์—์„œ ์ง€์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค * `limit=10`: ๊ธฐ๋ณธ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค -## ์„ ํƒ์  ๋งค๊ฐœ๋ณ€์ˆ˜ +## ์„ ํƒ์  ๋งค๊ฐœ๋ณ€์ˆ˜ { #optional-parameters } ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ธฐ๋ณธ๊ฐ’์„ `None`์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ์„ ํƒ์  ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/query_params/tutorial002.py hl[9] *} +{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *} ์ด ๊ฒฝ์šฐ ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `q`๋Š” ์„ ํƒ์ ์ด๋ฉฐ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ `None` ๊ฐ’์ด ๋ฉ๋‹ˆ๋‹ค. -/// check | ํ™•์ธ +/// check -**FastAPI**๋Š” `item_id`๊ฐ€ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์ด๊ณ  `q`๋Š” ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์•„๋‹Œ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ผ๋Š” ๊ฒƒ์„ ์•Œ ์ •๋„๋กœ ์ถฉ๋ถ„ํžˆ ๋˜‘๋˜‘ํ•ฉ๋‹ˆ๋‹ค. +๋˜ํ•œ **FastAPI**๋Š” `item_id`๊ฐ€ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์ด๊ณ  `q`๋Š” ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ์„œ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ผ๋Š” ๊ฒƒ์„ ์•Œ ์ •๋„๋กœ ์ถฉ๋ถ„ํžˆ ๋˜‘๋˜‘ํ•˜๋‹ค๋Š” ์ ๋„ ํ™•์ธํ•˜์„ธ์š”. /// -/// note | ์ฐธ๊ณ  - -FastAPI๋Š” `q`๊ฐ€ `= None`์ด๋ฏ€๋กœ ์„ ํƒ์ ์ด๋ผ๋Š” ๊ฒƒ์„ ์ธ์ง€ํ•ฉ๋‹ˆ๋‹ค. - -`Union[str, None]`์— ์žˆ๋Š” `Union`์€ FastAPI(FastAPI๋Š” `str` ๋ถ€๋ถ„๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค)๊ฐ€ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ์ง€๋งŒ, `Union[str, None]`์€ ํŽธ์ง‘๊ธฐ์—๊ฒŒ ์ฝ”๋“œ์—์„œ ์˜ค๋ฅ˜๋ฅผ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค. - -/// - -## ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ํ˜•๋ณ€ํ™˜ +## ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ํ˜•๋ณ€ํ™˜ { #query-parameter-type-conversion } `bool` ํ˜•์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ๊ณ , ์•„๋ž˜์ฒ˜๋Ÿผ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/query_params/tutorial003.py hl[9] *} +{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *} ์ด ๊ฒฝ์šฐ, ์•„๋ž˜๋กœ ์ด๋™ํ•˜๋ฉด: @@ -115,10 +107,10 @@ http://127.0.0.1:8000/items/foo?short=on http://127.0.0.1:8000/items/foo?short=yes ``` -๋˜๋Š” ๋‹ค๋ฅธ ์–ด๋–ค ๋ณ€ํ˜•(๋Œ€๋ฌธ์ž, ์ฒซ๊ธ€์ž๋งŒ ๋Œ€๋ฌธ์ž ๋“ฑ)์ด๋”๋ผ๋„ ํ•จ์ˆ˜๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜ `bool`ํ˜•์„ ๊ฐ€์ง„ `short`์˜ ๊ฐ’์ด `True`์ž„์„ ์••๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ `False`์ž…๋‹ˆ๋‹ค. +๋˜๋Š” ๋‹ค๋ฅธ ์–ด๋–ค ๋ณ€ํ˜•(๋Œ€๋ฌธ์ž, ์ฒซ๊ธ€์ž๋งŒ ๋Œ€๋ฌธ์ž ๋“ฑ)์ด๋”๋ผ๋„ ํ•จ์ˆ˜๋Š” `bool` ๊ฐ’์ด `True`์ธ ๋งค๊ฐœ๋ณ€์ˆ˜ `short`๋ฅผ ๋ณด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ `False`์ž…๋‹ˆ๋‹ค. -## ์—ฌ๋Ÿฌ ๊ฒฝ๋กœ/์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ +## ์—ฌ๋Ÿฌ ๊ฒฝ๋กœ/์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ { #multiple-path-and-query-parameters } ์—ฌ๋Ÿฌ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋™์‹œ์— ์„ ์–ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ **FastAPI**๋Š” ์–ด๋А ๊ฒƒ์ด ๋ฌด์—‡์ธ์ง€ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. @@ -126,9 +118,9 @@ http://127.0.0.1:8000/items/foo?short=yes ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์€ ์ด๋ฆ„์œผ๋กœ ๊ฐ์ง€๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/query_params/tutorial004.py hl[8,10] *} +{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} -## ํ•„์ˆ˜ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ +## ํ•„์ˆ˜ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ { #required-query-parameters } ๊ฒฝ๋กœ๊ฐ€ ์•„๋‹Œ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ๊ธฐ๋ณธ๊ฐ’์„ ์„ ์–ธํ•  ๋•Œ(์ง€๊ธˆ์€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋งŒ ๋ณด์•˜์Šต๋‹ˆ๋‹ค), ํ•ด๋‹น ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ํ•„์ˆ˜์ (Required)์ด์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. @@ -136,7 +128,7 @@ http://127.0.0.1:8000/items/foo?short=yes ๊ทธ๋Ÿฌ๋‚˜ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ•„์ˆ˜๋กœ ๋งŒ๋“ค๋ ค๋ฉด ๋‹จ์ˆœํžˆ ๊ธฐ๋ณธ๊ฐ’์„ ์„ ์–ธํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} ์—ฌ๊ธฐ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ `needy`๋Š” `str`ํ˜•์ธ ํ•„์ˆ˜ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. @@ -150,16 +142,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 + } + ] } ``` @@ -180,7 +173,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy ๊ทธ๋ฆฌ๊ณ  ๋ฌผ๋ก , ์ผ๋ถ€ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ํ•„์ˆ˜๋กœ, ๋‹ค๋ฅธ ์ผ๋ถ€๋Š” ๊ธฐ๋ณธ๊ฐ’์„, ๋˜ ๋‹ค๋ฅธ ์ผ๋ถ€๋Š” ์„ ํƒ์ ์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/query_params/tutorial006.py hl[10] *} +{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *} ์œ„ ์˜ˆ์‹œ์—์„œ๋Š” 3๊ฐ€์ง€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -188,8 +181,8 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy * `skip`, ๊ธฐ๋ณธ๊ฐ’์ด `0`์ธ `int`. * `limit`, ์„ ํƒ์ ์ธ `int`. -/// tip | ํŒ +/// tip -[๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜](path-params.md#_8){.internal-link target=_blank}์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ `Enum`์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +[๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜](path-params.md#predefined-values){.internal-link target=_blank}์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ `Enum`์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// diff --git a/docs/ko/docs/tutorial/request-files.md b/docs/ko/docs/tutorial/request-files.md index 9162b353cd..cc00009211 100644 --- a/docs/ko/docs/tutorial/request-files.md +++ b/docs/ko/docs/tutorial/request-files.md @@ -1,4 +1,4 @@ -# ํŒŒ์ผ ์š”์ฒญ +# ํŒŒ์ผ ์š”์ฒญ { #request-files } `File`์„ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—…๋กœ๋“œํ•  ํŒŒ์ผ๋“ค์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -6,23 +6,27 @@ ์—…๋กœ๋“œ๋œ ํŒŒ์ผ์„ ์ „๋‹ฌ๋ฐ›๊ธฐ ์œ„ํ•ด ๋จผ์ € <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>๋ฅผ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ์‹œ) `pip install python-multipart`. +[๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ์ƒ์„ฑํ•˜๊ณ , ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์น˜ํ•˜์„ธ์š”: + +```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 } `Body` ๋ฐ `Form` ๊ณผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ํŒŒ์ผ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/request_files/tutorial001.py hl[7] *} +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} /// info | ์ •๋ณด @@ -40,20 +44,21 @@ File์˜ ๋ณธ๋ฌธ์„ ์„ ์–ธํ•  ๋•Œ, ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋˜๋Š” ๋ณธ ํŒŒ์ผ๋“ค์€ "ํผ ๋ฐ์ดํ„ฐ"์˜ ํ˜•ํƒœ๋กœ ์—…๋กœ๋“œ ๋ฉ๋‹ˆ๋‹ค. -*๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `bytes` ๋กœ ์„ ์–ธํ•˜๋Š” ๊ฒฝ์šฐ **FastAPI**๋Š” ํŒŒ์ผ์„ ์ฝ๊ณ  `bytes` ํ˜•ํƒœ์˜ ๋‚ด์šฉ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `bytes` ๋กœ ์„ ์–ธํ•˜๋Š” ๊ฒฝ์šฐ **FastAPI**๋Š” ํŒŒ์ผ์„ ์ฝ๊ณ  `bytes` ํ˜•ํƒœ์˜ ๋‚ด์šฉ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ „์ฒด ๋‚ด์šฉ์ด ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค๋Š” ๊ฑธ ์—ผ๋‘ํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์ด๋Š” ์ž‘์€ ํฌ๊ธฐ์˜ ํŒŒ์ผ๋“ค์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฒฝ์šฐ์—๋Š” `UploadFile` ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. -## `File` ๋งค๊ฐœ๋ณ€์ˆ˜์™€ `UploadFile` +## `UploadFile`์„ ์‚ฌ์šฉํ•˜๋Š” `File` ๋งค๊ฐœ๋ณ€์ˆ˜ { #file-parameters-with-uploadfile } `File` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `UploadFile` ํƒ€์ž…์œผ๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/request_files/tutorial001.py hl[12] *} +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} `UploadFile` ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ `bytes` ๊ณผ ๋น„๊ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค: +* ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๊ธฐ๋ณธ๊ฐ’์—์„œ `File()`์„ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. * "์Šคํ’€ ํŒŒ์ผ"์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. * ์ตœ๋Œ€ ํฌ๊ธฐ ์ œํ•œ๊นŒ์ง€๋งŒ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋˜๋ฉฐ, ์ด๋ฅผ ์ดˆ๊ณผํ•˜๋Š” ๊ฒฝ์šฐ ๋””์Šคํฌ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. * ๋”ฐ๋ผ์„œ ์ด๋ฏธ์ง€, ๋™์˜์ƒ, ํฐ ์ด์ง„์ฝ”๋“œ์™€ ๊ฐ™์€ ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ๋“ค์„ ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์†Œ๋ชจํ•˜์ง€ ์•Š๊ณ  ์ฒ˜๋ฆฌํ•˜๊ธฐ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. @@ -61,13 +66,13 @@ File์˜ ๋ณธ๋ฌธ์„ ์„ ์–ธํ•  ๋•Œ, ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋˜๋Š” ๋ณธ * <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `async` ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. * file-like object๋ฅผ ํ•„์š”๋กœํ•˜๋Š” ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ง์ ‘์ ์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ํŒŒ์ด์ฌ <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -### `UploadFile` +### `UploadFile` { #uploadfile } `UploadFile` ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: * `filename` : ๋ฌธ์ž์—ด(`str`)๋กœ ๋œ ์—…๋กœ๋“œ๋œ ํŒŒ์ผ์˜ ํŒŒ์ผ๋ช…์ž…๋‹ˆ๋‹ค (์˜ˆ: `myimage.jpg`). * `content_type` : ๋ฌธ์ž์—ด(`str`)๋กœ ๋œ ํŒŒ์ผ ํ˜•์‹(MIME type / media type)์ž…๋‹ˆ๋‹ค (์˜ˆ:ย `image/jpeg`). -* `file` : <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> (<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">ํŒŒ์ผ๋ฅ˜</a> ๊ฐ์ฒด)์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ "ํŒŒ์ผ๋ฅ˜" ๊ฐ์ฒด๋ฅผ ํ•„์š”๋กœํ•˜๋Š” ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ง์ ‘์ ์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์งˆ์ ์ธ ํŒŒ์ด์ฌ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค. +* `file` : <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> object)์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ "file-like" ๊ฐ์ฒด๋ฅผ ํ•„์š”๋กœํ•˜๋Š” ๋‹ค๋ฅธ ํ•จ์ˆ˜๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ง์ ‘์ ์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์งˆ์ ์ธ ํŒŒ์ด์ฌ ํŒŒ์ผ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. `UploadFile` ์—๋Š” ๋‹ค์Œ์˜ `async` ๋ฉ”์†Œ๋“œ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค์€ ๋‚ด๋ถ€์ ์ธ `SpooledTemporaryFile` ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹นํ•˜๋Š” ํŒŒ์ผ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. @@ -80,55 +85,67 @@ File์˜ ๋ณธ๋ฌธ์„ ์„ ์–ธํ•  ๋•Œ, ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋˜๋Š” ๋ณธ ์ƒ๊ธฐ ๋ชจ๋“  ๋ฉ”์†Œ๋“œ๋“ค์ด `async` ๋ฉ”์†Œ๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์— โ€œawaitโ€์„ ์‚ฌ์šฉํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ๋“ค์–ด, `async` *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์˜ ๋‚ด๋ถ€์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์˜ˆ๋ฅผ๋“ค์–ด, `async` *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋‚ด๋ถ€์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```Python contents = await myfile.read() ``` -๋งŒ์•ฝ ์ผ๋ฐ˜์ ์ธ `def` *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์˜ ๋‚ด๋ถ€๋ผ๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด `UploadFile.file` ์— ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋งŒ์•ฝ ์ผ๋ฐ˜์ ์ธ `def` *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋‚ด๋ถ€๋ผ๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด `UploadFile.file` ์— ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```Python contents = myfile.file.read() ``` -/// note | "`async` ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ" +/// note | `async` ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `async` ๋ฉ”์†Œ๋“œ๋“ค์„ ์‚ฌ์šฉํ•  ๋•Œ **FastAPI**๋Š” ์Šค๋ ˆ๋“œํ’€์—์„œ ํŒŒ์ผ ๋ฉ”์†Œ๋“œ๋“ค์„ ์‹คํ–‰ํ•˜๊ณ  ๊ทธ๋“ค์„ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. /// -/// note | Starlette ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | Starlette ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ **FastAPI**์˜ `UploadFile` ์€ **Starlette**์˜ `UploadFile` ์„ ์ง์ ‘์ ์œผ๋กœ ์ƒ์†๋ฐ›์ง€๋งŒ, **Pydantic** ๋ฐ FastAPI์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„๋“ค๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ํ•„์š”ํ•œ ๋ถ€๋ถ„๋“ค์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. /// -## "ํผ ๋ฐ์ดํ„ฐ"๋ž€ +## "ํผ ๋ฐ์ดํ„ฐ"๋ž€ { #what-is-form-data } HTML์˜ ํผ๋“ค(`<form></form>`)์ด ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹์€ ๋Œ€๊ฐœ ๋ฐ์ดํ„ฐ์— JSON๊ณผ๋Š” ๋‹ค๋ฅธ "ํŠน๋ณ„ํ•œ" ์ธ์ฝ”๋”ฉ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. **FastAPI**๋Š” JSON ๋Œ€์‹  ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. -/// note | ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ ํผ์˜ ๋ฐ์ดํ„ฐ๋Š” ํŒŒ์ผ์ด ํฌํ•จ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ผ๋ฐ˜์ ์œผ๋กœ "๋ฏธ๋””์–ด ์œ ํ˜•" `application/x-www-form-urlencoded` ์„ ์‚ฌ์šฉํ•ด ์ธ์ฝ”๋”ฉ ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํŒŒ์ผ์ด ํฌํ•จ๋œ ๊ฒฝ์šฐ, `multipart/form-data`๋กœ ์ธ์ฝ”๋”ฉ๋ฉ๋‹ˆ๋‹ค. `File`์„ ์‚ฌ์šฉํ•˜์˜€๋‹ค๋ฉด, **FastAPI**๋Š” ๋ณธ๋ฌธ์˜ ์ ํ•ฉํ•œ ๋ถ€๋ถ„์—์„œ ํŒŒ์ผ์„ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ธ์ง€ํ•ฉ๋‹ˆ๋‹ค. -์ธ์ฝ”๋”ฉ๊ณผ ํผ ํ•„๋“œ์— ๋Œ€ํ•ด ๋” ์•Œ๊ณ ์‹ถ๋‹ค๋ฉด, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><code>POST</code>์— ๊ด€ํ•œ<abbr title="Mozilla Developer Network">MDN</abbr>์›น ๋ฌธ์„œ</a> ๋ฅผ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค,. +์ธ์ฝ”๋”ฉ๊ณผ ํผ ํ•„๋“œ์— ๋Œ€ํ•ด ๋” ์•Œ๊ณ ์‹ถ๋‹ค๋ฉด, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>๋ฅผ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. /// /// warning | ๊ฒฝ๊ณ  -๋‹ค์ˆ˜์˜ `File` ๊ณผ `Form` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ•œ *๊ฒฝ๋กœ ์ž‘๋™*์— ์„ ์–ธํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์š”์ฒญ์˜ ๋ณธ๋ฌธ์ด `application/json` ๊ฐ€ ์•„๋‹Œ `multipart/form-data` ๋กœ ์ธ์ฝ”๋”ฉ ๋˜๊ธฐ ๋•Œ๋ฌธ์— JSON์œผ๋กœ ๋ฐ›์•„์•ผํ•˜๋Š” `Body` ํ•„๋“œ๋ฅผ ํ•จ๊ป˜ ์„ ์–ธํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. +๋‹ค์ˆ˜์˜ `File` ๊ณผ `Form` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ•œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ์„ ์–ธํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์š”์ฒญ์˜ ๋ณธ๋ฌธ์ด `application/json` ๊ฐ€ ์•„๋‹Œ `multipart/form-data` ๋กœ ์ธ์ฝ”๋”ฉ ๋˜๊ธฐ ๋•Œ๋ฌธ์— JSON์œผ๋กœ ๋ฐ›์•„์•ผํ•˜๋Š” `Body` ํ•„๋“œ๋ฅผ ํ•จ๊ป˜ ์„ ์–ธํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” **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_py39.py hl[9,15] *} + +## ๋‹ค์ค‘ ํŒŒ์ผ ์—…๋กœ๋“œ { #multiple-file-uploads } ์—ฌ๋Ÿฌ ํŒŒ์ผ์„ ๋™์‹œ์— ์—…๋กœ๋“œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -136,21 +153,11 @@ HTML์˜ ํผ๋“ค(`<form></form>`)์ด ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹์€ ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด , `bytes` ์˜ `List` ๋˜๋Š” `UploadFile` ๋ฅผ ์„ ์–ธํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค: -{* ../../docs_src/request_files/tutorial002.py hl[10,15] *} +{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} ์„ ์–ธํ•œ๋Œ€๋กœ, `bytes` ์˜ `list` ๋˜๋Š” `UploadFile` ๋“ค์„ ์ „์†ก๋ฐ›์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -/// note | ์ฐธ๊ณ  - -2019๋…„ 4์›” 14์ผ๋ถ€ํ„ฐ Swagger UI๊ฐ€ ํ•˜๋‚˜์˜ ํผ ํ•„๋“œ๋กœ ๋‹ค์ˆ˜์˜ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๋Š” ๊ฒƒ์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ์›ํ•˜๋ฉด, <a href="https://github.com/swagger-api/swagger-ui/issues/4276" class="external-link" target="_blank">#4276</a>๊ณผ <a href="https://github.com/swagger-api/swagger-ui/issues/3641" class="external-link" target="_blank">#3641</a>์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. - -๊ทธ๋Ÿผ์—๋„, **FastAPI**๋Š” ํ‘œ์ค€ Open API๋ฅผ ์‚ฌ์šฉํ•ด ์ด๋ฏธ ํ˜ธํ™˜์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. - -๋”ฐ๋ผ์„œ Swagger UI ๋˜๋Š” ๊ธฐํƒ€ ๊ทธ ์™ธ์˜ OpenAPI๋ฅผ ์ง€์›ํ•˜๋Š” ํˆด์ด ๋‹ค์ค‘ ํŒŒ์ผ ์—…๋กœ๋“œ๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฒฝ์šฐ, ์ด๋“ค์€ **FastAPI**์™€ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. - -/// - -/// note | ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `from starlette.responses import HTMLResponse` ์—ญ์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -158,6 +165,12 @@ HTML์˜ ํผ๋“ค(`<form></form>`)์ด ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹์€ /// -## ์š”์•ฝ +### ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•œ ๋‹ค์ค‘ ํŒŒ์ผ ์—…๋กœ๋“œ { #multiple-file-uploads-with-additional-metadata } -ํผ ๋ฐ์ดํ„ฐ๋กœ์จ ์ž…๋ ฅ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์—…๋กœ๋“œํ•  ํŒŒ์ผ์„ ์„ ์–ธํ•  ๊ฒฝ์šฐ `File` ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. +์ด์ „๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ `UploadFile`์— ๋Œ€ํ•ด์„œ๋„ `File()`์„ ์‚ฌ์šฉํ•ด ์ถ”๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} + +## ์š”์•ฝ { #recap } + +`File`, `bytes`, `UploadFile`์„ ์‚ฌ์šฉํ•˜์—ฌ ํผ ๋ฐ์ดํ„ฐ๋กœ ์ „์†ก๋˜๋Š” ์š”์ฒญ์—์„œ ์—…๋กœ๋“œํ•  ํŒŒ์ผ์„ ์„ ์–ธํ•˜์„ธ์š”. diff --git a/docs/ko/docs/tutorial/request-form-models.md b/docs/ko/docs/tutorial/request-form-models.md index 3316a93d52..b37186dfb8 100644 --- a/docs/ko/docs/tutorial/request-form-models.md +++ b/docs/ko/docs/tutorial/request-form-models.md @@ -1,12 +1,12 @@ -# ํผ ๋ชจ๋ธ +# ํผ ๋ชจ๋ธ { #form-models } FastAPI์—์„œ **Pydantic ๋ชจ๋ธ**์„ ์ด์šฉํ•˜์—ฌ **ํผ ํ•„๋“œ**๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// info | ์ •๋ณด -ํผ(Form)์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด, ๋จผ์ € <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”. +ํผ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด, ๋จผ์ € <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”. -[๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ์ƒ์„ฑํ•˜๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +[๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ์ƒ์„ฑํ•˜๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, ์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์น˜ํ•˜์„ธ์š”: ```console $ pip install python-multipart @@ -20,7 +20,7 @@ $ pip install python-multipart /// -## Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•œ ํผ +## ํผ์„ ์œ„ํ•œ Pydantic ๋ชจ๋ธ { #pydantic-models-for-forms } **ํผ ํ•„๋“œ**๋กœ ๋ฐ›๊ณ  ์‹ถ์€ ํ•„๋“œ๋ฅผ **Pydantic ๋ชจ๋ธ**๋กœ ์„ ์–ธํ•œ ๋‹ค์Œ, ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `Form`์œผ๋กœ ์„ ์–ธํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: @@ -28,7 +28,7 @@ $ pip install python-multipart **FastAPI**๋Š” ์š”์ฒญ์—์„œ ๋ฐ›์€ **ํผ ๋ฐ์ดํ„ฐ**์—์„œ **๊ฐ ํ•„๋“œ**์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ **์ถ”์ถœ**ํ•˜๊ณ  ์ •์˜ํ•œ Pydantic ๋ชจ๋ธ์„ ์ค๋‹ˆ๋‹ค. -## ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ +## ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ { #check-the-docs } ๋ฌธ์„œ UI `/docs`์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: @@ -36,9 +36,9 @@ $ pip install python-multipart <img src="/img/tutorial/request-form-models/image01.png"> </div> -## ์ถ”๊ฐ€ ํผ ํ•„๋“œ ๊ธˆ์ง€ํ•˜๊ธฐ +## ์ถ”๊ฐ€ ํผ ํ•„๋“œ ๊ธˆ์ง€ํ•˜๊ธฐ { #forbid-extra-form-fields } -์ผ๋ถ€ ํŠน๋ณ„ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€(ํ”ํ•˜์ง€๋Š” ์•Š๊ฒ ์ง€๋งŒ)์—์„œ๋Š” Pydantic ๋ชจ๋ธ์—์„œ ์ •์˜ํ•œ ํผ ํ•„๋“œ๋ฅผ **์ œํ•œ**ํ•˜๊ธธ ์›ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  **์ถ”๊ฐ€** ํ•„๋“œ๋ฅผ **๊ธˆ์ง€**ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +์ผ๋ถ€ ํŠน๋ณ„ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€(์•„๋งˆ๋„ ํ”ํ•˜์ง€๋Š” ์•Š๊ฒ ์ง€๋งŒ)์—์„œ๋Š” Pydantic ๋ชจ๋ธ์—์„œ ์„ ์–ธ๋œ ํผ ํ•„๋“œ๋กœ๋งŒ **์ œํ•œ**ํ•˜๊ธธ ์›ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  **์ถ”๊ฐ€** ํ•„๋“œ๋ฅผ **๊ธˆ์ง€**ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. /// note | ์ฐธ๊ณ  @@ -46,7 +46,7 @@ $ pip install python-multipart /// -Pydantic์˜ ๋ชจ๋ธ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€(`extra`) ํ•„๋“œ๋ฅผ ๊ธˆ์ง€(`forbid`)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +Pydantic์˜ ๋ชจ๋ธ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ `extra` ํ•„๋“œ๋ฅผ `forbid`ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: {* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} @@ -73,6 +73,6 @@ Pydantic์˜ ๋ชจ๋ธ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€(`extra`) ํ•„๋“œ๋ฅผ ๊ธˆ์ง€(`forb } ``` -## ์š”์•ฝ +## ์š”์•ฝ { #summary } Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ FastAPI์—์„œ ํผ ํ•„๋“œ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž diff --git a/docs/ko/docs/tutorial/request-forms-and-files.md b/docs/ko/docs/tutorial/request-forms-and-files.md index dc1bda21a8..a5309b5c07 100644 --- a/docs/ko/docs/tutorial/request-forms-and-files.md +++ b/docs/ko/docs/tutorial/request-forms-and-files.md @@ -1,37 +1,41 @@ -# ํผ ๋ฐ ํŒŒ์ผ ์š”์ฒญ +# ํผ ๋ฐ ํŒŒ์ผ ์š”์ฒญ { #request-forms-and-files } -`File` ๊ณผ `Form` ์„ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ๊ณผ ํผ์„ ํ•จ๊ป˜ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`File` ๊ณผ `Form` ์„ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ๊ณผ ํผ ํ•„๋“œ๋ฅผ ๋™์‹œ์— ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// info | ์ •๋ณด -ํŒŒ์ผ๊ณผ ํผ ๋ฐ์ดํ„ฐ๋ฅผ ํ•จ๊ป˜, ๋˜๋Š” ๊ฐ๊ฐ ์—…๋กœ๋“œํ•˜๊ธฐ ์œ„ํ•ด ๋จผ์ € <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>๋ฅผ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. +์—…๋กœ๋“œ๋œ ํŒŒ์ผ ๋ฐ/๋˜๋Š” ํผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์œผ๋ ค๋ฉด ๋จผ์ € <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ ) `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] *} -ํŒŒ์ผ๊ณผ ํผ ํ•„๋“œ๋Š” ํผ ๋ฐ์ดํ„ฐ ํ˜•์‹์œผ๋กœ ์—…๋กœ๋“œ๋˜์–ด ํŒŒ์ผ๊ณผ ํผ ํ•„๋“œ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. +ํŒŒ์ผ๊ณผ ํผ ํ•„๋“œ๋Š” ํผ ๋ฐ์ดํ„ฐ๋กœ ์—…๋กœ๋“œ๋˜๋ฉฐ, ํŒŒ์ผ๊ณผ ํผ ํ•„๋“œ๋ฅผ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -์–ด๋–ค ํŒŒ์ผ๋“ค์€ `bytes`๋กœ, ๋˜ ์–ด๋–ค ํŒŒ์ผ๋“ค์€ `UploadFile`๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ ์ผ๋ถ€ ํŒŒ์ผ์€ `bytes`๋กœ, ์ผ๋ถ€ ํŒŒ์ผ์€ `UploadFile`๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// warning | ๊ฒฝ๊ณ  -๋‹ค์ˆ˜์˜ `File`๊ณผ `Form` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ•œ *๊ฒฝ๋กœ ์ž‘๋™*์— ์„ ์–ธํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์š”์ฒญ์˜ ๋ณธ๋ฌธ์ด `application/json`๊ฐ€ ์•„๋‹Œ `multipart/form-data`๋กœ ์ธ์ฝ”๋”ฉ ๋˜๊ธฐ ๋•Œ๋ฌธ์— JSON์œผ๋กœ ๋ฐ›์•„์•ผํ•˜๋Š” `Body` ํ•„๋“œ๋ฅผ ํ•จ๊ป˜ ์„ ์–ธํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. +๋‹ค์ˆ˜์˜ `File`๊ณผ `Form` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ•œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ์„ ์–ธํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์š”์ฒญ์˜ ๋ณธ๋ฌธ์ด `application/json`๊ฐ€ ์•„๋‹Œ `multipart/form-data`๋กœ ์ธ์ฝ”๋”ฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— JSON์œผ๋กœ ๋ฐ›๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•˜๋Š” `Body` ํ•„๋“œ๋ฅผ ํ•จ๊ป˜ ์„ ์–ธํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. -์ด๋Š” **FastAPI**์˜ ํ•œ๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ, HTTP ํ”„๋กœํ† ์ฝœ์— ์˜ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ด๋Š” **FastAPI**์˜ ํ•œ๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ, HTTP ํ”„๋กœํ† ์ฝœ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. /// -## ์š”์•ฝ +## ์š”์•ฝ { #recap } ํ•˜๋‚˜์˜ ์š”์ฒญ์œผ๋กœ ๋ฐ์ดํ„ฐ์™€ ํŒŒ์ผ๋“ค์„ ๋ฐ›์•„์•ผ ํ•  ๊ฒฝ์šฐ `File`๊ณผ `Form`์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/request-forms.md b/docs/ko/docs/tutorial/request-forms.md index 5ca17b0d68..584cbba35c 100644 --- a/docs/ko/docs/tutorial/request-forms.md +++ b/docs/ko/docs/tutorial/request-forms.md @@ -1,4 +1,4 @@ -# ํผ ๋ฐ์ดํ„ฐ +# ํผ ๋ฐ์ดํ„ฐ { #form-data } JSON ๋Œ€์‹  ํผ ํ•„๋“œ๋ฅผ ๋ฐ›์•„์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ `Form`์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -14,13 +14,13 @@ $ pip install python-multipart /// -## `Form` ์ž„ํฌํŠธํ•˜๊ธฐ +## `Form` ์ž„ํฌํŠธํ•˜๊ธฐ { #import-form } `fastapi`์—์„œ `Form`์„ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค: {* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} -## `Form` ๋งค๊ฐœ๋ณ€์ˆ˜ ์ •์˜ํ•˜๊ธฐ +## `Form` ๋งค๊ฐœ๋ณ€์ˆ˜ ์ •์˜ํ•˜๊ธฐ { #define-form-parameters } `Body` ๋˜๋Š” `Query`์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ํผ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค: @@ -28,7 +28,7 @@ $ pip install python-multipart ์˜ˆ๋ฅผ ๋“ค์–ด, OAuth2 ์‚ฌ์–‘์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜("ํŒจ์Šค์›Œ๋“œ ํ”Œ๋กœ์šฐ"๋ผ๊ณ  ํ•จ)๋กœ `username`๊ณผ `password`๋ฅผ ํผ ํ•„๋“œ๋กœ ๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -<abbr title="specification">์‚ฌ์–‘</abbr>์—์„œ๋Š” ํ•„๋“œ ์ด๋ฆ„์ด `username` ๋ฐ `password`๋กœ ์ •ํ™•ํ•˜๊ฒŒ ๋ช…๋ช…๋˜์–ด์•ผ ํ•˜๊ณ , JSON์ด ์•„๋‹Œ ํผ ํ•„๋“œ๋กœ ์ „์†กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +<abbr title="specification">spec</abbr>์—์„œ๋Š” ํ•„๋“œ ์ด๋ฆ„์ด `username` ๋ฐ `password`๋กœ ์ •ํ™•ํ•˜๊ฒŒ ๋ช…๋ช…๋˜์–ด์•ผ ํ•˜๊ณ , JSON์ด ์•„๋‹Œ ํผ ํ•„๋“œ๋กœ ์ „์†กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. `Form`์„ ์‚ฌ์šฉํ•˜๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์˜ˆ์ œ, ๋ณ„์นญ(์˜ˆ: `username` ๋Œ€์‹  `user-name`) ๋“ฑ์„ ํฌํ•จํ•˜์—ฌ `Body`(๋ฐ `Query`, `Path`, `Cookie`)์™€ ๋™์ผํ•œ ๊ตฌ์„ฑ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -44,7 +44,7 @@ $ pip install python-multipart /// -## "ํผ ํ•„๋“œ"์— ๋Œ€ํ•ด +## "ํผ ํ•„๋“œ"์— ๋Œ€ํ•ด { #about-form-fields } HTML ํผ(`<form></form>`)์ด ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๋Š” ๋ฐฉ์‹์€ ์ผ๋ฐ˜์ ์œผ๋กœ ํ•ด๋‹น ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด "ํŠน์ˆ˜" ์ธ์ฝ”๋”ฉ์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ์ด๋Š” JSON๊ณผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. @@ -56,19 +56,18 @@ HTML ํผ(`<form></form>`)์ด ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๋Š” ๋ฐฉ์‹์€ ์ผ๋ฐ˜ ๊ทธ๋Ÿฌ๋‚˜ ํผ์— ํŒŒ์ผ์ด ํฌํ•จ๋œ ๊ฒฝ์šฐ, `multipart/form-data`๋กœ ์ธ์ฝ”๋”ฉํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์žฅ์—์„œ ํŒŒ์ผ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด ์ฝ์„ ๊ฒ๋‹ˆ๋‹ค. - -์ด๋Ÿฌํ•œ ์ธ์ฝ”๋”ฉ ๋ฐ ํผ ํ•„๋“œ์— ๋Œ€ํ•ด ๋” ์ฝ๊ณ  ์‹ถ๋‹ค๋ฉด, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><code>POST</code>์— ๋Œ€ํ•œ <abbr title="Mozilla Developer Network">MDN</a> ์›น ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. +์ด๋Ÿฌํ•œ ์ธ์ฝ”๋”ฉ ๋ฐ ํผ ํ•„๋“œ์— ๋Œ€ํ•ด ๋” ์ฝ๊ณ  ์‹ถ๋‹ค๋ฉด, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><code>POST</code>์— ๋Œ€ํ•œ <abbr title="Mozilla Developer Network">MDN</abbr> ์›น ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”</a>. /// /// warning | ๊ฒฝ๊ณ  -*๊ฒฝ๋กœ ์ž‘์—…*์—์„œ ์—ฌ๋Ÿฌ `Form` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, JSON์œผ๋กœ ์ˆ˜์‹ ํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š” `Body` ํ•„๋“œ์™€ ํ•จ๊ป˜ ์„ ์–ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์š”์ฒญ ๋ณธ๋ฌธ์€ `application/json` ๋Œ€์‹ ์— `application/x-www-form-urlencoded`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฝ”๋”ฉ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ ์—ฌ๋Ÿฌ `Form` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, JSON์œผ๋กœ ์ˆ˜์‹ ํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š” `Body` ํ•„๋“œ์™€ ํ•จ๊ป˜ ์„ ์–ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์š”์ฒญ ๋ณธ๋ฌธ์€ `application/json` ๋Œ€์‹ ์— `application/x-www-form-urlencoded`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฝ”๋”ฉ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋Š” **FastAPI**์˜ ์ œํ•œ ์‚ฌํ•ญ์ด ์•„๋‹ˆ๋ฉฐ HTTP ํ”„๋กœํ† ์ฝœ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. /// -## ์š”์•ฝ +## ์š”์•ฝ { #recap } ํผ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋ ค๋ฉด `Form`์„ ์‚ฌ์šฉํ•˜์„ธ์š”. diff --git a/docs/ko/docs/tutorial/response-model.md b/docs/ko/docs/tutorial/response-model.md index a71d649f90..6246ed9ad1 100644 --- a/docs/ko/docs/tutorial/response-model.md +++ b/docs/ko/docs/tutorial/response-model.md @@ -1,81 +1,173 @@ -# ์‘๋‹ต ๋ชจ๋ธ +# ์‘๋‹ต ๋ชจ๋ธ - ๋ฐ˜ํ™˜ ํƒ€์ž… { #response-model-return-type } -์–ด๋–ค *๊ฒฝ๋กœ ์ž‘๋™*์ด๋“  ๋งค๊ฐœ๋ณ€์ˆ˜ `response_model`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต์„ ์œ„ํ•œ ๋ชจ๋ธ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ **๋ฐ˜ํ™˜ ํƒ€์ž…**์„ ์–ด๋…ธํ…Œ์ด์…˜ํ•˜์—ฌ ์‘๋‹ต์— ์‚ฌ์šฉ๋  ํƒ€์ž…์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•จ์ˆ˜ **๋งค๊ฐœ๋ณ€์ˆ˜**์—์„œ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•˜๊ฒŒ **ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜**์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, Pydantic ๋ชจ๋ธ, ๋ฆฌ์ŠคํŠธ, ๋”•์…”๋„ˆ๋ฆฌ, ์ •์ˆ˜/๋ถˆ๋ฆฌ์–ธ ๊ฐ™์€ ์Šค์นผ๋ผ ๊ฐ’ ๋“ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} + +FastAPI๋Š” ์ด ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์—ฌ: + +* ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ **๊ฒ€์ฆ**ํ•ฉ๋‹ˆ๋‹ค. + * ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด(์˜ˆ: ํ•„๋“œ๊ฐ€ ๋ˆ„๋ฝ๋œ ๊ฒฝ์šฐ), ์ด๋Š” *์—ฌ๋Ÿฌ๋ถ„์˜* ์•ฑ ์ฝ”๋“œ๊ฐ€ ๊นจ์ ธ์„œ ์˜๋„ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ๋ชปํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๋ฉฐ, ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹  ์„œ๋ฒ„ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์—ฌ๋Ÿฌ๋ถ„๊ณผ ํด๋ผ์ด์–ธํŠธ๋Š” ๊ธฐ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ์™€ ๋ฐ์ดํ„ฐ ํ˜•ํƒœ๋ฅผ ๋ฐ›๋Š”๋‹ค๋Š” ๊ฒƒ์„ ํ™•์‹คํžˆ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* OpenAPI *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ ์‘๋‹ต์— **JSON Schema**๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. + * ์ด๋Š” **์ž๋™ ๋ฌธ์„œ**์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + * ๋˜ํ•œ ์ž๋™ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ์ƒ์„ฑ ๋„๊ตฌ์—์„œ๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€: + +* ๋ฐ˜ํ™˜ ํƒ€์ž…์— ์ •์˜๋œ ๋‚ด์šฉ์œผ๋กœ ์ถœ๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ **์ œํ•œํ•˜๊ณ  ํ•„ํ„ฐ๋ง**ํ•ฉ๋‹ˆ๋‹ค. + * ์ด๋Š” ํŠนํžˆ **๋ณด์•ˆ**์— ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์—์„œ ๋” ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +## `response_model` ๋งค๊ฐœ๋ณ€์ˆ˜ { #response-model-parameter } + +ํƒ€์ž… ์„ ์–ธ์ด ๋งํ•˜๋Š” ๊ฒƒ๊ณผ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, **๋”•์…”๋„ˆ๋ฆฌ**๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ์ฒด๋ฅผ **๋ฐ˜ํ™˜**ํ•˜๊ณ  ์‹ถ์ง€๋งŒ, **Pydantic ๋ชจ๋ธ๋กœ ์„ ์–ธ**ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Pydantic ๋ชจ๋ธ์ด ๋ฐ˜ํ™˜ํ•œ ๊ฐ์ฒด(์˜ˆ: ๋”•์…”๋„ˆ๋ฆฌ๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ์ฒด)์— ๋Œ€ํ•ด ๋ฐ์ดํ„ฐ ๋ฌธ์„œํ™”, ๊ฒ€์ฆ ๋“ฑ ๋ชจ๋“  ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. + +๋ฐ˜ํ™˜ ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค๋ฉด, ๋„๊ตฌ์™€ ์—๋””ํ„ฐ๊ฐ€ ํ•จ์ˆ˜๊ฐ€ ์„ ์–ธํ•œ ํƒ€์ž…(์˜ˆ: Pydantic ๋ชจ๋ธ)๊ณผ ๋‹ค๋ฅธ ํƒ€์ž…(์˜ˆ: dict)์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ๋‹ค๋Š” (์˜ฌ๋ฐ”๋ฅธ) ์˜ค๋ฅ˜๋กœ ๋ถˆํ‰ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ๋ฐ˜ํ™˜ ํƒ€์ž… ๋Œ€์‹  *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `response_model`์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +`response_model` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๋ชจ๋“  *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: * `@app.get()` * `@app.post()` * `@app.put()` * `@app.delete()` -* ๊ธฐํƒ€. +* ๋“ฑ. -{* ../../docs_src/response_model/tutorial001.py hl[17] *} +{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *} /// note | ์ฐธ๊ณ  -`response_model`์€ "๋ฐ์ฝ”๋ ˆ์ดํ„ฐ" ๋ฉ”์†Œ๋“œ(`get`, `post`, ๋“ฑ)์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค๊ณผ ๋ณธ๋ฌธ(body)์ฒ˜๋Ÿผ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. +`response_model`์€ "๋ฐ์ฝ”๋ ˆ์ดํ„ฐ" ๋ฉ”์„œ๋“œ(`get`, `post` ๋“ฑ)์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜์™€ body์ฒ˜๋Ÿผ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. /// -Pydantic ๋ชจ๋ธ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์„ ์–ธํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ํƒ€์ž…์„ ์ˆ˜์‹ ํ•˜๋ฏ€๋กœ Pydantic ๋ชจ๋ธ์ด ๋  ์ˆ˜ ์žˆ์ง€๋งŒ, `List[Item]`๊ณผ ๊ฐ™์ด Pydantic ๋ชจ๋ธ๋“ค์˜ `list`์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +`response_model`์€ Pydantic ๋ชจ๋ธ ํ•„๋“œ์— ์„ ์–ธํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ํƒ€์ž…์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Pydantic ๋ชจ๋ธ์ด ๋  ์ˆ˜๋„ ์žˆ๊ณ , `List[Item]`์ฒ˜๋Ÿผ Pydantic ๋ชจ๋ธ์˜ `list`๊ฐ€ ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -FastAPI๋Š” ์ด `response_model`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ: +FastAPI๋Š” ์ด `response_model`์„ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ ๋ฌธ์„œํ™”, ๊ฒ€์ฆ ๋“ฑ์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ๋˜ํ•œ ์ถœ๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ํƒ€์ž… ์„ ์–ธ์— ๋งž๊ฒŒ **๋ณ€ํ™˜ํ•˜๊ณ  ํ•„ํ„ฐ๋ง**ํ•ฉ๋‹ˆ๋‹ค. -* ์ถœ๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ํƒ€์ž… ์„ ์–ธ์œผ๋กœ ๋ณ€ํ™˜. -* ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ. -* OpenAPI *๊ฒฝ๋กœ ์ž‘๋™*์˜ ์‘๋‹ต์— JSON ์Šคํ‚ค๋งˆ ์ถ”๊ฐ€. -* ์ž๋™ ์ƒ์„ฑ ๋ฌธ์„œ ์‹œ์Šคํ…œ์— ์‚ฌ์šฉ. +/// 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 ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋“ค์— ๋Œ€ํ•ด ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒฝ์šฐ์— ํ•„์š”ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์•„๋ž˜ ์„น์…˜ ์ค‘ ํ•˜๋‚˜์—์„œ ์˜ˆ์‹œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ๋™์ผํ•œ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ { #return-the-same-input-data } + +์—ฌ๊ธฐ์„œ๋Š” ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜๋Š” `UserIn` ๋ชจ๋ธ์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *} + +/// info | ์ •๋ณด + +`EmailStr`์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ € <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”. + +[๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ์ƒ์„ฑํ•˜๊ณ , ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: + +```console +$ pip install email-validator +``` + +๋˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด: + +```console +$ pip install "pydantic[email]" +``` + +/// ๊ทธ๋ฆฌ๊ณ  ์ด ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ์„ ์„ ์–ธํ•˜๊ณ  ๊ฐ™์€ ๋ชจ๋ธ๋กœ ์ถœ๋ ฅ์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/response_model/tutorial002.py hl[17:18] *} +{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *} ์ด์ œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ๋งŒ๋“ค ๋•Œ๋งˆ๋‹ค API๋Š” ์‘๋‹ต์œผ๋กœ ๋™์ผํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์Šค์Šค๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐœ์‹ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ๊ฒฝ์šฐ, ๋™์ผํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ ๋™์ผํ•œ ๋ชจ๋ธ์„ ๋‹ค๋ฅธ *๊ฒฝ๋กœ ์ž‘๋™*์—์„œ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐœ์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๋™์ผํ•œ ๋ชจ๋ธ์„ ๋‹ค๋ฅธ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ ์‚ฌ์šฉํ•˜๋ฉด, ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณด๋‚ด๊ฒŒ ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. /// danger | ์œ„ํ—˜ -์ ˆ๋Œ€๋กœ ์‚ฌ์šฉ์ž์˜ ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์‘๋‹ต์œผ๋กœ ๋ฐœ์‹ ํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค. +๋ชจ๋“  ์ฃผ์˜์‚ฌํ•ญ์„ ์•Œ๊ณ  ์žˆ์œผ๋ฉฐ ๋ฌด์—‡์„ ํ•˜๋Š”์ง€ ์ •ํ™•ํžˆ ์•Œ๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋ฉด, ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์‘๋‹ต์œผ๋กœ ๋ณด๋‚ด์ง€ ๋งˆ์„ธ์š”. /// -## ์ถœ๋ ฅ ๋ชจ๋ธ ์ถ”๊ฐ€ +## ์ถœ๋ ฅ ๋ชจ๋ธ ์ถ”๊ฐ€ { #add-an-output-model } -๋Œ€์‹  ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ์ž…๋ ฅ ๋ชจ๋ธ์„ ๋งŒ๋“ค๊ณ  ํ•ด๋‹น ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†์ด ์ถœ๋ ฅ ๋ชจ๋ธ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋Œ€์‹  ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜๋Š” ์ž…๋ ฅ ๋ชจ๋ธ๊ณผ, ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์—†๋Š” ์ถœ๋ ฅ ๋ชจ๋ธ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/response_model/tutorial003.py hl[9,11,16] *} +{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *} -์—ฌ๊ธฐ์„œ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜๋Š” ๋™์ผํ•œ ์ž…๋ ฅ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ• ์ง€๋ผ๋„: +์—ฌ๊ธฐ์„œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜๋Š” ๋™์ผํ•œ ์ž…๋ ฅ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋”๋ผ๋„: -{* ../../docs_src/response_model/tutorial003.py hl[24] *} +{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *} -...`response_model`์„ `UserOut` ๋ชจ๋ธ๋กœ ์„ ์–ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: +...๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๋Š” `UserOut` ๋ชจ๋ธ๋กœ `response_model`์„ ์„ ์–ธํ–ˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/response_model/tutorial003.py hl[22] *} +{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} -๋”ฐ๋ผ์„œ **FastAPI**๋Š” ์ถœ๋ ฅ ๋ชจ๋ธ์—์„œ ์„ ์–ธํ•˜์ง€ ์•Š์€ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ (Pydantic์„ ์‚ฌ์šฉํ•˜์—ฌ) ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ **FastAPI**๋Š” ์ถœ๋ ฅ ๋ชจ๋ธ์— ์„ ์–ธ๋˜์ง€ ์•Š์€ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ (Pydantic์„ ์‚ฌ์šฉํ•˜์—ฌ) ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค. -## ๋ฌธ์„œ์—์„œ ๋ณด๊ธฐ +### `response_model` ๋˜๋Š” ๋ฐ˜ํ™˜ ํƒ€์ž… { #response-model-or-return-type } -์ž๋™ ์ƒ์„ฑ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด ์ž…๋ ฅ ๋ชจ๋ธ๊ณผ ์ถœ๋ ฅ ๋ชจ๋ธ์ด ๊ฐ์ž์˜ JSON ์Šคํ‚ค๋งˆ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์ด ๊ฒฝ์šฐ ๋‘ ๋ชจ๋ธ์ด ์„œ๋กœ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์—, ํ•จ์ˆ˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ `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`์ธ ์–ด๋–ค ๊ฒƒ์ด ๊ธฐ๋Œ€๋˜๋Š” ๊ณณ์—์„œ๋Š” *์œ ํšจํ•œ* ํƒ€์ž…์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +### FastAPI ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง { #fastapi-data-filtering } + +์ด์ œ FastAPI๋Š” ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ๋ณด๊ณ , ์—ฌ๋Ÿฌ๋ถ„์ด ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์ด ํ•ด๋‹น ํƒ€์ž…์— ์„ ์–ธ๋œ ํ•„๋“œ **๋งŒ** ํฌํ•จํ•˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + +FastAPI๋Š” Pydantic์„ ๋‚ด๋ถ€์ ์œผ๋กœ ์—ฌ๋Ÿฌ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜์—ฌ, ํด๋ž˜์Šค ์ƒ์†์˜ ๋™์ผํ•œ ๊ทœ์น™์ด ๋ฐ˜ํ™˜ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง์—๋Š” ์ ์šฉ๋˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋Œ€ํ•œ ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ๋” ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด **๋„๊ตฌ ์ง€์›**์ด ์žˆ๋Š” ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜๊ณผ **๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง**์ด๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ์žฅ์ ์„ ๋ชจ๋‘ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ๋ฌธ์„œ์—์„œ ๋ณด๊ธฐ { #see-it-in-the-docs } + +์ž๋™ ์ƒ์„ฑ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด ์ž…๋ ฅ ๋ชจ๋ธ๊ณผ ์ถœ๋ ฅ ๋ชจ๋ธ์ด ๊ฐ์ž์˜ JSON Schema๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <img src="/img/tutorial/response-model/image01.png"> @@ -83,29 +175,73 @@ FastAPI๋Š” ์ด `response_model`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ: <img src="/img/tutorial/response-model/image02.png"> -## ์‘๋‹ต ๋ชจ๋ธ ์ธ์ฝ”๋”ฉ ๋งค๊ฐœ๋ณ€์ˆ˜ +## ๊ธฐํƒ€ ๋ฐ˜ํ™˜ ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜ { #other-return-type-annotations } + +์œ ํšจํ•œ Pydantic ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๊ฒƒ์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด์„œ๋„, ๋„๊ตฌ(์—๋””ํ„ฐ, mypy ๋“ฑ)๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ง€์›์„ ๋ฐ›๊ธฐ ์œ„ํ•ด ํ•จ์ˆ˜์— ์–ด๋…ธํ…Œ์ด์…˜์„ ๋‹ฌ์•„๋‘๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #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] *} + +์ด ๊ฐ„๋‹จํ•œ ๊ฒฝ์šฐ๋Š” ๋ฐ˜ํ™˜ ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์ด `Response` ํด๋ž˜์Šค(๋˜๋Š” ๊ทธ ์„œ๋ธŒํด๋ž˜์Šค)์ด๊ธฐ ๋•Œ๋ฌธ์— FastAPI์—์„œ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  `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 ํƒ€์ž…์ด ์•„๋‹Œ ํƒ€์ž…์ด ํ•˜๋‚˜ ์ด์ƒ ํฌํ•จ๋œ ์—ฌ๋Ÿฌ ํƒ€์ž… ๊ฐ„์˜ <abbr title='์—ฌ๋Ÿฌ ํƒ€์ž… ๊ฐ„ union์€ "์ด ํƒ€์ž…๋“ค ์ค‘ ์•„๋ฌด๊ฑฐ๋‚˜"๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.'>union</abbr>์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋„ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜๋Š” ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค ๐Ÿ’ฅ: + +{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} + +...์ด๋Š” ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์ด Pydantic ํƒ€์ž…์ด ์•„๋‹ˆ๊ณ , ๋‹จ์ผ `Response` ํด๋ž˜์Šค/์„œ๋ธŒํด๋ž˜์Šค๋„ ์•„๋‹ˆ๋ฉฐ, `Response`์™€ `dict` ๊ฐ„ union(๋‘˜ ์ค‘ ์•„๋ฌด๊ฑฐ๋‚˜)์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. + +### ์‘๋‹ต ๋ชจ๋ธ ๋น„ํ™œ์„ฑํ™” { #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: Optional[str] = None`์€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ `None`์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. +* `description: Union[str, None] = None` (๋˜๋Š” Python 3.10์—์„œ `str | None = None`)์€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ `None`์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. * `tax: float = 10.5`๋Š” ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ `10.5`๋ฅผ ๊ฐ–์Šต๋‹ˆ๋‹ค. -* `tags: List[str] = []` ๋นˆ ๋ฆฌ์ŠคํŠธ์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ: `[]`. +* `tags: List[str] = []`๋Š” ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋นˆ ๋ฆฌ์ŠคํŠธ `[]`๋ฅผ ๊ฐ–์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ ์‹ค์ œ๋กœ ์ €์žฅ๋˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ ๊ฒฐ๊ณผ์—์„œ ๊ฐ’์„ ์ƒ๋žตํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ ์ €์žฅ๋˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ ๊ฒฐ๊ณผ์—์„œ ์ด๋ฅผ ์ƒ๋žตํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, 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`์ธ ํ•ญ๋ชฉ(items)์„ ์š”์ฒญ์œผ๋กœ ๋ณด๋‚ด๋ฉด (๊ธฐ๋ณธ๊ฐ’์„ ์ œ์™ธํ•œ) ์‘๋‹ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: +๋”ฐ๋ผ์„œ ID๊ฐ€ `foo`์ธ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ํ•ด๋‹น *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, (๊ธฐ๋ณธ๊ฐ’์„ ์ œ์™ธํ•œ) ์‘๋‹ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: ```JSON { @@ -116,24 +252,18 @@ FastAPI๋Š” ์ด `response_model`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ: /// info | ์ •๋ณด -FastAPI๋Š” ์ด๋ฅผ ์œ„ํ•ด Pydantic ๋ชจ๋ธ์˜ `.dict()`์˜ <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank"> `exclude_unset` ๋งค๊ฐœ๋ณ€์ˆ˜</a>๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. - -/// - -/// info | ์ •๋ณด - -์•„๋ž˜ ๋˜ํ•œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋‹ค์Œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: * `response_model_exclude_defaults=True` * `response_model_exclude_none=True` -<a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">Pydantic ๋ฌธ์„œ</a>์—์„œ `exclude_defaults` ๋ฐ `exclude_none`์— ๋Œ€ํ•ด ์„ค๋ช…ํ•œ ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`exclude_defaults` ๋ฐ `exclude_none`์— ๋Œ€ํ•ด <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">Pydantic ๋ฌธ์„œ</a>์— ์„ค๋ช…๋œ ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -#### ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ๋Š” ํ•„๋“œ๋ฅผ ๊ฐ–๋Š” ๊ฐ’์˜ ๋ฐ์ดํ„ฐ +#### ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ๋Š” ํ•„๋“œ์— ๊ฐ’์ด ์žˆ๋Š” ๋ฐ์ดํ„ฐ { #data-with-values-for-fields-with-defaults } -ํ•˜์ง€๋งŒ ๋ชจ๋ธ์˜ ํ•„๋“œ๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ์–ด๋„ ID๊ฐ€ `bar`์ธ ํ•ญ๋ชฉ(items)์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค๋ฉด: +ํ•˜์ง€๋งŒ ID๊ฐ€ `bar`์ธ ํ•ญ๋ชฉ์ฒ˜๋Ÿผ, ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ๋Š” ๋ชจ๋ธ์˜ ํ•„๋“œ์— ๊ฐ’์ด ์žˆ๋‹ค๋ฉด: ```Python hl_lines="3 5" { @@ -144,12 +274,11 @@ FastAPI๋Š” ์ด๋ฅผ ์œ„ํ•ด Pydantic ๋ชจ๋ธ์˜ `.dict()`์˜ <a href="https://docs.p } ``` -์‘๋‹ต์— ํ•ด๋‹น ๊ฐ’๋“ค์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. +์‘๋‹ต์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. -#### ๊ธฐ๋ณธ๊ฐ’๊ณผ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ–๋Š” ๋ฐ์ดํ„ฐ +#### ๊ธฐ๋ณธ๊ฐ’๊ณผ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ–๋Š” ๋ฐ์ดํ„ฐ { #data-with-the-same-values-as-the-defaults } -If the data has the same values as the default ones, like the item with ID `baz`: -ID๊ฐ€ `baz`์ธ ํ•ญ๋ชฉ(items)์ฒ˜๋Ÿผ ๊ธฐ๋ณธ๊ฐ’๊ณผ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค๋ฉด: +๋ฐ์ดํ„ฐ๊ฐ€ ID๊ฐ€ `baz`์ธ ํ•ญ๋ชฉ์ฒ˜๋Ÿผ ๊ธฐ๋ณธ๊ฐ’๊ณผ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค๋ฉด: ```Python hl_lines="3 5-6" { @@ -161,37 +290,37 @@ ID๊ฐ€ `baz`์ธ ํ•ญ๋ชฉ(items)์ฒ˜๋Ÿผ ๊ธฐ๋ณธ๊ฐ’๊ณผ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค๋ฉด: } ``` -`description`, `tax` ๊ทธ๋ฆฌ๊ณ  `tags`๊ฐ€ ๊ธฐ๋ณธ๊ฐ’๊ณผ ๊ฐ™๋”๋ผ๋„ (๊ธฐ๋ณธ๊ฐ’์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๋Œ€์‹ ) ๊ฐ’๋“ค์ด ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ์ธ์ง€ํ•  ์ •๋„๋กœ FastAPI๋Š” ์ถฉ๋ถ„ํžˆ ๋˜‘๋˜‘ํ•ฉ๋‹ˆ๋‹ค(์‚ฌ์‹ค, Pydantic์ด ์ถฉ๋ถ„ํžˆ ๋˜‘๋˜‘ํ•ฉ๋‹ˆ๋‹ค). +FastAPI๋Š” ์ถฉ๋ถ„ํžˆ ๋˜‘๋˜‘ํ•ด์„œ(์‚ฌ์‹ค, Pydantic์ด ์ถฉ๋ถ„ํžˆ ๋˜‘๋˜‘ํ•ฉ๋‹ˆ๋‹ค) `description`, `tax`, `tags`๊ฐ€ ๊ธฐ๋ณธ๊ฐ’๊ณผ ๋™์ผํ•˜๋”๋ผ๋„, ๊ธฐ๋ณธ๊ฐ’์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•„๋ƒ…๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ JSON ์Šคํ‚ค๋งˆ์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋ž˜์„œ JSON ์‘๋‹ต์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. /// tip | ํŒ -`None` ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ ์–ด๋–ค ๊ฒƒ๋„ ๊ธฐ๋ณธ๊ฐ’์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ธฐ๋ณธ๊ฐ’์€ `None`๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์–ด๋–ค ๊ฒƒ์ด๋“  ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ์ŠคํŠธ(`[]`), `float`์ธ `10.5` ๋“ฑ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -### `response_model_include` ๋ฐ `response_model_exclude` +### `response_model_include` ๋ฐ `response_model_exclude` { #response-model-include-and-response-model-exclude } -*๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ* ๋งค๊ฐœ๋ณ€์ˆ˜ `response_model_include` ๋ฐ `response_model_exclude`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ* ๋งค๊ฐœ๋ณ€์ˆ˜ `response_model_include` ๋ฐ `response_model_exclude`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๋“ค์€ ํฌํ•จ(๋‚˜๋จธ์ง€ ์ƒ๋žต)ํ•˜๊ฑฐ๋‚˜ ์ œ์™ธ(๋‚˜๋จธ์ง€ ํฌํ•จ) ํ•  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์˜ ์ด๋ฆ„๊ณผ `str`์˜ `set`์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. +์ด๋“ค์€ ํฌํ•จ(๋‚˜๋จธ์ง€ ์ƒ๋žต)ํ•˜๊ฑฐ๋‚˜ ์ œ์™ธ(๋‚˜๋จธ์ง€ ํฌํ•จ)ํ•  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ ์ด๋ฆ„์„ ๋‹ด์€ `str`์˜ `set`์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. -Pydantic ๋ชจ๋ธ์ด ํ•˜๋‚˜๋งŒ ์žˆ๊ณ  ์ถœ๋ ฅ์—์„œ โ€‹โ€‹์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ๋น ๋ฅธ ์ง€๋ฆ„๊ธธ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +Pydantic ๋ชจ๋ธ์ด ํ•˜๋‚˜๋งŒ ์žˆ๊ณ  ์ถœ๋ ฅ์—์„œ ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๋ ค๋Š” ๊ฒฝ์šฐ, ๋น ๋ฅธ ์ง€๋ฆ„๊ธธ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// tip | ํŒ -ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋Œ€์‹  ์—ฌ๋Ÿฌ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์œ„ ์•„์ด๋””์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋Œ€์‹ , ์œ„์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์—ฌ์ „ํžˆ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. -์ด๋Š” ์ผ๋ถ€ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์ƒ๋žตํ•˜๊ธฐ ์œ„ํ•ด `response_model_include` ๋˜๋Š” `response_model_exclude`๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ์•ฑ์˜ OpenAPI(๋ฐ ๋ฌธ์„œ)๊ฐ€ ์ƒ์„ฑํ•œ JSON ์Šคํ‚ค๋งˆ๊ฐ€ ์—ฌ์ „ํžˆ ์ „์ฒด ๋ชจ๋ธ์— ๋Œ€ํ•œ ์Šคํ‚ค๋งˆ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +์ด๋Š” ์ผ๋ถ€ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์ƒ๋žตํ•˜๊ธฐ ์œ„ํ•ด `response_model_include` ๋˜๋Š” `response_model_exclude`๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„, ์•ฑ์˜ OpenAPI(๋ฐ ๋ฌธ์„œ)์— ์ƒ์„ฑ๋˜๋Š” JSON Schema๊ฐ€ ์—ฌ์ „ํžˆ ์ „์ฒด ๋ชจ๋ธ์— ๋Œ€ํ•œ ์Šคํ‚ค๋งˆ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -๋น„์Šทํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋Š” `response_model_by_alias` ์—ญ์‹œ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. +๋น„์Šทํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š” `response_model_by_alias`์—๋„ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. /// -{* ../../docs_src/response_model/tutorial005.py hl[31,37] *} +{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *} /// tip | ํŒ @@ -201,14 +330,14 @@ Pydantic ๋ชจ๋ธ์ด ํ•˜๋‚˜๋งŒ ์žˆ๊ณ  ์ถœ๋ ฅ์—์„œ โ€‹โ€‹์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ /// -#### `set` ๋Œ€์‹  `list` ์‚ฌ์šฉํ•˜๊ธฐ +#### `set` ๋Œ€์‹  `list` ์‚ฌ์šฉํ•˜๊ธฐ { #using-lists-instead-of-sets } -`list` ๋˜๋Š” `tuple` ๋Œ€์‹  `set`์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•์„ ์žŠ์—ˆ๋”๋ผ๋„, FastAPI๋Š” `set`์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค: +`set`์„ ์“ฐ๋Š” ๊ฒƒ์„ ์žŠ๊ณ  `list`๋‚˜ `tuple`์„ ๋Œ€์‹  ์‚ฌ์šฉํ•˜๋”๋ผ๋„, FastAPI๋Š” ์ด๋ฅผ `set`์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋ฏ€๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค: -{* ../../docs_src/response_model/tutorial006.py hl[31,37] *} +{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *} -## ์š”์•ฝ +## ์š”์•ฝ { #recap } -์‘๋‹ต ๋ชจ๋ธ์„ ์ •์˜ํ•˜๊ณ  ๊ฐœ์ธ์ •๋ณด๊ฐ€ ํ•„ํ„ฐ๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด *๊ฒฝ๋กœ ์ž‘๋™ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `response_model`์„ ์‚ฌ์šฉํ•˜์„ธ์š”. +์‘๋‹ต ๋ชจ๋ธ์„ ์ •์˜ํ•˜๊ณ  ํŠนํžˆ ๊ฐœ์ธ์ •๋ณด๊ฐ€ ํ•„ํ„ฐ๋ง๋˜๋„๋ก ๋ณด์žฅํ•˜๋ ค๋ฉด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ `response_model`์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •๋œ ๊ฐ’๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ ค๋ฉด `response_model_exclude_unset`์„ ์‚ฌ์šฉํ•˜์„ธ์š”. diff --git a/docs/ko/docs/tutorial/response-status-code.md b/docs/ko/docs/tutorial/response-status-code.md index bcaf7843b9..257f6a8d6f 100644 --- a/docs/ko/docs/tutorial/response-status-code.md +++ b/docs/ko/docs/tutorial/response-status-code.md @@ -1,18 +1,18 @@ -# ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ +# ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ { #response-status-code } -์‘๋‹ต ๋ชจ๋ธ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ, ์–ด๋–ค *๊ฒฝ๋กœ ์ž‘๋™*์ด๋“  `status_code` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต์— ๋Œ€ํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์‘๋‹ต ๋ชจ๋ธ์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ, ์–ด๋–ค *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ๋“  `status_code` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต์— ์‚ฌ์šฉํ•  HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: * `@app.get()` * `@app.post()` * `@app.put()` * `@app.delete()` -* ๊ธฐํƒ€ +* ๋“ฑ -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} /// note | ์ฐธ๊ณ  -`status_code` ๋Š” "๋ฐ์ฝ”๋ ˆ์ดํ„ฐ" ๋ฉ”์†Œ๋“œ(`get`, `post` ๋“ฑ)์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค๊ณผ ๋ณธ๋ฌธ์ฒ˜๋Ÿผ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. +`status_code` ๋Š” "๋ฐ์ฝ”๋ ˆ์ดํ„ฐ" ๋ฉ”์†Œ๋“œ(`get`, `post` ๋“ฑ)์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค๊ณผ ๋ณธ๋ฌธ์ฒ˜๋Ÿผ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. /// @@ -20,75 +20,75 @@ /// info | ์ •๋ณด -`status_code` ๋Š” ํŒŒ์ด์ฌ์˜ `http.HTTPStatus` ์™€ ๊ฐ™์€ `IntEnum` ์„ ์ž…๋ ฅ๋ฐ›์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +`status_code` ๋Š” ํŒŒ์ด์ฌ์˜ <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a> ์™€ ๊ฐ™์€ `IntEnum` ์„ ์ž…๋ ฅ๋ฐ›์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. /// `status_code` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š”: * ์‘๋‹ต์—์„œ ํ•ด๋‹น ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -* ์ƒํƒœ ์ฝ”๋“œ๋ฅผ OpenAPI ์Šคํ‚ค๋งˆ(๋ฐ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค)์— ๋ฌธ์„œํ™” ํ•ฉ๋‹ˆ๋‹ค. +* ์ƒํƒœ ์ฝ”๋“œ๋ฅผ OpenAPI ์Šคํ‚ค๋งˆ(๋”ฐ๋ผ์„œ, ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์—๋„)์— ๋ฌธ์„œํ™”ํ•ฉ๋‹ˆ๋‹ค: -<img src="https://fastapi.tiangolo.com/img/tutorial/response-status-code/image01.png"> +<img src="/img/tutorial/response-status-code/image01.png"> /// note | ์ฐธ๊ณ  -์–ด๋–ค ์‘๋‹ต ์ฝ”๋“œ๋“ค์€ ํ•ด๋‹น ์‘๋‹ต์— ๋ณธ๋ฌธ์ด ์—†๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค (๋‹ค์Œ ํ•ญ๋ชฉ ์ฐธ๊ณ ). +์ผ๋ถ€ ์‘๋‹ต ์ฝ”๋“œ(๋‹ค์Œ ์„น์…˜ ์ฐธ๊ณ )๋Š” ์‘๋‹ต์— ๋ณธ๋ฌธ์ด ์—†๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. -์ด์— ๋”ฐ๋ผ FastAPI๋Š” ์‘๋‹ต ๋ณธ๋ฌธ์ด ์—†์Œ์„ ๋ช…์‹œํ•˜๋Š” OpenAPI๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +FastAPI๋Š” ์ด๋ฅผ ์•Œ๊ณ  ์žˆ์œผ๋ฉฐ, ์‘๋‹ต ๋ณธ๋ฌธ์ด ์—†๋‹ค๊ณ  ๋ช…์‹œํ•˜๋Š” OpenAPI ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. /// -## HTTP ์ƒํƒœ ์ฝ”๋“œ์— ๋Œ€ํ•˜์—ฌ +## HTTP ์ƒํƒœ ์ฝ”๋“œ์— ๋Œ€ํ•˜์—ฌ { #about-http-status-codes } /// note | ์ฐธ๊ณ  -๋งŒ์•ฝ HTTP ์ƒํƒœ ์ฝ”๋“œ์— ๋Œ€ํ•˜์—ฌ ์ด๋ฏธ ์•Œ๊ณ ์žˆ๋‹ค๋ฉด, ๋‹ค์Œ ํ•ญ๋ชฉ์œผ๋กœ ๋„˜์–ด๊ฐ€์‹ญ์‹œ์˜ค. +๋งŒ์•ฝ HTTP ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ด๋ฏธ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด, ๋‹ค์Œ ์„น์…˜์œผ๋กœ ๋„˜์–ด๊ฐ€์„ธ์š”. /// -HTTP๋Š” ์„ธ์ž๋ฆฌ์˜ ์ˆซ์ž ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์‘๋‹ต์˜ ์ผ๋ถ€๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. +HTTP์—์„œ๋Š” ์‘๋‹ต์˜ ์ผ๋ถ€๋กœ 3์ž๋ฆฌ ์ˆซ์ž ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค. -์ด ์ƒํƒœ ์ฝ”๋“œ๋“ค์€ ๊ฐ์ž๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์ •๋œ ์ด๋ฆ„์ด ์žˆ์œผ๋‚˜, ์ค‘์š”ํ•œ ๊ฒƒ์€ ์ˆซ์ž ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. +์ด ์ƒํƒœ ์ฝ”๋“œ๋“ค์€ ์ด๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ด๋ฆ„์ด ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€๋งŒ, ์ค‘์š”ํ•œ ๋ถ€๋ถ„์€ ์ˆซ์ž์ž…๋‹ˆ๋‹ค. ์š”์•ฝํ•˜์ž๋ฉด: -* `1xx` ์ƒํƒœ ์ฝ”๋“œ๋Š” "์ •๋ณด"์šฉ์ž…๋‹ˆ๋‹ค. ์ด๋“ค์€ ์ง์ ‘์ ์œผ๋กœ๋Š” ์ž˜ ์‚ฌ์šฉ๋˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์ด ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๊ฐ–๋Š” ์‘๋‹ต๋“ค์€ ๋ณธ๋ฌธ์„ ๊ฐ€์งˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -* **`2xx`** ์ƒํƒœ ์ฝ”๋“œ๋Š” "์„ฑ๊ณต์ ์ธ" ์‘๋‹ต์„ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ์œ ํ˜•์ž…๋‹ˆ๋‹ค. - * `200` ์€ ๋””ํดํŠธ ์ƒํƒœ ์ฝ”๋“œ๋กœ, ๋ชจ๋“  ๊ฒƒ์ด "์„ฑ๊ณต์ ์ž„"์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. - * ๋‹ค๋ฅธ ์˜ˆ๋กœ๋Š” `201` "์ƒ์„ฑ๋จ"์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ƒˆ๋กœ์šด ๋ ˆ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•œ ํ›„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. - * ๋‹จ, `204` "๋‚ด์šฉ ์—†์Œ"์€ ํŠน๋ณ„ํ•œ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜ํ•  ๋‚ด์šฉ์ด ์—†๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‘๋‹ต์€ ๋ณธ๋ฌธ์„ ๊ฐ€์งˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -* **`3xx`** ์ƒํƒœ ์ฝ”๋“œ๋Š” "๋ฆฌ๋‹ค์ด๋ ‰์…˜"์šฉ์ž…๋‹ˆ๋‹ค. ๋ณธ๋ฌธ์„ ๊ฐ€์งˆ ์ˆ˜ ์—†๋Š” `304` "์ˆ˜์ •๋˜์ง€ ์•Š์Œ"์„ ์ œ์™ธํ•˜๊ณ , ์ด ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๊ฐ–๋Š” ์‘๋‹ต์—๋Š” ๋ณธ๋ฌธ์ด ์žˆ์„ ์ˆ˜๋„, ์—†์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -* **`4xx`** ์ƒํƒœ ์ฝ”๋“œ๋Š” "ํด๋ผ์ด์–ธํŠธ ์˜ค๋ฅ˜" ์‘๋‹ต์„ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์•„๋งˆ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ๋‘๋ฒˆ์งธ ์œ ํ˜•์ž…๋‹ˆ๋‹ค. - * ์ผ๋ก€๋กœ `404` ๋Š” "์ฐพ์„ ์ˆ˜ ์—†์Œ" ์‘๋‹ต์„ ์œ„ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. - * ์ผ๋ฐ˜์ ์ธ ํด๋ผ์ด์–ธํŠธ ์˜ค๋ฅ˜์˜ ๊ฒฝ์šฐ `400` ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* `5xx` ์ƒํƒœ ์ฝ”๋“œ๋Š” ์„œ๋ฒ„ ์˜ค๋ฅ˜์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ๋“ค์„ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ผ์€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค. ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์ฝ”๋“œ๋‚˜ ์„œ๋ฒ„์˜ ์ผ๋ถ€์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ด๋“ค ์ƒํƒœ ์ฝ”๋“œ ์ค‘ ํ•˜๋‚˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +* `100 - 199` ๋Š” "์ •๋ณด"์šฉ์ž…๋‹ˆ๋‹ค. ์ง์ ‘ ์‚ฌ์šฉํ•  ์ผ์€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๊ฐ–๋Š” ์‘๋‹ต์€ ๋ณธ๋ฌธ์„ ๊ฐ€์งˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. +* **`200 - 299`** ๋Š” "์„ฑ๊ณต์ ์ธ" ์‘๋‹ต์„ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ์œ ํ˜•์ž…๋‹ˆ๋‹ค. + * `200` ์€ ๊ธฐ๋ณธ ์ƒํƒœ ์ฝ”๋“œ๋กœ, ๋ชจ๋“  ๊ฒƒ์ด "OK"์ž„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. + * ๋‹ค๋ฅธ ์˜ˆ๋กœ๋Š” `201` "์ƒ์„ฑ๋จ"์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ƒˆ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•œ ํ›„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * ํŠน๋ณ„ํ•œ ๊ฒฝ์šฐ๋กœ `204` "๋‚ด์šฉ ์—†์Œ"์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์‘๋‹ต์€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜ํ•  ๋‚ด์šฉ์ด ์—†์„ ๋•Œ ์‚ฌ์šฉ๋˜๋ฉฐ, ๋”ฐ๋ผ์„œ ์‘๋‹ต์€ ๋ณธ๋ฌธ์„ ๊ฐ€์ง€๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. +* **`300 - 399`** ๋Š” "๋ฆฌ๋‹ค์ด๋ ‰์…˜"์šฉ์ž…๋‹ˆ๋‹ค. ์ด ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๊ฐ–๋Š” ์‘๋‹ต์€ ๋ณธ๋ฌธ์ด ์žˆ์„ ์ˆ˜๋„ ์—†์„ ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, ๋ณธ๋ฌธ์ด ์—†์–ด์•ผ ํ•˜๋Š” `304` "์ˆ˜์ •๋˜์ง€ ์•Š์Œ"์„ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค. +* **`400 - 499`** ๋Š” "ํด๋ผ์ด์–ธํŠธ ์˜ค๋ฅ˜" ์‘๋‹ต์„ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋งˆ ๋‘ ๋ฒˆ์งธ๋กœ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ์œ ํ˜•์ž…๋‹ˆ๋‹ค. + * ์˜ˆ๋ฅผ ๋“ค์–ด `404` ๋Š” "์ฐพ์„ ์ˆ˜ ์—†์Œ" ์‘๋‹ต์„ ์œ„ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * ํด๋ผ์ด์–ธํŠธ์˜ ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜์—๋Š” `400` ์„ ๊ทธ๋ƒฅ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* `500 - 599` ๋Š” ์„œ๋ฒ„ ์˜ค๋ฅ˜์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ง์ ‘ ์‚ฌ์šฉํ•  ์ผ์€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์˜ ์ผ๋ถ€๋‚˜ ์„œ๋ฒ„์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ด๋“ค ์ƒํƒœ ์ฝ”๋“œ ์ค‘ ํ•˜๋‚˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. /// tip | ํŒ -๊ฐ๊ฐ์˜ ์ƒํƒœ ์ฝ”๋“œ์™€ ์ด๋“ค์ด ์˜๋ฏธํ•˜๋Š” ๋‚ด์šฉ์— ๋Œ€ํ•ด ๋” ์•Œ๊ณ ์‹ถ๋‹ค๋ฉด <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> HTTP ์ƒํƒœ ์ฝ”๋“œ์— ๊ด€ํ•œ ๋ฌธ์„œ</a> ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. +๊ฐ ์ƒํƒœ ์ฝ”๋“œ์™€ ์–ด๋–ค ์ฝ”๋“œ๊ฐ€ ์–ด๋–ค ์šฉ๋„์ธ์ง€ ๋” ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr>์˜ HTTP ์ƒํƒœ ์ฝ”๋“œ์— ๊ด€ํ•œ ๋ฌธ์„œ</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. /// -## ์ด๋ฆ„์„ ๊ธฐ์–ตํ•˜๋Š” ์‰ฌ์šด ๋ฐฉ๋ฒ• +## ์ด๋ฆ„์„ ๊ธฐ์–ตํ•˜๋Š” ์‰ฌ์šด ๋ฐฉ๋ฒ• { #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] *} -์ด๊ฒƒ์€ ๋‹จ์ˆœํžˆ ์ž‘์—…์„ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์œผ๋กœ, HTTP ์ƒํƒœ ์ฝ”๋“œ์™€ ๋™์ผํ•œ ๋ฒˆํ˜ธ๋ฅผ ๊ฐ–๊ณ ์žˆ์ง€๋งŒ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŽธ์ง‘๊ธฐ์˜ ์ž๋™์™„์„ฑ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์ด๊ฒƒ๋“ค์€ ๋‹จ์ง€ ํŽธ์˜๋ฅผ ์œ„ํ•œ ๊ฒƒ์œผ๋กœ, ๋™์ผํ•œ ์ˆซ์ž๋ฅผ ๊ฐ–๊ณ  ์žˆ์ง€๋งŒ, ์ด๋ฅผ ํ†ตํ•ด ํŽธ์ง‘๊ธฐ์˜ ์ž๋™์™„์„ฑ ๊ธฐ๋Šฅ์œผ๋กœ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -<img src="https://fastapi.tiangolo.com/img/tutorial/response-status-code/image02.png"> +<img src="/img/tutorial/response-status-code/image02.png"> -/// note | ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `from starlette import status` ์—ญ์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -96,6 +96,6 @@ HTTP๋Š” ์„ธ์ž๋ฆฌ์˜ ์ˆซ์ž ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์‘๋‹ต์˜ ์ผ๋ถ€๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค /// -## ๊ธฐ๋ณธ๊ฐ’ ๋ณ€๊ฒฝ +## ๊ธฐ๋ณธ๊ฐ’ ๋ณ€๊ฒฝ { #changing-the-default } -์ถ”ํ›„ ์—ฌ๊ธฐ์„œ ์„ ์–ธํ•˜๋Š” ๊ธฐ๋ณธ ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ [์ˆ™๋ จ๋œ ์‚ฌ์šฉ์ž ์ง€์นจ์„œ](https://fastapi.tiangolo.com/ko/advanced/response-change-status-code/){.internal-link target=_blank}์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋‚˜์ค‘์— [๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ์ง€์นจ์„œ](../advanced/response-change-status-code.md){.internal-link target=_blank}์—์„œ, ์—ฌ๊ธฐ์„œ ์„ ์–ธํ•˜๋Š” ๊ธฐ๋ณธ๊ฐ’๊ณผ ๋‹ค๋ฅธ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/schema-extra-example.md b/docs/ko/docs/tutorial/schema-extra-example.md index 77e94db720..b2b54836ac 100644 --- a/docs/ko/docs/tutorial/schema-extra-example.md +++ b/docs/ko/docs/tutorial/schema-extra-example.md @@ -1,43 +1,21 @@ -# ์š”์ฒญ ์˜ˆ์ œ ๋ฐ์ดํ„ฐ ์„ ์–ธ +# ์š”์ฒญ ์˜ˆ์ œ ๋ฐ์ดํ„ฐ ์„ ์–ธ { #declare-request-example-data } ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์ด ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์˜ˆ์ œ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ ์ด๋ฅผ ์œ„ํ•œ ๋ช‡๊ฐ€์ง€ ๋ฐฉ์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค. -## Pydantic ๋ชจ๋ธ ์† ์ถ”๊ฐ€ JSON ์Šคํ‚ค๋งˆ ๋ฐ์ดํ„ฐ +## Pydantic ๋ชจ๋ธ ์† ์ถ”๊ฐ€ JSON ์Šคํ‚ค๋งˆ ๋ฐ์ดํ„ฐ { #extra-json-schema-data-in-pydantic-models } ์ƒ์„ฑ๋œ JSON ์Šคํ‚ค๋งˆ์— ์ถ”๊ฐ€๋  Pydantic ๋ชจ๋ธ์„ ์œ„ํ•œ `examples`์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -//// tab | Pydantic v2 - {* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *} - -//// - ์ถ”๊ฐ€ ์ •๋ณด๋Š” ์žˆ๋Š” ๊ทธ๋Œ€๋กœ ํ•ด๋‹น ๋ชจ๋ธ์˜ **JSON ์Šคํ‚ค๋งˆ** ๊ฒฐ๊ณผ์— ์ถ”๊ฐ€๋˜๊ณ , API ๋ฌธ์„œ์—์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -//// tab | Pydantic v2 - -Pydantic ๋ฒ„์ „ 2์—์„œ <a href="https://docs.pydantic.dev/latest/usage/model_config/" class="external-link" target="_blank">Pydantic ๊ณต์‹ ๋ฌธ์„œ: Model Config</a>์— ๋‚˜์™€ ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ `dict`๋ฅผ ๋ฐ›๋Š” `model_config` ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +<a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Pydantic ๋ฌธ์„œ: Configuration</a>์— ์„ค๋ช…๋œ ๊ฒƒ์ฒ˜๋Ÿผ `dict`๋ฅผ ๋ฐ›๋Š” `model_config` ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. `"json_schema_extra"`๋ฅผ ์ƒ์„ฑ๋œ JSON ์Šคํ‚ค๋งˆ์—์„œ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์€ ๋ณ„๋„์˜ ๋ฐ์ดํ„ฐ์™€ `examples`๋ฅผ ํฌํ•จํ•˜๋Š” `dict`์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -//// - -//// tab | Pydantic v1 - -Pydantic v1์—์„œ <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">Pydantic ๊ณต์‹ ๋ฌธ์„œ: Schema customization</a>์—์„œ ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, ๋‚ด๋ถ€ ํด๋ž˜์Šค์ธ `Config`์™€ `schema_extra`๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. - -`schema_extra`๋ฅผ ์ƒ์„ฑ๋œ JSON ์Šคํ‚ค๋งˆ์—์„œ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์€ ๋ณ„๋„์˜ ๋ฐ์ดํ„ฐ์™€ `examples`๋ฅผ ํฌํ•จํ•˜๋Š” `dict`์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -//// - /// tip | ํŒ JSON ์Šคํ‚ค๋งˆ๋ฅผ ํ™•์žฅํ•˜๊ณ  ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ณ„๋„์˜ ์ž์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ™์€ ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -52,19 +30,19 @@ JSON ์Šคํ‚ค๋งˆ๋ฅผ ํ™•์žฅํ•˜๊ณ  ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ณ„๋„์˜ ์ž์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ ๊ทธ ์ „์—๋Š”, ํ•˜๋‚˜์˜ ์˜ˆ์ œ๋งŒ ๊ฐ€๋Šฅํ•œ `example` ํ‚ค์›Œ๋“œ๋งŒ ์ง€์›ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์•„์ง OpenAPI 3.1.0์—์„œ ์ง€์›ํ•˜์ง€๋งŒ, ์ง€์›์ด ์ข…๋ฃŒ๋  ๊ฒƒ์ด๋ฉฐ JSON ์Šคํ‚ค๋งˆ ํ‘œ์ค€์— ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— `example`์„ `examples`์œผ๋กœ ์ด์ „ํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค. ๐Ÿค“ -์ด ๋ฌธ์„œ ๋์— ๋” ๋งŽ์€ ์ฝ์„๊ฑฐ๋ฆฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ํŽ˜์ด์ง€ ๋์—์„œ ๋” ๋งŽ์€ ๋‚ด์šฉ์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## `Field` ์ถ”๊ฐ€ ์ธ์ž +## `Field` ์ถ”๊ฐ€ ์ธ์ž { #field-additional-arguments } Pydantic ๋ชจ๋ธ๊ณผ ๊ฐ™์ด `Field()`๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ถ”๊ฐ€์ ์ธ `examples`๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: {* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} -## JSON Schema์—์„œ์˜ `examples` - OpenAPI +## JSON Schema์—์„œ์˜ `examples` - OpenAPI { #examples-in-json-schema-openapi } -์ด๋“ค ์ค‘์—์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: +๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ: * `Path()` * `Query()` @@ -74,45 +52,45 @@ Pydantic ๋ชจ๋ธ๊ณผ ๊ฐ™์ด `Field()`๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ถ”๊ฐ€์ ์ธ `examples`๋ฅผ * `Form()` * `File()` -**OpenAPI**์˜ **JSON ์Šคํ‚ค๋งˆ**์— ์ถ”๊ฐ€๋  ๋ถ€๊ฐ€์ ์ธ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ `examples` ๋ชจ์Œ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**OpenAPI** ์•ˆ์˜ **JSON ์Šคํ‚ค๋งˆ**์— ์ถ”๊ฐ€๋  ๋ถ€๊ฐ€์ ์ธ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ `examples` ๋ชจ์Œ์„ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -### `examples`๋ฅผ ํฌํ•จํ•œ `Body` +### `examples`๋ฅผ ํฌํ•จํ•œ `Body` { #body-with-examples } ์—ฌ๊ธฐ, `Body()`์— ์˜ˆ์ƒ๋˜๋Š” ์˜ˆ์ œ ๋ฐ์ดํ„ฐ ํ•˜๋‚˜๋ฅผ ํฌํ•จํ•œ `examples`๋ฅผ ๋„˜๊ฒผ์Šต๋‹ˆ๋‹ค: {* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} -### ๋ฌธ์„œ UI ์˜ˆ์‹œ +### ๋ฌธ์„œ UI ์˜ˆ์‹œ { #example-in-the-docs-ui } ์œ„์˜ ์–ด๋А ๋ฐฉ๋ฒ•๊ณผ ํ•จ๊ป˜๋ผ๋ฉด `/docs`์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค: <img src="/img/tutorial/body-fields/image01.png"> -### ๋‹ค์ค‘ `examples`๋ฅผ ํฌํ•จํ•œ `Body` +### ๋‹ค์ค‘ `examples`๋ฅผ ํฌํ•จํ•œ `Body` { #body-with-multiple-examples } ๋ฌผ๋ก  ์—ฌ๋Ÿฌ `examples`๋ฅผ ๋„˜๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: {* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} -์ด์™€ ๊ฐ™์ด ํ•˜๋ฉด ์ด ์˜ˆ์ œ๋Š” ๊ทธ ๋ณธ๋ฌธ ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ ๋‚ด๋ถ€ **JSON ์Šคํ‚ค๋งˆ**์˜ ์ผ๋ถ€๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ด์™€ ๊ฐ™์ด ํ•˜๋ฉด ์˜ˆ์ œ๋“ค์€ ๊ทธ ๋ณธ๋ฌธ ๋ฐ์ดํ„ฐ์˜ ๋‚ด๋ถ€ **JSON ์Šคํ‚ค๋งˆ**์˜ ์ผ๋ถ€๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ์ง€๊ธˆ <abbr title="2023-08-26">์ด ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์‹œ๊ฐ„</abbr>์—, ๋ฌธ์„œ UI๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์—ญํ• ์„ ๋งก์€ Swagger UI๋Š” **JSON ์Šคํ‚ค๋งˆ** ์† ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ ์—ฌ๋Ÿฌ ์˜ˆ์ œ์˜ ํ‘œํ˜„์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ์„ ๋ฐ‘์—์„œ ์ฝ์–ด๋ณด์„ธ์š”. -### OpenAPI-ํŠนํ™” `examples` +### OpenAPI-ํŠนํ™” `examples` { #openapi-specific-examples } -**JSON ์Šคํ‚ค๋งˆ**๊ฐ€ `examples`๋ฅผ ์ง€์›ํ•˜๊ธฐ ์ „ ๋ถ€ํ„ฐ, OpenAPI๋Š” `examples`์ด๋ผ ๋ถˆ๋ฆฌ๋Š” ๋‹ค๋ฅธ ํ•„๋“œ๋ฅผ ์ง€์›ํ•ด ์™”์Šต๋‹ˆ๋‹ค. +**JSON ์Šคํ‚ค๋งˆ**๊ฐ€ `examples`๋ฅผ ์ง€์›ํ•˜๊ธฐ ์ „๋ถ€ํ„ฐ OpenAPI๋Š” `examples`์ด๋ผ ๋ถˆ๋ฆฌ๋Š” ๋‹ค๋ฅธ ํ•„๋“œ๋ฅผ ์ง€์›ํ•ด ์™”์Šต๋‹ˆ๋‹ค. -์ด **OpenAPI-ํŠนํ™”** `examples`๋Š” OpenAPI ๋ช…์„ธ์„œ์˜ ๋‹ค๋ฅธ ๊ตฌ์—ญ์œผ๋กœ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค. ๊ฐ JSON ์Šคํ‚ค๋งˆ ๋‚ด๋ถ€๊ฐ€ ์•„๋‹ˆ๋ผ **๊ฐ *๊ฒฝ๋กœ ์ž‘๋™* ์„ธ๋ถ€ ์ •๋ณด**์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. +์ด **OpenAPI-ํŠนํ™”** `examples`๋Š” OpenAPI ๋ช…์„ธ์„œ์˜ ๋‹ค๋ฅธ ๊ตฌ์—ญ์œผ๋กœ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค. ๊ฐ JSON ์Šคํ‚ค๋งˆ ๋‚ด๋ถ€๊ฐ€ ์•„๋‹ˆ๋ผ **๊ฐ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์„ธ๋ถ€ ์ •๋ณด**์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Swagger UI๋Š” ์ด ํŠน์ •ํ•œ `examples` ํ•„๋“œ๋ฅผ ํ•œ๋™์•ˆ ์ง€์›ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ, ์ด๋ฅผ ๋‹ค๋ฅธ **๋ฌธ์„œ UI์— ์žˆ๋Š” ์˜ˆ์ œ**๋ฅผ **ํ‘œ์‹œ**ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด OpenAPI-ํŠนํ™” ํ•„๋“œ์ธ `examples`์˜ ํ˜•ํƒœ๋Š” (`list`๋Œ€์‹ ์—) **๋‹ค์ค‘ ์˜ˆ์ œ**๊ฐ€ ํฌํ•จ๋œ `dict`์ด๋ฉฐ, ๊ฐ๊ฐ์˜ ๋ณ„๋„ ์ •๋ณด ๋˜ํ•œ **OpenAPI**์— ์ถ”๊ฐ€๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ด OpenAPI-ํŠนํ™” ํ•„๋“œ์ธ `examples`์˜ ํ˜•ํƒœ๋Š” (`list` ๋Œ€์‹ ์—) **๋‹ค์ค‘ ์˜ˆ์ œ**๊ฐ€ ํฌํ•จ๋œ `dict`์ด๋ฉฐ, ๊ฐ๊ฐ์˜ ๋ณ„๋„ ์ •๋ณด ๋˜ํ•œ **OpenAPI**์— ์ถ”๊ฐ€๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -์ด๋Š” OpenAPI์— ํฌํ•จ๋œ JSON ์Šคํ‚ค๋งˆ ์•ˆ์œผ๋กœ ํฌํ•จ๋˜์ง€ ์•Š์œผ๋ฉฐ, *๊ฒฝ๋กœ ์ž‘๋™*์— ์ง์ ‘์ ์œผ๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. +์ด๋Š” OpenAPI์— ํฌํ•จ๋œ ๊ฐ JSON ์Šคํ‚ค๋งˆ ์•ˆ์œผ๋กœ ํฌํ•จ๋˜์ง€ ์•Š์œผ๋ฉฐ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ์ง์ ‘์ ์œผ๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. -### `openapi_examples` ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ +### `openapi_examples` ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ { #using-the-openapi-examples-parameter } -๋‹ค์Œ ์˜ˆ์‹œ ์†์— OpenAPI-ํŠนํ™” `examples`๋ฅผ FastAPI ์•ˆ์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜ `openapi_examples` ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ํ•จ๊ป˜ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋‹ค์Œ์— ๋Œ€ํ•ด FastAPI์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜ `openapi_examples`๋กœ OpenAPI-ํŠนํ™” `examples`๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: * `Path()` * `Query()` @@ -122,26 +100,26 @@ Pydantic ๋ชจ๋ธ๊ณผ ๊ฐ™์ด `Field()`๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ถ”๊ฐ€์ ์ธ `examples`๋ฅผ * `Form()` * `File()` -`dict`์˜ ํ‚ค๊ฐ€ ๋˜ ๋‹ค๋ฅธ `dict`์ธ ๊ฐ ์˜ˆ์ œ์™€ ๊ฐ’์„ ๊ตฌ๋ณ„ํ•ฉ๋‹ˆ๋‹ค. +`dict`์˜ ํ‚ค๋Š” ๊ฐ ์˜ˆ์ œ๋ฅผ ์‹๋ณ„ํ•˜๊ณ , ๊ฐ ๊ฐ’์€ ๋˜ ๋‹ค๋ฅธ `dict`์ž…๋‹ˆ๋‹ค. -๊ฐ๊ฐ์˜ ํŠน์ • `examples` ์† `dict` ์˜ˆ์ œ๋Š” ๋‹ค์Œ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +`examples` ์•ˆ์˜ ๊ฐ ํŠน์ • ์˜ˆ์ œ `dict`์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: * `summary`: ์˜ˆ์ œ์— ๋Œ€ํ•œ ์งง์€ ์„ค๋ช…๋ฌธ. * `description`: ๋งˆํฌ๋‹ค์šด ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ๊ธด ์„ค๋ช…๋ฌธ. * `value`: ์‹ค์ œ๋กœ ๋ณด์—ฌ์ง€๋Š” ์˜ˆ์‹œ, ์˜ˆ๋ฅผ ๋“ค๋ฉด `dict`. -* `externalValue`: `value`์˜ ๋Œ€์•ˆ์ด๋ฉฐ ์˜ˆ์ œ๋ฅผ ๊ฐ€๋ฅดํ‚ค๋Š” URL. ๋น„๋ก `value`์ฒ˜๋Ÿผ ๋งŽ์€ ๋„๊ตฌ๋ฅผ ์ง€์›ํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* `externalValue`: `value`์˜ ๋Œ€์•ˆ์ด๋ฉฐ ์˜ˆ์ œ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” URL. ๋น„๋ก `value`์ฒ˜๋Ÿผ ๋งŽ์€ ๋„๊ตฌ๋ฅผ ์ง€์›ํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: {* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} -### ๋ฌธ์„œ UI์—์„œ์˜ OpenAPI ์˜ˆ์‹œ +### ๋ฌธ์„œ UI์—์„œ์˜ OpenAPI ์˜ˆ์‹œ { #openapi-examples-in-the-docs-ui } -`Body()`์— ์ถ”๊ฐ€๋œ `openapi_examples`๋ฅผ ํฌํ•จํ•œ `/docs`๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค: +`Body()`์— `openapi_examples`๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด `/docs`๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค: <img src="/img/tutorial/body-fields/image02.png"> -## ๊ธฐ์ˆ ์  ์„ธ๋ถ€ ์‚ฌํ•ญ +## ๊ธฐ์ˆ ์  ์„ธ๋ถ€ ์‚ฌํ•ญ { #technical-details } /// tip | ํŒ @@ -167,12 +145,12 @@ JSON ์Šคํ‚ค๋งˆ๋Š” `examples`๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์•˜๊ณ , ๋”ฐ๋ผ์„œ OpenAPI๋Š” OpenAPI๋Š” ๋˜ํ•œ `example`๊ณผ `examples` ํ•„๋“œ๋ฅผ ๋ช…์„ธ์„œ์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค: -* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`(๋ช…์„ธ์„œ์— ์žˆ๋Š”) Parameter Object`</a>๋Š” FastAPI์˜ ๋‹ค์Œ ๊ธฐ๋Šฅ์—์„œ ์“ฐ์˜€์Šต๋‹ˆ๋‹ค: +* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`Parameter Object` (๋ช…์„ธ์„œ์— ์žˆ๋Š”)</a>๋Š” FastAPI์˜ ๋‹ค์Œ ๊ธฐ๋Šฅ์—์„œ ์“ฐ์˜€์Šต๋‹ˆ๋‹ค: * `Path()` * `Query()` * `Header()` * `Cookie()` -* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">(๋ช…์„ธ์„œ์— ์žˆ๋Š”)`Media Type Object`์† `content`์— ์žˆ๋Š” `Request Body Object`</a>๋Š” FastAPI์˜ ๋‹ค์Œ ๊ธฐ๋Šฅ์—์„œ ์“ฐ์˜€์Šต๋‹ˆ๋‹ค: +* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">`Request Body Object`, `Media Type Object` (๋ช…์„ธ์„œ์— ์žˆ๋Š”)์˜ `content` ํ•„๋“œ์— ์žˆ๋Š”</a>๋Š” FastAPI์˜ ๋‹ค์Œ ๊ธฐ๋Šฅ์—์„œ ์“ฐ์˜€์Šต๋‹ˆ๋‹ค: * `Body()` * `File()` * `Form()` @@ -183,15 +161,15 @@ OpenAPI๋Š” ๋˜ํ•œ `example`๊ณผ `examples` ํ•„๋“œ๋ฅผ ๋ช…์„ธ์„œ์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„ /// -### JSON ์Šคํ‚ค๋งˆ์˜ `examples` ํ•„๋“œ +### JSON ์Šคํ‚ค๋งˆ์˜ `examples` ํ•„๋“œ { #json-schemas-examples-field } -ํ•˜์ง€๋งŒ, ํ›„์— JSON ์Šคํ‚ค๋งˆ๋Š” <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>ํ•„๋“œ๋ฅผ ๋ช…์„ธ์„œ์˜ ์ƒˆ ๋ฒ„์ „์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ, ํ›„์— JSON ์Šคํ‚ค๋งˆ๋Š” <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a> ํ•„๋“œ๋ฅผ ๋ช…์„ธ์„œ์˜ ์ƒˆ ๋ฒ„์ „์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ƒˆ๋กœ์šด OpenAPI 3.1.0์€ ์ด ์ƒˆ๋กœ์šด `examples` ํ•„๋“œ๊ฐ€ ํฌํ•จ๋œ ์ตœ์‹  ๋ฒ„์ „ (JSON ์Šคํ‚ค๋งˆ 2020-12)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. -์ด์ œ ์ƒˆ๋กœ์šด `examples` ํ•„๋“œ๋Š” ์ด์ „์˜ ๋‹จ์ผ (๊ทธ๋ฆฌ๊ณ  ์ปค์Šคํ…€) `example` ํ•„๋“œ๋ณด๋‹ค ์šฐ์„ ๋˜๋ฉฐ, `example`์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์ด์ œ ์ด ์ƒˆ๋กœ์šด `examples` ํ•„๋“œ๋Š” ์ด์ œ ์ง€์› ์ค‘๋‹จ๋œ, ์˜ˆ์ „์˜ ๋‹จ์ผ (๊ทธ๋ฆฌ๊ณ  ์ปค์Šคํ…€) `example` ํ•„๋“œ๋ณด๋‹ค ์šฐ์„ ๋ฉ๋‹ˆ๋‹ค. -JSON ์Šคํ‚ค๋งˆ์˜ ์ƒˆ๋กœ์šด `examples` ํ•„๋“œ๋Š” ์˜ˆ์ œ ์† **๋‹จ์ˆœํ•œ `list`**์ด๋ฉฐ, (์œ„์—์„œ ์ƒ์ˆ ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ) OpenAPI์˜ ๋‹ค๋ฅธ ๊ณณ์— ์กด์žฌํ•˜๋Š” dict์œผ๋กœ ๋œ ์ถ”๊ฐ€์ ์ธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. +JSON ์Šคํ‚ค๋งˆ์˜ ์ƒˆ๋กœ์šด `examples` ํ•„๋“œ๋Š” ์˜ˆ์ œ์˜ **๋‹จ์ˆœํ•œ `list`**์ผ ๋ฟ์ด๋ฉฐ, (์œ„์—์„œ ์ƒ์ˆ ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ) OpenAPI์˜ ๋‹ค๋ฅธ ๊ณณ์— ์กด์žฌํ•˜๋Š” ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” dict๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. /// info | ์ •๋ณด @@ -201,24 +179,24 @@ JSON ์Šคํ‚ค๋งˆ์˜ ์ƒˆ๋กœ์šด `examples` ํ•„๋“œ๋Š” ์˜ˆ์ œ ์† **๋‹จ์ˆœํ•œ `list`** /// -### Pydantic๊ณผ FastAPI `examples` +### Pydantic๊ณผ FastAPI `examples` { #pydantic-and-fastapi-examples } -`examples`๋ฅผ Pydantic ๋ชจ๋ธ ์†์— ์ถ”๊ฐ€ํ•  ๋•Œ, `schema_extra` ํ˜น์€ `Field(examples=["something"])`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Pydantic ๋ชจ๋ธ์˜ **JSON ์Šคํ‚ค๋งˆ**์— ํ•ด๋‹น ์˜ˆ์‹œ๊ฐ€ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. +Pydantic ๋ชจ๋ธ ์•ˆ์— `examples`๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ, `schema_extra` ๋˜๋Š” `Field(examples=["something"])`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ทธ ์˜ˆ์ œ๋Š” ํ•ด๋‹น Pydantic ๋ชจ๋ธ์˜ **JSON ์Šคํ‚ค๋งˆ**์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Pydantic ๋ชจ๋ธ์˜ **JSON ์Šคํ‚ค๋งˆ**๋Š” API์˜ **OpenAPI**์— ํฌํ•จ๋˜๊ณ , ๊ทธ ํ›„ ๋ฌธ์„œ UI ์†์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -FastAPI 0.99.0 ์ด์ „ ๋ฒ„์ „์—์„œ (0.99.0 ์ด์ƒ ๋ฒ„์ „์€ ์ƒˆ๋กœ์šด OpenAPI 3.1.0์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค), `example` ํ˜น์€ `examples`๋ฅผ ๋‹ค๋ฅธ ์œ ํ‹ธ๋ฆฌํ‹ฐ(`Query()`, `Body()` ๋“ฑ)์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ, ์ €๋Ÿฌํ•œ ์˜ˆ์‹œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์„ค๋ช…ํ•˜๋Š” JSON ์Šคํ‚ค๋งˆ์— ์ถ”๊ฐ€๋˜์ง€ ์•Š์œผ๋ฉฐ (์‹ฌ์ง€์–ด OpenAPI์˜ ์ž์ฒด JSON ์Šคํ‚ค๋งˆ์—๋„ ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค), OpenAPI์˜ *๊ฒฝ๋กœ ์ž‘๋™* ์„ ์–ธ์— ์ง์ ‘์ ์œผ๋กœ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค (JSON ์Šคํ‚ค๋งˆ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” OpenAPI ๋ถ€๋ถ„ ์™ธ์—๋„). +FastAPI 0.99.0 ์ด์ „ ๋ฒ„์ „์—์„œ (0.99.0 ์ด์ƒ ๋ฒ„์ „์€ ์ƒˆ๋กœ์šด OpenAPI 3.1.0์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค), ๋‹ค๋ฅธ ์œ ํ‹ธ๋ฆฌํ‹ฐ(`Query()`, `Body()` ๋“ฑ)์™€ ํ•จ๊ป˜ `example` ๋˜๋Š” `examples`๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ, ๊ทธ๋Ÿฌํ•œ ์˜ˆ์ œ๋Š” ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค๋ช…ํ•˜๋Š” JSON ์Šคํ‚ค๋งˆ์— ์ถ”๊ฐ€๋˜์ง€ ์•Š๊ณ  (OpenAPI ์ž์ฒด์˜ JSON ์Šคํ‚ค๋งˆ์—๋„ ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค), OpenAPI์˜ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์„ ์–ธ์— ์ง์ ‘์ ์œผ๋กœ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค (JSON ์Šคํ‚ค๋งˆ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” OpenAPI ๋ถ€๋ถ„ ๋ฐ–์—์„œ). -ํ•˜์ง€๋งŒ ์ง€๊ธˆ์€ FastAPI 0.99.0 ๋ฐ ์ดํ›„ ๋ฒ„์ „์—์„œ๋Š” JSON ์Šคํ‚ค๋งˆ 2020-12๋ฅผ ์‚ฌ์šฉํ•˜๋Š” OpenAPI 3.1.0๊ณผ Swagger UI 5.0.0 ๋ฐ ์ดํ›„ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋ชจ๋“  ๊ฒƒ์ด ๋” ์ผ๊ด€์„ฑ์„ ๋„๊ณ  ์˜ˆ์‹œ๋Š” JSON ์Šคํ‚ค๋งˆ์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์ด์ œ FastAPI 0.99.0 ๋ฐ ์ดํ›„ ๋ฒ„์ „์—์„œ๋Š” JSON ์Šคํ‚ค๋งˆ 2020-12๋ฅผ ์‚ฌ์šฉํ•˜๋Š” OpenAPI 3.1.0๊ณผ Swagger UI 5.0.0 ๋ฐ ์ดํ›„ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ชจ๋“  ๊ฒƒ์ด ๋” ์ผ๊ด€์„ฑ์„ ๋„๊ณ  ์˜ˆ์ œ๋„ JSON ์Šคํ‚ค๋งˆ์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. -### Swagger UI์™€ OpenAPI-ํŠนํ™” `examples` +### Swagger UI์™€ OpenAPI-ํŠนํ™” `examples` { #swagger-ui-and-openapi-specific-examples } -ํ˜„์žฌ (2023-08-26), Swagger UI๊ฐ€ ๋‹ค์ค‘ JSON ์Šคํ‚ค๋งˆ ์˜ˆ์‹œ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์‚ฌ์šฉ์ž๋Š” ๋‹ค์ค‘ ์˜ˆ์‹œ๋ฅผ ๋ฌธ์„œ์— ํ‘œ์‹œํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์—†์—ˆ์Šต๋‹ˆ๋‹ค. +Swagger UI๋Š” ๋‹ค์ค‘ JSON ์Šคํ‚ค๋งˆ ์˜ˆ์ œ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์—(2023-08-26 ๊ธฐ์ค€), ์‚ฌ์šฉ์ž๋Š” ๋ฌธ์„œ์— ์—ฌ๋Ÿฌ ์˜ˆ์ œ๋ฅผ ํ‘œ์‹œํ•  ๋ฐฉ๋ฒ•์ด ์—†์—ˆ์Šต๋‹ˆ๋‹ค. -์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, FastAPI `0.103.0`์€ ์ƒˆ๋กœ์šด ๋งค๊ฐœ๋ณ€์ˆ˜์ธ `openapi_examples`๋ฅผ ํฌํ•จํ•˜๋Š” ์˜ˆ์ „ **OpenAPI-ํŠนํ™”** `examples` ํ•„๋“œ๋ฅผ ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•œ **์ง€์›์„ ์ถ”๊ฐ€**ํ–ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ +์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, FastAPI `0.103.0`์€ ์ƒˆ๋กœ์šด ๋งค๊ฐœ๋ณ€์ˆ˜์ธ `openapi_examples`๋กœ ๋™์ผํ•œ ์˜ˆ์ „ **OpenAPI-ํŠนํ™”** `examples` ํ•„๋“œ๋ฅผ ์„ ์–ธํ•˜๋Š” **์ง€์›**์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ -### ์š”์•ฝ +### ์š”์•ฝ { #summary } -์ €๋Š” ์—ญ์‚ฌ๋ฅผ ๊ทธ๋‹ค์ง€ ์ข‹์•„ํ•˜๋Š” ํŽธ์ด ์•„๋‹ˆ๋ผ๊ณ  ๋งํ•˜๊ณ ๋Š” ํ–ˆ์ง€๋งŒ... "๊ธฐ์ˆ  ์—ญ์‚ฌ" ๊ฐ•์˜๋ฅผ ๊ฐ€๋ฅด์น˜๋Š” ์ง€๊ธˆ์˜ ์ €๋ฅผ ๋ณด์„ธ์š”. +์ €๋Š” ์—ญ์‚ฌ๋ฅผ ๊ทธ๋‹ค์ง€ ์ข‹์•„ํ•˜๋Š” ํŽธ์ด ์•„๋‹ˆ๋ผ๊ณ  ๋งํ•˜๊ณ ๋Š” ํ–ˆ์ง€๋งŒ... "๊ธฐ์ˆ  ์—ญ์‚ฌ" ๊ฐ•์˜๋ฅผ ํ•˜๋Š” ์ง€๊ธˆ์˜ ์ €๋ฅผ ๋ณด์„ธ์š”. ๐Ÿ˜… -์š”์•ฝํ•˜์ž๋ฉด **FastAPI 0.99.0 ํ˜น์€ ๊ทธ ์ด์ƒ์˜ ๋ฒ„์ „**์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋Š” ๊ฒƒ์€ ๋งŽ์€ ๊ฒƒ๋“ค์ด ๋” **์‰ฝ๊ณ , ์ผ๊ด€์ ์ด๋ฉฐ ์ง๊ด€์ ์ด๊ฒŒ** ๋˜๋ฉฐ, ์—ฌ๋Ÿฌ๋ถ„์€ ์ด ๋ชจ๋“  ์—ญ์‚ฌ์  ์„ธ๋ถ€ ์‚ฌํ•ญ์„ ์•Œ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž +์š”์•ฝํ•˜์ž๋ฉด **FastAPI 0.99.0 ํ˜น์€ ๊ทธ ์ด์ƒ์˜ ๋ฒ„์ „**์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋ฉด, ๋งŽ์€ ๊ฒƒ๋“ค์ด ํ›จ์”ฌ ๋” **๋‹จ์ˆœํ•˜๊ณ , ์ผ๊ด€์ ์ด๋ฉฐ ์ง๊ด€์ **์ด ๋˜๋ฉฐ, ์—ฌ๋Ÿฌ๋ถ„์€ ์ด ๋ชจ๋“  ์—ญ์‚ฌ์  ์„ธ๋ถ€ ์‚ฌํ•ญ์„ ์•Œ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž diff --git a/docs/ko/docs/tutorial/security/get-current-user.md b/docs/ko/docs/tutorial/security/get-current-user.md index 98ef3885e4..f21a22b7a0 100644 --- a/docs/ko/docs/tutorial/security/get-current-user.md +++ b/docs/ko/docs/tutorial/security/get-current-user.md @@ -1,105 +1,105 @@ -# ํ˜„์žฌ ์‚ฌ์šฉ์ž ๊ฐ€์ ธ์˜ค๊ธฐ +# ํ˜„์žฌ ์‚ฌ์šฉ์ž ๊ฐ€์ ธ์˜ค๊ธฐ { #get-current-user } -์ด์ „ ์žฅ์—์„œ (์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ)๋ณด์•ˆ ์‹œ์Šคํ…œ์€ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์—์„œ `str`๋กœ `token`์„ ์ œ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค: +์ด์ „ ์žฅ์—์„œ (์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ) ๋ณด์•ˆ ์‹œ์Šคํ…œ์€ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์— `str`๋กœ `token`์„ ์ œ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/security/tutorial001.py hl[10] *} +{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} -๊ทธ๋Ÿฌ๋‚˜ ์•„์ง๋„ ์œ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์ด๋Š” ์—ฌ์ „ํžˆ ๊ทธ๋‹ค์ง€ ์œ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ์ œ๊ณตํ•˜๋„๋ก ํ•ฉ์‹œ๋‹ค. +ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ์ œ๊ณตํ•˜๋„๋ก ํ•ด๋ด…์‹œ๋‹ค. -## ์œ ์ € ๋ชจ๋ธ ์ƒ์„ฑํ•˜๊ธฐ +## ์‚ฌ์šฉ์ž ๋ชจ๋ธ ์ƒ์„ฑํ•˜๊ธฐ { #create-a-user-model } -๋จผ์ € Pydantic ์œ ์ € ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. +๋จผ์ € Pydantic ์‚ฌ์šฉ์ž ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค. -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`๋ฅผ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค. -์˜์กด์„ฑ์ด ํ•˜์œ„ ์˜์กด์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•˜์‹ญ๋‹ˆ๊นŒ? +์˜์กด์„ฑ์ด ํ•˜์œ„ ์˜์กด์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•˜์‹œ๋‚˜์š”? -`get_current_user`๋Š” ์ด์ „์— ์ƒ์„ฑํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ `oauth2_scheme`๊ณผ ์ข…์†์„ฑ์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +`get_current_user`๋Š” ์ด์ „์— ์ƒ์„ฑํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ `oauth2_scheme`์— ๋Œ€ํ•œ ์˜์กด์„ฑ์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -์ด์ „์— *๊ฒฝ๋กœ ์ž‘๋™*์—์„œ ์ง์ ‘ ์ˆ˜ํ–‰ํ–ˆ๋˜ ๊ฒƒ๊ณผ ๋™์ผํ•˜๊ฒŒ ์ƒˆ ์ข…์†์„ฑ `get_current_user`๋Š” ํ•˜์œ„ ์ข…์†์„ฑ `oauth2_scheme`์—์„œ `str`๋กœ `token`์„ ์ˆ˜์‹ ํ•ฉ๋‹ˆ๋‹ค. +์ด์ „์— *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ ์ง์ ‘ ์ˆ˜ํ–‰ํ–ˆ๋˜ ๊ฒƒ๊ณผ ๋™์ผํ•˜๊ฒŒ, ์ƒˆ ์˜์กด์„ฑ `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` ๋ชจ๋ธ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  (๊ฐ€์งœ) ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +`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 } -์ด์ œ *๊ฒฝ๋กœ ์ž‘๋™*์—์„œ `get_current_user`์™€ ๋™์ผํ•œ `Depends`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด์ œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ `get_current_user`์™€ ํ•จ๊ป˜ ๊ฐ™์€ `Depends`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/security/tutorial002.py hl[31] *} +{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *} -Pydantic ๋ชจ๋ธ์ธ `User`๋กœ `current_user`์˜ ํƒ€์ž…์„ ์„ ์–ธํ•˜๋Š” ๊ฒƒ์„ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. +`current_user`์˜ ํƒ€์ž…์„ Pydantic ๋ชจ๋ธ `User`๋กœ ์„ ์–ธํ•œ๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. -์ด๊ฒƒ์€ ๋ชจ๋“  ์™„๋ฃŒ ๋ฐ ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ํ†ตํ•ด ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ์šฐ๋ฆฌ๋ฅผ ๋„์šธ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ด๋Š” ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ์ž๋™ ์™„์„ฑ๊ณผ ํƒ€์ž… ์ฒดํฌ์— ๋„์›€์„ ์ค๋‹ˆ๋‹ค. /// tip | ํŒ -์š”์ฒญ ๋ณธ๋ฌธ๋„ Pydantic ๋ชจ๋ธ๋กœ ์„ ์–ธ๋œ๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์š”์ฒญ ๋ณธ๋ฌธ๋„ Pydantic ๋ชจ๋ธ๋กœ ์„ ์–ธ๋œ๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•˜์‹ค์ง€๋„ ๋ชจ๋ฆ…๋‹ˆ๋‹ค. -์—ฌ๊ธฐ์„œ **FastAPI**๋Š” `Depends`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ˜ผ๋™๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์„œ **FastAPI**๋Š” `Depends`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ˜ผ๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. /// /// check | ํ™•์ธ -์ด ์˜์กด์„ฑ ์‹œ์Šคํ…œ์ด ์„ค๊ณ„๋œ ๋ฐฉ์‹์€ ๋ชจ๋‘ `User` ๋ชจ๋ธ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋‹ค์–‘ํ•œ ์˜์กด์„ฑ(๋‹ค๋ฅธ "์˜์กด์ ์ธ")์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. +์ด ์˜์กด์„ฑ ์‹œ์Šคํ…œ์ด ์„ค๊ณ„๋œ ๋ฐฉ์‹์€ ๋ชจ๋‘ `User` ๋ชจ๋ธ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์˜์กด์„ฑ(์„œ๋กœ ๋‹ค๋ฅธ "dependables")์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. -ํ•ด๋‹น ํƒ€์ž…์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ์˜์กด์„ฑ์ด ํ•˜๋‚˜๋งŒ ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ์ œํ•œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +ํ•ด๋‹น ํƒ€์ž…์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ์˜์กด์„ฑ์ด ํ•˜๋‚˜๋งŒ ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ œํ•œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. /// -## ๋‹ค๋ฅธ ๋ชจ๋ธ +## ๋‹ค๋ฅธ ๋ชจ๋ธ { #other-models } -์ด์ œ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์—์„œ ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ์ง์ ‘ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์œผ๋ฉฐ `Depends`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ **์˜์กด์„ฑ ์ฃผ์ž…** ์ˆ˜์ค€์—์„œ ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด์ œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ์ง์ ‘ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์œผ๋ฉฐ, `Depends`๋ฅผ ์‚ฌ์šฉํ•ด **์˜์กด์„ฑ ์ฃผ์ž…** ์ˆ˜์ค€์—์„œ ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ๋ณด์•ˆ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋Œ€ํ•œ ๋ชจ๋“  ๋ชจ๋ธ ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ด ๊ฒฝ์šฐ Pydantic ๋ชจ๋ธ `User`). +๊ทธ๋ฆฌ๊ณ  ๋ณด์•ˆ ์š”๊ตฌ ์‚ฌํ•ญ์„ ์œ„ํ•ด ์–ด๋–ค ๋ชจ๋ธ์ด๋‚˜ ๋ฐ์ดํ„ฐ๋“  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ด ๊ฒฝ์šฐ Pydantic ๋ชจ๋ธ `User`). -๊ทธ๋Ÿฌ๋‚˜ ์ผ๋ถ€ ํŠน์ • ๋ฐ์ดํ„ฐ ๋ชจ๋ธ, ํด๋ž˜์Šค ๋˜๋Š” ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋„๋ก ์ œํ•œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ํŠน์ • ๋ฐ์ดํ„ฐ ๋ชจ๋ธ, ํด๋ž˜์Šค ๋˜๋Š” ํƒ€์ž…๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. -๋ชจ๋ธ์— `id`์™€ `email`์ด ์žˆ๊ณ  `username`์ด ์—†๊ธธ ์›ํ•˜์‹ญ๋‹ˆ๊นŒ? ๋งž์Šต๋‹ˆ๋‹ค. ์ด๋“ค์€ ๋™์ผํ•œ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ชจ๋ธ์— `id`์™€ `email`์ด ์žˆ๊ณ  `username`์€ ์—†๊ฒŒ ํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? ๋ฌผ๋ก ์ž…๋‹ˆ๋‹ค. ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -`str`๋งŒ ๊ฐ–๊ณ  ์‹ถ์Šต๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด ๊ทธ๋ƒฅ `dict`๋ฅผ ๊ฐ–๊ณ  ์‹ถ์Šต๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํด๋ž˜์Šค ๋ชจ๋ธ ์ธ์Šคํ„ด์Šค๋ฅผ ์ง์ ‘ ๊ฐ–๊ณ  ์‹ถ์Šต๋‹ˆ๊นŒ? ๊ทธ๋“ค์€ ๋ชจ๋‘ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. +`str`๋งŒ ๊ฐ–๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? ์•„๋‹ˆ๋ฉด `dict`๋งŒ์š”? ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํด๋ž˜์Šค ๋ชจ๋ธ ์ธ์Šคํ„ด์Šค๋ฅผ ์ง์ ‘ ์“ฐ๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? ๋ชจ๋‘ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. -์‹ค์ œ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋กœ๊ทธ์ธํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—†์ง€๋งŒ ์•ก์„ธ์Šค ํ† ํฐ๋งŒ ์žˆ๋Š” ๋กœ๋ด‡, ๋ด‡ ๋˜๋Š” ๊ธฐํƒ€ ์‹œ์Šคํ…œ์ด ์žˆ์Šต๋‹ˆ๊นŒ? ๋‹ค์‹œ ๋งํ•˜์ง€๋งŒ ๋ชจ๋‘ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋กœ๊ทธ์ธํ•˜๋Š” ์‚ฌ์šฉ์ž๋Š” ์—†๊ณ , ์•ก์„ธ์Šค ํ† ํฐ๋งŒ ๊ฐ€์ง„ ๋กœ๋ด‡, ๋ด‡ ๋˜๋Š” ๋‹ค๋ฅธ ์‹œ์Šคํ…œ๋งŒ ์žˆ๋‚˜์š”? ์ด๊ฒƒ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ชจ๋‘ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. -์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํ•„์š”ํ•œ ๋ชจ๋“  ์ข…๋ฅ˜์˜ ๋ชจ๋ธ, ๋ชจ๋“  ์ข…๋ฅ˜์˜ ํด๋ž˜์Šค, ๋ชจ๋“  ์ข…๋ฅ˜์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. **FastAPI**๋Š” ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์„ ๋‹ค๋ฃจ์—ˆ์Šต๋‹ˆ๋‹ค. +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํ•„์š”ํ•œ ์–ด๋–ค ์ข…๋ฅ˜์˜ ๋ชจ๋ธ, ์–ด๋–ค ์ข…๋ฅ˜์˜ ํด๋ž˜์Šค, ์–ด๋–ค ์ข…๋ฅ˜์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋“  ์‚ฌ์šฉํ•˜์„ธ์š”. **FastAPI**๋Š” ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์œผ๋กœ ์ด๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. -## ์ฝ”๋“œ ์‚ฌ์ด์ฆˆ +## ์ฝ”๋“œ ํฌ๊ธฐ { #code-size } -์ด ์˜ˆ๋Š” ์žฅํ™ฉํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋™์ผํ•œ ํŒŒ์ผ์—์„œ ๋ณด์•ˆ, ๋ฐ์ดํ„ฐ ๋ชจ๋ธ, ์œ ํ‹ธ๋ฆฌํ‹ฐ ๊ธฐ๋Šฅ ๋ฐ *๊ฒฝ๋กœ ์ž‘๋™*์„ ํ˜ผํ•ฉํ•˜๊ณ  ์žˆ์Œ์„ ์—ผ๋‘์— ๋‘์‹ญ์‹œ์˜ค. +์ด ์˜ˆ์‹œ๋Š” ์žฅํ™ฉํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋™์ผํ•œ ํŒŒ์ผ์—์„œ ๋ณด์•ˆ, ๋ฐ์ดํ„ฐ ๋ชจ๋ธ, ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ๋ฐ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์„ž์–ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. -๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒŒ ํ‚คํฌ์ธํŠธ์ž…๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ ํ•ต์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค. -๋ณด์•ˆ๊ณผ ์ข…์†์„ฑ ์ฃผ์ž… ํ•ญ๋ชฉ์„ ํ•œ ๋ฒˆ๋งŒ ์ž‘์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. +๋ณด์•ˆ๊ณผ ์˜์กด์„ฑ ์ฃผ์ž… ๊ด€๋ จ ์ฝ”๋“œ๋Š” ํ•œ ๋ฒˆ๋งŒ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ์›ํ•˜๋Š” ๋งŒํผ ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜๋„ ์œ ์—ฐ์„ฑ๊ณผ ํ•จ๊ป˜ ํ•œ ๊ณณ์— ํ•œ ๋ฒˆ์— ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์›ํ•˜๋Š” ๋งŒํผ ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ์—๋„ ์—ฌ์ „ํžˆ ํ•œ ๋ฒˆ๋งŒ, ํ•œ ๊ณณ์—๋งŒ ์ž‘์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์œ ์—ฐ์„ฑ์„ ๋ชจ๋‘ ์œ ์ง€ํ•˜๋ฉด์„œ์š”. -๊ทธ๋Ÿฌ๋‚˜ ๋™์ผํ•œ ๋ณด์•ˆ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ˆ˜์ฒœ ๊ฐœ์˜ ์—”๋“œํฌ์ธํŠธ(*๊ฒฝ๋กœ ์ž‘๋™*)๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๊ฐ™์€ ๋ณด์•ˆ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•ด ์ˆ˜์ฒœ ๊ฐœ์˜ ์—”๋“œํฌ์ธํŠธ(*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*)๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ๊ทธ๋“ค ๋ชจ๋‘(๋˜๋Š” ์›ํ•˜๋Š” ๋ถ€๋ถ„)๋Š” ์ด๋Ÿฌํ•œ ์˜์กด์„ฑ ๋˜๋Š” ์ƒ์„ฑํ•œ ๋‹ค๋ฅธ ์˜์กด์„ฑ์„ ์žฌ์‚ฌ์šฉํ•˜๋Š” ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๊ทธ๋“ค ๋ชจ๋‘(๋˜๋Š” ์›ํ•˜๋Š” ์ผ๋ถ€)๋Š” ์ด๋Ÿฌํ•œ ์˜์กด์„ฑ ๋˜๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ์ƒ์„ฑํ•œ ๋‹ค๋ฅธ ์˜์กด์„ฑ์„ ์žฌ์‚ฌ์šฉํ•˜๋Š” ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ์ด ์ˆ˜์ฒœ ๊ฐœ์˜ *๊ฒฝ๋กœ ์ž‘๋™*์€ ๋ชจ๋‘ 3์ค„ ์ •๋„๋กœ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์ด ์ˆ˜์ฒœ ๊ฐœ์˜ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋Š” 3์ค„ ์ •๋„๋กœ๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/security/tutorial002.py hl[30:32] *} +{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *} -## ์š”์•ฝ +## ์š”์•ฝ { #recap } -์ด์ œ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์—์„œ ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ์ง์ ‘ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด์ œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ์ง์ ‘ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ์ด๋“ค ์‚ฌ์ด์— ์žˆ์Šต๋‹ˆ๋‹ค. +์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ์ ˆ๋ฐ˜์€ ์™”์Šต๋‹ˆ๋‹ค. -์‚ฌ์šฉ์ž/ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‹ค์ œ๋กœ `username`๊ณผ `password`๋ฅผ ๋ณด๋‚ด๋ ค๋ฉด *๊ฒฝ๋กœ ์ž‘๋™*์„ ์ถ”๊ฐ€ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. +์‚ฌ์šฉ์ž/ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‹ค์ œ๋กœ `username`๊ณผ `password`๋ฅผ ๋ณด๋‚ด๋„๋ก ํ•˜๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. -๋‹ค์Œ ์žฅ์„ ํ™•์ธํ•ด ๋ด…์‹œ๋‹ค. +๋‹ค์Œ์— ์ด์–ด์ง‘๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/security/oauth2-jwt.md b/docs/ko/docs/tutorial/security/oauth2-jwt.md index 8d27856e84..907795ca4b 100644 --- a/docs/ko/docs/tutorial/security/oauth2-jwt.md +++ b/docs/ko/docs/tutorial/security/oauth2-jwt.md @@ -1,36 +1,36 @@ -# ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ์„ ์ด์šฉํ•œ OAuth2, JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” Bearer ์ธ์ฆ +# ํŒจ์Šค์›Œ๋“œ(ํ•ด์‹ฑ ํฌํ•จ)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” OAuth2, JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” Bearer { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } -๋ชจ๋“  ๋ณด์•ˆ ํ๋ฆ„์„ ๊ตฌ์„ฑํ–ˆ์œผ๋ฏ€๋กœ, ์ด์ œ <abbr title="JSON Web Tokens">JWT</abbr> ํ† ํฐ๊ณผ ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ์„ ์‚ฌ์šฉํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๋ชจ๋“  ๋ณด์•ˆ ํ๋ฆ„์„ ๊ตฌ์„ฑํ–ˆ์œผ๋ฏ€๋กœ, ์ด์ œ <abbr title="JSON Web Tokens">JWT</abbr> ํ† ํฐ๊ณผ ์•ˆ์ „ํ•œ ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ์„ ์‚ฌ์šฉํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹ค์ œ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ค๊ฒ ์Šต๋‹ˆ๋‹ค. -์ด ์ฝ”๋“œ๋Š” ์‹ค์ œ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ํ•ด์‹ฑํ•˜์—ฌ DB์— ์ €์žฅํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ์ฝ”๋“œ๋Š” ์‹ค์ œ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํŒจ์Šค์›Œ๋“œ ํ•ด์‹œ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด์ „ ์žฅ์— ์ด์–ด์„œ ์‹œ์ž‘ํ•ด ๋ด…์‹œ๋‹ค. +์ด์ „ ์žฅ์—์„œ ๋ฉˆ์ถ˜ ์ง€์ ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด ๋‚ด์šฉ์„ ํ™•์žฅํ•ด ๋‚˜๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค. -## JWT +## JWT ์•Œ์•„๋ณด๊ธฐ { #about-jwt } -JWT ๋Š” "JSON Web Tokens" ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +JWT๋Š” "JSON Web Tokens"๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -JSON ๊ฐ์ฒด๋ฅผ ๊ณต๋ฐฑ์ด ์—†๋Š” ๊ธด ๋ฌธ์ž์—ด๋กœ ์ธ์ฝ”๋”ฉํ•˜๋Š” ํ‘œ์ค€์ด๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค: +JSON ๊ฐ์ฒด๋ฅผ ๊ณต๋ฐฑ์ด ์—†๋Š” ๊ธธ๊ณ  ๋ฐ€์ง‘๋œ ๋ฌธ์ž์—ด๋กœ ๋ถ€ํ˜ธํ™”ํ•˜๋Š” ํ‘œ์ค€์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ``` -JWT๋Š” ์•”ํ˜ธํ™”๋˜์ง€ ์•Š์•„ ๋ˆ„๊ตฌ๋“ ์ง€ ํ† ํฐ์—์„œ ์ •๋ณด๋ฅผ ๋ณต์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์•”ํ˜ธํ™”๋œ ๊ฒƒ์ด ์•„๋‹ˆ๋ฏ€๋กœ, ๋ˆ„๊ตฌ๋‚˜ ๋‚ด์šฉ์—์„œ ์ •๋ณด๋ฅผ ๋ณต์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ JWT๋Š” ์„œ๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ž์‹ ์ด ๋ฐœ๊ธ‰ํ•œ ํ† ํฐ์„ ๋ฐ›์•˜์„ ๋•Œ, ์‹ค์ œ๋กœ ์ž์‹ ์ด ๋ฐœ๊ธ‰ํ•œ๊ฒŒ ๋งž๋Š”์ง€ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์„œ๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ž์‹ ์ด ๋ฐœ๊ธ‰ํ•œ ํ† ํฐ์„ ๋ฐ›์•˜์„ ๋•Œ, ์‹ค์ œ๋กœ ์ž์‹ ์ด ๋ฐœ๊ธ‰ํ•œ ๊ฒƒ์ด ๋งž๋Š”์ง€ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์ด ์ผ์ฃผ์ผ์ธ ํ† ํฐ์„ ๋ฐœํ–‰ํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. ๋‹ค์Œ ๋‚  ์‚ฌ์šฉ์ž๊ฐ€ ํ† ํฐ์„ ๊ฐ€์ ธ์™”์„ ๋•Œ, ๊ทธ ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์Šคํ…œ์— ์—ฌ์ „ํžˆ ๋กœ๊ทธ์ธ๋˜์–ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์ด 1์ฃผ์ผ์ธ ํ† ํฐ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์Œ ๋‚  ํ† ํฐ์„ ๊ฐ€์ง€๊ณ  ๋Œ์•„์˜ค๋ฉด, ๊ทธ ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์Šคํ…œ์— ์—ฌ์ „ํžˆ ๋กœ๊ทธ์ธ๋˜์–ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ผ์ฃผ์ผ ๋’ค์—๋Š” ํ† ํฐ์ด ๋งŒ๋ฃŒ๋  ๊ฒƒ์ด๊ณ , ์‚ฌ์šฉ์ž๋Š” ์ธ๊ฐ€๋˜์ง€ ์•Š์•„ ์ƒˆ ํ† ํฐ์„ ๋ฐ›๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์‚ฌ์šฉ์ž(๋˜๋Š” ์ œ3์ž)๊ฐ€ ํ† ํฐ์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ์ผ์„ ๋ณ€๊ฒฝํ•˜๋ฉด, ์„œ๋ช…์ด ์ผ์น˜ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์•Œ์•„์ฑŒ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +1์ฃผ์ผ ๋’ค์—๋Š” ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๊ณ  ์‚ฌ์šฉ์ž๋Š” ์ธ๊ฐ€๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ƒˆ ํ† ํฐ์„ ๋ฐ›๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž(๋˜๋Š” ์ œ3์ž)๊ฐ€ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ๋ฐ”๊พธ๊ธฐ ์œ„ํ•ด ํ† ํฐ์„ ์ˆ˜์ •ํ•˜๋ ค๊ณ  ํ•˜๋ฉด, ์„œ๋ช…์ด ์ผ์น˜ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์•Œ์•„์ฑŒ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋งŒ์•ฝ JWT ํ† ํฐ์„ ๋‹ค๋ค„๋ณด๊ณ , ์ž‘๋™ ๋ฐฉ์‹๋„ ์•Œ์•„๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a> ์„ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. +JWT ํ† ํฐ์„ ์ง์ ‘ ๋‹ค๋ค„๋ณด๊ณ  ๋™์ž‘ ๋ฐฉ์‹์„ ํ™•์ธํ•ด๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. -## `PyJWT` ์„ค์น˜ +## `PyJWT` ์„ค์น˜ { #install-pyjwt } -ํŒŒ์ด์ฌ์œผ๋กœ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋ ค๋ฉด `PyJWT` ๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +Python์—์„œ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋ ค๋ฉด `PyJWT`๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -[๊ฐ€์ƒํ™˜๊ฒฝ](../../virtual-environments.md){.internal-link target=_blank} ์„ ๋งŒ๋“ค๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ `pyjwt` ๋ฅผ ์„ค์น˜ํ•˜์‹ญ์‹œ์˜ค: +[๊ฐ€์ƒํ™˜๊ฒฝ](../../virtual-environments.md){.internal-link target=_blank}์„ ๋งŒ๋“ค๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ `pyjwt`๋ฅผ ์„ค์น˜ํ•˜์‹ญ์‹œ์˜ค: <div class="termy"> @@ -42,77 +42,77 @@ $ pip install pyjwt </div> -/// info | ์ฐธ๊ณ  +/// info -RSA๋‚˜ ECDSA ๊ฐ™์€ ์ „์ž ์„œ๋ช… ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด, `pyjwt[crypto]`๋ผ๋Š” ์•”ํ˜ธํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์˜์กด์„ฑ์„ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +RSA๋‚˜ ECDSA ๊ฐ™์€ ์ „์ž ์„œ๋ช… ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•  ๊ณ„ํš์ด๋ผ๋ฉด, cryptography ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์˜์กด์„ฑ์ธ `pyjwt[crypto]`๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -๋” ์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">PyJWT ์„ค์น˜</a> ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">PyJWT Installation docs</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ +## ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ { #password-hashing } -"ํ•ด์‹ฑ(Hashing)"์€ ์–ด๋–ค ๋‚ด์šฉ(์—ฌ๊ธฐ์„œ๋Š” ํŒจ์Šค์›Œ๋“œ)์„ ํ•ด์„ํ•  ์ˆ˜ ์—†๋Š” ์ผ๋ จ์˜ ๋ฐ”์ดํŠธ ์ง‘ํ•ฉ(๋‹จ์ˆœ ๋ฌธ์ž์—ด)์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +"ํ•ด์‹ฑ(Hashing)"์€ ์–ด๋–ค ๋‚ด์šฉ(์—ฌ๊ธฐ์„œ๋Š” ํŒจ์Šค์›Œ๋“œ)์„ ์•Œ์•„๋ณผ ์ˆ˜ ์—†๋Š” ๋ฐ”์ดํŠธ ์‹œํ€€์Šค(๊ทธ๋ƒฅ ๋ฌธ์ž์—ด)๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -๋™์ผํ•œ ๋‚ด์šฉ(๋˜‘๊ฐ™์€ ํŒจ์Šค์›Œ๋“œ)์„ ํ•ด์‹ฑํ•˜๋ฉด ๋™์ผํ•œ ๋ฌธ์ž์—ด์„ ์–ป์Šต๋‹ˆ๋‹ค. +์ •ํ™•ํžˆ ๊ฐ™์€ ๋‚ด์šฉ(์ •ํ™•ํžˆ ๊ฐ™์€ ํŒจ์Šค์›Œ๋“œ)์„ ๋„ฃ์œผ๋ฉด ์ •ํ™•ํžˆ ๊ฐ™์€ ์•Œ์•„๋ณผ ์ˆ˜ ์—†๋Š” ๋ฌธ์ž์—ด์ด ๋‚˜์˜ต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ๊ทธ ๋ฌธ์ž์—ด์„ ๋‹ค์‹œ ํŒจ์Šค์›Œ๋“œ๋กœ ๋˜๋Œ๋ฆด ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๊ทธ ์•Œ์•„๋ณผ ์ˆ˜ ์—†๋Š” ๋ฌธ์ž์—ด์—์„œ ๋‹ค์‹œ ํŒจ์Šค์›Œ๋“œ๋กœ ๋˜๋Œ๋ฆด ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. -### ํŒจ์Šค์›Œ๋“œ๋ฅผ ํ•ด์‹ฑํ•˜๋Š” ์ด์œ  +### ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ  { #why-use-password-hashing } -๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ํƒˆ์ทจ๋‹นํ•˜๋”๋ผ๋„, ์นจ์ž…์ž๋Š” ์‚ฌ์šฉ์ž์˜ ํ‰๋ฌธ ํŒจ์Šค์›Œ๋“œ ๋Œ€์‹  ํ•ด์‹œ ๊ฐ’๋งŒ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ํƒˆ์ทจ๋‹นํ•˜๋”๋ผ๋„, ์นจ์ž…์ž๋Š” ์‚ฌ์šฉ์ž์˜ ํ‰๋ฌธ ํŒจ์Šค์›Œ๋“œ ๋Œ€์‹  ํ•ด์‹œ๋งŒ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ ์นจ์ž…์ž๋Š” ํ›”์นœ ์‚ฌ์šฉ์ž ํŒจ์Šค์›Œ๋“œ๋ฅผ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (๋Œ€๋‹ค์ˆ˜ ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ๋Ÿฌ ์‹œ์Šคํ…œ์—์„œ ๋™์ผํ•œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ‰๋ฌธ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์œ ์ถœ๋˜๋ฉด ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค.) +๋”ฐ๋ผ์„œ ์นจ์ž…์ž๋Š” ๊ทธ ํŒจ์Šค์›Œ๋“œ๋ฅผ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉํ•ด ๋ณด๋ ค๊ณ  ์‹œ๋„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค(๋งŽ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์–ด๋””์„œ๋‚˜ ๊ฐ™์€ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, ์ด๋Š” ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค). -## `passlib` ์„ค์น˜ +## `pwdlib` ์„ค์น˜ { #install-pwdlib } -PassLib๋Š” ํŒจ์Šค์›Œ๋“œ ํ•ด์‹œ๋ฅผ ๋‹ค๋ฃจ๋Š” ํ›Œ๋ฅญํ•œ ํŒŒ์ด์ฌ ํŒจํ‚ค์ง€์ž…๋‹ˆ๋‹ค. +pwdlib๋Š” ํŒจ์Šค์›Œ๋“œ ํ•ด์‹œ๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ ํ›Œ๋ฅญํ•œ Python ํŒจํ‚ค์ง€์ž…๋‹ˆ๋‹ค. -๋งŽ์€ ์•ˆ์ „ํ•œ ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜๊ณผ ๋„๊ตฌ๋“ค์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +๋งŽ์€ ์•ˆ์ „ํ•œ ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜๊ณผ ์ด๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. -์ถ”์ฒœํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ "Bcrypt"์ž…๋‹ˆ๋‹ค. +์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ "Argon2"์ž…๋‹ˆ๋‹ค. -[๊ฐ€์ƒํ™˜๊ฒฝ](../../virtual-environments.md){.internal-link target=_blank} ์„ ๋งŒ๋“ค๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ PassLib์™€ Bcrypt๋ฅผ ์„ค์น˜ํ•˜์‹ญ์‹œ์˜ค: +[๊ฐ€์ƒํ™˜๊ฒฝ](../../virtual-environments.md){.internal-link target=_blank}์„ ๋งŒ๋“ค๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ Argon2์™€ ํ•จ๊ป˜ pwdlib๋ฅผ ์„ค์น˜ํ•˜์‹ญ์‹œ์˜ค: <div class="termy"> ```console -$ pip install "passlib[bcrypt]" +$ pip install "pwdlib[argon2]" ---> 100% ``` </div> -/// tip | ํŒ +/// tip -`passlib`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, **Django**, **Flask** ์˜ ๋ณด์•ˆ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด๋‚˜ ๋‹ค๋ฅธ ๋„๊ตฌ๋กœ ์ƒ์„ฑํ•œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +`pwdlib`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด **Django**, **Flask** ๋ณด์•ˆ ํ”Œ๋Ÿฌ๊ทธ์ธ ๋˜๋Š” ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ ๋„๊ตฌ๋กœ ์ƒ์„ฑํ•œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์ž๋ฉด, FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ Django ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Django ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ ์ง„์ ์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ ์˜ˆ๋ฅผ ๋“ค์–ด, FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ Django ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ Django ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ ์ง„์ ์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž๋Š” FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ Django ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋™์‹œ์— ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž๋Š” Django ์•ฑ ๋˜๋Š” **FastAPI** ์•ฑ์—์„œ ๋™์‹œ์— ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## ํŒจ์Šค์›Œ๋“œ์˜ ํ•ด์‹œ์™€ ๊ฒ€์ฆ +## ํŒจ์Šค์›Œ๋“œ ํ•ด์‹œ ๋ฐ ๊ฒ€์ฆ { #hash-and-verify-the-passwords } -ํ•„์š”ํ•œ ๋„๊ตฌ๋ฅผ `passlib`์—์„œ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค. +`pwdlib`์—์„œ ํ•„์š”ํ•œ ๋„๊ตฌ๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค. -PassLib "์ปจํ…์ŠคํŠธ(context)"๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํŒจ์Šค์›Œ๋“œ๋ฅผ ํ•ด์‹ฑํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +๊ถŒ์žฅ ์„ค์ •์œผ๋กœ PasswordHash ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํŒจ์Šค์›Œ๋“œ๋ฅผ ํ•ด์‹ฑํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -/// tip | ํŒ +/// tip -PassLib ์ปจํ…์ŠคํŠธ๋Š” ๋‹ค์–‘ํ•œ ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ, ๋” ์ด์ƒ ์‚ฌ์šฉ์ด ๊ถŒ์žฅ๋˜์ง€ ์•Š๋Š” ์˜ค๋ž˜๋œ ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ฒ€์ฆํ•˜๋Š” ๊ธฐ๋Šฅ๋„ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +pwdlib๋Š” bcrypt ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜๋„ ์ง€์›ํ•˜์ง€๋งŒ ๋ ˆ๊ฑฐ์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ค๋ž˜๋œ ํ•ด์‹œ๋กœ ์ž‘์—…ํ•ด์•ผ ํ•œ๋‹ค๋ฉด passlib ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค๋ฅธ ์‹œ์Šคํ…œ(Django ๊ฐ™์€)์—์„œ ์ƒ์„ฑํ•œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ฝ๊ณ  ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ƒˆ๋กœ์šด ํŒจ์Šค์›Œ๋“œ๋ฅผ Bcrypt ๊ฐ™์€ ๋‹ค๋ฅธ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ํ•ด์‹ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค๋ฅธ ์‹œ์Šคํ…œ(Django ๊ฐ™์€)์—์„œ ์ƒ์„ฑํ•œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ฝ๊ณ  ๊ฒ€์ฆํ•˜๋˜, ์ƒˆ ํŒจ์Šค์›Œ๋“œ๋Š” Argon2๋‚˜ Bcrypt ๊ฐ™์€ ๋‹ค๋ฅธ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ํ•ด์‹ฑํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ๋™์‹œ์— ๊ทธ๋Ÿฐ ๋ชจ๋“  ์•Œ๊ณ ๋ฆฌ์ฆ˜๊ณผ ํ˜ธํ™˜์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๋™์‹œ์— ๊ทธ ๋ชจ๋“  ๊ฒƒ๊ณผ ํ˜ธํ™˜๋˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ํŒจ์Šค์›Œ๋“œ๋ฅผ ํ•ด์‹ฑํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ๋ฐ›์€ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์ €์žฅ๋œ ํ•ด์‹œ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๋ฐ›์€ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์ €์žฅ๋œ ํ•ด์‹œ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. @@ -120,17 +120,17 @@ PassLib ์ปจํ…์ŠคํŠธ๋Š” ๋‹ค์–‘ํ•œ ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” /// 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 ํ† ํฐ์„ ์„œ๋ช…ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ž„์˜์˜ ๋น„๋ฐ€ ํ‚ค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -์•ˆ์ „ํ•œ ์ž„์˜์˜ ๋น„๋ฐ€ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค: +์•ˆ์ „ํ•œ ์ž„์˜์˜ ๋น„๋ฐ€ ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค: <div class="termy"> @@ -142,67 +142,67 @@ $ openssl rand -hex 32 </div> -๊ทธ๋ฆฌ๊ณ  ์ƒ์„ฑํ•œ ๋น„๋ฐ€ํ‚ค๋ฅผ ๋ณต์‚ฌํ•ด ๋ณ€์ˆ˜ `SECRET_KEY`์— ๋Œ€์ž…ํ•ฉ๋‹ˆ๋‹ค. (์ด ์˜ˆ์ œ์˜ ๋ณ€์ˆ˜ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค.) +๊ทธ๋ฆฌ๊ณ  ์ถœ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ๋ณ€์ˆ˜ `SECRET_KEY`์— ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค(์˜ˆ์ œ์˜ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค). -JWT ํ† ํฐ์„ ์„œ๋ช…ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋  ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์œ„ํ•œ ๋ณ€์ˆ˜ `ALGORITHM` ์„ ์ƒ์„ฑํ•˜๊ณ  `"HS256"` ์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. +JWT ํ† ํฐ์„ ์„œ๋ช…ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋  ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์œ„ํ•œ ๋ณ€์ˆ˜ `ALGORITHM`์„ ์ƒ์„ฑํ•˜๊ณ  `"HS256"`์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. -ํ† ํฐ ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์„ ์œ„ํ•œ ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +ํ† ํฐ ๋งŒ๋ฃŒ๋ฅผ ์œ„ํ•œ ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -์‘๋‹ต์„ ์œ„ํ•œ ํ† ํฐ ์—”๋“œํฌ์ธํŠธ์— ์‚ฌ์šฉ๋  Pydantic ๋ชจ๋ธ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. +์‘๋‹ต์„ ์œ„ํ•ด ํ† ํฐ ์—”๋“œํฌ์ธํŠธ์—์„œ ์‚ฌ์šฉ๋  Pydantic ๋ชจ๋ธ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ ์•ก์„ธ์Šค ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. {* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} -## ์˜์กด์„ฑ ์ˆ˜์ • +## ์˜์กด์„ฑ ์—…๋ฐ์ดํŠธ { #update-the-dependencies } -`get_current_user` ํ•จ์ˆ˜๋ฅผ ์ด์ „๊ณผ ๋™์ผํ•œ ํ† ํฐ์„ ๋ฐ›๋„๋ก ์ˆ˜์ •ํ•˜๋˜, ์ด๋ฒˆ์—๋Š” JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. +`get_current_user`๊ฐ€ ์ด์ „๊ณผ ๋™์ผํ•œ ํ† ํฐ์„ ๋ฐ›๋„๋ก ์—…๋ฐ์ดํŠธํ•˜๋˜, ์ด๋ฒˆ์—๋Š” JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. -๋ฐ›์€ ํ† ํฐ์„ ๋””์ฝ”๋”ฉํ•˜์—ฌ ๊ฒ€์ฆํ•œ ํ›„ ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +๋ฐ›์€ ํ† ํฐ์„ ๋””์ฝ”๋”ฉํ•˜๊ณ  ๊ฒ€์ฆํ•œ ๋’ค ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด HTTP ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. +ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด ์ฆ‰์‹œ HTTP ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. {* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} -## `/token` ๊ฒฝ๋กœ ์ž‘์—… ์ˆ˜์ • +## `/token` *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์—…๋ฐ์ดํŠธ { #update-the-token-path-operation } -ํ† ํฐ์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ์„ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด `timedelta` ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +ํ† ํฐ์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์œผ๋กœ `timedelta`๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ JWT ์•ก์„ธ์Šค ํ† ํฐ์„ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. {* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} -### JWT "์ฃผ์ฒด(subject)" `sub`์— ๋Œ€ํ•œ ๊ธฐ์ˆ  ์„ธ๋ถ€ ์‚ฌํ•ญ +### JWT "์ฃผ์ฒด(subject)" `sub`์— ๋Œ€ํ•œ ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ { #technical-details-about-the-jwt-subject-sub } -JWT ๋ช…์„ธ์— ๋”ฐ๋ฅด๋ฉด ํ† ํฐ์˜ ์ฃผ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” `sub`๋ผ๋Š” ํ‚ค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. +JWT ๋ช…์„ธ์— ๋”ฐ๋ฅด๋ฉด ํ† ํฐ์˜ ์ฃผ์ฒด๋ฅผ ๋‹ด๋Š” `sub` ํ‚ค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. -์‚ฌ์šฉ ์—ฌ๋ถ€๋Š” ์„ ํƒ์‚ฌํ•ญ์ด์ง€๋งŒ, ์‚ฌ์šฉ์ž์˜ ์‹๋ณ„ ์ •๋ณด๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ๋Š” ์ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +์„ ํƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์—ฌ๊ธฐ์— ์‚ฌ์šฉ์ž ์‹๋ณ„ ์ •๋ณด๋ฅผ ๋„ฃ๊ฒŒ ๋˜๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ๋Š” ์ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -JWT๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์‹๋ณ„ํ•˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ API๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜๋Š” ๊ฒƒ ์™ธ์—๋„ ๋‹ค๋ฅธ ์šฉ๋„๋กœ ์‚ฌ์šฉ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +JWT๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์‹๋ณ„ํ•˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ API์—์„œ ์ง์ ‘ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜๋Š” ๊ฒƒ ์™ธ์—๋„ ๋‹ค๋ฅธ ์šฉ๋„๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด "์ž๋™์ฐจ"๋‚˜ "๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ"์„ ์‹๋ณ„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด "์ž๋™์ฐจ"๋‚˜ "๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ"์„ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  "์ž๋™์ฐจ๋ฅผ ์šด์ „ํ•˜๋‹ค"๋‚˜ "๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์„ ์ˆ˜์ •ํ•˜๋‹ค"์ฒ˜๋Ÿผ ํ•ด๋‹น ์—”ํ„ฐํ‹ฐ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•ด๋‹น ์—”ํ„ฐํ‹ฐ์— ๋Œ€ํ•œ ๊ถŒํ•œ(์ž๋™์ฐจ์˜ ๊ฒฝ์šฐ "drive", ๋ธ”๋กœ๊ทธ์˜ ๊ฒฝ์šฐ "edit" ๋“ฑ)์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ ํ›„ ์ด JWT ํ† ํฐ์„ ์‚ฌ์šฉ์ž(๋˜๋Š” ๋ด‡)์—๊ฒŒ ์ œ๊ณตํ•˜๋ฉด, ๊ทธ๋“ค์€ ๊ณ„์ •์„ ๋”ฐ๋กœ ๋งŒ๋“ค ํ•„์š” ์—†์ด API๊ฐ€ ์ƒ์„ฑํ•œ JWT ํ† ํฐ๋งŒ์œผ๋กœ ์ž‘์—…(์ž๋™์ฐจ ์šด์ „ ๋˜๋Š” ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ ํŽธ์ง‘)์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๊ทธ JWT ํ† ํฐ์„ ์‚ฌ์šฉ์ž(๋˜๋Š” ๋ด‡)์—๊ฒŒ ์ œ๊ณตํ•˜๋ฉด, ๊ณ„์ •์ด ์—†์–ด๋„ API๊ฐ€ ์ƒ์„ฑํ•œ JWT ํ† ํฐ๋งŒ์œผ๋กœ ๊ทธ ๋™์ž‘๋“ค(์ž๋™์ฐจ ์šด์ „, ๋ธ”๋กœ๊ทธ ํŽธ์ง‘)์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๋Ÿฌํ•œ ๊ฐœ๋…์„ ํ™œ์šฉํ•˜๋ฉด JWT๋Š” ํ›จ์”ฌ ๋” ๋ณต์žกํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋Ÿฌํ•œ ์•„์ด๋””์–ด๋ฅผ ํ™œ์šฉํ•˜๋ฉด JWT๋Š” ํ›จ์”ฌ ๋” ์ •๊ตํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค์—๋„ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด ๊ฒฝ์šฐ ์—ฌ๋Ÿฌ ์—”ํ„ฐํ‹ฐ๊ฐ€ ๋™์ผํ•œ ID๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด foo๋ผ๋Š” ID๋ฅผ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž, ์ž๋™์ฐจ, ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๊ฒฝ์šฐ ์—ฌ๋Ÿฌ ์—”ํ„ฐํ‹ฐ๊ฐ€ ๋™์ผํ•œ ID(์˜ˆ: `foo`)๋ฅผ ๊ฐ€์งˆ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค(์‚ฌ์šฉ์ž `foo`, ์ž๋™์ฐจ `foo`, ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ `foo`). -๊ทธ๋ž˜์„œ ID ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ์‚ฌ์šฉ์ž์˜ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•  ๋•Œ ์ ‘๋‘์‚ฌ๋กœ `sub` ํ‚ค๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด `username:` ์„ ๋ถ™์ด๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” `sub` ๊ฐ’์ด `username:johndoe`์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ ID ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•  ๋•Œ `sub` ํ‚ค์˜ ๊ฐ’์— ์ ‘๋‘์‚ฌ๋ฅผ ๋ถ™์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด `username:` ๊ฐ™์€ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ด ์˜ˆ์ œ์—์„œ `sub` ๊ฐ’์€ `username:johndoe`๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ ์€ `sub` ํ‚ค๋Š” ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๊ณ ์œ ํ•œ ์‹๋ณ„์ž๊ฐ€ ๋˜์–ด์•ผ ํ•˜๋ฉฐ ๋ฌธ์ž์—ด์ด์–ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. +๊ธฐ์–ตํ•ด์•ผ ํ•  ์ค‘์š”ํ•œ ์ ์€ `sub` ํ‚ค๊ฐ€ ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๊ณ ์œ ํ•œ ์‹๋ณ„์ž์—ฌ์•ผ ํ•˜๊ณ , ๋ฌธ์ž์—ด์ด์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. -## ํ™•์ธํ•ด๋ด…์‹œ๋‹ค +## ํ™•์ธํ•˜๊ธฐ { #check-it } ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋ฌธ์„œ๋กœ ์ด๋™ํ•˜์‹ญ์‹œ์˜ค: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. -๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค: <img src="/img/tutorial/security/image07.png"> -์ด์ „๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ธ์ฆํ•˜์‹ญ์‹œ์˜ค. +์ด์ „๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ธ๊ฐ€ํ•˜์‹ญ์‹œ์˜ค. ๋‹ค์Œ ์ธ์ฆ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค: @@ -211,13 +211,13 @@ Password: `secret` /// check -์ฝ”๋“œ ์–ด๋””์—๋„ ํ‰๋ฌธ ํŒจ์Šค์›Œ๋“œ "`secret`" ์ด ์—†๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์‹ญ์‹œ์˜ค. ํ•ด์‹œ๋œ ๋ฒ„์ „๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค. +์ฝ”๋“œ ์–ด๋””์—๋„ ํ‰๋ฌธ ํŒจ์Šค์›Œ๋“œ "`secret`"์€ ์—†๊ณ , ํ•ด์‹œ๋œ ๋ฒ„์ „๋งŒ ์žˆ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์‹ญ์‹œ์˜ค. /// <img src="/img/tutorial/security/image08.png"> -`/users/me/` ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‘๋‹ต์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์—”๋“œํฌ์ธํŠธ `/users/me/`๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: ```JSON { @@ -230,44 +230,44 @@ Password: `secret` <img src="/img/tutorial/security/image09.png"> -๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์—ด์–ด๋ณด๋ฉด ์ „์†ก๋œ ๋ฐ์ดํ„ฐ์— ํ† ํฐ๋งŒ ํฌํ•จ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŒจ์Šค์›Œ๋“œ๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๊ณ  ์•ก์„ธ์Šค ํ† ํฐ์„ ๋ฐ›๊ธฐ ์œ„ํ•œ ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ์—๋งŒ ์ „์†ก๋˜๋ฉฐ, ์ดํ›„์—๋Š” ์ „์†ก๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: +๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์—ด์–ด๋ณด๋ฉด ์ „์†ก๋œ ๋ฐ์ดํ„ฐ์—๋Š” ํ† ํฐ๋งŒ ํฌํ•จ๋˜์–ด ์žˆ๊ณ , ํŒจ์Šค์›Œ๋“œ๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๊ณ  ํ•ด๋‹น ์•ก์„ธ์Šค ํ† ํฐ์„ ์–ป๊ธฐ ์œ„ํ•œ ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ์—์„œ๋งŒ ์ „์†ก๋˜๋ฉฐ ์ดํ›„์—๋Š” ์ „์†ก๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <img src="/img/tutorial/security/image10.png"> /// note -`Bearer `๋กœ ์‹œ์ž‘ํ•˜๋Š” `Authorization` ํ—ค๋”์— ์ฃผ๋ชฉํ•˜์‹ญ์‹œ์˜ค. +`Bearer `๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ’์„ ๊ฐ€์ง„ `Authorization` ํ—ค๋”์— ์ฃผ๋ชฉํ•˜์‹ญ์‹œ์˜ค. /// -## `scopes` ์˜ ๊ณ ๊ธ‰ ์‚ฌ์šฉ๋ฒ• +## `scopes`์˜ ๊ณ ๊ธ‰ ์‚ฌ์šฉ๋ฒ• { #advanced-usage-with-scopes } -OAuth2๋Š” "์Šค์ฝ”ํ”„(scopes)" ๋ผ๋Š” ๊ฐœ๋…์„ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +OAuth2์—๋Š” "scopes"๋ผ๋Š” ๊ฐœ๋…์ด ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JWT ํ† ํฐ์— ํŠน์ • ๊ถŒํ•œ ์ง‘ํ•ฉ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ฅผ ์‚ฌ์šฉํ•ด JWT ํ† ํฐ์— ํŠน์ • ๊ถŒํ•œ ์ง‘ํ•ฉ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ ํ›„ ์ด ํ† ํฐ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ง์ ‘ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ์ œ3์ž์—๊ฒŒ ์ œ๊ณตํ•˜์—ฌ, ํŠน์ • ์ œํ•œ์‚ฌํ•ญ ํ•˜์—์žˆ๋Š” API์™€ ํ†ต์‹ ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด ํ† ํฐ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ง์ ‘ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ์ œ3์ž์—๊ฒŒ ์ œ๊ณตํ•˜์—ฌ, ํŠน์ • ์ œํ•œ์‚ฌํ•ญ ํ•˜์—์„œ API์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI** ์—์„œ์˜ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•๊ณผ ํ†ตํ•ฉ ๋ฐฉ์‹์€ **์‹ฌํ™” ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ** ์—์„œ ์ž์„ธํžˆ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  **FastAPI**์— ์–ด๋–ป๊ฒŒ ํ†ตํ•ฉ๋˜๋Š”์ง€๋Š” ์ดํ›„ **์‹ฌํ™” ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**์—์„œ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ์š”์•ฝ +## ์š”์•ฝ { #recap } -์ง€๊ธˆ๊นŒ์ง€ ์‚ดํŽด๋ณธ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ, OAuth2์™€ JWT ๊ฐ™์€ ํ‘œ์ค€์„ ์‚ฌ์šฉํ•˜์—ฌ ์•ˆ์ „ํ•œ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ง€๊ธˆ๊นŒ์ง€ ์‚ดํŽด๋ณธ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ, OAuth2์™€ JWT ๊ฐ™์€ ํ‘œ์ค€์„ ์‚ฌ์šฉํ•ด ์•ˆ์ „ํ•œ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ฑฐ์˜ ๋ชจ๋“  ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๋ณด์•ˆ ์ฒ˜๋ฆฌ๋Š” ์ƒ๋‹นํžˆ ๋ณต์žกํ•œ ์ฃผ์ œ์ž…๋‹ˆ๋‹ค. +๊ฑฐ์˜ ๋ชจ๋“  ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๋ณด์•ˆ ์ฒ˜๋ฆฌ๋Š” ๊ฝค ๋น ๋ฅด๊ฒŒ ๋ณต์žกํ•œ ์ฃผ์ œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. -์ด๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๋Š” ๋งŽ์€ ํŒจํ‚ค์ง€๋Š” ๋ฐ์ดํ„ฐ ๋ชจ๋ธ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ธฐ๋Šฅ๋“ค์— ๋Œ€ํ•ด ์—ฌ๋Ÿฌ ์ œ์•ฝ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ง€๋‚˜์น˜๊ฒŒ ๋‹จ์ˆœํ™”ํ•˜๋Š” ์ผ๋ถ€ ํŒจํ‚ค์ง€๋“ค์€ ์‹ฌ๊ฐํ•œ ๋ณด์•ˆ ๊ฒฐํ•จ์„ ๊ฐ€์งˆ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ฅผ ํฌ๊ฒŒ ๋‹จ์ˆœํ™”ํ•˜๋Š” ๋งŽ์€ ํŒจํ‚ค์ง€๋“ค์€ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ธฐ๋Šฅ๋“ค์— ๋Œ€ํ•ด ๋งŽ์€ ํƒ€ํ˜‘์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ง€๋‚˜์น˜๊ฒŒ ๋‹จ์ˆœํ™”ํ•˜๋Š” ์ผ๋ถ€ ํŒจํ‚ค์ง€๋“ค์€ ์‹ค์ œ๋กœ ๋‚ด๋ถ€์— ๋ณด์•ˆ ๊ฒฐํ•จ์ด ์žˆ๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. --- -**FastAPI** ๋Š” ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๋ฐ์ดํ„ฐ ๋ชจ๋ธ, ๋„๊ตฌ๋„ ๊ฐ•์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +**FastAPI**๋Š” ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๋ฐ์ดํ„ฐ ๋ชจ๋ธ, ๋„๊ตฌ์—๋„ ํƒ€ํ˜‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -ํ”„๋กœ์ ํŠธ์— ๊ฐ€์žฅ ์ ํ•ฉํ•œ ๊ฒƒ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +ํ”„๋กœ์ ํŠธ์— ๊ฐ€์žฅ ์ž˜ ๋งž๋Š” ๊ฒƒ๋“ค์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  `passlib` ์™€ `PyJWT` ์ฒ˜๋Ÿผ ์ž˜ ๊ด€๋ฆฌ๋˜๊ณ  ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ํŒจํ‚ค์ง€๋“ค์„ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. **FastAPI** ๋Š” ์™ธ๋ถ€ ํŒจํ‚ค์ง€ ํ†ตํ•ฉ์„ ์œ„ํ•ด ๋ณต์žกํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ํ•„์š”ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  **FastAPI**๋Š” ์™ธ๋ถ€ ํŒจํ‚ค์ง€๋ฅผ ํ†ตํ•ฉํ•˜๊ธฐ ์œ„ํ•ด ๋ณต์žกํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์š”๊ตฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— `pwdlib`์™€ `PyJWT` ๊ฐ™์€ ์ž˜ ๊ด€๋ฆฌ๋˜๊ณ  ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ํŒจํ‚ค์ง€๋“ค์„ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ ์œ ์—ฐ์„ฑ, ๊ฒฌ๊ณ ์„ฑ, ๋ณด์•ˆ์„ฑ์„ ํ•ด์น˜์ง€ ์•Š์œผ๋ฉด์„œ ๊ณผ์ •์„ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์œ ์—ฐ์„ฑ, ๊ฒฌ๊ณ ์„ฑ, ๋ณด์•ˆ์„ฑ์„ ํ•ด์น˜์ง€ ์•Š์œผ๋ฉด์„œ ๊ณผ์ •์„ ๊ฐ€๋Šฅํ•œ ํ•œ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„๊ตฌ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  OAuth2์™€ ๊ฐ™์€ ํ‘œ์ค€ ํ”„๋กœํ† ์ฝœ์„ ๋น„๊ต์  ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ OAuth2 ๊ฐ™์€ ์•ˆ์ „ํ•œ ํ‘œ์ค€ ํ”„๋กœํ† ์ฝœ์„ ๋น„๊ต์  ๊ฐ„๋‹จํ•œ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋” ์„ธ๋ถ„ํ™”๋œ ๊ถŒํ•œ ์ฒด๊ณ„๋ฅผ ์œ„ํ•ด OAuth2์˜ "์Šค์ฝ”ํ”„"๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ **์‹ฌํ™” ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**์—์„œ ๋” ์ž์„ธํžˆ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. OAuth2์˜ ์Šค์ฝ”ํ”„๋Š” ์ œ3์ž ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€์‹ ํ•ด ๊ทธ๋“ค์˜ API์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋„๋ก ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๊ธฐ ์œ„ํ•ด, Facebook, Google, GitHub, Microsoft, X (Twitter) ๋“ฑ์˜ ๋งŽ์€ ๋Œ€ํ˜• ์ธ์ฆ ์ œ๊ณต์—…์ฒด๋“ค์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. +๋” ์„ธ๋ถ„ํ™”๋œ ๊ถŒํ•œ ์‹œ์Šคํ…œ์„ ์œ„ํ•ด OAuth2 "scopes"๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€, ๊ฐ™์€ ํ‘œ์ค€์„ ๋”ฐ๋ฅด๋Š” ๋ฐฉ์‹์œผ๋กœ **์‹ฌํ™” ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**์—์„œ ๋” ์ž์„ธํžˆ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šค์ฝ”ํ”„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” OAuth2๋Š” Facebook, Google, GitHub, Microsoft, X (Twitter) ๋“ฑ ๋งŽ์€ ๋Œ€ํ˜• ์ธ์ฆ ์ œ๊ณต์—…์ฒด๋“ค์ด ์ œ3์ž ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‚ฌ์šฉ์ž ๋Œ€์‹  ๊ทธ๋“ค์˜ API์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ธ๊ฐ€ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/security/simple-oauth2.md b/docs/ko/docs/tutorial/security/simple-oauth2.md index f10c4f588d..189dd89f2a 100644 --- a/docs/ko/docs/tutorial/security/simple-oauth2.md +++ b/docs/ko/docs/tutorial/security/simple-oauth2.md @@ -1,8 +1,8 @@ -# ํŒจ์Šค์›Œ๋“œ์™€ Bearer๋ฅผ ์ด์šฉํ•œ ๊ฐ„๋‹จํ•œ OAuth2 +# ํŒจ์Šค์›Œ๋“œ์™€ Bearer๋ฅผ ์ด์šฉํ•œ ๊ฐ„๋‹จํ•œ OAuth2 { #simple-oauth2-with-password-and-bearer } ์ด์ œ ์ด์ „ ์žฅ์—์„œ ๋นŒ๋“œํ•˜๊ณ  ๋ˆ„๋ฝ๋œ ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์™„์ „ํ•œ ๋ณด์•ˆ ํ๋ฆ„์„ ๊ฐ–๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. -## `username`์™€ `password` ์–ป๊ธฐ +## `username`์™€ `password` ์–ป๊ธฐ { #get-the-username-and-password } **FastAPI** ๋ณด์•ˆ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `username` ๋ฐ `password`๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ž…๋‹ˆ๋‹ค. @@ -14,11 +14,11 @@ OAuth2๋Š” (์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”) "ํŒจ์Šค์›Œ๋“œ ํ”Œ๋กœ์šฐ"์„ ์‚ฌ์šฉํ•  ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ์€ ์›ํ•˜๋Š” ๋‹ค๋ฅธ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ ๋กœ๊ทธ์ธ *๊ฒฝ๋กœ ์ž‘๋™*์˜ ๊ฒฝ์šฐ ์‚ฌ์–‘๊ณผ ํ˜ธํ™˜๋˜๋„๋ก ์ด๋Ÿฌํ•œ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด ํ†ตํ•ฉ API ๋ฌธ์„œ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค). +๊ทธ๋Ÿฌ๋‚˜ ๋กœ๊ทธ์ธ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ ๊ฒฝ์šฐ ์‚ฌ์–‘๊ณผ ํ˜ธํ™˜๋˜๋„๋ก ์ด๋Ÿฌํ•œ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด ํ†ตํ•ฉ API ๋ฌธ์„œ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค). ์‚ฌ์–‘์—๋Š” ๋˜ํ•œ `username`๊ณผ `password`๊ฐ€ ํผ ๋ฐ์ดํ„ฐ๋กœ ์ „์†ก๋˜์–ด์•ผ ํ•œ๋‹ค๊ณ  ๋ช…์‹œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค(๋”ฐ๋ผ์„œ ์—ฌ๊ธฐ์—๋Š” JSON์ด ์—†์Šต๋‹ˆ๋‹ค). -### `scope` +### `scope` { #scope } ์‚ฌ์–‘์—๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋‹ค๋ฅธ ํผ ํ•„๋“œ "`scope`"๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋‚˜์™€ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -44,15 +44,15 @@ OAuth2์˜ ๊ฒฝ์šฐ ๋ฌธ์ž์—ด์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. /// -## `username`๊ณผ `password`๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ฝ”๋“œ +## `username`๊ณผ `password`๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ฝ”๋“œ { #code-to-get-the-username-and-password } ์ด์ œ **FastAPI**์—์„œ ์ œ๊ณตํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -### `OAuth2PasswordRequestForm` +### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform } -๋จผ์ € `OAuth2PasswordRequestForm`์„ ๊ฐ€์ ธ์™€ `/token`์— ๋Œ€ํ•œ *๊ฒฝ๋กœ ์ž‘๋™*์—์„œ `Depends`์˜ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +๋จผ์ € `OAuth2PasswordRequestForm`์„ ๊ฐ€์ ธ์™€ `/token`์— ๋Œ€ํ•œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ `Depends`์˜ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -{* ../../docs_src/security/tutorial003.py hl[4,76] *} +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} `OAuth2PasswordRequestForm`์€ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ํผ ๋ณธ๋ฌธ์„ ์„ ์–ธํ•˜๋Š” ํด๋ž˜์Šค ์˜์กด์„ฑ์ž…๋‹ˆ๋‹ค: @@ -84,7 +84,7 @@ OAuth2 ์‚ฌ์–‘์€ ์‹ค์ œ๋กœ `password`๋ผ๋Š” ๊ณ ์ • ๊ฐ’์ด ์žˆ๋Š” `grant_type` /// -### ํผ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉํ•˜๊ธฐ +### ํผ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉํ•˜๊ธฐ { #use-the-form-data } /// tip | ํŒ @@ -100,9 +100,9 @@ OAuth2 ์‚ฌ์–‘์€ ์‹ค์ œ๋กœ `password`๋ผ๋Š” ๊ณ ์ • ๊ฐ’์ด ์žˆ๋Š” `grant_type` ์˜ค๋ฅ˜์˜ ๊ฒฝ์šฐ `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`๋ผ๋Š” ๊ณ ์ • ๊ฐ’์ด ์žˆ๋Š” `grant_type` ๋‘ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๋™์ผํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค. -#### ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ +#### ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ { #password-hashing } "ํ•ด์‹ฑ"์€ ์ผ๋ถ€ ์ฝ˜ํ…์ธ (์ด ๊ฒฝ์šฐ ํŒจ์Šค์›Œ๋“œ)๋ฅผ ํšก์„ค์ˆ˜์„คํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋Š” ์ผ๋ จ์˜ ๋ฐ”์ดํŠธ(๋ฌธ์ž์—ด)๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. @@ -120,21 +120,15 @@ OAuth2 ์‚ฌ์–‘์€ ์‹ค์ œ๋กœ `password`๋ผ๋Š” ๊ณ ์ • ๊ฐ’์ด ์žˆ๋Š” `grant_type` ๊ทธ๋Ÿฌ๋‚˜ ํšก์„ค์ˆ˜์„ค์—์„œ ์•”ํ˜ธ๋กœ ๋‹ค์‹œ ๋ณ€ํ™˜ํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. -##### ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์ด์œ  +##### ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์ด์œ  { #why-use-password-hashing } ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์œ ์ถœ๋œ ๊ฒฝ์šฐ ํ•ด์ปค๋Š” ์‚ฌ์šฉ์ž์˜ ์ผ๋ฐ˜ ํ…์ŠคํŠธ ์•”ํ˜ธ๊ฐ€ ์•„๋‹ˆ๋ผ ํ•ด์‹œ๋งŒ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•ด์ปค๋Š” ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์—์„œ ๋™์ผํ•œ ์•”ํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ์‹œ๋„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค(๋งŽ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ๋“  ๊ณณ์—์„œ ๋™์ผํ•œ ์•”ํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ด๋Š” ์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). -//// tab | ํŒŒ์ด์ฌ 3.7 ์ด์ƒ +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} -{* ../../docs_src/security/tutorial003.py hl[80:83] *} - -//// - -{* ../../docs_src/security/tutorial003_py310.py hl[78:81] *} - -#### `**user_dict`์— ๋Œ€ํ•ด +#### `**user_dict`์— ๋Œ€ํ•ด { #about-user-dict } `UserInDB(**user_dict)`๋Š” ๋‹ค์Œ์„ ์˜๋ฏธํ•œ๋‹ค: @@ -152,11 +146,11 @@ UserInDB( /// info | ์ •๋ณด -`**user_dict`์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ [**์ถ”๊ฐ€ ๋ชจ๋ธ** ๋ฌธ์„œ](../extra-models.md#about-user_indict){.internal-link target=_blank}๋ฅผ ๋‹ค์‹œ ์ฝ์–ด๋ด…์‹œ๋‹ค. +`**user_dict`์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ [**์ถ”๊ฐ€ ๋ชจ๋ธ** ๋ฌธ์„œ](../extra-models.md#about-user-in-dict){.internal-link target=_blank}๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด๋ณด์„ธ์š”. /// -## ํ† ํฐ ๋ฐ˜ํ™˜ํ•˜๊ธฐ +## ํ† ํฐ ๋ฐ˜ํ™˜ํ•˜๊ธฐ { #return-the-token } `token` ์—”๋“œํฌ์ธํŠธ์˜ ์‘๋‹ต์€ JSON ๊ฐ์ฒด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. @@ -174,7 +168,7 @@ UserInDB( /// -{* ../../docs_src/security/tutorial003.py hl[85] *} +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} /// tip | ํŒ @@ -188,19 +182,19 @@ UserInDB( /// -## ์˜์กด์„ฑ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ +## ์˜์กด์„ฑ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ { #update-the-dependencies } ์ด์ œ ์˜์กด์„ฑ์„ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•  ๊ฒ๋‹ˆ๋‹ค. ์ด ์‚ฌ์šฉ์ž๊ฐ€ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š” *๊ฒฝ์šฐ์—๋งŒ* `current_user`๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฒ๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ `get_current_user`๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ถ”๊ฐ€ ์ข…์†์„ฑ `get_current_active_user`๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ `get_current_user`๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ถ”๊ฐ€ ์˜์กด์„ฑ `get_current_active_user`๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์˜์กด์„ฑ ๋ชจ๋‘, ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑ์ธ ๊ฒฝ์šฐ HTTP ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์—”๋“œํฌ์ธํŠธ์—์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ธ์ฆ๋˜์—ˆ์œผ๋ฉฐ ํ™œ์„ฑ ์ƒํƒœ์ธ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉ์ž๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/security/tutorial003.py hl[58:66,69:72,90] *} +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} /// info | ์ •๋ณด @@ -220,11 +214,11 @@ UserInDB( /// -## ํ™•์ธํ•˜๊ธฐ +## ํ™•์ธํ•˜๊ธฐ { #see-it-in-action } ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ ์—ด๊ธฐ: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. -### ์ธ์ฆํ•˜๊ธฐ +### ์ธ์ฆํ•˜๊ธฐ { #authenticate } "Authorize" ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ๋ด…์‹œ๋‹ค. @@ -240,7 +234,7 @@ UserInDB( <img src="/img/tutorial/security/image05.png"> -### ์ž์‹ ์˜ ์œ ์ € ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ +### ์ž์‹ ์˜ ์œ ์ € ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ { #get-your-own-user-data } ์ด์ œ `/users/me` ๊ฒฝ๋กœ์— `GET` ์ž‘์—…์„ ์ง„ํ–‰ํ•ฉ์‹œ๋‹ค. @@ -266,7 +260,7 @@ UserInDB( } ``` -### ๋น„ํ™œ์„ฑ๋œ ์œ ์ € +### ๋น„ํ™œ์„ฑ๋œ ์œ ์ € { #inactive-user } ์ด์ œ ๋น„ํ™œ์„ฑ๋œ ์‚ฌ์šฉ์ž๋กœ ์‹œ๋„ํ•˜๊ณ , ์ธ์ฆํ•ด๋ด…์‹œ๋‹ค: @@ -284,7 +278,7 @@ UserInDB( } ``` -## ์š”์•ฝ +## ์š”์•ฝ { #recap } ์ด์ œ API์— ๋Œ€ํ•œ `username` ๋ฐ `password`๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์™„์ „ํ•œ ๋ณด์•ˆ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/sql-databases.md b/docs/ko/docs/tutorial/sql-databases.md index 58c7017d6a..3d64cf627a 100644 --- a/docs/ko/docs/tutorial/sql-databases.md +++ b/docs/ko/docs/tutorial/sql-databases.md @@ -1,18 +1,18 @@ -# SQL (๊ด€๊ณ„ํ˜•) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค +# SQL (๊ด€๊ณ„ํ˜•) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค { #sql-relational-databases } -**FastAPI**์—์„œ SQL(๊ด€๊ณ„ํ˜•) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ์€ ํ•„์ˆ˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์ด ์›ํ•˜๋Š” **์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋“ ** ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**FastAPI**์—์„œ SQL(๊ด€๊ณ„ํ˜•) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ์€ ํ•„์ˆ˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—ฌ๋Ÿฌ๋ถ„์ด ์›ํ•˜๋Š” **์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋“ ** ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>์„ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -**SQLModel**์€ <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a>์™€ Pydantic์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค.SQLModel์€ **SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค**๋ฅผ ์‚ฌ์šฉํ•˜๋Š” FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์™„๋ฒฝํžˆ ์–ด์šธ๋ฆฌ๋„๋ก **FastAPI**์˜ ์ œ์ž‘์ž๊ฐ€ ์„ค๊ณ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. +**SQLModel**์€ <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a>์™€ Pydantic์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค. **SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค**๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์™„๋ฒฝํžˆ ์–ด์šธ๋ฆฌ๋„๋ก **FastAPI**์™€ ๊ฐ™์€ ์ œ์ž‘์ž๊ฐ€ ๋งŒ๋“  ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. /// tip | ํŒ -๋‹ค๋ฅธ SQL ๋˜๋Š” NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค (์ผ๋ถ€๋Š” <abbr title="๊ฐ์ฒด ๊ด€๊ณ„ ๋งคํผ(Object Relational Mapper), SQL ํ…Œ์ด๋ธ”์„ ๋‚˜ํƒ€๋‚ด๋Š” ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•˜๊ณ  ํ…Œ์ด๋ธ”์˜ ํ–‰์„ ์ธ์Šคํ„ด์Šค๋กœ ํ‘œํ˜„ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ง€์นญํ•˜๋Š” ์šฉ์–ด">"ORM"</abbr>์ด๋ผ๊ณ ๋„ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค), FastAPI๋Š” ํŠน์ • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์‚ฌ์šฉ์„ ๊ฐ•์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž +๋‹ค๋ฅธ SQL ๋˜๋Š” NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค (์ผ๋ถ€๋Š” <abbr title="Object Relational Mapper: a fancy term for a library where some classes represent SQL tables and instances represent rows in those tables">"ORMs"</abbr>์ด๋ผ๊ณ ๋„ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค), FastAPI๋Š” ํŠน์ • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์‚ฌ์šฉ์„ ๊ฐ•์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž /// -SQLModel์€ SQLAlchemy๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฏ€๋กœ, SQLAlchemy์—์„œ **์ง€์›ํ•˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค**๋ฅผ ์†์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(SQLModel์—์„œ๋„ ๋™์ผํ•˜๊ฒŒ ์ง€์›๋ฉ๋‹ˆ๋‹ค). ์˜ˆ๋ฅผ ๋“ค๋ฉด: +SQLModel์€ SQLAlchemy๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฏ€๋กœ, SQLAlchemy์—์„œ **์ง€์›ํ•˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค**๋ฅผ ์†์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ด๊ฒƒ๋“ค์€ SQLModel์—์„œ๋„ ์ง€์›๋ฉ๋‹ˆ๋‹ค). ์˜ˆ๋ฅผ ๋“ค๋ฉด: * PostgreSQL * MySQL @@ -20,19 +20,19 @@ SQLModel์€ SQLAlchemy๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฏ€๋กœ, SQLAlchemy์—์„œ **์ง€์›ํ•˜ * Oracle * Microsoft SQL Server ๋“ฑ. -์ด ์˜ˆ์ œ์—์„œ๋Š” **SQLite**๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. SQLite๋Š” ๋‹จ์ผ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๊ณ  ํŒŒ์ด์ฌ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ์˜ˆ์ œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•˜์—ฌ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ์˜ˆ์ œ์—์„œ๋Š” **SQLite**๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. SQLite๋Š” ๋‹จ์ผ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๊ณ  Python์—์„œ ํ†ตํ•ฉ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ์˜ˆ์ œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•˜์—ฌ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋‚˜์ค‘์— ์‹ค์ œ ํ”„๋กœ๋•์…˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” **PostgreSQL**๊ณผ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. +๋‚˜์ค‘์— ํ”„๋กœ๋•์…˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” **PostgreSQL**๊ณผ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. /// tip | ํŒ -**FastAPI**์™€ **PostgreSQL**๋ฅผ ํฌํ•จํ•˜์—ฌ ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋‹ค์–‘ํ•œ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ณต์‹ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ๊ธฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a> +ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋” ๋งŽ์€ ๋„๊ตฌ๋ฅผ ํฌํ•จํ•˜์—ฌ **FastAPI**์™€ **PostgreSQL**์„ ํฌํ•จํ•œ ๊ณต์‹ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ๊ธฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a> /// -์ด ํŠœํ† ๋ฆฌ์–ผ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ์งง์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ณธ ๊ฐœ๋…, SQL, ๋˜๋Š” ๋” ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด, <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel ๋ฌธ์„œ</a>๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. +์ด ํŠœํ† ๋ฆฌ์–ผ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ์งง์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ณธ ๊ฐœ๋…, SQL, ๋˜๋Š” ๋” ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด, <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel ๋ฌธ์„œ</a>๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. -## `SQLModel` ์„ค์น˜ํ•˜๊ธฐ +## `SQLModel` ์„ค์น˜ํ•˜๊ธฐ { #install-sqlmodel } ๋จผ์ €, [๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ์ƒ์„ฑํ•˜๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, `sqlmodel`์„ ์„ค์น˜ํ•˜์„ธ์š”: @@ -45,13 +45,13 @@ $ pip install sqlmodel </div> -## ๋‹จ์ผ ๋ชจ๋ธ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒ์„ฑํ•˜๊ธฐ +## ๋‹จ์ผ ๋ชจ๋ธ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒ์„ฑํ•˜๊ธฐ { #create-the-app-with-a-single-model } ์šฐ์„  ๋‹จ์ผ **SQLModel** ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์ฒซ ๋ฒˆ์งธ ๋ฒ„์ „์„ ์ƒ์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -์ดํ›„ **๋‹ค์ค‘ ๋ชจ๋ธ**์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ณด์•ˆ๊ณผ ์œ ์—ฐ์„ฑ์„ ๊ฐ•ํ™”ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿค“ +์ดํ›„ ์•„๋ž˜์—์„œ **์—ฌ๋Ÿฌ ๋ชจ๋ธ**๋กœ ๋ณด์•ˆ๊ณผ ์œ ์—ฐ์„ฑ์„ ๊ฐ•ํ™”ํ•˜๋ฉฐ ๊ฐœ์„ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ -### ๋ชจ๋ธ ์ƒ์„ฑํ•˜๊ธฐ +### ๋ชจ๋ธ ์ƒ์„ฑํ•˜๊ธฐ { #create-models } `SQLModel`์„ ๊ฐ€์ ธ์˜ค๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: @@ -61,45 +61,45 @@ $ pip install sqlmodel ๋ช‡ ๊ฐ€์ง€ ์ฐจ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค: -* `table=True`๋Š” SQLModel์— ์ด ๋ชจ๋ธ์ด *ํ…Œ์ด๋ธ” ๋ชจ๋ธ*์ด๋ฉฐ, ๋‹จ์ˆœํ•œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ **ํ…Œ์ด๋ธ”**์„ ๋‚˜ํƒ€๋‚ธ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. (๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ Pydantic ํด๋ž˜์Šค์ฒ˜๋Ÿผ) ๋‹จ์ˆœํ•œ *๋ฐ์ดํ„ฐ ๋ชจ๋ธ*์ด ์•„๋‹™๋‹ˆ๋‹ค. +* `table=True`๋Š” SQLModel์— ์ด ๋ชจ๋ธ์ด *ํ…Œ์ด๋ธ” ๋ชจ๋ธ*์ด๋ฉฐ, SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ **ํ…Œ์ด๋ธ”**์„ ๋‚˜ํƒ€๋‚ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. (๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ Pydantic ํด๋ž˜์Šค์ฒ˜๋Ÿผ) ๋‹จ์ˆœํ•œ *๋ฐ์ดํ„ฐ ๋ชจ๋ธ*์ด ์•„๋‹™๋‹ˆ๋‹ค. * `Field(primary_key=True)`๋Š” SQLModel์— `id`๊ฐ€ SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ **๊ธฐ๋ณธ ํ‚ค**์ž„์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค (SQL ๊ธฐ๋ณธ ํ‚ค์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ SQLModel ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”). - `int | None` ์œ ํ˜•์œผ๋กœ ์„ค์ •ํ•˜๋ฉด, SQLModel์€ ํ•ด๋‹น ์—ด์ด SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ `INTEGER` ์œ ํ˜•์ด๋ฉฐ `NULLABLE` ๊ฐ’์ด์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + **์ฐธ๊ณ :** ๊ธฐ๋ณธ ํ‚ค ํ•„๋“œ์— `int | None`์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š”, Python ์ฝ”๋“œ์—์„œ *`id` ์—†์ด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ*ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค(`id=None`). ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ *์ €์žฅํ•  ๋•Œ ์ƒ์„ฑํ•ด ์ค„ ๊ฒƒ*์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. SQLModel์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ `id`๋ฅผ ์ œ๊ณตํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ดํ•ดํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ์—์„œ *ํ•ด๋‹น ์—ด์„ null์ด ์•„๋‹Œ `INTEGER`*๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/#primary-key-id" class="external-link" target="_blank">๊ธฐ๋ณธ ํ‚ค์— ๋Œ€ํ•œ SQLModel ๋ฌธ์„œ</a>๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. -* `Field(index=True)`๋Š” SQLModel์— ํ•ด๋‹น ์—ด์— ๋Œ€ํ•ด **SQL ์ธ๋ฑ์Šค**๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ด ์—ด์œผ๋กœ ํ•„ํ„ฐ๋ง๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ๋•Œ ๋” ๋น ๋ฅด๊ฒŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* `Field(index=True)`๋Š” SQLModel์— ํ•ด๋‹น ์—ด์— ๋Œ€ํ•ด **SQL ์ธ๋ฑ์Šค**๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ด ์—ด๋กœ ํ•„ํ„ฐ๋ง๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ๋•Œ ๋” ๋น ๋ฅด๊ฒŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. SQLModel์€ `str`์œผ๋กœ ์„ ์–ธ๋œ ํ•ญ๋ชฉ์ด SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ `TEXT` (๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋”ฐ๋ผ `VARCHAR`) ์œ ํ˜•์˜ ์—ด๋กœ ์ €์žฅ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค. -### ์—”์ง„ ์ƒ์„ฑํ•˜๊ธฐ +### ์—”์ง„ ์ƒ์„ฑํ•˜๊ธฐ { #create-an-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 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” **ํ•˜๋‚˜์˜ ๋‹จ์ผ ์š”์ฒญ**์ด **์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ**๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: ์˜์กด์„ฑ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ). +`check_same_thread=False`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด FastAPI์—์„œ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ๋™์ผํ•œ SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” **ํ•˜๋‚˜์˜ ๋‹จ์ผ ์š”์ฒญ**์ด **๋‘˜ ์ด์ƒ์˜ ์Šค๋ ˆ๋“œ**๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: ์˜์กด์„ฑ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ). -๊ฑฑ์ •ํ•˜์ง€ ๋งˆ์„ธ์š”. ์ฝ”๋“œ๊ฐ€ ๊ตฌ์กฐํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ์ธํ•ด, ์ดํ›„์— **๊ฐ ์š”์ฒญ๋งˆ๋‹ค ๋‹จ์ผ SQLModel *์„ธ์…˜*์„ ์‚ฌ์šฉ**ํ•˜๋„๋ก ๋ณด์žฅํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ๊ทธ๊ฒƒ์ด `check_same_thread`๊ฐ€ ํ•˜๋ ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๊ฑฑ์ •ํ•˜์ง€ ๋งˆ์„ธ์š”. ์ฝ”๋“œ๊ฐ€ ๊ตฌ์กฐํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ์ธํ•ด, ์ดํ›„์— **๊ฐ ์š”์ฒญ๋งˆ๋‹ค ๋‹จ์ผ 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`์„ ํ†ตํ•ด** ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค. `yield`๋ฅผ ์‚ฌ์šฉํ•ด FastAPI์˜ **์˜์กด์„ฑ**์„ ์ƒ์„ฑํ•˜์—ฌ ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ๋กœ์šด `Session`์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์š”์ฒญ๋‹น ํ•˜๋‚˜์˜ ์„ธ์…˜๋งŒ ์‚ฌ์šฉ๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ๐Ÿค“ -๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๊ฐ„์†Œํ™”ํ•˜๊ธฐ ์œ„ํ•ด `Annotated` ์˜์กด์„ฑ `SessionDep`์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋ฅผ ๊ฐ„์†Œํ™”ํ•˜๊ธฐ ์œ„ํ•ด `Annotated` ์˜์กด์„ฑ `SessionDep`์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} -### ์‹œ์ž‘ ์‹œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ์ƒ์„ฑํ•˜๊ธฐ +### ์‹œ์ž‘ ์‹œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ์ƒ์„ฑํ•˜๊ธฐ { #create-database-tables-on-startup } ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. @@ -115,9 +115,9 @@ SQLModel์€ Alembic์„ ๊ฐ์‹ธ๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์ œ๊ณตํ•  /// -### Hero ์ƒ์„ฑํ•˜๊ธฐ +### Hero ์ƒ์„ฑํ•˜๊ธฐ { #create-a-hero } -๊ฐ SQLModel ๋ชจ๋ธ์€ Pydantic ๋ชจ๋ธ์ด๊ธฐ๋„ ํ•˜๋ฏ€๋กœ, Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” **ํƒ€์ž… ์–ด๋…ธํ…Œ์ด**์…˜์—์„œ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ฐ SQLModel ๋ชจ๋ธ์€ Pydantic ๋ชจ๋ธ์ด๊ธฐ๋„ ํ•˜๋ฏ€๋กœ, Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋™์ผํ•œ **ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜**์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ `Hero` ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•˜๋ฉด **JSON ๋ณธ๋ฌธ**์—์„œ ๊ฐ’์„ ์ฝ์–ด์˜ต๋‹ˆ๋‹ค. @@ -125,31 +125,29 @@ SQLModel์€ Alembic์„ ๊ฐ์‹ธ๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์ œ๊ณตํ•  {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} -</details> +์—ฌ๊ธฐ์„œ๋Š” `SessionDep` ์˜์กด์„ฑ(`Session`)์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด `Hero`๋ฅผ `Session` ์ธ์Šคํ„ด์Šค์— ์ถ”๊ฐ€ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ปค๋ฐ‹ํ•˜๊ณ , `hero` ๋ฐ์ดํ„ฐ์˜ ์ตœ์‹  ์ƒํƒœ๋ฅผ ๊ฐฑ์‹ ํ•œ ๋‹ค์Œ ์ด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -์—ฌ๊ธฐ์„œ `SessionDep` ์˜์กด์„ฑ (์ฆ‰, `Session`)์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด `Hero`๋ฅผ `Session` ์ธ์Šคํ„ด์Šค์— ์ถ”๊ฐ€ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ปค๋ฐ‹ํ•˜๊ณ , `hero` ๋ฐ์ดํ„ฐ์˜ ์ตœ์‹  ์ƒํƒœ๋ฅผ ๊ฐฑ์‹ ํ•œ ๋‹ค์Œ ์ด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. - -### Heroes ์กฐํšŒํ•˜๊ธฐ +### Heroes ์กฐํšŒํ•˜๊ธฐ { #read-heroes } `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`๋ฅผ **์กฐํšŒ**ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} -### Hero ์‚ญ์ œํ•˜๊ธฐ +### Hero ์‚ญ์ œํ•˜๊ธฐ { #delete-a-hero } `Hero`๋ฅผ **์‚ญ์ œ**ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} -### ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ํ•˜๊ธฐ +### ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ํ•˜๊ธฐ { #run-the-app } -์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <div class="termy"> @@ -161,33 +159,33 @@ $ fastapi dev main.py </div> -๊ทธ๋Ÿฐ ๋‹ค์Œ `/docs` UI๋กœ ์ด๋™ํ•˜๋ฉด, **FastAPI**๊ฐ€ ํ•ด๋‹น **model๋“ค**์„ ์‚ฌ์šฉํ•˜์—ฌ API **๋ฌธ์„œ๋ฅผ ์ƒ์„ฑ**ํ•˜๋Š” ๊ฒƒ์œผ๋ฅด ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด ๋ชจ๋ธ๋“ค์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ง๋ ฌํ™”ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š” ๋ฐ์—๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ `/docs` UI๋กœ ์ด๋™ํ•˜๋ฉด, **FastAPI**๊ฐ€ ์ด **๋ชจ๋ธ**๋“ค์„ ์‚ฌ์šฉํ•ด API๋ฅผ **๋ฌธ์„œํ™”**ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ **์ง๋ ฌํ™”**ํ•˜๊ณ  **๊ฒ€์ฆ**ํ•˜๋Š” ๋ฐ์—๋„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <div class="screenshot"> <img src="/img/tutorial/sql-databases/image01.png"> </div> -## ์—ฌ๋Ÿฌ ๋ชจ๋ธ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—…๋ฐ์ดํŠธ +## ์—ฌ๋Ÿฌ ๋ชจ๋ธ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—…๋ฐ์ดํŠธ { #update-the-app-with-multiple-models } -์ด์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์•ฝ๊ฐ„ **๋ฆฌํŒฉํ† ๋ง**ํ•˜์—ฌ **๋ณด์•ˆ**๊ณผ **์œ ์—ฐ์„ฑ**์„ ๊ฐœ์„ ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. +์ด์ œ ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์•ฝ๊ฐ„ **๋ฆฌํŒฉํ„ฐ๋ง**ํ•˜์—ฌ **๋ณด์•ˆ**๊ณผ **์œ ์—ฐ์„ฑ**์„ ๊ฐœ์„ ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -์ด์ „ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ UI๋ฅผ ๋ณด๋ฉด, ์ง€๊ธˆ๊นŒ์ง€๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ƒ์„ฑํ•  `Hero`์˜ `id`๋ฅผ ์ง์ ‘ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ฑ +์ด์ „ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ™•์ธํ•ด ๋ณด๋ฉด, ์ง€๊ธˆ๊นŒ์ง€๋Š” UI์—์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ƒ์„ฑํ•  `Hero`์˜ `id`๋ฅผ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ฑ -์ด๋Š” ํ—ˆ์šฉ๋˜์–ด์„  ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ด๋ฏธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ `id`๋ฅผ ๋ฎ์–ด์“ธ ์œ„ํ—˜์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. `id`๋Š” **๋ฐฑ์—”๋“œ** ๋˜๋Š” **๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค**๊ฐ€ ๊ฒฐ์ •ํ•ด์•ผ ํ•˜๋ฉฐ, **ํด๋ผ์ด์–ธํŠธ**๊ฐ€ ๊ฒฐ์ •ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ DB์— ์ด๋ฏธ ํ• ๋‹น๋˜์–ด ์žˆ๋Š” `id`๋ฅผ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. `id`๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒƒ์€ **๋ฐฑ์—”๋“œ** ๋˜๋Š” **๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค**๊ฐ€ ํ•ด์•ผ ํ•˜๋ฉฐ, **ํด๋ผ์ด์–ธํŠธ**๊ฐ€ ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. -๋˜ํ•œ hero์˜ `secret_name`์„ ์ƒ์„ฑํ•˜๊ธด ํ–ˆ์ง€๋งŒ, ์ง€๊ธˆ๊นŒ์ง€๋Š” ์ด ๊ฐ’์„ ์–ด๋””์—์„œ๋‚˜ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ทธ๋‹ค์ง€ **๋น„๋ฐ€์Šค๋Ÿฝ์ง€** ์•Š์Šต๋‹ˆ๋‹ค... ๐Ÿ˜… +๋˜ํ•œ hero์— ๋Œ€ํ•œ `secret_name`์„ ์ƒ์„ฑํ•˜์ง€๋งŒ, ์ง€๊ธˆ๊นŒ์ง€๋Š” ์ด ๊ฐ’์„ ์–ด๋””์—์„œ๋‚˜ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ทธ๋‹ค์ง€ **๋น„๋ฐ€์Šค๋Ÿฝ์ง€** ์•Š์Šต๋‹ˆ๋‹ค... ๐Ÿ˜… -์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ช‡ ๊ฐ€์ง€ **์ถ”๊ฐ€ ๋ชจ๋ธ**์„ ์ถ”๊ฐ€ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์—ฌ๊ธฐ์„œ SQLModel์ด ๋น›์„ ๋ฐœํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. โœจ +์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋Š” ๋ช‡ ๊ฐ€์ง€ **์ถ”๊ฐ€ ๋ชจ๋ธ**์„ ์ถ”๊ฐ€ํ•ด ํ•ด๊ฒฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์—ฌ๊ธฐ์„œ SQLModel์ด ๋น›์„ ๋ฐœํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. โœจ -### ์—ฌ๋Ÿฌ ๋ชจ๋ธ ์ƒ์„ฑํ•˜๊ธฐ +### ์—ฌ๋Ÿฌ ๋ชจ๋ธ ์ƒ์„ฑํ•˜๊ธฐ { #create-multiple-models } **SQLModel**์—์„œ `table=True`๊ฐ€ ์„ค์ •๋œ ๋ชจ๋ธ ํด๋ž˜์Šค๋Š” **ํ…Œ์ด๋ธ” ๋ชจ๋ธ**์ž…๋‹ˆ๋‹ค. -`table=True`๊ฐ€ ์—†๋Š” ๋ชจ๋ธ ํด๋ž˜์Šค๋Š” **๋ฐ์ดํ„ฐ ๋ชจ๋ธ**๋กœ, ์ด๋Š” ์‹ค์ œ๋กœ ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ Pydantic ๋ชจ๋ธ์— ๋ถˆ๊ณผํ•ฉ๋‹ˆ๋‹ค. ๐Ÿค“ +๊ทธ๋ฆฌ๊ณ  `table=True`๊ฐ€ ์—†๋Š” ๋ชจ๋ธ ํด๋ž˜์Šค๋Š” **๋ฐ์ดํ„ฐ ๋ชจ๋ธ**์ธ๋ฐ, ์ด๊ฒƒ๋“ค์€ ์‹ค์ œ๋กœ๋Š” (๋ช‡ ๊ฐ€์ง€ ์ž‘์€ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์ด ์žˆ๋Š”) Pydantic ๋ชจ๋ธ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ๐Ÿค“ SQLModel์„ ์‚ฌ์šฉํ•˜๋ฉด **์ƒ์†**์„ ํ†ตํ•ด ๋ชจ๋“  ๊ฒฝ์šฐ์— ํ•„๋“œ๋ฅผ **์ค‘๋ณต ์„ ์–ธํ•˜์ง€ ์•Š์•„๋„** ๋ฉ๋‹ˆ๋‹ค. -#### `HeroBase` - ๊ธฐ๋ณธ ํด๋ž˜์Šค +#### `HeroBase` - ๊ธฐ๋ณธ ํด๋ž˜์Šค { #herobase-the-base-class } ๋ชจ๋“  ๋ชจ๋ธ์—์„œ **๊ณต์œ ๋˜๋Š” ํ•„๋“œ**๋ฅผ ๊ฐ€์ง„ `HeroBase` ๋ชจ๋ธ์„ ์‹œ์ž‘ํ•ด ๋ด…์‹œ๋‹ค: @@ -196,14 +194,14 @@ SQLModel์„ ์‚ฌ์šฉํ•˜๋ฉด **์ƒ์†**์„ ํ†ตํ•ด ๋ชจ๋“  ๊ฒฝ์šฐ์— ํ•„๋“œ๋ฅผ **์ค‘ {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} -#### `Hero` - *ํ…Œ์ด๋ธ” ๋ชจ๋ธ* +#### `Hero` - *ํ…Œ์ด๋ธ” ๋ชจ๋ธ* { #hero-the-table-model } ๋‹ค์Œ์œผ๋กœ ์‹ค์ œ *ํ…Œ์ด๋ธ” ๋ชจ๋ธ*์ธ `Hero`๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ชจ๋ธ์€ ๋‹ค๋ฅธ ๋ชจ๋ธ์—๋Š” ํ•ญ์ƒ ํฌํ•จ๋˜๋Š” ๊ฑด ์•„๋‹Œ **์ถ”๊ฐ€ ํ•„๋“œ**๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค: * `id` * `secret_name` -`Hero`๋Š” `HeroBase`๋ฅผ ์ƒ์†ํ•˜๋ฏ€๋กœ `HeroBase`์— ์„ ์–ธ๋œ ํ•„๋“œ๋„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ `Hero`๋Š” ๋‹ค์Œ **ํ•„๋“œ๋“ค๋„** ๊ฐ€์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: +`Hero`๋Š” `HeroBase`๋ฅผ ์ƒ์†ํ•˜๋ฏ€๋กœ `HeroBase`์— ์„ ์–ธ๋œ ํ•„๋“œ๋„ **๋˜ํ•œ** ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ `Hero`์˜ ๋ชจ๋“  ํ•„๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: * `id` * `name` @@ -212,11 +210,11 @@ SQLModel์„ ์‚ฌ์šฉํ•˜๋ฉด **์ƒ์†**์„ ํ†ตํ•ด ๋ชจ๋“  ๊ฒฝ์šฐ์— ํ•„๋“œ๋ฅผ **์ค‘ {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} -#### `HeroPublic` - ๊ณต๊ฐœ *๋ฐ์ดํ„ฐ ๋ชจ๋ธ* +#### `HeroPublic` - ๊ณต๊ฐœ *๋ฐ์ดํ„ฐ ๋ชจ๋ธ* { #heropublic-the-public-data-model } ๋‹ค์Œ์œผ๋กœ `HeroPublic` ๋ชจ๋ธ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ชจ๋ธ์€ API ํด๋ผ์ด์–ธํŠธ์— **๋ฐ˜ํ™˜**๋˜๋Š” ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. -`HeroPublic`์€ `HeroBase`์™€ ๋™์ผํ•œ ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๋ฉฐ, `secret_name`์€ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +`HeroPublic`์€ `HeroBase`์™€ ๋™์ผํ•œ ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ, `secret_name`์€ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋งˆ์นจ๋‚ด ์šฐ๋ฆฌ์˜ heroes์˜ ์ •์ฒด๊ฐ€ ๋ณดํ˜ธ๋ฉ๋‹ˆ๋‹ค! ๐Ÿฅท @@ -224,9 +222,9 @@ SQLModel์„ ์‚ฌ์šฉํ•˜๋ฉด **์ƒ์†**์„ ํ†ตํ•ด ๋ชจ๋“  ๊ฒฝ์šฐ์— ํ•„๋“œ๋ฅผ **์ค‘ /// tip | ํŒ -๋ฐ˜ํ™˜ ๋ชจ๋ธ์ด ๊ฐ’์ด ํ•ญ์ƒ ์กด์žฌํ•˜๊ณ  ํ•ญ์ƒ `int`(`None`์ด ์•„๋‹˜)๋ฅผ ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์€ API ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด API์™€ ํ†ต์‹ ํ•˜๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ํ›จ์”ฌ ๋” ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ฐ˜ํ™˜ ๋ชจ๋ธ์ด ๊ฐ’์ด ํ•ญ์ƒ ์กด์žฌํ•˜๊ณ  ํ•ญ์ƒ `int`(`None`์ด ์•„๋‹˜)๋ฅผ ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์€ API ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด API ํด๋ผ์ด์–ธํŠธ๋Š” ์ด๋Ÿฐ ํ™•์‹ ์„ ๋ฐ”ํƒ•์œผ๋กœ ํ›จ์”ฌ ๋” ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋˜ํ•œ **์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ํด๋ผ์ด์–ธํŠธ**๋Š” ๋” ๋‹จ์ˆœํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๋ฏ€๋กœ, API์™€ ์†Œํ†ตํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋“ค์ด ํ›จ์”ฌ ์ˆ˜์›”ํ•˜๊ฒŒ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž +๋˜ํ•œ **์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ํด๋ผ์ด์–ธํŠธ**๋Š” ๋” ๋‹จ์ˆœํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๋ฏ€๋กœ, API์™€ ์†Œํ†ตํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋“ค์ด API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ํ›จ์”ฌ ๋” ์ข‹์€ ๊ฒฝํ—˜์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž /// @@ -235,23 +233,22 @@ SQLModel์„ ์‚ฌ์šฉํ•˜๋ฉด **์ƒ์†**์„ ํ†ตํ•ด ๋ชจ๋“  ๊ฒฝ์šฐ์— ํ•„๋“œ๋ฅผ **์ค‘ * `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์„` ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. +`HeroCreate`๋Š” `HeroBase`์™€ ๋™์ผํ•œ ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๋ฉฐ, `secret_name`๋„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. -ํด๋ผ์ด์–ธํŠธ๊ฐ€ **์ƒˆ hero์„ ์ƒ์„ฑ**ํ•  ๋•Œ `secret_name`์„ ๋ณด๋‚ด๊ณ , ์ด๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜์ง€๋งŒ, ํ•ด๋‹น ๋น„๋ฐ€ ์ด๋ฆ„์€ API๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +์ด์ œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ **์ƒˆ hero๋ฅผ ์ƒ์„ฑ**ํ•  ๋•Œ `secret_name`์„ ๋ณด๋‚ด๋ฉด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜์ง€๋งŒ, ๊ทธ ๋น„๋ฐ€ ์ด๋ฆ„์€ API๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. /// tip | ํŒ ์ด ๋ฐฉ์‹์€ **๋น„๋ฐ€๋ฒˆํ˜ธ**๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ›์ง€๋งŒ, ์ด๋ฅผ API์—์„œ ๋ฐ˜ํ™˜ํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. -๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ’์„ ์ €์žฅํ•˜๊ธฐ ์ „์— **ํ•ด์‹ฑ**ํ•˜์—ฌ ์ €์žฅํ•˜๊ณ , **ํ‰๋ฌธ์œผ๋กœ ์ €์žฅํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค**. +๋˜ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ’์„ ์ €์žฅํ•˜๊ธฐ ์ „์— **ํ•ด์‹ฑ**ํ•˜์—ฌ ์ €์žฅํ•˜๊ณ , **ํ‰๋ฌธ์œผ๋กœ ์ €์žฅํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค**. /// @@ -263,15 +260,15 @@ SQLModel์„ ์‚ฌ์šฉํ•˜๋ฉด **์ƒ์†**์„ ํ†ตํ•ด ๋ชจ๋“  ๊ฒฝ์šฐ์— ํ•„๋“œ๋ฅผ **์ค‘ {* ../../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๋ฅผ ์ˆ˜์ •**ํ•  ๋ฐฉ๋ฒ•์ด ์—†์—ˆ์ง€๋งŒ, ์ด์ œ **๋‹ค์ค‘ ๋ชจ๋ธ**์„ ํ†ตํ•ด ์ˆ˜์ • ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ +์ด์ „ ๋ฒ„์ „์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” **hero๋ฅผ ์ˆ˜์ •**ํ•  ๋ฐฉ๋ฒ•์ด ์—†์—ˆ์ง€๋งŒ, ์ด์ œ **์—ฌ๋Ÿฌ ๋ชจ๋ธ**๋กœ ์ด๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ -`HeroUpdate` *๋ฐ์ดํ„ฐ ๋ชจ๋ธ*์€ ์•ฝ๊ฐ„ ํŠน๋ณ„ํ•œ๋ฐ, ์ƒˆ hero์„ ์ƒ์„ฑํ•  ๋•Œ ํ•„์š”ํ•œ **๋ชจ๋“  ๋™์ผํ•œ ํ•„๋“œ**๋ฅผ ๊ฐ€์ง€์ง€๋งŒ, ๋ชจ๋“  ํ•„๋“œ๊ฐ€ **์„ ํƒ์ **(๊ธฐ๋ณธ๊ฐ’์ด ์žˆ์Œ)์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด hero์„ ์ˆ˜์ •ํ•  ๋•Œ ์ˆ˜์ •ํ•˜๋ ค๋Š” ํ•„๋“œ๋งŒ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`HeroUpdate` *๋ฐ์ดํ„ฐ ๋ชจ๋ธ*์€ ์•ฝ๊ฐ„ ํŠน๋ณ„ํ•œ๋ฐ, ์ƒˆ hero๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ํ•„์š”ํ•œ **๋ชจ๋“  ๋™์ผํ•œ ํ•„๋“œ**๋ฅผ ๊ฐ€์ง€์ง€๋งŒ, ๋ชจ๋“  ํ•„๋“œ๊ฐ€ **์„ ํƒ์ **(๊ธฐ๋ณธ๊ฐ’์ด ์žˆ์Œ)์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด hero๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ ์ˆ˜์ •ํ•˜๋ ค๋Š” ํ•„๋“œ๋งŒ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋ชจ๋“  **ํ•„๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ธฐ** ๋•Œ๋ฌธ์—(ํƒ€์ž…์ด `None`์„ ํฌํ•จํ•˜๊ณ , ๊ธฐ๋ณธ๊ฐ’์ด `None`์œผ๋กœ ์„ค์ •๋จ), ๋ชจ๋“  ํ•„๋“œ๋ฅผ **๋‹ค์‹œ ์„ ์–ธ**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +๋ชจ๋“  **ํ•„๋“œ๊ฐ€ ์‹ค์ œ๋กœ ๋ณ€๊ฒฝ**๋˜๊ธฐ ๋•Œ๋ฌธ์—(ํƒ€์ž…์ด ์ด์ œ `None`์„ ํฌํ•จํ•˜๊ณ , ๊ธฐ๋ณธ๊ฐ’๋„ ์ด์ œ `None`์ด ๋จ), ์šฐ๋ฆฌ๋Š” ํ•„๋“œ๋ฅผ **๋‹ค์‹œ ์„ ์–ธ**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -์—„๋ฐ€ํžˆ ๋งํ•˜๋ฉด `HeroBase`๋ฅผ ์ƒ์†ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๋‹ค์‹œ ์„ ์–ธํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด ์ƒ์†์„ ์œ ์ง€ํ•˜๊ธด ํ–ˆ์ง€๋งŒ, ํ•„์ˆ˜๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐœ์ธ์ ์ธ ์ทจํ–ฅ์˜ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ๐Ÿคท +`HeroBase`๋ฅผ ์ƒ์†ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๋‹ค์‹œ ์„ ์–ธํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด ์ƒ์†์„ ์œ ์ง€ํ•˜๊ธด ํ–ˆ์ง€๋งŒ, ํ•„์ˆ˜๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐœ์ธ์ ์ธ ์ทจํ–ฅ์˜ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ๐Ÿคท `HeroUpdate`์˜ ํ•„๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: @@ -281,61 +278,61 @@ SQLModel์„ ์‚ฌ์šฉํ•˜๋ฉด **์ƒ์†**์„ ํ†ตํ•ด ๋ชจ๋“  ๊ฒฝ์šฐ์— ํ•„๋“œ๋ฅผ **์ค‘ {* ../../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`๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ `response_model`๋กœ `HeroPublic` *๋ฐ์ดํ„ฐ ๋ชจ๋ธ*์„ ์„ ์–ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, **FastAPI**๋Š” `HeroPublic`์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ์ง๋ ฌํ™”ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋™์ผํ•œ *ํ…Œ์ด๋ธ” ๋ชจ๋ธ* `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` ๋Œ€์‹  `response_model=HeroPublic`์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์ด ์‹ค์ œ๋กœ `HeroPublic`์ด *์•„๋‹ˆ๊ธฐ* ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -๋งŒ์•ฝ `-> HeroPublic`์œผ๋กœ ์„ ์–ธํ–ˆ๋‹ค๋ฉด, ์—๋””ํ„ฐ์™€ ๋ฆฐํ„ฐ์—์„œ ๋ฐ˜ํ™˜๊ฐ’์ด `HeroPublic`์ด ์•„๋‹ˆ๋ผ `Hero`๋ผ๊ณ  ๊ฒฝ๊ณ ํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์ ์ ˆํ•œ ๊ฒฝ๊ณ ์ž…๋‹ˆ๋‹ค. +๋งŒ์•ฝ `-> HeroPublic`์œผ๋กœ ์„ ์–ธํ–ˆ๋‹ค๋ฉด, ์—๋””ํ„ฐ์™€ ๋ฆฐํ„ฐ์—์„œ `HeroPublic` ๋Œ€์‹  `Hero`๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๊ณ  (๋‹น์—ฐํžˆ) ๋ถˆํ‰ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -`response_model`์— ์„ ์–ธํ•จ์œผ๋กœ์จ **FastAPI**๊ฐ€ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜๊ณ , ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ์—๋””ํ„ฐ ๋ฐ ๋‹ค๋ฅธ ๋„๊ตฌ์˜ ๋„์›€์—๋Š” ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. +`response_model`์— ์„ ์–ธํ•จ์œผ๋กœ์จ **FastAPI**๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜๊ณ , ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ์—๋””ํ„ฐ ๋ฐ ๋‹ค๋ฅธ ๋„๊ตฌ์˜ ๋„์›€์—๋Š” ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. /// -### `HeroPublic`์œผ๋กœ Heroes ์กฐํšŒํ•˜๊ธฐ +### `HeroPublic`์œผ๋กœ Heroes ์กฐํšŒํ•˜๊ธฐ { #read-heroes-with-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` ์ž‘์—…์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -์ฝ”๋“œ์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋”•์…”๋„ˆ๋ฆฌ ํ˜•ํƒœ(`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 **์‚ญ์ œ**๋Š” ์ด์ „๊ณผ ๊ฑฐ์˜ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. -์ด๋ฒˆ์—๋Š” ๋ชจ๋“  ๊ฒƒ์„ ๋ฆฌํŒฉํ† ๋งํ•˜๊ณ  ์‹ถ์€ ์š•๊ตฌ๋ฅผ ๋งŒ์กฑ์‹œํ‚ค์ง€ ๋ชปํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๐Ÿ˜… +์ด๋ฒˆ์—๋Š” ๋ชจ๋“  ๊ฒƒ์„ ๋ฆฌํŒฉํ„ฐ๋งํ•˜๊ณ  ์‹ถ์€ ์š•๊ตฌ๋ฅผ ๋งŒ์กฑ์‹œํ‚ค์ง€ ๋ชปํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜… {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} -### ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๊ธฐ +### ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๊ธฐ { #run-the-app-again } -๋‹ค์Œ ๋ช…๋ น์„ ์‚ฌ์šฉํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค์‹œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค์‹œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <div class="termy"> @@ -347,14 +344,14 @@ $ fastapi dev main.py </div> -`/docs` API UI๋กœ ์ด๋™ํ•˜๋ฉด ์—…๋ฐ์ดํŠธ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์˜์›…์„ ์ƒ์„ฑํ•  ๋•Œ `id`๋ฅผ ์ œ๊ณตํ•  ํ•„์š”๊ฐ€ ์—†๊ฒŒ ๋˜๋Š” ๋“ฑ์˜ ๋ณ€ํ™”๋„ ๋ณด์ž…๋‹ˆ๋‹ค. +`/docs` API UI๋กœ ์ด๋™ํ•˜๋ฉด ์ด์ œ ์—…๋ฐ์ดํŠธ๋˜์–ด ์žˆ๊ณ , hero๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ `id`๋ฅผ ๋ณด๋‚ผ ๊ฒƒ์ด๋ผ๊ณ  ๊ธฐ๋Œ€ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๋“ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <div class="screenshot"> <img src="/img/tutorial/sql-databases/image02.png"> </div> -## ์š”์•ฝ +## ์š”์•ฝ { #recap } <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a>์„ ์‚ฌ์šฉํ•˜์—ฌ SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ณ , *๋ฐ์ดํ„ฐ ๋ชจ๋ธ* ๋ฐ *ํ…Œ์ด๋ธ” ๋ชจ๋ธ*๋กœ ์ฝ”๋“œ๋ฅผ ๊ฐ„์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋” ๋งŽ์€ ๋‚ด์šฉ์„ ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด, **SQLModel** ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">SQLModel์„ **FastAPI**์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ๋” ๊ธด ๋ฏธ๋‹ˆ ํŠœํ† ๋ฆฌ์–ผ</a>๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๐Ÿš€ +๋” ๋งŽ์€ ๋‚ด์šฉ์„ ๋ฐฐ์šฐ๋ ค๋ฉด **SQLModel** ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. **FastAPI**์™€ ํ•จ๊ป˜ SQLModel์„ ์‚ฌ์šฉํ•˜๋Š” ๋” ๊ธด ๋ฏธ๋‹ˆ <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">ํŠœํ† ๋ฆฌ์–ผ</a>๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿš€ diff --git a/docs/ko/docs/tutorial/static-files.md b/docs/ko/docs/tutorial/static-files.md index 4f3e3ab286..aa4c571793 100644 --- a/docs/ko/docs/tutorial/static-files.md +++ b/docs/ko/docs/tutorial/static-files.md @@ -1,41 +1,40 @@ -# ์ •์  ํŒŒ์ผ +# ์ •์  ํŒŒ์ผ { #static-files } -'StaticFiles'๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ์ •์  ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`StaticFiles`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ์ •์  ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `StaticFiles` ์‚ฌ์šฉ +## `StaticFiles` ์‚ฌ์šฉ { #use-staticfiles } -* `StaticFiles` ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค. -* ํŠน์ • ๊ฒฝ๋กœ์— `StaticFiles()` ์ธ์Šคํ„ด์Šค๋ฅผ "๋งˆ์šดํŠธ" ํ•ฉ๋‹ˆ๋‹ค. +* `StaticFiles`๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค. +* ํŠน์ • ๊ฒฝ๋กœ์— `StaticFiles()` ์ธ์Šคํ„ด์Šค๋ฅผ "๋งˆ์šดํŠธ"ํ•ฉ๋‹ˆ๋‹ค. -{* ../../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` ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +`from starlette.staticfiles import StaticFiles`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” ๋‹จ์ง€ ๊ฐœ๋ฐœ์ž์ธ, ๋‹น์‹ ์—๊ฒŒ ํŽธ์˜๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด `fastapi.static files` ์™€ ๋™์ผํ•œ `starlett.static files`๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‚ฌ์‹ค ์ด๊ฒƒ์€ Starlett์—์„œ ์ง์ ‘ ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์ธ ์—ฌ๋Ÿฌ๋ถ„์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `fastapi.staticfiles`๋กœ `starlette.staticfiles`์™€ ๋™์ผํ•œ ๊ฒƒ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” Starlette์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. /// -### "๋งˆ์šดํŒ…" ์ด๋ž€ +### "๋งˆ์šดํŒ…"์ด๋ž€ { #what-is-mounting } -"๋งˆ์šดํŒ…"์€ ํŠน์ • ๊ฒฝ๋กœ์— ์™„์ „ํžˆ "๋…๋ฆฝ์ ์ธ" ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜๋Š”๋ฐ, ๊ทธ ํ›„ ๋ชจ๋“  ํ•˜์œ„ ๊ฒฝ๋กœ์— ๋Œ€ํ•ด์„œ๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. +"๋งˆ์šดํŒ…"์€ ํŠน์ • ๊ฒฝ๋กœ์— ์™„์ „ํ•œ "๋…๋ฆฝ์ ์ธ" ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ , ๊ทธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ชจ๋“  ํ•˜์œ„ ๊ฒฝ๋กœ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -๋งˆ์šดํŠธ๋œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ ์™„์ „ํžˆ ๋…๋ฆฝ์ ์ด๊ธฐ ๋•Œ๋ฌธ์— `APIRouter`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค. OpenAPI ๋ฐ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ ๋ฌธ์„œ๋Š” ๋งˆ์šดํŠธ๋œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๋“ฑ์—์„œ ์–ด๋–ค ๊ฒƒ๋„ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +๋งˆ์šดํŠธ๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์™„์ „ํžˆ ๋…๋ฆฝ์ ์ด๋ฏ€๋กœ `APIRouter`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ OpenAPI ๋ฐ ๋ฌธ์„œ์—๋Š” ๋งˆ์šดํŠธ๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‚ด์šฉ ๋“ฑ์ด ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -์ž์„ธํ•œ ๋‚ด์šฉ์€ **์ˆ™๋ จ๋œ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ**์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ž์„ธํ•œ ๋‚ด์šฉ์€ [๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ](../advanced/index.md){.internal-link target=_blank}์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ์„ธ๋ถ€์‚ฌํ•ญ +## ์„ธ๋ถ€์‚ฌํ•ญ { #details } -์ฒซ ๋ฒˆ์งธ `"/static"`์€ ์ด "ํ•˜์œ„ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ"์ด "๋งˆ์šดํŠธ"๋  ํ•˜์œ„ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ `"/static"`์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ๋Š” `"/static"`์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. +์ฒซ ๋ฒˆ์งธ `"/static"`์€ ์ด "ํ•˜์œ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜"์ด "๋งˆ์šดํŠธ"๋  ํ•˜์œ„ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ `"/static"`์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ๋Š” ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. -`'directory="static"`์€ ์ •์  ํŒŒ์ผ์ด ๋“ค์–ด ์žˆ๋Š” ๋””๋ ‰ํ† ๋ฆฌ์˜ ์ด๋ฆ„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. +`directory="static"`์€ ์ •์  ํŒŒ์ผ์ด ๋“ค์–ด ์žˆ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ์ด๋ฆ„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. `name="static"`์€ **FastAPI**์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฆ„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -์ด ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” "`static`"๊ณผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์‚ฌ์šฉ์ž ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ ์š”๊ตฌ ์‚ฌํ•ญ ๋ฐ ๊ตฌ์ฒด์ ์ธ ์„ธ๋ถ€ ์ •๋ณด์— ๋”ฐ๋ผ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” "`static`"๊ณผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์—ฌ๋Ÿฌ๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์š”๊ตฌ ์‚ฌํ•ญ ๋ฐ ๊ตฌ์ฒด์ ์ธ ์„ธ๋ถ€ ์ •๋ณด์— ๋งž๊ฒŒ ์กฐ์ •ํ•˜์„ธ์š”. +## ์ถ”๊ฐ€ ์ •๋ณด { #more-info } -## ์ถ”๊ฐ€ ์ •๋ณด - -์ž์„ธํ•œ ๋‚ด์šฉ๊ณผ ์„ ํƒ ์‚ฌํ•ญ์„ ๋ณด๋ ค๋ฉด <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">Starlette์˜ ์ •์  ํŒŒ์ผ์— ๊ด€ํ•œ ๋ฌธ์„œ</a>๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. +์ž์„ธํ•œ ๋‚ด์šฉ๊ณผ ์˜ต์…˜์€ <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">Starlette์˜ ์ •์  ํŒŒ์ผ ๋ฌธ์„œ</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. diff --git a/docs/ko/docs/tutorial/testing.md b/docs/ko/docs/tutorial/testing.md index 915ff6d22d..db7fb17ead 100644 --- a/docs/ko/docs/tutorial/testing.md +++ b/docs/ko/docs/tutorial/testing.md @@ -1,18 +1,18 @@ -# ํ…Œ์ŠคํŒ… +# ํ…Œ์ŠคํŒ… { #testing } -<a href="https://www.starlette.dev/testclient/" class="external-link" target="_blank">Starlette</a> ๋•๋ถ„์— **FastAPI** ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ผ์€ ์‰ฝ๊ณ  ์ฆ๊ฑฐ์šด ์ผ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +<a href="https://www.starlette.dev/testclient/" class="external-link" target="_blank">Starlette</a> ๋•๋ถ„์— **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ผ์€ ์‰ฝ๊ณ  ์ฆ๊ฑฐ์šด ์ผ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -Starlette๋Š” <a href="https://www.python-httpx.org\" class="external-link" target="_blank">HTTPX</a>๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ, ์ด๋Š” Requests๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ์นœ์ˆ™ํ•˜๊ณ  ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค. +Starlette๋Š” <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ, ์ด๋Š” Requests๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ์นœ์ˆ™ํ•˜๊ณ  ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค. -์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด FastAPI์—์„œ <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a>๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด **FastAPI**์—์„œ <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a>๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## `TestClient` ์‚ฌ์šฉํ•˜๊ธฐ +## `TestClient` ์‚ฌ์šฉํ•˜๊ธฐ { #using-testclient } /// info | ์ •๋ณด -`TestClient` ์‚ฌ์šฉํ•˜๋ ค๋ฉด, ์šฐ์„  <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a> ๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +`TestClient` ์‚ฌ์šฉํ•˜๋ ค๋ฉด, ์šฐ์„  <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -[virtual environment](../virtual-environments.md){.internal-link target=_blank} ๋ฅผ ๋งŒ๋“ค๊ณ , ํ™œ์„ฑํ™” ์‹œํ‚จ ๋’ค์— ์„ค์น˜ํ•˜์„ธ์š”. ์˜ˆ์‹œ: +[virtual environment](../virtual-environments.md){.internal-link target=_blank}๋ฅผ ๋งŒ๋“ค๊ณ , ํ™œ์„ฑํ™” ์‹œํ‚จ ๋’ค์— ์„ค์น˜ํ•˜์„ธ์š”. ์˜ˆ์‹œ: ```console $ pip install httpx @@ -20,52 +20,51 @@ $ pip install httpx /// -`TestClient` ๋ฅผ ์ž„ํฌํŠธํ•˜์„ธ์š”. +`TestClient`๋ฅผ ์ž„ํฌํŠธํ•˜์„ธ์š”. -**FastAPI** ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ „๋‹ฌํ•˜์—ฌ `TestClient` ๋ฅผ ๋งŒ๋“œ์„ธ์š”. +**FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ „๋‹ฌํ•˜์—ฌ `TestClient`๋ฅผ ๋งŒ๋“œ์„ธ์š”. -์ด๋ฆ„์ด `test_` ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“œ์„ธ์š”(`pytest` ์˜ ํ‘œ์ค€์ ์ธ ๊ด€๋ก€์ž…๋‹ˆ๋‹ค). +์ด๋ฆ„์ด `test_`๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“œ์„ธ์š”(`pytest`์˜ ํ‘œ์ค€์ ์ธ ๊ด€๋ก€์ž…๋‹ˆ๋‹ค). -`httpx` ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ `TestClient` ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. +`httpx`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ `TestClient` ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. -ํ‘œ์ค€์ ์ธ ํŒŒ์ด์ฌ ๋ฌธ๋ฒ•์„ ์ด์šฉํ•˜์—ฌ ํ™•์ธ์ด ํ•„์š”ํ•œ ๊ณณ์— ๊ฐ„๋‹จํ•œ `assert` ๋ฌธ์žฅ์„ ์ž‘์„ฑํ•˜์„ธ์š”(์—ญ์‹œ ํ‘œ์ค€์ ์ธ `pytest` ๊ด€๋ก€์ž…๋‹ˆ๋‹ค). +ํ‘œ์ค€์ ์ธ ํŒŒ์ด์ฌ ํ‘œํ˜„์‹์œผ๋กœ ํ™•์ธ์ด ํ•„์š”ํ•œ ๊ณณ์— ๊ฐ„๋‹จํ•œ `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 | ํŒ -ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํ•จ์ˆ˜๋Š” `async def` ๊ฐ€ ์•„๋‹ˆ๋ผ `def` ๋กœ ์ž‘์„ฑ๋จ์— ์ฃผ์˜ํ•˜์„ธ์š”. +ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํ•จ์ˆ˜๋Š” `async def`๊ฐ€ ์•„๋‹ˆ๋ผ `def`๋กœ ์ž‘์„ฑ๋จ์— ์ฃผ์˜ํ•˜์„ธ์š”. -๊ทธ๋ฆฌ๊ณ  ํด๋ผ์ด์–ธํŠธ์— ๋Œ€ํ•œ ํ˜ธ์ถœ๋„ `await` ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ผ๋ฐ˜ ํ˜ธ์ถœ์ž…๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ํด๋ผ์ด์–ธํŠธ์— ๋Œ€ํ•œ ํ˜ธ์ถœ๋„ `await`๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ผ๋ฐ˜ ํ˜ธ์ถœ์ž…๋‹ˆ๋‹ค. -์ด๋ ‡๊ฒŒ ํ•˜์—ฌ ๋ณต์žกํ•œ ๊ณผ์ • ์—†์ด `pytest` ๋ฅผ ์ง์ ‘์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜์—ฌ ๋ณต์žกํ•œ ๊ณผ์ • ์—†์ด `pytest`๋ฅผ ์ง์ ‘์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ +/// note Technical Details | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ `from starlette.testclient import TestClient` ์—ญ์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI** ๋Š” ๊ฐœ๋ฐœ์ž์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `starlette.testclient` ๋ฅผ `fastapi.testclient` ๋กœ๋„ ์ œ๊ณตํ•  ๋ฟ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๋‹จ์ง€ `Starlette` ์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜ค๋Š”์ง€์˜ ์ฐจ์ด์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `starlette.testclient`๋ฅผ `fastapi.testclient`๋กœ๋„ ์ œ๊ณตํ•  ๋ฟ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” Starlette์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. /// /// tip | ํŒ -FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ ์™ธ์—๋„ ํ…Œ์ŠคํŠธ์—์„œ `async` ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด (์˜ˆ: ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ•จ์ˆ˜), ์‹ฌํ™” ํŠœํ† ๋ฆฌ์–ผ์˜ [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. +FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ ์™ธ์—๋„ ํ…Œ์ŠคํŠธ์—์„œ `async` ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด (์˜ˆ: ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ•จ์ˆ˜), ์‹ฌํ™” ํŠœํ† ๋ฆฌ์–ผ์˜ [Async Tests](../advanced/async-tests.md){.internal-link target=_blank}๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. /// -## ํ…Œ์ŠคํŠธ ๋ถ„๋ฆฌํ•˜๊ธฐ +## ํ…Œ์ŠคํŠธ ๋ถ„๋ฆฌํ•˜๊ธฐ { #separating-tests } ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋ณ„๋„์˜ ํŒŒ์ผ๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. - ๊ทธ๋ฆฌ๊ณ  **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋„ ์—ฌ๋Ÿฌ ํŒŒ์ผ์ด๋‚˜ ๋ชจ๋“ˆ ๋“ฑ์œผ๋กœ ๊ตฌ์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### **FastAPI** app ํŒŒ์ผ +### **FastAPI** app ํŒŒ์ผ { #fastapi-app-file } -[Bigger Applications](bigger-applications.md){.internal-link target=_blank} ์— ๋ฌ˜์‚ฌ๋œ ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. +[Bigger Applications](bigger-applications.md){.internal-link target=_blank}์— ๋ฌ˜์‚ฌ๋œ ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. ``` . @@ -76,11 +75,12 @@ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ ์™ธ์—๋„ ํ…Œ์ŠคํŠธ์—์„œ `main.py` ํŒŒ์ผ ์•ˆ์— **FastAPI** app ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค: -{* ../../docs_src/app_testing/main.py *} -### ํ…Œ์ŠคํŠธ ํŒŒ์ผ +{* ../../docs_src/app_testing/app_a_py39/main.py *} -ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด `test_main.py` ๋ผ๋Š” ํŒŒ์ผ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์€ ๋™์ผํ•œ Python ํŒจํ‚ค์ง€(์ฆ‰, `__init__.py` ํŒŒ์ผ์ด ์žˆ๋Š” ๋™์ผํ•œ ๋””๋ ‰ํ„ฐ๋ฆฌ)์— ์œ„์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +### ํ…Œ์ŠคํŠธ ํŒŒ์ผ { #testing-file } + +ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด `test_main.py`๋ผ๋Š” ํŒŒ์ผ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์€ ๋™์ผํ•œ Python ํŒจํ‚ค์ง€(์ฆ‰, `__init__.py` ํŒŒ์ผ์ด ์žˆ๋Š” ๋™์ผํ•œ ๋””๋ ‰ํ„ฐ๋ฆฌ)์— ์œ„์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ``` hl_lines="5" . @@ -90,18 +90,18 @@ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ ์™ธ์—๋„ ํ…Œ์ŠคํŠธ์—์„œ โ”‚ย ย  โ””โ”€โ”€ test_main.py ``` -ํŒŒ์ผ๋“ค์ด ๋™์ผํ•œ ํŒจํ‚ค์ง€์— ์œ„์น˜ํ•ด ์žˆ์œผ๋ฏ€๋กœ, ์ƒ๋Œ€ ์ฐธ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `main` ์—์„œ `app` ๊ฐ์ฒด๋ฅผ ์ž„ํฌํŠธ ํ•ด์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํŒŒ์ผ๋“ค์ด ๋™์ผํ•œ ํŒจํ‚ค์ง€์— ์œ„์น˜ํ•ด ์žˆ์œผ๋ฏ€๋กœ, ์ƒ๋Œ€ ์ž„ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `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 ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํŒŒ์ผ +### ํ™•์žฅ๋œ **FastAPI** app ํŒŒ์ผ { #extended-fastapi-app-file } ์ด์ „๊ณผ ๊ฐ™์€ ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ๊ณ„์† ์‚ฌ์šฉํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. @@ -113,100 +113,50 @@ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ ์™ธ์—๋„ ํ…Œ์ŠคํŠธ์—์„œ โ”‚ย ย  โ””โ”€โ”€ test_main.py ``` -์ด์ œ **FastAPI** ์•ฑ์ด ์žˆ๋Š” `main.py` ํŒŒ์ผ์— ๋ช‡ ๊ฐ€์ง€ ๋‹ค๋ฅธ **๊ฒฝ๋กœ ์ž‘์—…** ์ด ์ถ”๊ฐ€๋œ ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค. +์ด์ œ **FastAPI** ์•ฑ์ด ์žˆ๋Š” `main.py` ํŒŒ์ผ์— ๋ช‡ ๊ฐ€์ง€ ๋‹ค๋ฅธ **๊ฒฝ๋กœ ์ฒ˜๋ฆฌ**๊ฐ€ ์ถ”๊ฐ€๋œ ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค. ๋‹จ์ผ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” `GET` ์ž‘์—…์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๋‹ค๋ฅธ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” `POST` ์ž‘์—…์ด ์žˆ์Šต๋‹ˆ๋‹ค. -๋‘ *๊ฒฝ๋กœ ์ž‘์—…* ๋ชจ๋‘ `X-Token` ํ—ค๋”๋ฅผ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค. +๋‘ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ๋ชจ๋‘ `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!} -``` +### ํ™•์žฅ๋œ ํ…Œ์ŠคํŠธ ํŒŒ์ผ { #extended-testing-file } -//// +์ด์ œ๋Š” `test_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 | ํŒ - -๋  ์ˆ˜ ์žˆ์œผ๋ฉด `Annotated` ๋ฒ„์ „ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•ฉ๋‚˜๋‹ค. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b_py310/main.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | ํŒ - -๋  ์ˆ˜ ์žˆ์œผ๋ฉด `Annotated` ๋ฒ„์ „ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•ฉ๋‚˜๋‹ค. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b/main.py!} -``` - -//// - -### ํ™•์žฅ๋œ ํ…Œ์ŠคํŠธ ํŒŒ์ผ - -์ด์ œ๋Š” `test_main.py` ๋ฅผ ํ™•์žฅ๋œ ํ…Œ์ŠคํŠธ๋“ค๋กœ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: - -{* ../../docs_src/app_testing/app_b/test_main.py *} +{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *} -ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์— ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š”๋ฐ ๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด, `httpx`์—์„œ ํ•ด๋‹น ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ฒ€์ƒ‰(Google)ํ•˜๊ฑฐ๋‚˜, `requests`์—์„œ์˜ ๋ฐฉ๋ฒ•์„ ๊ฒ€์ƒ‰ํ•ด๋ณด์„ธ์š”. HTTPX๋Š” Requests์˜ ๋””์ž์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์— ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š”๋ฐ ๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด, Requests์˜ ๋””์ž์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๋œ HTTPX์ฒ˜๋Ÿผ `httpx`์—์„œ ํ•ด๋‹น ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ฒ€์ƒ‰(Google)ํ•˜๊ฑฐ๋‚˜, `requests`์—์„œ์˜ ๋ฐฉ๋ฒ•์„ ๊ฒ€์ƒ‰ํ•ด๋ณด์„ธ์š”. ๊ทธ ํ›„, ํ…Œ์ŠคํŠธ์—์„œ๋„ ๋™์ผํ•˜๊ฒŒ ์ ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ์‹œ: * *๊ฒฝ๋กœ* ํ˜น์€ *์ฟผ๋ฆฌ* ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด, URL ์ž์ฒด์— ์ถ”๊ฐ€ํ•œ๋‹ค. -* JSON ๋ณธ๋ฌธ์„ ์ „๋‹ฌํ•˜๋ ค๋ฉด, ํŒŒ์ด์ฌ ๊ฐ์ฒด (์˜ˆ๋ฅผ๋“ค๋ฉด `dict`) ๋ฅผ `json` ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•œ๋‹ค. -* JSON ๋Œ€์‹  *ํผ ๋ฐ์ดํ„ฐ* ๋ฅผ ๋ณด๋‚ด์•ผํ•œ๋‹ค๋ฉด, `data` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋Œ€์‹  ์ „๋‹ฌํ•œ๋‹ค. -* *ํ—ค๋”* ๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด, `headers` ํŒŒ๋ผ๋ฏธํ„ฐ์— `dict` ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. -* *์ฟ ํ‚ค* ๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด, `cookies` ํŒŒ๋ผ๋ฏธํ„ฐ์— `dict` ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. +* JSON ๋ณธ๋ฌธ์„ ์ „๋‹ฌํ•˜๋ ค๋ฉด, ํŒŒ์ด์ฌ ๊ฐ์ฒด (์˜ˆ๋ฅผ๋“ค๋ฉด `dict`)๋ฅผ `json` ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•œ๋‹ค. +* JSON ๋Œ€์‹  *ํผ ๋ฐ์ดํ„ฐ*๋ฅผ ๋ณด๋‚ด์•ผํ•œ๋‹ค๋ฉด, `data` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋Œ€์‹  ์ „๋‹ฌํ•œ๋‹ค. +* *ํ—ค๋”*๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด, `headers` ํŒŒ๋ผ๋ฏธํ„ฐ์— `dict`๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. +* *์ฟ ํ‚ค*๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด, `cookies` ํŒŒ๋ผ๋ฏธํ„ฐ์— `dict`๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. -๋ฐฑ์—”๋“œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ณด๋‚ด๋Š”์ง€ ์ •๋ณด๋ฅผ ๋” ์–ป์œผ๋ ค๋ฉด (`httpx` ํ˜น์€ `TestClient` ๋ฅผ ์ด์šฉํ•ด์„œ) <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX documentation</a> ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. +๋ฐฑ์—”๋“œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ณด๋‚ด๋Š”์ง€ ์ •๋ณด๋ฅผ ๋” ์–ป์œผ๋ ค๋ฉด (`httpx` ํ˜น์€ `TestClient`๋ฅผ ์ด์šฉํ•ด์„œ) <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX documentation</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. /// info | ์ •๋ณด -`TestClient` ๋Š” Pydantic ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ JSON ์œผ๋กœ ๋ณ€ํ™˜๋  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. +`TestClient`๋Š” Pydantic ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ JSON์œผ๋กœ ๋ณ€ํ™˜๋  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. -๋งŒ์•ฝ ํ…Œ์ŠคํŠธ์ค‘ Pydantic ๋ชจ๋ธ์„ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ์— ๋ณด๋‚ด๊ณ  ์‹ถ๋‹ค๋ฉด, [JSON ํ˜ธํ™˜ ๊ฐ€๋Šฅ ์ธ์ฝ”๋”](encoder.md){.internal-link target=_blank} ์— ์„ค๋ช…๋˜์–ด ์žˆ๋Š” `jsonable_encoder` ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋งŒ์•ฝ ํ…Œ์ŠคํŠธ ์ค‘ Pydantic ๋ชจ๋ธ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ  ํ…Œ์ŠคํŠธ ์ค‘์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ณ  ์‹ถ๋‹ค๋ฉด, [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}์— ์„ค๋ช…๋˜์–ด ์žˆ๋Š” `jsonable_encoder`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// -## ์‹คํ–‰ํ•˜๊ธฐ +## ์‹คํ–‰ํ•˜๊ธฐ { #run-it } -ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , `pytest` ๋ฅผ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. +๊ทธ ํ›„์—๋Š” `pytest`๋ฅผ ์„ค์น˜ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. -[virtual environment](../virtual-environments.md){.internal-link target=_blank} ๋ฅผ ๋งŒ๋“ค๊ณ , ํ™œ์„ฑํ™” ์‹œํ‚จ ๋’ค์— ์„ค์น˜ํ•˜์„ธ์š”. ์˜ˆ์‹œ: +[virtual environment](../virtual-environments.md){.internal-link target=_blank}๋ฅผ ๋งŒ๋“ค๊ณ , ํ™œ์„ฑํ™” ์‹œํ‚จ ๋’ค์— ์„ค์น˜ํ•˜์„ธ์š”. ์˜ˆ์‹œ: <div class="termy"> @@ -218,7 +168,7 @@ $ pip install pytest </div> -`pytest` ํŒŒ์ผ๊ณผ ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ์‹คํ–‰ํ•œ ๋‹ค์Œ, ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +`pytest`๋Š” ํŒŒ์ผ๊ณผ ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ์‹คํ–‰ํ•œ ๋‹ค์Œ, ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ์‹คํ–‰ํ•˜์„ธ์š”. diff --git a/docs/ko/docs/virtual-environments.md b/docs/ko/docs/virtual-environments.md index 0d10c3200d..b639f8a3e4 100644 --- a/docs/ko/docs/virtual-environments.md +++ b/docs/ko/docs/virtual-environments.md @@ -1,16 +1,16 @@ -# ๊ฐ€์ƒ ํ™˜๊ฒฝ +# ๊ฐ€์ƒ ํ™˜๊ฒฝ { #virtual-environments } -Python ํ”„๋กœ์ ํŠธ๋ฅผ ์ž‘์—…ํ•  ๋•Œ๋Š” **๊ฐ€์ƒ ํ™˜๊ฒฝ** (๋˜๋Š” ์ด์™€ ์œ ์‚ฌํ•œ ๋„๊ตฌ)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๊ฐ ํ”„๋กœ์ ํŠธ ๋งˆ๋‹ค ์„ค์น˜ํ•˜๋Š” ํŒจํ‚ค์ง€๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +Python ํ”„๋กœ์ ํŠธ๋ฅผ ์ž‘์—…ํ•  ๋•Œ๋Š” **๊ฐ€์ƒ ํ™˜๊ฒฝ**(๋˜๋Š” ์ด์™€ ์œ ์‚ฌํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜)์„ ์‚ฌ์šฉํ•ด ๊ฐ ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค ์„ค์น˜ํ•˜๋Š” ํŒจํ‚ค์ง€๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. -/// info | ์ •๋ณด +/// info -์ด๋ฏธ ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ๋Œ€ํ•ด ์ž˜ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด, ์ด ์„น์…˜์€ ๊ฑด๋„ˆ ๋›ฐ์–ด๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ +์ด๋ฏธ ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ๋Œ€ํ•ด ์•Œ๊ณ  ์žˆ๊ณ , ์–ด๋–ป๊ฒŒ ์ƒ์„ฑํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š”์ง€๋„ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด, ์ด ์„น์…˜์€ ๊ฑด๋„ˆ๋›ฐ์–ด๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ /// -/// tip | ํŒ +/// tip -**๊ฐ€์ƒ ํ™˜๊ฒฝ(Virtual Environment)** ์€ **ํ™˜๊ฒฝ ๋ณ€์ˆ˜(Environment Variable)** ์™€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. +**๊ฐ€์ƒ ํ™˜๊ฒฝ**์€ **ํ™˜๊ฒฝ ๋ณ€์ˆ˜**์™€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. **ํ™˜๊ฒฝ ๋ณ€์ˆ˜**๋Š” ์‹œ์Šคํ…œ์— ์กด์žฌํ•˜๋ฉฐ, ํ”„๋กœ๊ทธ๋žจ์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. @@ -18,50 +18,52 @@ Python ํ”„๋กœ์ ํŠธ๋ฅผ ์ž‘์—…ํ•  ๋•Œ๋Š” **๊ฐ€์ƒ ํ™˜๊ฒฝ** (๋˜๋Š” ์ด์™€ ์œ ์‚ฌ /// -/// info | ์ •๋ณด +/// info -์ด ํŽ˜์ด์ง€์—์„œ๋Š” **๊ฐ€์ƒ ํ™˜๊ฒฝ**์˜ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•๊ณผ ์ž‘๋™ ๋ฐฉ์‹์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. +์ด ํŽ˜์ด์ง€์—์„œ๋Š” **๊ฐ€์ƒ ํ™˜๊ฒฝ**์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ์ž‘๋™ ๋ฐฉ์‹์„ ์•Œ๋ ค๋“œ๋ฆฝ๋‹ˆ๋‹ค. -๋งŒ์•ฝ **๋ชจ๋“  ๊ฒƒ์„ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ๋„๊ตฌ** (Python ์„ค์น˜๊นŒ์ง€ ํฌํ•จ)๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์„ธ์š”. +Python ์„ค์น˜๊นŒ์ง€ ํฌํ•จํ•ด **๋ชจ๋“  ๊ฒƒ์„ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ๋„๊ตฌ**๋ฅผ ๋„์ž…ํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ๋‹ค๋ฉด <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด์„ธ์š”. /// -## ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ +## ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ { #create-a-project } ๋จผ์ €, ํ”„๋กœ์ ํŠธ๋ฅผ ์œ„ํ•œ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -๋ณดํ†ต ์‚ฌ์šฉ์ž ํ™ˆ ๋””๋ ‰ํ„ฐ๋ฆฌ ์•ˆ์— `code`๋ผ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ , ๊ทธ ์•ˆ์— ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค ํ•˜๋‚˜์”ฉ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. +์ œ๊ฐ€ ๋ณดํ†ต ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์‚ฌ์šฉ์ž ํ™ˆ/์œ ์ € ๋””๋ ‰ํ„ฐ๋ฆฌ ์•ˆ์— `code`๋ผ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๊ทธ ์•ˆ์— ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ํ•˜๋‚˜์”ฉ ๋งŒ๋“ญ๋‹ˆ๋‹ค. <div class="termy"> ```console -// ํ™ˆ ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ์ด๋™ +// Go to the home directory $ cd -// ๋ชจ๋“  ์ฝ”๋“œ ํ”„๋กœ์ ํŠธ๋ฅผ ์œ„ํ•œ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ƒ์„ฑ +// Create a directory for all your code projects $ mkdir code -// 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 ``` </div> -## ๊ฐ€์ƒ ํ™˜๊ฒฝ ์ƒ์„ฑ +## ๊ฐ€์ƒ ํ™˜๊ฒฝ ์ƒ์„ฑ { #create-a-virtual-environment } -Python ํ”„๋กœ์ ํŠธ๋ฅผ **์ฒ˜์Œ ์‹œ์ž‘ํ•  ๋•Œ**, ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ **<abbr title="๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋“ค๋„ ์žˆ์ง€๋งŒ, ์ด๊ฑด ๊ฐ„๋‹จํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์ž…๋‹ˆ๋‹ค">ํ”„๋กœ์ ํŠธ ๋‚ด๋ถ€</abbr>**์— ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +Python ํ”„๋กœ์ ํŠธ๋ฅผ **์ฒ˜์Œ ์‹œ์ž‘ํ•  ๋•Œ**, **<abbr title="๋‹ค๋ฅธ ์˜ต์…˜๋„ ์žˆ์ง€๋งŒ, ์ด๊ฒƒ์€ ๊ฐ„๋‹จํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์ž…๋‹ˆ๋‹ค">ํ”„๋กœ์ ํŠธ ๋‚ด๋ถ€</abbr>**์— ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•˜์„ธ์š”. -/// tip | ํŒ +/// tip -์ด ์ž‘์—…์€ **ํ”„๋กœ์ ํŠธ๋ฅผ ์ฒ˜์Œ ์„ค์ •ํ•  ๋•Œ ํ•œ๋ฒˆ๋งŒ** ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ดํ›„ ์ž‘์—…ํ•  ๋•Œ ๋ฐ˜๋ณตํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. +์ด ์ž‘์—…์€ **ํ”„๋กœ์ ํŠธ๋‹น ํ•œ ๋ฒˆ๋งŒ** ํ•˜๋ฉด ๋˜๋ฉฐ, ์ž‘์—…ํ•  ๋•Œ๋งˆ๋‹ค ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. /// //// tab | `venv` -Python ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํฌํ•จ๋œ venv ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•ด ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋งŒ๋“ค๋ ค๋ฉด Python์— ํฌํ•จ๋œ `venv` ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <div class="termy"> @@ -71,12 +73,12 @@ $ python -m venv .venv </div> -/// details | ๋ช…๋ น์–ด ์ƒ์„ธ ์„ค๋ช… +/// details | ๋ช…๋ น์–ด ์˜๋ฏธ -* `python`: `python` ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. -* `-m`: ํŠน์ • ๋ชจ๋“ˆ์„ ์Šคํฌ๋ฆฝํŠธ์ฒ˜๋Ÿผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์ƒ ๋ชจ๋“ˆ์„ ๋ฐ”๋กœ ๋’ค์— ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. -* `venv`: Python ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํฌํ•จ๋œ `venv` ๋ชจ๋“ˆ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. -* `.venv`: ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ `.venv` ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +* `python`: `python`์ด๋ผ๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค +* `-m`: ๋ชจ๋“ˆ์„ ์Šคํฌ๋ฆฝํŠธ๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์— ์–ด๋–ค ๋ชจ๋“ˆ์ธ์ง€ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค +* `venv`: ๋ณดํ†ต Python์— ๊ธฐ๋ณธ์œผ๋กœ ์„ค์น˜๋˜์–ด ์žˆ๋Š” `venv` ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค +* `.venv`: ์ƒˆ ๋””๋ ‰ํ„ฐ๋ฆฌ์ธ `.venv`์— ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค /// @@ -84,7 +86,7 @@ $ python -m venv .venv //// tab | `uv` -<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ๋‹ค๋ฉด, uv๋ฅผ ํ†ตํ•ด ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ๋‹ค๋ฉด, ์ด๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <div class="termy"> @@ -94,31 +96,31 @@ $ uv venv </div> -/// tip | ํŒ +/// tip -`uv`๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ `.venv` ๋””๋ ‰ํ„ฐ๋ฆฌ์— ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +๊ธฐ๋ณธ์ ์œผ๋กœ `uv`๋Š” `.venv`๋ผ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ์— ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -๋ณ„๋„๋กœ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ด๋ฆ„์„ ์ถ”๊ฐ€ ์ธ์ž๋กœ ๋„˜๊ฒจ ์ฃผ๋ฉด ๊ฒฝ๋กœ๋ฅผ ์ง€์ • ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ด๋ฆ„์„ ์ถ”๊ฐ€ ์ธ์ž๋กœ ์ „๋‹ฌํ•ด ์ด๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// //// -ํ•ด๋‹น ๋ช…๋ น์–ด๋Š” `.venv` ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ƒˆ๋กœ์šด ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +ํ•ด๋‹น ๋ช…๋ น์–ด๋Š” `.venv`๋ผ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ƒˆ๋กœ์šด ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. /// details | `.venv` ๋˜๋Š” ๋‹ค๋ฅธ ์ด๋ฆ„ -๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋‹ค๋ฅธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ƒ์„ฑํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ด€๋ก€์ ์œผ๋กœ `.venv` ๋””๋ ‰ํ„ฐ๋ฆฌ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋‹ค๋ฅธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ƒ์„ฑํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ด€๋ก€์ ์œผ๋กœ `.venv`๋ผ๋Š” ์ด๋ฆ„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. /// -## ๊ฐ€์ƒ ํ™˜๊ฒฝ ํ™œ์„ฑํ™” +## ๊ฐ€์ƒ ํ™˜๊ฒฝ ํ™œ์„ฑํ™” { #activate-the-virtual-environment } -์ดํ›„ ์‹คํ–‰ํ•˜๋Š” Python ๋ช…๋ น์–ด์™€ ํŒจํ‚ค์ง€ ์„ค์น˜๊ฐ€ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋”ฐ๋ฅด๋„๋ก, ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. +์ดํ›„ ์‹คํ–‰ํ•˜๋Š” Python ๋ช…๋ น์–ด์™€ ์„ค์น˜ํ•˜๋Š” ํŒจํ‚ค์ง€๊ฐ€ ์ƒˆ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜๋„๋ก, ์ƒˆ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. -/// tip | ํŒ +/// tip -**ํ„ฐ๋ฏธ๋„์„ ์ƒˆ๋กœ ์—ด๊ณ ** ํ”„๋กœ์ ํŠธ ์ž‘์—…์„ ์‹œ์ž‘ํ•  ๋•Œ๋Š”, **ํ•ญ์ƒ ์ด ์ž‘์—…์„** ํ•ด์ฃผ์„ธ์š”. +ํ”„๋กœ์ ํŠธ ์ž‘์—…์„ ์œ„ํ•ด **์ƒˆ ํ„ฐ๋ฏธ๋„ ์„ธ์…˜**์„ ์‹œ์ž‘ํ•  ๋•Œ๋งˆ๋‹ค **๋งค๋ฒˆ** ์ด ์ž‘์—…์„ ํ•˜์„ธ์š”. /// @@ -148,7 +150,7 @@ $ .venv\Scripts\Activate.ps1 //// tab | Windows Bash -Windows์—์„œ Bash(์˜ˆ: <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ: +๋˜๋Š” Windows์—์„œ Bash(์˜ˆ: <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ: <div class="termy"> @@ -160,21 +162,21 @@ $ source .venv/Scripts/activate //// -/// tip | ํŒ +/// tip -๊ฐ€์ƒ ํ™˜๊ฒฝ์— ์ƒˆ๋กœ์šด ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•  ๋•Œ๋งˆ๋‹ค, ํ•ด๋‹น ํ™˜๊ฒฝ์„ ๋‹ค์‹œ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. +ํ•ด๋‹น ํ™˜๊ฒฝ์— **์ƒˆ ํŒจํ‚ค์ง€**๋ฅผ ์„ค์น˜ํ•  ๋•Œ๋งˆ๋‹ค, ํ™˜๊ฒฝ์„ ๋‹ค์‹œ **ํ™œ์„ฑํ™”**ํ•˜์„ธ์š”. -์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•ด๋‹น ํŒจํ‚ค์ง€๋กœ ์„ค์น˜๋œ **ํ„ฐ๋ฏธ๋„(<abbr title="command line interface">CLI</abbr>) ํ”„๋กœ๊ทธ๋žจ**์„ ์‚ฌ์šฉํ•  ๋•Œ, ์ „์—ญ์— ์„ค์น˜๋œ ๋‹ค๋ฅธ ๋ฒ„์ „์ด ์•„๋‹ˆ๋ผ, ๊ฐ€์ƒ ํ™˜๊ฒฝ ์•ˆ์— ์„ค์น˜๋œ ์ •ํ™•ํ•œ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•ด๋‹น ํŒจํ‚ค์ง€๊ฐ€ ์„ค์น˜ํ•œ **ํ„ฐ๋ฏธ๋„(<abbr title="command line interface">CLI</abbr>) ํ”„๋กœ๊ทธ๋žจ**์„ ์‚ฌ์šฉํ•  ๋•Œ, ์ „์—ญ์œผ๋กœ ์„ค์น˜๋˜์–ด ์žˆ์„ ์ˆ˜๋„ ์žˆ๋Š”(์•„๋งˆ ํ•„์š”ํ•œ ๋ฒ„์ „๊ณผ๋Š” ๋‹ค๋ฅธ ๋ฒ„์ „์ธ) ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋žจ์ด ์•„๋‹ˆ๋ผ ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ์žˆ๋Š” ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. /// -## ๊ฐ€์ƒ ํ™˜๊ฒฝ์ด ํ™œ์„ฑํ™” ์—ฌ๋ถ€ ํ™•์ธ +## ๊ฐ€์ƒ ํ™˜๊ฒฝ ํ™œ์„ฑํ™” ์—ฌ๋ถ€ ํ™•์ธ { #check-the-virtual-environment-is-active } -๊ฐ€์ƒ ํ™˜๊ฒฝ์ด ํ™œ์„ฑํ™”๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. (์ด์ „ ๋ช…๋ น์–ด๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ–ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค). +๊ฐ€์ƒ ํ™˜๊ฒฝ์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š”์ง€(์ด์ „ ๋ช…๋ น์–ด๊ฐ€ ์ž‘๋™ํ–ˆ๋Š”์ง€) ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. -/// tip | ํŒ +/// tip -์ด ๋‹จ๊ณ„๋Š” **์„ ํƒ ์‚ฌํ•ญ**์ด์ง€๋งŒ, ๋ชจ๋“  ๊ฒƒ์ด ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•˜๊ณ  ์žˆ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์˜๋„ํ•œ ๊ฐ€์ƒ ํ™˜๊ฒฝ์ด ํ™œ์„ฑํ™” ๋˜์—ˆ๋Š” ์ง€ **ํ™•์ธ**ํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. +์ด ๋‹จ๊ณ„๋Š” **์„ ํƒ ์‚ฌํ•ญ**์ด์ง€๋งŒ, ๋ชจ๋“  ๊ฒƒ์ด ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•˜๊ณ  ์žˆ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์˜๋„ํ•œ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ **ํ™•์ธ**ํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. /// @@ -190,7 +192,7 @@ $ which python </div> -`python` ์œ„์น˜๊ฐ€ ํ”„๋กœ์ ํŠธ ๋‚ด๋ถ€(์ด ์˜ˆ์‹œ์—์„œ๋Š” `awesome-project`)์˜ `.venv/bin/python` ๊ฒฝ๋กœ๋กœ ํ‘œ์‹œ๋œ๋‹ค๋ฉด ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค. ๐ŸŽ‰ +ํ”„๋กœ์ ํŠธ ๋‚ด๋ถ€(์ด ๊ฒฝ์šฐ `awesome-project`)์˜ `.venv/bin/python`์— ์žˆ๋Š” `python` ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค๋ฉด, ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐ŸŽ‰ //// @@ -206,29 +208,29 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python </div> -`python` ์œ„์น˜๊ฐ€ ํ”„๋กœ์ ํŠธ ๋‚ด๋ถ€(์ด ์˜ˆ์‹œ์—์„œ๋Š” `awesome-project`)์˜ `.venv\bin\python` ๊ฒฝ๋กœ๋กœ ํ‘œ์‹œ๋œ๋‹ค๋ฉด ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค. ๐ŸŽ‰ +ํ”„๋กœ์ ํŠธ ๋‚ด๋ถ€(์ด ๊ฒฝ์šฐ `awesome-project`)์˜ `.venv\Scripts\python`์— ์žˆ๋Š” `python` ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค๋ฉด, ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐ŸŽ‰ //// -## pip ์—…๊ทธ๋ ˆ์ด๋“œ +## `pip` ์—…๊ทธ๋ ˆ์ด๋“œ { #upgrade-pip } -/// tip | ํŒ +/// tip -<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, `pip` ๋Œ€์‹  `uv`๋กœ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ `pip`์„ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž +<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, `pip` ๋Œ€์‹  `uv`๋กœ ์„ค์น˜ํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ `pip`์„ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž /// -`pip`์„ ์‚ฌ์šฉํ•˜์—ฌ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๋Š” ๊ฒฝ์šฐ (Python ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค), **์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ**ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. +`pip`๋กœ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•œ๋‹ค๋ฉด(Python์— ๊ธฐ๋ณธ์œผ๋กœ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค) ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ **์—…๊ทธ๋ ˆ์ด๋“œ**ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. -ํŒจํ‚ค์ง€ ์„ค์น˜ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ๋‹ค์–‘ํ•˜๊ณ  ํŠน์ดํ•œ ์—๋Ÿฌ๋“ค์€ `pip` ์—…๊ทธ๋ ˆ์ด๋“œ๋กœ ์‰ฝ๊ฒŒ ํ•ด๊ฒฐ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. +ํŒจํ‚ค์ง€ ์„ค์น˜ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ๋‹ค์–‘ํ•œ ํŠน์ดํ•œ ์˜ค๋ฅ˜๋Š” ๋จผ์ € `pip`๋ฅผ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ํ•ด๊ฒฐ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. -/// tip | ํŒ +/// tip -์ด ์ž‘์—…์€ ๋ณดํ†ต ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•œ **์งํ›„ ํ•œ ๋ฒˆ๋งŒ** ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. +๋ณดํ†ต ์ด ์ž‘์—…์€ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋งŒ๋“  ์งํ›„ **ํ•œ ๋ฒˆ๋งŒ** ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. /// -๊ฐ€์ƒ ํ™˜๊ฒฝ์ด ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•œ ํ›„(์•ž์„œ ์„ค๋ช…ํ•œ ๋ช…๋ น์–ด ์‚ฌ์šฉ), ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”: +๊ฐ€์ƒ ํ™˜๊ฒฝ์ด ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•œ ๋‹ค์Œ(์œ„์˜ ๋ช…๋ น์–ด ์‚ฌ์šฉ) ์•„๋ž˜๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”: <div class="termy"> @@ -240,19 +242,39 @@ $ python -m pip install --upgrade pip </div> -## `.gitignore` ์ถ”๊ฐ€ํ•˜๊ธฐ +/// tip -**Git**์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด (์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค), `.gitignore` ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•ด์„œ `.venv` ๋””๋ ‰ํ„ฐ๋ฆฌ ์ „์ฒด๋ฅผ Git์—์„œ ์ œ์™ธํ•˜์„ธ์š”. +๋•Œ๋กœ๋Š” pip๋ฅผ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋ ค๊ณ  ํ•  ๋•Œ **`No module named pip`** ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -/// tip | ํŒ +์ด ๊ฒฝ์šฐ ์•„๋ž˜ ๋ช…๋ น์–ด๋กœ pip๋ฅผ ์„ค์น˜ํ•˜๊ณ  ์—…๊ทธ๋ ˆ์ด๋“œํ•˜์„ธ์š”: -<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ–ˆ๋‹ค๋ฉด, ์ด๋ฏธ ์ด ์ž‘์—…์ด ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์ด ๋‹จ๊ณ„๋Š” ๊ฑด๋„ˆ๋›ฐ์–ด๋„ ๋ฉ๋‹ˆ๋‹ค. ๐Ÿ˜Ž +<div class="termy"> + +```console +$ python -m ensurepip --upgrade + +---> 100% +``` + +</div> + +์ด ๋ช…๋ น์–ด๋Š” pip๊ฐ€ ์•„์ง ์„ค์น˜๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด ์„ค์น˜ํ•˜๋ฉฐ, ์„ค์น˜๋œ pip ๋ฒ„์ „์ด `ensurepip`์—์„œ ์ œ๊ณต ๊ฐ€๋Šฅํ•œ ๋ฒ„์ „๋งŒํผ ์ตœ์‹ ์ž„์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. /// -/// tip | ํŒ +## `.gitignore` ์ถ”๊ฐ€ํ•˜๊ธฐ { #add-gitignore } -์ด ์ž‘์—…๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•œ **์งํ›„ ํ•œ ๋ฒˆ๋งŒ** ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. +**Git**์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด(์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค), `.venv`์˜ ๋ชจ๋“  ๋‚ด์šฉ์„ Git์—์„œ ์ œ์™ธํ•˜๋„๋ก `.gitignore` ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”. + +/// tip + +<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๋กœ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋งŒ๋“ค์—ˆ๋‹ค๋ฉด, ์ด๋ฏธ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์ด ๋‹จ๊ณ„๋Š” ๊ฑด๋„ˆ๋›ฐ์–ด๋„ ๋ฉ๋‹ˆ๋‹ค. ๐Ÿ˜Ž + +/// + +/// tip + +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋งŒ๋“  ์งํ›„ **ํ•œ ๋ฒˆ๋งŒ** ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. /// @@ -264,16 +286,15 @@ $ echo "*" > .venv/.gitignore </div> -/// details | ๋ช…๋ น์–ด ์ƒ์„ธ ์„ค๋ช… +/// details | ๋ช…๋ น์–ด ์˜๋ฏธ -* `echo "*"`: ํ„ฐ๋ฏธ๋„์— `*` ํ…์ŠคํŠธ๋ฅผ "์ถœ๋ ฅ"ํ•ฉ๋‹ˆ๋‹ค (๋‹ค์Œ ์„ค๋ช…์—์„œ ์กฐ๊ธˆ ๋ฐ”๋€๋‹ˆ๋‹ค) -* `>`: ์™ผ์ชฝ ๋ช…๋ น์–ด์˜ ์ถœ๋ ฅ ๋‚ด์šฉ์„ ํ„ฐ๋ฏธ๋„์— ์ถœ๋ ฅํ•˜์ง€ ์•Š๊ณ , ์˜ค๋ฅธ์ชฝ์— ์ง€์ •๋œ ํŒŒ์ผ๋กœ **๊ธฐ๋ก(write)** ํ•˜๋ผ๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. -* `.gitignore`: ์ถœ๋ ฅ๋œ ํ…์ŠคํŠธ๊ฐ€ ๊ธฐ๋ก๋  ํŒŒ์ผ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. +* `echo "*"`: ํ„ฐ๋ฏธ๋„์— `*` ํ…์ŠคํŠธ๋ฅผ "์ถœ๋ ฅ"ํ•ฉ๋‹ˆ๋‹ค(๋‹ค์Œ ๋ถ€๋ถ„์ด ์ด๋ฅผ ์•ฝ๊ฐ„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค) +* `>`: `>` ์™ผ์ชฝ ๋ช…๋ น์–ด๊ฐ€ ํ„ฐ๋ฏธ๋„์— ์ถœ๋ ฅํ•œ ๋‚ด์šฉ์„ ํ„ฐ๋ฏธ๋„์— ์ถœ๋ ฅํ•˜์ง€ ์•Š๊ณ , `>` ์˜ค๋ฅธ์ชฝ์— ์žˆ๋Š” ํŒŒ์ผ์— ๊ธฐ๋กํ•˜๋ผ๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค +* `.gitignore`: ํ…์ŠคํŠธ๊ฐ€ ๊ธฐ๋ก๋  ํŒŒ์ผ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค -๊ทธ๋ฆฌ๊ณ  Git์—์„œ `*`๋Š” "๋ชจ๋“  ๊ฒƒ"์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ `.venv` ๋””๋ ‰ํ„ฐ๋ฆฌ ์•ˆ์˜ ๋ชจ๋“  ๊ฒƒ์„ ๋ฌด์‹œํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. - -์ด ๋ช…๋ น์–ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด์šฉ์„ ๊ฐ€์ง„ `.gitignore` ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: +๊ทธ๋ฆฌ๊ณ  Git์—์„œ `*`๋Š” "๋ชจ๋“  ๊ฒƒ"์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ `.venv` ๋””๋ ‰ํ„ฐ๋ฆฌ ์•ˆ์˜ ๋ชจ๋“  ๊ฒƒ์„ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค. +์ด ๋ช…๋ น์–ด๋Š” ๋‹ค์Œ ๋‚ด์šฉ์„ ๊ฐ€์ง„ `.gitignore` ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: ```gitignore * @@ -281,25 +302,25 @@ $ echo "*" > .venv/.gitignore /// -## ํŒจํ‚ค์ง€ ์„ค์น˜ +## ํŒจํ‚ค์ง€ ์„ค์น˜ { #install-packages } -๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ ํ›„, ๊ทธ ์•ˆ์— ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋“ค์„ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ ๋’ค, ๊ทธ ์•ˆ์— ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -/// tip | ํŒ +/// tip -ํ”„๋กœ์ ํŠธ์—์„œ ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ฑฐ๋‚˜ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ๋•Œ๋Š” ์ด ์ž‘์—…์„ **ํ•œ ๋ฒˆ๋งŒ** ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. +ํ”„๋กœ์ ํŠธ์— ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ฑฐ๋‚˜ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ๋•Œ๋Š” **ํ•œ ๋ฒˆ**๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. -๋งŒ์•ฝ ํŠน์ • ํŒจํ‚ค์ง€์˜ ๋ฒ„์ „์„ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๊ฑฐ๋‚˜, ์ƒˆ๋กœ์šด ํŒจํ‚ค์ง€๋ฅผ ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์ƒ๊ธฐ๋ฉด **๋‹ค์‹œ ์ด ์ž‘์—…์„ ๋ฐ˜๋ณต**ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. +๋ฒ„์ „์„ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๊ฑฐ๋‚˜ ์ƒˆ ํŒจํ‚ค์ง€๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค๋ฉด **๋‹ค์‹œ ์ด ์ž‘์—…์„** ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. /// -### ํŒจํ‚ค์ง€ ์ง์ ‘ ์„ค์น˜ +### ํŒจํ‚ค์ง€ ์ง์ ‘ ์„ค์น˜ { #install-packages-directly } -๊ธ‰ํ•˜๊ฒŒ ์ž‘์—…ํ•˜๊ฑฐ๋‚˜, ํ”„๋กœ์ ํŠธ์— ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ๋ชฉ๋ก์„ ๋”ฐ๋กœ ํŒŒ์ผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ์ง€ ์•Š์€ ๊ฒฝ์šฐ, ํŒจํ‚ค์ง€๋ฅผ ์ง์ ‘ ์„ค์น˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ธ‰ํ•˜๊ฒŒ ์ž‘์—… ์ค‘์ด๊ณ  ํ”„๋กœ์ ํŠธ์˜ ํŒจํ‚ค์ง€ ์š”๊ตฌ์‚ฌํ•ญ์„ ์„ ์–ธํ•˜๋Š” ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋ฉด, ํŒจํ‚ค์ง€๋ฅผ ์ง์ ‘ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -/// tip | ํŒ +/// tip -ํŒจํ‚ค์ง€ ์ด๋ฆ„๊ณผ ๋ฒ„์ „ ์ •๋ณด๋ฅผ ํŒŒ์ผ์— ์ •๋ฆฌํ•ด๋‘๋Š” ๊ฒƒ(์˜ˆ: `requirements.txt` ๋˜๋Š” `pyproject.toml`)์€ (๋งค์šฐ) ์ข‹์€ ์ƒ๊ฐ์ž…๋‹ˆ๋‹ค. +ํ”„๋กœ๊ทธ๋žจ์— ํ•„์š”ํ•œ ํŒจํ‚ค์ง€์™€ ๋ฒ„์ „์„ ํŒŒ์ผ(์˜ˆ: `requirements.txt` ๋˜๋Š” `pyproject.toml`)์— ์ ์–ด๋‘๋Š” ๊ฒƒ์€ (๋งค์šฐ) ์ข‹์€ ์ƒ๊ฐ์ž…๋‹ˆ๋‹ค. /// @@ -319,7 +340,7 @@ $ pip install "fastapi[standard]" //// tab | `uv` -<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ: +<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๊ฐ€ ์žˆ๋‹ค๋ฉด: <div class="termy"> @@ -332,9 +353,9 @@ $ uv pip install "fastapi[standard]" //// -### `requirements.txt`์—์„œ ์„ค์น˜ +### `requirements.txt`์—์„œ ์„ค์น˜ { #install-from-requirements-txt } -`requirements.txt` ํŒŒ์ผ์ด ์žˆ๋‹ค๋ฉด, ๊ทธ ์•ˆ์— ๋ช…์‹œ๋œ ํŒจํ‚ค์ง€๋“ค์„ ํ•œ ๋ฒˆ์— ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +`requirements.txt`๊ฐ€ ์žˆ๋‹ค๋ฉด, ์ด์ œ ์ด๋ฅผ ์‚ฌ์šฉํ•ด ๊ทธ ์•ˆ์˜ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. //// tab | `pip` @@ -351,7 +372,7 @@ $ pip install -r requirements.txt //// tab | `uv` -<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ: +<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>๊ฐ€ ์žˆ๋‹ค๋ฉด: <div class="termy"> @@ -366,7 +387,7 @@ $ uv pip install -r requirements.txt /// details | `requirements.txt` -๋‹ค์Œ์€ ๋ช‡ ๊ฐ€์ง€ ํŒจํ‚ค์ง€๋ฅผ ํฌํ•จํ•œ `requirements.txt`์˜ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค: +์ผ๋ถ€ ํŒจํ‚ค์ง€๊ฐ€ ์žˆ๋Š” `requirements.txt`๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ๊ฒผ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```requirements.txt fastapi[standard]==0.113.0 @@ -375,9 +396,9 @@ pydantic==2.8.0 /// -## ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ +## ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ { #run-your-program } -๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ ํ›„์—๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ ํ•ด๋‹น ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ์„ค์น˜๋œ Python๊ณผ ํŒจํ‚ค์ง€๋“ค์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ ๋’ค์—๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์„ค์น˜ํ•œ ํŒจํ‚ค์ง€๊ฐ€ ๋“ค์–ด์žˆ๋Š” ๊ฐ€์ƒ ํ™˜๊ฒฝ ๋‚ด๋ถ€์˜ Python์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. <div class="termy"> @@ -389,25 +410,24 @@ Hello World </div> -## ์—๋””ํ„ฐ ์„ค์ • +## ์—๋””ํ„ฐ ์„ค์ • { #configure-your-editor } -์—๋””ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ์•ž์„œ ๋งŒ๋“  ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. (๋Œ€๋ถ€๋ถ„์˜ ์—๋””ํ„ฐ๋Š” ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.) -์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ž๋™ ์™„์„ฑ ๊ธฐ๋Šฅ์ด๋‚˜ ์ฝ”๋“œ ๋‚ด ์˜ค๋ฅ˜ ํ‘œ์‹œ ๊ธฐ๋Šฅ์„ ์ œ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์•„๋งˆ ์—๋””ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ํ…๋ฐ, ์ž๋™ ์™„์„ฑ๊ณผ ์ธ๋ผ์ธ ์˜ค๋ฅ˜ ํ‘œ์‹œ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ์ƒ์„ฑํ•œ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •ํ•˜์„ธ์š”(๋Œ€๋ถ€๋ถ„ ์ž๋™ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค). -์˜ˆ์‹œ: +์˜ˆ๋ฅผ ๋“ค๋ฉด: * <a href="https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment" class="external-link" target="_blank">VS Code</a> * <a href="https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html" class="external-link" target="_blank">PyCharm</a> -/// tip | ํŒ +/// tip -์ด ์„ค์ •์€ ๋ณดํ†ต ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ **์ฒ˜์Œ ๋งŒ๋“ค์—ˆ์„ ๋•Œ ํ•œ ๋ฒˆ๋งŒ** ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. +๋ณดํ†ต ์ด ์„ค์ •์€ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋งŒ๋“ค ๋•Œ **ํ•œ ๋ฒˆ๋งŒ** ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. /// -## ๊ฐ€์ƒ ํ™˜๊ฒฝ ๋น„ํ™œ์„ฑํ™” +## ๊ฐ€์ƒ ํ™˜๊ฒฝ ๋น„ํ™œ์„ฑํ™” { #deactivate-the-virtual-environment } -ํ”„๋กœ์ ํŠธ ์ž‘์—…์ด ๋๋‚ฌ๋‹ค๋ฉด, ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ **๋น„ํ™œ์„ฑํ™”**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ”„๋กœ์ ํŠธ ์ž‘์—…์„ ๋งˆ์ณค๋‹ค๋ฉด ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ **๋น„ํ™œ์„ฑํ™”**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <div class="termy"> @@ -417,54 +437,55 @@ $ deactivate </div> -์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ดํ›„์— `python` ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ, ๊ฐ€์ƒ ํ™˜๊ฒฝ์˜ Python์ด๋‚˜ ๊ทธ ์•ˆ์— ์„ค์น˜๋œ ํŒจํ‚ค์ง€๋“ค์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด `python`์„ ์‹คํ–‰ํ•  ๋•Œ, ํ•ด๋‹น ๊ฐ€์ƒ ํ™˜๊ฒฝ๊ณผ ๊ทธ ์•ˆ์— ์„ค์น˜๋œ ํŒจํ‚ค์ง€์—์„œ ์‹คํ–‰ํ•˜๋ ค๊ณ  ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -## ์ด์ œ ์ž‘์—…ํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค +## ์ž‘์—…ํ•  ์ค€๋น„ ์™„๋ฃŒ { #ready-to-work } -์ด์ œ ํ”„๋กœ์ ํŠธ ์ž‘์—…์„ ์‹œ์ž‘ํ•  ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +์ด์ œ ํ”„๋กœ์ ํŠธ ์ž‘์—…์„ ์‹œ์ž‘ํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -/// tip | ํŒ -์œ„ ๋‚ด์šฉ์„ ๋” ๊นŠ์ด ์ดํ•ดํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? +/// tip -๊ทธ๋ ‡๋‹ค๋ฉด ๊ณ„์† ์ฝ์–ด ์ฃผ์„ธ์š”. ๐Ÿ‘‡๐Ÿค“ +์œ„์˜ ๋‚ด์šฉ์ด ๋ฌด์—‡์ธ์ง€ ๋” ์ดํ•ดํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? + +๊ณ„์† ์ฝ์–ด๋ณด์„ธ์š”. ๐Ÿ‘‡๐Ÿค“ /// -## ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์™œ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€ +## ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์™œ ์‚ฌ์šฉํ•˜๋‚˜์š” { #why-virtual-environments } -FastAPI๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ € <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>์„ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +FastAPI๋กœ ์ž‘์—…ํ•˜๋ ค๋ฉด <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>์„ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -๊ทธ ํ›„์—๋Š” FastAPI์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  **๊ธฐํƒ€ ํŒจํ‚ค์ง€๋“ค**์„ **์„ค์น˜**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ ๋‹ค์Œ FastAPI์™€ ์‚ฌ์šฉํ•˜๋ ค๋Š” ๋‹ค๋ฅธ **ํŒจํ‚ค์ง€**๋ฅผ **์„ค์น˜**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•  ๋•Œ ๋ณดํ†ต Python์— ๊ธฐ๋ณธ ํฌํ•จ๋œ `pip` ๋ช…๋ น์–ด(๋˜๋Š” ์œ ์‚ฌํ•œ ๋„๊ตฌ)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•  ๋•Œ๋Š” ๋ณดํ†ต Python์— ํฌํ•จ๋œ `pip` ๋ช…๋ น์–ด(๋˜๋Š” ์œ ์‚ฌํ•œ ๋Œ€์•ˆ)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ `pip`์„ ๊ทธ๋ƒฅ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋ฉด, ํ•ด๋‹น ํŒจํ‚ค์ง€๋“ค์€ **์ „์—ญ Python ํ™˜๊ฒฝ**(์‹œ์Šคํ…œ ์ „์ฒด์— ์„ค์น˜๋œ Python)์— ์„ค์น˜๋ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ `pip`๋ฅผ ๊ทธ๋Œ€๋กœ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋ฉด, ํŒจํ‚ค์ง€๋Š” **์ „์—ญ Python ํ™˜๊ฒฝ**(์ „์—ญ Python ์„ค์น˜)์— ์„ค์น˜๋ฉ๋‹ˆ๋‹ค. -### ๋ฌธ์ œ์  +### ๋ฌธ์ œ์  { #the-problem } -๊ทธ๋ ‡๋‹ค๋ฉด, ์ „์—ญ Python ํ™˜๊ฒฝ์— ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๋ฉด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ• ๊นŒ์š”? +๊ทธ๋ ‡๋‹ค๋ฉด, ์ „์—ญ Python ํ™˜๊ฒฝ์— ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๋ฉด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ์žˆ์„๊นŒ์š”? -์–ด๋А ์‹œ์ ์ด ๋˜๋ฉด, **์„œ๋กœ ๋‹ค๋ฅธ ํŒจํ‚ค์ง€๋“ค**์— ์˜์กดํ•˜๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋“ค ์ค‘ ์ผ๋ถ€๋Š” **๊ฐ™์€ ํŒจํ‚ค์ง€์˜ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฒ„์ „**์„ ํ•„์š”๋กœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ฑ +์–ด๋А ์‹œ์ ์ด ๋˜๋ฉด **์„œ๋กœ ๋‹ค๋ฅธ ํŒจํ‚ค์ง€**์— ์˜์กดํ•˜๋Š” ๋‹ค์–‘ํ•œ ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ž‘์—…ํ•˜๋Š” ํ”„๋กœ์ ํŠธ ์ค‘ ์ผ๋ถ€๋Š” ๊ฐ™์€ ํŒจํ‚ค์ง€์˜ **์„œ๋กœ ๋‹ค๋ฅธ ๋ฒ„์ „**์— ์˜์กดํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ฑ -์˜ˆ๋ฅผ ๋“ค์–ด, `๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ(philosophers-stone)` ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. ์ด ํ”„๋กœ๊ทธ๋žจ์€ `ํ•ด๋ฆฌ ํฌํ„ฐ(harry)`๋ผ๋Š” ํŒจํ‚ค์ง€์˜ `v1` ๋ฒ„์ „์„ **์˜์กด**ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ `harry`๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด `philosophers-stone`์ด๋ผ๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ”„๋กœ๊ทธ๋žจ์€ **`harry`๋ผ๋Š” ๋‹ค๋ฅธ ํŒจํ‚ค์ง€์˜ ๋ฒ„์ „ `1`**์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ `harry`๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ```mermaid flowchart LR stone(philosophers-stone) -->|requires| harry-1[harry v1] ``` -๊ทธ๋Ÿฐ๋ฐ ๋‚˜์ค‘์— `์•„์ฆˆ์นด๋ฐ˜์˜ ์ฃ„์ˆ˜(prisoner-of-azkaban)`์ด๋ผ๋Š” ๋˜ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ฒŒ ๋˜์—ˆ๊ณ , ์ด ํ”„๋กœ์ ํŠธ๋„ ์—ญ์‹œ `harry` ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด ํ”„๋กœ์ ํŠธ๋Š” `harry`์˜ `v3` ๋ฒ„์ „์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋‹ค์Œ, ๋‚˜์ค‘์— `prisoner-of-azkaban`์ด๋ผ๋Š” ๋˜ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , ์ด ํ”„๋กœ์ ํŠธ๋„ `harry`์— ์˜์กดํ•˜์ง€๋งŒ, ์ด ํ”„๋กœ์ ํŠธ๋Š” **`harry` ๋ฒ„์ „ `3`**์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ```mermaid flowchart LR azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] ``` -ํ•˜์ง€๋งŒ ์ด์ œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. ๋กœ์ปฌ ๊ฐ€์ƒ ํ™˜๊ฒฝ ๋Œ€์‹ ์— ์ „์—ญ ํ™˜๊ฒฝ์— ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ฒŒ ๋˜๋ฉด, ์–ด๋–ค ๋ฒ„์ „์˜ `harry`๋ฅผ ์„ค์น˜ํ• ์ง€๋ฅผ ์„ ํƒํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์ด์ œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. ๋กœ์ปฌ **๊ฐ€์ƒ ํ™˜๊ฒฝ**์ด ์•„๋‹ˆ๋ผ ์ „์—ญ(์ „์—ญ ํ™˜๊ฒฝ)์— ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•œ๋‹ค๋ฉด, ์–ด๋–ค ๋ฒ„์ „์˜ `harry`๋ฅผ ์„ค์น˜ํ• ์ง€ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, `๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ(philosophers-stone)`์„ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋จผ์ € `harry` `v1` ๋ฒ„์ „์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์น˜ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: +`philosophers-stone`์„ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ๋จผ์ € `harry` ๋ฒ„์ „ `1`์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: <div class="termy"> @@ -474,7 +495,7 @@ $ pip install "harry==1" </div> -๊ทธ๋Ÿฌ๋ฉด ๊ฒฐ๊ตญ ์ „์—ญ Python ํ™˜๊ฒฝ์—๋Š” `harry` `v1`๋ฒ„์ „์ด ์„ค์น˜๋œ ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์ „์—ญ Python ํ™˜๊ฒฝ์— `harry` ๋ฒ„์ „ `1`์ด ์„ค์น˜๋œ ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ```mermaid flowchart LR @@ -486,7 +507,7 @@ flowchart LR end ``` -ํ•˜์ง€๋งŒ ์ด์ œ `์•„์ฆˆ์นด๋ฐ˜์˜ ์ฃ„์ˆ˜(prisoner-of-azkaban)`์„ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, `harry` `v1`๋ฒ„์ „์„ ์ œ๊ฑฐํ•˜๊ณ  `harry` `v3`๋ฒ„์ „์„ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (๋˜๋Š” ๋‹จ์ˆœํžˆ `v3`๋ฒ„์ „์„ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋„ ๊ธฐ์กด์˜ `v1`๋ฒ„์ „์ด ์ž๋™์œผ๋กœ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.) +ํ•˜์ง€๋งŒ `prisoner-of-azkaban`์„ ์‹คํ–‰ํ•˜๋ ค๋ฉด `harry` ๋ฒ„์ „ `1`์„ ์ œ๊ฑฐํ•˜๊ณ  `harry` ๋ฒ„์ „ `3`์„ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๋˜๋Š” ๋ฒ„์ „ `3`์„ ์„ค์น˜ํ•˜๊ธฐ๋งŒ ํ•ด๋„ ๋ฒ„์ „ `1`์ด ์ž๋™์œผ๋กœ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค). <div class="termy"> @@ -496,9 +517,9 @@ $ pip install "harry==3" </div> -๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์ด์ œ ์ „์—ญ Python ํ™˜๊ฒฝ์—๋Š” `harry` `v3`๋ฒ„์ „์ด ์„ค์น˜๋œ ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋Ÿฌ๋ฉด ์ „์—ญ Python ํ™˜๊ฒฝ์— `harry` ๋ฒ„์ „ `3`์ด ์„ค์น˜๋œ ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ๋‹ค์‹œ `๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ(philosophers-stone)`์„ ์‹คํ–‰ํ•˜๋ ค๊ณ  ํ•˜๋ฉด, **์ž‘๋™ํ•˜์ง€** ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์ด ํ”„๋กœ๊ทธ๋žจ์€ `harry` `v1`๋ฒ„์ „์„ ํ•„์š”๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  `philosophers-stone`์„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋ ค๊ณ  ํ•˜๋ฉด, `harry` ๋ฒ„์ „ `1`์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— **์ž‘๋™ํ•˜์ง€ ์•Š์„** ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ```mermaid flowchart LR @@ -515,50 +536,49 @@ flowchart LR end ``` -/// tip | ํŒ +/// tip -Python ํŒจํ‚ค์ง€๋“ค์€ **์ƒˆ ๋ฒ„์ „**์—์„œ **ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ(breaking changes)**๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ์ตœ๋Œ€ํ•œ ๋…ธ๋ ฅํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ๋ž˜๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์ž‘์—…ํ•˜๋ ค๋ฉด, ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ด๋ณด๋ฉด์„œ ์ƒˆ ๋ฒ„์ „์„ ์˜๋„์ ์œผ๋กœ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. +Python ํŒจํ‚ค์ง€์—์„œ๋Š” **์ƒˆ ๋ฒ„์ „**์—์„œ **ํ˜ธํ™˜์„ฑ์„ ๊นจ๋œจ๋ฆฌ๋Š” ๋ณ€๊ฒฝ(breaking changes)**์„ **ํ”ผํ•˜๋ ค๊ณ ** ์ตœ์„ ์„ ๋‹คํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ์ผ๋ฐ˜์ ์ด์ง€๋งŒ, ์•ˆ์ „์„ ์œ„ํ•ด ๋” ์ตœ์‹  ๋ฒ„์ „์€ ์˜๋„์ ์œผ๋กœ ์„ค์น˜ํ•˜๊ณ , ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ด ๋ชจ๋“  ๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์„ ๋•Œ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. /// -์ด์ œ, ์ด๋Ÿฐ ์ผ์ด ์—ฌ๋Ÿฌ๋ถ„์˜ **๋ชจ๋“  ํ”„๋กœ์ ํŠธ**๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” **์ˆ˜๋งŽ์€ ํŒจํ‚ค์ง€๋“ค**์—์„œ ๋™์‹œ์— ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ์ƒ์ƒํ•ด๋ณด์„ธ์š”. ์ด๋Š” ๋งค์šฐ ๊ด€๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์šฐ๋ฉฐ, ๊ฒฐ๊ตญ **์„œ๋กœ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๋ฒ„์ „**์˜ ํŒจํ‚ค์ง€๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๊ณ , ๊ทธ๋กœ ์ธํ•ด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ์™œ ๋ฐœ์ƒํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๊ฒŒ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด์ œ ์ด๋Ÿฐ ์ผ์ด ์—ฌ๋Ÿฌ๋ถ„์˜ **๋ชจ๋“  ํ”„๋กœ์ ํŠธ๊ฐ€ ์˜์กดํ•˜๋Š”** **๋งŽ์€** ๋‹ค๋ฅธ **ํŒจํ‚ค์ง€**์—์„œ๋„ ์ผ์–ด๋‚œ๋‹ค๊ณ  ์ƒ์ƒํ•ด ๋ณด์„ธ์š”. ์ด๋Š” ๊ด€๋ฆฌํ•˜๊ธฐ๊ฐ€ ๋งค์šฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฒฐ๊ตญ ์ผ๋ถ€ ํ”„๋กœ์ ํŠธ๋Š” ํŒจํ‚ค์ง€์˜ **ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๋ฒ„์ „**์œผ๋กœ ์‹คํ–‰ํ•˜๊ฒŒ ๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์œผ๋ฉฐ, ์™œ ๋ฌด์–ธ๊ฐ€๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”์ง€ ์•Œ์ง€ ๋ชปํ•˜๊ฒŒ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋˜ํ•œ ์‚ฌ์šฉํ•˜๋Š” ์šด์˜์ฒด์ œ(Linux, Windows, macOS ๋“ฑ)์— ๋”ฐ๋ผ Python์ด **๋ฏธ๋ฆฌ ์„ค์น˜๋˜์–ด ์žˆ์„ ์ˆ˜๋„** ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ์šด์˜์ฒด์ œ์˜ ๋™์ž‘์— ํ•„์š”ํ•œ ํŠน์ • ๋ฒ„์ „์˜ ํŒจํ‚ค์ง€๋“ค์ด ํ•จ๊ป˜ ์„ค์น˜๋˜์–ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ƒํƒœ์—์„œ ์ „์—ญ Python ํ™˜๊ฒฝ์— ์ž„์˜์˜ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๋ฉด, ์šด์˜์ฒด์ œ์— ํฌํ•จ๋œ ํ”„๋กœ๊ทธ๋žจ ์ผ๋ถ€๊ฐ€ **๊นจ์งˆ ์œ„ํ—˜**๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ ์šด์˜์ฒด์ œ(Linux, Windows, macOS ๋“ฑ)์— ๋”ฐ๋ผ Python์ด ์ด๋ฏธ ์„ค์น˜๋˜์–ด ์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ์‹œ์Šคํ…œ์— **ํ•„์š”ํ•œ ํŠน์ • ๋ฒ„์ „**์˜ ํŒจํ‚ค์ง€๊ฐ€ ์ผ๋ถ€ ๋ฏธ๋ฆฌ ์„ค์น˜๋˜์–ด ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. ์ „์—ญ Python ํ™˜๊ฒฝ์— ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๋ฉด, ์šด์˜์ฒด์ œ์— ํฌํ•จ๋œ ํ”„๋กœ๊ทธ๋žจ ์ผ๋ถ€๊ฐ€ **๊นจ์งˆ** ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ํŒจํ‚ค์ง€๋“ค์€ ์–ด๋””์— ์„ค์น˜๋˜๋Š”๊ฐ€ +## ํŒจํ‚ค์ง€๋Š” ์–ด๋””์— ์„ค์น˜๋˜๋‚˜์š” { #where-are-packages-installed } -Python์„ ์„ค์น˜ํ•˜๋ฉด, ์ปดํ“จํ„ฐ์— ์—ฌ๋Ÿฌ ๋””๋ ‰ํ„ฐ๋ฆฌ์™€ ํŒŒ์ผ๋“ค์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. +Python์„ ์„ค์น˜ํ•˜๋ฉด ์ปดํ“จํ„ฐ์— ๋ช‡๋ช‡ ํŒŒ์ผ์ด ๋“ค์–ด ์žˆ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. -์ด ์ค‘ ์ผ๋ถ€ ๋””๋ ‰ํ„ฐ๋ฆฌ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์น˜ํ•œ ํŒจํ‚ค์ง€๋“ค์„ ๋ณด๊ด€ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. +์ด ๋””๋ ‰ํ„ฐ๋ฆฌ ์ค‘ ์ผ๋ถ€๋Š” ์„ค์น˜ํ•œ ๋ชจ๋“  ํŒจํ‚ค์ง€๋ฅผ ๋‹ด๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด: +๋‹ค์Œ์„ ์‹คํ–‰ํ•˜๋ฉด: <div class="termy"> ```console -// ์ง€๊ธˆ ์‹คํ–‰ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค, ๊ทธ๋ƒฅ ์˜ˆ์ œ์ผ ๋ฟ์ด์—์š” ๐Ÿค“ +// Don't run this now, it's just an example ๐Ÿค“ $ pip install "fastapi[standard]" ---> 100% ``` </div> -ํ•ด๋‹น ๋ช…๋ น์–ด๋Š” FastAPI ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•œ ์••์ถ• ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์€ ๋ณดํ†ต <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a>์—์„œ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค. +FastAPI ์ฝ”๋“œ๋ฅผ ๋‹ด์€ ์••์ถ• ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a>์—์„œ ๋ฐ›์Šต๋‹ˆ๋‹ค. -๋˜ํ•œ FastAPI๊ฐ€ ์˜์กดํ•˜๋Š” ๋‹ค๋ฅธ ํŒจํ‚ค์ง€๋“ค๋„ ํ•จ๊ป˜ **๋‹ค์šด๋กœ๋“œ**๋ฉ๋‹ˆ๋‹ค. +๋˜ํ•œ FastAPI๊ฐ€ ์˜์กดํ•˜๋Š” ๋‹ค๋ฅธ ํŒจํ‚ค์ง€๋“ค์˜ ํŒŒ์ผ๋„ **๋‹ค์šด๋กœ๋“œ**ํ•ฉ๋‹ˆ๋‹ค. -๊ทธ๋ฆฌ๊ณ  ๊ทธ ๋ชจ๋“  ํŒŒ์ผ๋“ค์„ **์••์ถ• ํ•ด์ œ**ํ•œ ๋’ค, ์ปดํ“จํ„ฐ์˜ ํŠน์ • ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. +๊ทธ ๋‹ค์Œ ๋ชจ๋“  ํŒŒ์ผ์„ **์••์ถ• ํ•ด์ œ**ํ•˜๊ณ  ์ปดํ“จํ„ฐ์˜ ํ•œ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ๋„ฃ์Šต๋‹ˆ๋‹ค. -๊ธฐ๋ณธ์ ์œผ๋กœ ์ด ํŒŒ์ผ๋“ค์€ Python์ด ์„ค์น˜๋œ ๋””๋ ‰ํ„ฐ๋ฆฌ ์•ˆ, ์ฆ‰ **์ „์—ญ ํ™˜๊ฒฝ**์— ๋‚ด์˜ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. +๊ธฐ๋ณธ์ ์œผ๋กœ, ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์••์ถ• ํ•ด์ œํ•œ ํŒŒ์ผ๋“ค์€ Python ์„ค์น˜์™€ ํ•จ๊ป˜ ์ œ๊ณต๋˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ, ์ฆ‰ **์ „์—ญ ํ™˜๊ฒฝ**์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. -## ๊ฐ€์ƒ ํ™˜๊ฒฝ์ด๋ž€ +## ๊ฐ€์ƒ ํ™˜๊ฒฝ์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š” { #what-are-virtual-environments } -์ „์—ญ ํ™˜๊ฒฝ์— ๋ชจ๋“  ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๋ฉด์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์€, ์ž‘์—…ํ•˜๋Š” **๊ฐ ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค ๊ฐ€์ƒ ํ™˜๊ฒฝ**์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ „์—ญ ํ™˜๊ฒฝ์— ๋ชจ๋“  ํŒจํ‚ค์ง€๋ฅผ ๋‘๋Š” ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์€ ์ž‘์—…ํ•˜๋Š” **๊ฐ ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค ๊ฐ€์ƒ ํ™˜๊ฒฝ**์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. -๊ฐ€์ƒ ํ™˜๊ฒฝ์€ ์ „์—ญ ํ™˜๊ฒฝ๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•œ ํ•˜๋‚˜์˜ **๋””๋ ‰ํ„ฐ๋ฆฌ**์ด๋ฉฐ, ๊ทธ ์•ˆ์— ํ•ด๋‹น ํ”„๋กœ์ ํŠธ๋ฅผ ์œ„ํ•œ ํŒจํ‚ค์ง€๋“ค์„ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ํ”„๋กœ์ ํŠธ๋Š” ์ž์ฒด์ ์ธ ๊ฐ€์ƒ ํ™˜๊ฒฝ(`.venv` ๋””๋ ‰ํ„ฐ๋ฆฌ)์„ ๊ฐ€์ง€๊ฒŒ ๋˜๋ฉฐ, ๊ทธ ์•ˆ์— ํ•ด๋‹น ํ”„๋กœ์ ํŠธ ์ „์šฉ ํŒจํ‚ค์ง€๋“ค์„ ๋ณด์œ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +๊ฐ€์ƒ ํ™˜๊ฒฝ์€ ์ „์—ญ ํ™˜๊ฒฝ๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•œ ํ•˜๋‚˜์˜ **๋””๋ ‰ํ„ฐ๋ฆฌ**์ด๋ฉฐ, ํ”„๋กœ์ ํŠธ์˜ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ํ”„๋กœ์ ํŠธ๋Š” ์ž์ฒด ๊ฐ€์ƒ ํ™˜๊ฒฝ(`.venv` ๋””๋ ‰ํ„ฐ๋ฆฌ)๊ณผ ์ž์ฒด ํŒจํ‚ค์ง€๋ฅผ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ```mermaid flowchart TB @@ -577,9 +597,9 @@ flowchart TB stone-project ~~~ azkaban-project ``` -## ๊ฐ€์ƒ ํ™˜๊ฒฝ ํ™œ์„ฑํ™” ์˜๋ฏธ +## ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋‚˜์š” { #what-does-activating-a-virtual-environment-mean } -๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ๋‹ค๋Š” ๊ฒƒ์€, ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค: +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ๋‹ค๋Š” ๊ฒƒ์€, ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ช…๋ น์–ด๋กœ: //// tab | Linux, macOS @@ -607,7 +627,7 @@ $ .venv\Scripts\Activate.ps1 //// tab | Windows Bash -Windows์—์„œ Bash(์˜ˆ: <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ: +๋˜๋Š” Windows์—์„œ Bash(์˜ˆ: <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ: <div class="termy"> @@ -619,19 +639,19 @@ $ source .venv/Scripts/activate //// -์ด ๋ช…๋ น์–ด๋Š” ์ดํ›„์— ์‹คํ–‰๋  ๋ช…๋ น์–ด์—์„œ ์‚ฌ์šฉ๋  [ํ™˜๊ฒฝ ๋ณ€์ˆ˜](environment-variables.md){.internal-link target=_blank} ๋ช‡ ๊ฐœ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. +๋‹ค์Œ ๋ช…๋ น์–ด๋“ค์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡๋ช‡ [ํ™˜๊ฒฝ ๋ณ€์ˆ˜](environment-variables.md){.internal-link target=_blank}๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. -์ด ๋ณ€์ˆ˜๋“ค ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ `PATH` ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. +๊ทธ ๋ณ€์ˆ˜ ์ค‘ ํ•˜๋‚˜๊ฐ€ `PATH` ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. -/// tip | ํŒ +/// tip -`PATH` ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ๋Œ€ํ•ด ๋” ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด [ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋ฌธ์„œ์˜ PATH ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„น์…˜](environment-variables.md#path-environment-variable){.internal-link target=_blank}์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. +`PATH` ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๋ ค๋ฉด [ํ™˜๊ฒฝ ๋ณ€์ˆ˜](environment-variables.md#path-environment-variable){.internal-link target=_blank} ์„น์…˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. /// -๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด, ๊ฐ€์ƒ ํ™˜๊ฒฝ์˜ ๊ฒฝ๋กœ์ธ `.venv/bin` (Linux์™€ macOS) ๋˜๋Š” `.venv\Scripts`(Windows)๋ฅผ `PATH` ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด ๊ฐ€์ƒ ํ™˜๊ฒฝ์˜ ๊ฒฝ๋กœ์ธ `.venv/bin`(Linux์™€ macOS) ๋˜๋Š” `.venv\Scripts`(Windows)๋ฅผ `PATH` ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜๊ธฐ ์ „์˜ `PATH` ๋ณ€์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์•˜๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค: +๊ฐ€๋ น ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜๊ธฐ ์ „์—๋Š” `PATH` ๋ณ€์ˆ˜๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์•˜๋‹ค๊ณ  ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: //// tab | Linux, macOS @@ -639,7 +659,7 @@ $ source .venv/Scripts/activate /usr/bin:/bin:/usr/sbin:/sbin ``` -์‹œ์Šคํ…œ์€ ๋‹ค์Œ ๊ฒฝ๋กœ๋“ค์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: +์ด๋Š” ์‹œ์Šคํ…œ์ด ๋‹ค์Œ ์œ„์น˜์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๋Š”๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค: * `/usr/bin` * `/bin` @@ -654,13 +674,13 @@ $ source .venv/Scripts/activate C:\Windows\System32 ``` -์‹œ์Šคํ…œ์€ ๋‹ค์Œ ๊ฒฝ๋กœ๋“ค์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: +์ด๋Š” ์‹œ์Šคํ…œ์ด ๋‹ค์Œ ์œ„์น˜์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๋Š”๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค: * `C:\Windows\System32` //// -๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ ํ›„์—๋Š”, `PATH` ๋ณ€์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค: +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ ๋’ค์—๋Š” `PATH` ๋ณ€์ˆ˜๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: //// tab | Linux, macOS @@ -668,21 +688,21 @@ C:\Windows\System32 /home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin ``` -์‹œ์Šคํ…œ์€ ๊ฐ€์žฅ ๋จผ์ € ๋‹ค์Œ ๊ฒฝ๋กœ์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค: +์ด๋Š” ์‹œ์Šคํ…œ์ด ์ด์ œ ๋‹ค์Œ ์œ„์น˜์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ๊ฐ€์žฅ ๋จผ์ € ์ฐพ๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค: ```plaintext /home/user/code/awesome-project/.venv/bin ``` -๊ทธ ํ›„์— ๋‹ค๋ฅธ ๋””๋ ‰ํ„ฐ๋ฆฌ๋“ค์„ ํƒ์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๋‚˜์„œ ๋‹ค๋ฅธ ๋””๋ ‰ํ„ฐ๋ฆฌ๋“ค์„ ํƒ์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ ํ„ฐ๋ฏธ๋„์— `python`์„ ์ž…๋ ฅํ•˜๋ฉด, ์‹œ์Šคํ…œ์€ ๋‹ค์Œ ์œ„์น˜์— ์žˆ๋Š” Python ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: +๋”ฐ๋ผ์„œ ํ„ฐ๋ฏธ๋„์— `python`์„ ์ž…๋ ฅํ•˜๋ฉด, ์‹œ์Šคํ…œ์€ ๋‹ค์Œ ์œ„์น˜์—์„œ Python ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ณ : ```plaintext /home/user/code/awesome-project/.venv/bin/python ``` -๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น Python์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. //// @@ -692,31 +712,31 @@ C:\Windows\System32 C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 ``` -์‹œ์Šคํ…œ์€ ๊ฐ€์žฅ ๋จผ์ € ๋‹ค์Œ ๊ฒฝ๋กœ์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค: +์ด๋Š” ์‹œ์Šคํ…œ์ด ์ด์ œ ๋‹ค์Œ ์œ„์น˜์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ๊ฐ€์žฅ ๋จผ์ € ์ฐพ๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค: ```plaintext C:\Users\user\code\awesome-project\.venv\Scripts ``` -๊ทธ ํ›„์— ๋‹ค๋ฅธ ๋””๋ ‰ํ„ฐ๋ฆฌ๋“ค์„ ํƒ์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๋‚˜์„œ ๋‹ค๋ฅธ ๋””๋ ‰ํ„ฐ๋ฆฌ๋“ค์„ ํƒ์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ ํ„ฐ๋ฏธ๋„์— `python`์„ ์ž…๋ ฅํ•˜๋ฉด, ์‹œ์Šคํ…œ์€ ๋‹ค์Œ ๊ฒฝ๋กœ์— ์žˆ๋Š” Python ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: +๋”ฐ๋ผ์„œ ํ„ฐ๋ฏธ๋„์— `python`์„ ์ž…๋ ฅํ•˜๋ฉด, ์‹œ์Šคํ…œ์€ ๋‹ค์Œ ์œ„์น˜์—์„œ Python ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ๊ณ : ```plaintext C:\Users\user\code\awesome-project\.venv\Scripts\python ``` -๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น Python์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. //// -์ค‘์š”ํ•œ ์„ธ๋ถ€ ์‚ฌํ•ญ ์ค‘ ํ•˜๋‚˜๋Š”, ๊ฐ€์ƒ ํ™˜๊ฒฝ์˜ ๊ฒฝ๋กœ๊ฐ€ `PATH` ๋ณ€์ˆ˜์˜ ๊ฐ€์žฅ **์•ž**์— ์ถ”๊ฐ€๋œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ์€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋‹ค๋ฅธ Python๋“ค๋ณด๋‹ค **๋จผ์ €** ์ด ๊ฒฝ๋กœ๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํ„ฐ๋ฏธ๋„์—์„œ `python`์„ ์‹คํ–‰ํ•˜๋ฉด, ์ „์—ญ ํ™˜๊ฒฝ์˜ Python์ด ์•„๋‹Œ **๊ฐ€์ƒ ํ™˜๊ฒฝ์— ์žˆ๋Š”** Python์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. (์˜ˆ: ์ „์—ญ ํ™˜๊ฒฝ์— ์„ค์น˜๋œ `python`์ด ์žˆ๋”๋ผ๋„ ๊ทธ๋ณด๋‹ค ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค.) +์ค‘์š”ํ•œ ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ๊ฐ€์ƒ ํ™˜๊ฒฝ ๊ฒฝ๋กœ๊ฐ€ `PATH` ๋ณ€์ˆ˜์˜ **๋งจ ์•ž**์— ๋“ค์–ด๊ฐ„๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ์€ ๋‹ค๋ฅธ ์–ด๋–ค Python๋ณด๋‹ค๋„ **๋จผ์ €** ์ด๋ฅผ ์ฐพ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด `python`์„ ์‹คํ–‰ํ•  ๋•Œ, ๋‹ค๋ฅธ ์–ด๋–ค `python`(์˜ˆ: ์ „์—ญ ํ™˜๊ฒฝ์˜ `python`)์ด ์•„๋‹ˆ๋ผ **๊ฐ€์ƒ ํ™˜๊ฒฝ์˜ Python**์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด ์ด ์™ธ์—๋„ ๋ช‡ ๊ฐ€์ง€ ๋‹ค๋ฅธ ๊ฒƒ๋“ค์ด ๋ณ€๊ฒฝ๋˜์ง€๋งŒ, ์ด๋Š” ๊ทธ์ค‘์—์„œ๋„ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ณ€ํ™” ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋‹ค๋ฅธ ๋ช‡ ๊ฐ€์ง€๋„ ๋ณ€๊ฒฝ๋˜์ง€๋งŒ, ์ด๊ฒƒ์ด ๊ทธ์ค‘ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. -## ๊ฐ€์ƒ ํ™˜๊ฒฝ ํ™•์ธํ•˜๊ธฐ +## ๊ฐ€์ƒ ํ™˜๊ฒฝ ํ™•์ธํ•˜๊ธฐ { #checking-a-virtual-environment } -๊ฐ€์ƒ ํ™˜๊ฒฝ์ด ํ™œ์„ฑํ™” ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด, ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ฐ€์ƒ ํ™˜๊ฒฝ์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•  ๋•Œ๋Š”, ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: //// tab | Linux, macOS, Windows Bash @@ -746,34 +766,33 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python //// -์ฆ‰, ํ˜„์žฌ ์‚ฌ์šฉ๋˜๋Š” `python` ํ”„๋กœ๊ทธ๋žจ์€ **๊ฐ€์ƒ ํ™˜๊ฒฝ ๋‚ด๋ถ€์— ์žˆ๋Š” ๊ฒƒ**์ž…๋‹ˆ๋‹ค. +์ด๋Š” ์‚ฌ์šฉ๋  `python` ํ”„๋กœ๊ทธ๋žจ์ด **๊ฐ€์ƒ ํ™˜๊ฒฝ ๋‚ด๋ถ€์— ์žˆ๋Š” ๊ฒƒ**์ด๋ผ๋Š” ๋œป์ž…๋‹ˆ๋‹ค. -Linux์™€ macOS์—์„œ๋Š” `which`, Windows PowerShell์—์„œ๋Š” `Get-Command` ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +Linux์™€ macOS์—์„œ๋Š” `which`, Windows PowerShell์—์„œ๋Š” `Get-Command`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -์ด ๋ช…๋ น์–ด๋Š” `PATH` ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ์ง€์ •๋œ ๊ฒฝ๋กœ๋“ค์„ **์ˆœ์„œ๋Œ€๋กœ ํƒ์ƒ‰**ํ•˜๋ฉด์„œ `python`์ด๋ผ๋Š” ์ด๋ฆ„์˜ ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ์Šต๋‹ˆ๋‹ค. -์ฐพ๋Š” ์ฆ‰์‹œ, ํ•ด๋‹น ํ”„๋กœ๊ทธ๋žจ์˜ **๊ฒฝ๋กœ๋ฅผ ์ถœ๋ ฅ**ํ•ฉ๋‹ˆ๋‹ค. +์ด ๋ช…๋ น์–ด๋Š” `PATH` ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ์žˆ๋Š” ๊ฒฝ๋กœ๋ฅผ **์ˆœ์„œ๋Œ€๋กœ** ํ™•์ธํ•˜๋ฉด์„œ `python`์ด๋ผ๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ฐพ์Šต๋‹ˆ๋‹ค. ์ฐพ๋Š” ์ฆ‰์‹œ, ๊ทธ ํ”„๋กœ๊ทธ๋žจ์˜ **๊ฒฝ๋กœ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค**. -์ค‘์š”ํ•œ ์ ์€ ํ„ฐ๋ฏธ๋„์—์„œ `python`์„ ์‹คํ–‰ํ–ˆ์„ ๋•Œ, ์‹ค์ œ๋กœ ์‹คํ–‰๋˜๋Š” "`python`"์ด ์–ด๋–ค ๊ฒƒ์ธ์ง€ ์ •ํ™•ํžˆ ์•Œ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์€ `python`์„ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ, ์‹คํ–‰๋  ์ •ํ™•ํ•œ "`python`"์ด ๋ฌด์—‡์ธ์ง€ ์•Œ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ ํ˜„์žฌ ์˜ฌ๋ฐ”๋ฅธ ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ ์˜ฌ๋ฐ”๋ฅธ ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -/// tip | ํŒ +/// tip -ํ•˜๋‚˜์˜ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•œ ๋’ค, ํ•ด๋‹น Python์„ ๊ฐ€์ง„ ์ƒํƒœ์—์„œ **๋˜ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ**๋กœ ์ด๋™ํ•˜๋Š” ๊ฒƒ์€ ํ”ํžˆ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ•˜๋‚˜ ํ™œ์„ฑํ™”ํ•ด์„œ Python์„ ์‚ฌ์šฉํ•œ ๋‹ค์Œ, **๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋กœ ์ด๋™**ํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์ด๋•Œ ์ด์ „ ํ”„๋กœ์ ํŠธ์˜ ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ์žˆ๋Š” **์ž˜๋ชป๋œ Python ์‹คํ–‰ ํŒŒ์ผ**์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์–ด ์ƒˆ ํ”„๋กœ์ ํŠธ๊ฐ€ **์ •์ƒ ์ž‘๋™ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.** +๊ทธ๋ฆฌ๊ณ  ๋‘ ๋ฒˆ์งธ ํ”„๋กœ์ ํŠธ๋Š” ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์˜ ๊ฐ€์ƒ ํ™˜๊ฒฝ์—์„œ ์˜จ **์ž˜๋ชป๋œ Python**์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— **์ž‘๋™ํ•˜์ง€ ์•Š์„** ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ž˜์„œ ํ˜„์žฌ ์–ด๋–ค `python`์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์€ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿค“ +์–ด๋–ค `python`์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿค“ /// -## ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ์ด์œ  +## ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์™œ ๋น„ํ™œ์„ฑํ™”ํ•˜๋‚˜์š” { #why-deactivate-a-virtual-environment } -์˜ˆ๋ฅผ ๋“ค์–ด `๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ(philosophers-stone)`์ด๋ผ๋Š” ํ”„๋กœ์ ํŠธ์—์„œ ์ž‘์—… ์ค‘์ด๋ผ๊ณ  ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ ํ•ด๋‹น **๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”**ํ•˜๊ณ , ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๋ฉฐ ์ž‘์—…์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. +์˜ˆ๋ฅผ ๋“ค์–ด `philosophers-stone` ํ”„๋กœ์ ํŠธ์—์„œ ์ž‘์—…ํ•˜๋ฉด์„œ, **๊ทธ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”**ํ•˜๊ณ , ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ณ , ๊ทธ ํ™˜๊ฒฝ์œผ๋กœ ์ž‘์—…ํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฐ๋ฐ ์ด์ œ๋Š” **๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ**์ธ `์•„์ฆˆ์นด๋ฐ˜์˜ ์ฃ„์ˆ˜(prisoner-of-azkaban)`์„ ์ž‘์—…ํ•˜๊ณ  ์‹ถ์–ด์กŒ์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ๋ฐ ์ด์ œ **๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ**์ธ `prisoner-of-azkaban`์—์„œ ์ž‘์—…ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ž˜์„œ ๊ทธ ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค: +ํ•ด๋‹น ํ”„๋กœ์ ํŠธ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค: <div class="termy"> @@ -783,7 +802,7 @@ $ cd ~/code/prisoner-of-azkaban </div> -๋งŒ์•ฝ `๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ(philosophers-stone)`์˜ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ํ„ฐ๋ฏธ๋„์—์„œ `python`์„ ์‹คํ–‰ํ•  ๋•Œ ์—ฌ์ „ํžˆ `๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ(philosophers-stone)` ๊ฐ€์ƒ ํ™˜๊ฒฝ์˜ Python์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +`philosophers-stone`์˜ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜์ง€ ์•Š์œผ๋ฉด, ํ„ฐ๋ฏธ๋„์—์„œ `python`์„ ์‹คํ–‰ํ•  ๋•Œ `philosophers-stone`์˜ Python์„ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. <div class="termy"> @@ -792,7 +811,7 @@ $ cd ~/code/prisoner-of-azkaban $ python main.py -// sirius๋ฅผ ์ž„ํฌํŠธํ•˜๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์„ค์น˜๋˜์–ด ์žˆ์ง€ ์•Š์•„์š” ๐Ÿ˜ฑ +// Error importing sirius, it's not installed ๐Ÿ˜ฑ Traceback (most recent call last): File "main.py", line 1, in <module> import sirius @@ -800,47 +819,46 @@ Traceback (most recent call last): </div> -ํ•˜์ง€๋งŒ `๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ(philosophers-stone)`์˜ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋น„ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, `์•„์ฆˆ์นด๋ฐ˜์˜ ์ฃ„์ˆ˜(prisoner-of-azkaban)` ํ”„๋กœ์ ํŠธ์˜ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด, ์ด์ œ `python` ๋ช…๋ น์–ด๋Š” `์•„์ฆˆ์นด๋ฐ˜์˜ ์ฃ„์ˆ˜(prisoner-of-azkaban)` ๊ฐ€์ƒ ํ™˜๊ฒฝ์˜ Python์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  `prisoner-of-askaban`์— ๋Œ€ํ•œ ์ƒˆ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด, `python`์„ ์‹คํ–‰ํ•  ๋•Œ `prisoner-of-azkaban`์˜ ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ์žˆ๋Š” Python์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. <div class="termy"> ```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 -// prisoner-of-azkaban/.venv ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค ๐Ÿš€ +// Activate the virtual environment in prisoner-of-azkaban/.venv ๐Ÿš€ $ source .venv/bin/activate -// ์ด์ œ python์„ ์‹คํ–‰ํ•˜๋ฉด, ์ด ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ์„ค์น˜๋œ sirius ํŒจํ‚ค์ง€๋ฅผ ์ฐพ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค โœจ +// Now when you run python, it will find the package sirius installed in this virtual environment โœจ $ python main.py -๋ชป๋œ ์ง“์„ ๊พธ๋ฏธ๊ณ  ์žˆ์Œ์„ ์—„์ˆ™ํžˆ ๋งน์„ธํ•ฉ๋‹ˆ๋‹ค.๐Ÿง™ -ImportError๋Š” ์ด์ œ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿบ +I solemnly swear ๐Ÿบ ``` </div> -## ๋Œ€์•ˆ๋“ค +## ๋Œ€์•ˆ๋“ค { #alternatives } -์ด ๋ฌธ์„œ๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด Python ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๊ณ , **๊ทธ ๋‚ด๋ถ€์—์„œ** ์–ด๋–ป๊ฒŒ ๋Œ์•„๊ฐ€๋Š”์ง€ ์•Œ๋ ค์ฃผ๋Š” ๊ฐ„๋‹จํ•œ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. +์ด ๋ฌธ์„œ๋Š” ์‹œ์ž‘์„ ๋•๊ณ , ๋‚ด๋ถ€์—์„œ ๋ชจ๋“  ๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์•Œ๋ ค์ฃผ๋Š” ๊ฐ„๋‹จํ•œ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. -๊ฐ€์ƒ ํ™˜๊ฒฝ, ํŒจํ‚ค์ง€ ์˜์กด์„ฑ(Requirements), ํ”„๋กœ์ ํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ์ด ์™ธ์—๋„ ๋‹ค์–‘ํ•œ **๋Œ€์•ˆ**๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. +๊ฐ€์ƒ ํ™˜๊ฒฝ, ํŒจํ‚ค์ง€ ์˜์กด์„ฑ(requirements), ํ”„๋กœ์ ํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ๋งŽ์€ **๋Œ€์•ˆ**์ด ์žˆ์Šต๋‹ˆ๋‹ค. -๋งŒ์•ฝ ์ค€๋น„๊ฐ€ ๋˜์—ˆ๋‹ค๋ฉด, **ํ”„๋กœ์ ํŠธ ์ „์ฒด**, ํŒจํ‚ค์ง€ ์˜์กด์„ฑ, ๊ฐ€์ƒ ํ™˜๊ฒฝ ๋“ฑ์„ ํ†ตํ•ฉ์ ์œผ๋กœ **๊ด€๋ฆฌ**ํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ๋ฅผ ์จ๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿด ๋•Œ ์ถ”์ฒœํ•˜๋Š” ๋„๊ตฌ๊ฐ€ ๋ฐ”๋กœ <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>์ž…๋‹ˆ๋‹ค. +์ค€๋น„๊ฐ€ ๋˜์—ˆ๊ณ  **ํ”„๋กœ์ ํŠธ ์ „์ฒด**, ํŒจํ‚ค์ง€ ์˜์กด์„ฑ, ๊ฐ€์ƒ ํ™˜๊ฒฝ ๋“ฑ์„ **๊ด€๋ฆฌ**ํ•˜๋Š” ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด์‹œ๊ธธ ๊ถŒํ•ฉ๋‹ˆ๋‹ค. -`uv`๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค: +`uv`๋Š” ๋งŽ์€ ์ผ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: -* ๋‹ค์–‘ํ•œ ๋ฒ„์ „์˜ **Python ์„ค์น˜** -* ๊ฐ ํ”„๋กœ์ ํŠธ ๋ณ„ **๊ฐ€์ƒ ํ™˜๊ฒฝ ๊ด€๋ฆฌ** -* **ํŒจํ‚ค์ง€ ์„ค์น˜** -* ํ”„๋กœ์ ํŠธ์˜ **์˜์กด์„ฑ๊ณผ ๋ฒ„์ „** ๊ด€๋ฆฌ -* ์„ค์น˜๋œ ํŒจํ‚ค์ง€๋“ค๊ณผ ๊ทธ ๋ฒ„์ „์„ **์ •ํ™•ํžˆ ๊ณ ์ •(lock)**ํ•ด์„œ,๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ์šด์˜ ํ™˜๊ฒฝ์ด ์™„์ „ํžˆ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณด์žฅ -* ์ด ์™ธ์—๋„ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ง€์› +* ์—ฌ๋Ÿฌ ๋ฒ„์ „์„ ํฌํ•จํ•ด **Python์„ ์„ค์น˜** +* ํ”„๋กœ์ ํŠธ์˜ **๊ฐ€์ƒ ํ™˜๊ฒฝ** ๊ด€๋ฆฌ +* **ํŒจํ‚ค์ง€** ์„ค์น˜ +* ํ”„๋กœ์ ํŠธ์˜ ํŒจํ‚ค์ง€ **์˜์กด์„ฑ๊ณผ ๋ฒ„์ „** ๊ด€๋ฆฌ +* ์˜์กด์„ฑ์„ ํฌํ•จํ•ด ์„ค์น˜ํ•  ํŒจํ‚ค์ง€์™€ ๋ฒ„์ „์˜ **์ •ํ™•ํ•œ** ์„ธํŠธ๋ฅผ ๋ณด์žฅํ•˜์—ฌ, ๊ฐœ๋ฐœ ์ค‘์ธ ์ปดํ“จํ„ฐ์™€ ๋™์ผํ•˜๊ฒŒ ํ”„๋กœ๋•์…˜์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ **locking**์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค +* ๊ทธ ์™ธ์—๋„ ๋งŽ์€ ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค -## ๊ฒฐ๋ก  +## ๊ฒฐ๋ก  { #conclusion } -์—ฌ๊ธฐ๊นŒ์ง€ ๋ชจ๋‘ ์ฝ๊ณ  ์ดํ•ดํ–ˆ๋‹ค๋ฉด, ์ด์ œ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค๋ณด๋‹ค ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ **ํ›จ์”ฌ ๋” ๊นŠ์ด ์žˆ๊ฒŒ ์ดํ•ด**ํ•˜๊ฒŒ ๋˜์…จ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ +์—ฌ๊ธฐ๊นŒ์ง€ ๋ชจ๋‘ ์ฝ๊ณ  ์ดํ•ดํ–ˆ๋‹ค๋ฉด, ์ด์ œ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค๋ณด๋‹ค ๊ฐ€์ƒ ํ™˜๊ฒฝ์— ๋Œ€ํ•ด **ํ›จ์”ฌ ๋” ๋งŽ์ด** ์•Œ๊ฒŒ ๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿค“ -์ด๋Ÿฐ ์„ธ๋ถ€์ ์ธ ๋‚ด์šฉ์„ ์•Œ๊ณ  ์žˆ์œผ๋ฉด, ์–ธ์  ๊ฐ€ ๋ณต์žกํ•ด ๋ณด์ด๋Š” ๋ฌธ์ œ๋ฅผ ๋””๋ฒ„๊น…ํ•  ๋•Œ ๋ถ„๋ช…ํžˆ ํฐ ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด์ œ๋Š” **์ด ๋ชจ๋“  ๊ฒƒ๋“ค์ด ๋‚ด๋ถ€์—์„œ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€** ์•Œ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜Ž +์ด ์„ธ๋ถ€ ์‚ฌํ•ญ์„ ์•Œ๊ณ  ์žˆ์œผ๋ฉด, ๋‚˜์ค‘์— ๋ณต์žกํ•ด ๋ณด์ด๋Š” ๋ฌด์–ธ๊ฐ€๋ฅผ ๋””๋ฒ„๊น…ํ•  ๋•Œ ์•„๋งˆ๋„ ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. **๋‚ด๋ถ€์—์„œ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€** ์•Œ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜Ž diff --git a/scripts/docs.py b/scripts/docs.py index a9abce56b5..c350ee8319 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -23,6 +23,7 @@ SUPPORTED_LANGS = { "en", "de", "es", + "ko", "pt", "ru", "uk", From 1fedd1c73b576ab9a969a30cde94bf19b329999a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:15:31 +0000 Subject: [PATCH 063/110] =?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 a25c94b7fa..5bd1b03aea 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 [#14587](https://github.com/fastapi/fastapi/pull/14587) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for es (update-outdated). PR [#14686](https://github.com/fastapi/fastapi/pull/14686) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add LLM prompt file for Turkish, generated from the existing translations. PR [#14547](https://github.com/fastapi/fastapi/pull/14547) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add LLM prompt file for Traditional Chinese, generated from the existing translations. PR [#14550](https://github.com/fastapi/fastapi/pull/14550) by [@tiangolo](https://github.com/tiangolo). From 16e583413c678f36e3f2204675c384bc884ecbd1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:16:33 +0000 Subject: [PATCH 064/110] =?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 5bd1b03aea..19efeec13a 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-outdated). PR [#14589](https://github.com/fastapi/fastapi/pull/14589) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for uk (update-outdated). PR [#14587](https://github.com/fastapi/fastapi/pull/14587) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for es (update-outdated). PR [#14686](https://github.com/fastapi/fastapi/pull/14686) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add LLM prompt file for Turkish, generated from the existing translations. PR [#14547](https://github.com/fastapi/fastapi/pull/14547) by [@tiangolo](https://github.com/tiangolo). From 5ec2615b1ac7b6970b8d548a8556cd6b2026c30f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sat, 10 Jan 2026 16:21:07 -0800 Subject: [PATCH 065/110] =?UTF-8?q?=F0=9F=91=B7=20Tweak=20CI=20input=20nam?= =?UTF-8?q?es=20(#14688)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/translate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index 734a3782fc..83518614b0 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -31,7 +31,7 @@ on: required: false default: "" commit_in_place: - description: Whether to commit changes directly instead of making a PR + description: Commit changes directly instead of making a PR type: boolean required: false default: false From 50fa3f7c882cea592e5488a50354bfc016ea65bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:21:31 +0000 Subject: [PATCH 066/110] =?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 19efeec13a..52166f4e8d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -22,6 +22,7 @@ hide: ### Internal +* ๐Ÿ‘ท Tweak CI input names. PR [#14688](https://github.com/fastapi/fastapi/pull/14688) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Refactor translation script to allow committing in place. PR [#14687](https://github.com/fastapi/fastapi/pull/14687) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ› Fix translation script path. PR [#14685](https://github.com/fastapi/fastapi/pull/14685) by [@tiangolo](https://github.com/tiangolo). * โœ… Enable tests in CI for scripts. PR [#14684](https://github.com/fastapi/fastapi/pull/14684) by [@tiangolo](https://github.com/tiangolo). From 0383fb3ab912e1e8e1549597afd510f0e857b70f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:41:45 +0100 Subject: [PATCH 067/110] =?UTF-8?q?=E2=AC=86=20Bump=20actions/download-art?= =?UTF-8?q?ifact=20from=206=20to=207=20(#14526)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy-docs.yml | 2 +- .github/workflows/smokeshow.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 79d7391c46..734fc244d3 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -45,7 +45,7 @@ jobs: run: | rm -rf ./site mkdir ./site - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: path: ./site/ pattern: docs-site-* diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index 1a45174dce..f23b962b70 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -28,7 +28,7 @@ jobs: pyproject.toml uv.lock - run: uv sync --locked --no-dev --group github-actions - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: name: coverage-html path: htmlcov diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d45c20eb1..605d7c8128 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -112,7 +112,7 @@ jobs: - name: Install Dependencies run: uv sync --locked --no-dev --group tests --extra all - name: Get coverage files - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: pattern: coverage-* path: coverage From 9a76f2fec9043cfa0834a70d8c879a03506e6860 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:42:08 +0000 Subject: [PATCH 068/110] =?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 52166f4e8d..46488d3263 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -22,6 +22,7 @@ hide: ### Internal +* โฌ† Bump actions/download-artifact from 6 to 7. PR [#14526](https://github.com/fastapi/fastapi/pull/14526) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Tweak CI input names. PR [#14688](https://github.com/fastapi/fastapi/pull/14688) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Refactor translation script to allow committing in place. PR [#14687](https://github.com/fastapi/fastapi/pull/14687) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ› Fix translation script path. PR [#14685](https://github.com/fastapi/fastapi/pull/14685) by [@tiangolo](https://github.com/tiangolo). From d05b18ec4012304355ec3df1d9cc4e7c78e5419a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:42:37 +0100 Subject: [PATCH 069/110] =?UTF-8?q?=E2=AC=86=20Bump=20actions/upload-artif?= =?UTF-8?q?act=20from=205=20to=206=20(#14525)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-docs.yml | 2 +- .github/workflows/test.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 2198fe6685..d1b38930cf 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -97,7 +97,7 @@ jobs: path: docs/${{ matrix.lang }}/.cache - name: Build Docs run: uv run ./scripts/docs.py build-lang ${{ matrix.lang }} - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: docs-site-${{ matrix.lang }} path: ./site/** diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 605d7c8128..891f767175 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -84,7 +84,7 @@ jobs: # Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow - name: Store coverage files if: matrix.coverage == 'coverage' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/coverage/.coverage.*') }} path: coverage @@ -121,7 +121,7 @@ jobs: - run: uv run coverage combine coverage - run: uv run coverage html --title "Coverage for ${{ github.sha }}" - name: Store coverage HTML - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: coverage-html path: htmlcov From 14f30687627bfde7056bea8066b3a006cd1abdad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:42:51 +0100 Subject: [PATCH 070/110] =?UTF-8?q?=E2=AC=86=20Bump=20actions/cache=20from?= =?UTF-8?q?=204=20to=205=20(#14511)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index d1b38930cf..77bce055c6 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -91,7 +91,7 @@ jobs: run: uv sync --locked --no-dev --group docs - name: Update Languages run: uv run ./scripts/docs.py update-languages - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: key: mkdocs-cards-${{ matrix.lang }}-${{ github.ref }} path: docs/${{ matrix.lang }}/.cache From 612a2d20bcbfa7041d2e87bbae7aa3db7bffee67 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:43:25 +0000 Subject: [PATCH 071/110] =?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 46488d3263..491caaee86 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -22,6 +22,7 @@ hide: ### Internal +* โฌ† Bump actions/upload-artifact from 5 to 6. PR [#14525](https://github.com/fastapi/fastapi/pull/14525) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump actions/download-artifact from 6 to 7. PR [#14526](https://github.com/fastapi/fastapi/pull/14526) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Tweak CI input names. PR [#14688](https://github.com/fastapi/fastapi/pull/14688) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Refactor translation script to allow committing in place. PR [#14687](https://github.com/fastapi/fastapi/pull/14687) by [@tiangolo](https://github.com/tiangolo). From effe493ae0d398e8237de1796ec3ec25ef19e4f0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:43:35 +0000 Subject: [PATCH 072/110] =?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 491caaee86..7727fa699c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -22,6 +22,7 @@ hide: ### Internal +* โฌ† Bump actions/cache from 4 to 5. PR [#14511](https://github.com/fastapi/fastapi/pull/14511) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump actions/upload-artifact from 5 to 6. PR [#14525](https://github.com/fastapi/fastapi/pull/14525) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump actions/download-artifact from 6 to 7. PR [#14526](https://github.com/fastapi/fastapi/pull/14526) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Tweak CI input names. PR [#14688](https://github.com/fastapi/fastapi/pull/14688) by [@tiangolo](https://github.com/tiangolo). From 1054fbd2563c91ac7798ae25c486c426df9d7909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sun, 11 Jan 2026 10:18:38 -0800 Subject: [PATCH 073/110] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20for=20co?= =?UTF-8?q?ntributing=20with=20translations=20(#14701)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/contributing.md | 235 +++-------------------------------- scripts/docs.py | 11 +- 2 files changed, 17 insertions(+), 229 deletions(-) diff --git a/docs/en/docs/contributing.md b/docs/en/docs/contributing.md index 18b7f60f72..be4706f742 100644 --- a/docs/en/docs/contributing.md +++ b/docs/en/docs/contributing.md @@ -177,252 +177,45 @@ as Uvicorn by default will use the port `8000`, the documentation on port `8008` ### Translations -/// warning | Attention - -**Update on Translations** - -We're updating the way we handle documentation translations. - -Until now, we invited community members to translate pages via pull requests, which were then reviewed by at least two native speakers. While this has helped bring FastAPI to many more users, weโ€™ve also run into several challenges - some languages have only a few translated pages, others are outdated and hard to maintain over time. -To improve this, weโ€™re working on automation tools ๐Ÿค– to manage translations more efficiently. Once ready, documentation will be machine-translated and still reviewed by at least two native speakers โœ… before publishing. This will allow us to keep translations up-to-date while reducing the review burden on maintainers. - -Whatโ€™s changing now: - -* ๐Ÿšซ Weโ€™re no longer accepting new community-submitted translation PRs. - -* โณ Existing open PRs will be reviewed and can still be merged if completed within the next 3 weeks (since July 11 2025). - -* ๐ŸŒ In the future, we will only support languages where at least three active native speakers are available to review and maintain translations. - -This transition will help us keep translations more consistent and timely while better supporting our contributors ๐Ÿ™Œ. Thank you to everyone who has contributed so far โ€” your help has been invaluable! ๐Ÿ’– - -/// - - 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. -#### Tips and guidelines +#### Review 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! * Check the currently <a href="https://github.com/fastapi/fastapi/pulls" class="external-link" target="_blank">existing pull requests</a> 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 <a href="https://github.com/fastapi/fastapi/pulls?q=is%3Aopen+sort%3Aupdated-desc+label%3Alang-es+label%3Aawaiting-review" class="external-link" target="_blank">`lang-es`</a>. -* Review those pull requests, requesting changes or approving them. For the languages I don't speak, I'll wait for several others to review the translation before merging. +* 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: <a href="https://github.com/fastapi/fastapi/blob/master/docs/es/llm-prompt.md" class="external-link" target="_blank">`docs/es/llm-prompt.md`</a>. /// tip -You can <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request" class="external-link" target="_blank">add comments with change suggestions</a> to existing pull requests. - Check the docs about <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-request-reviews" class="external-link" target="_blank">adding a pull request review</a> to approve it or request changes. /// +#### Subscribe to Notifications for Your Language + * Check if there's a <a href="https://github.com/fastapi/fastapi/discussions/categories/translations" class="external-link" target="_blank">GitHub Discussion</a> 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. -* If you translate pages, add a single pull request per page translated. That will make it much easier for others to review it. - * To check the 2-letter code for the language you want to translate, you can use the table <a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes" class="external-link" target="_blank">List of ISO 639-1 codes</a>. -#### Existing language - -Let's say you want to translate a page for a language that already has translations for some pages, like Spanish. - -In the case of Spanish, the 2-letter code is `es`. So, the directory for Spanish translations is located at `docs/es/`. - -/// tip - -The main ("official") language is English, located at `docs/en/`. - -/// - -Now run the live server for the docs in Spanish: - -<div class="termy"> - -```console -// Use the command "live" and pass the language code as a CLI argument -$ python ./scripts/docs.py live es - -<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 -<span style="color: green;">[INFO]</span> Start watching changes -<span style="color: green;">[INFO]</span> Start detecting changes -``` - -</div> - -/// tip - -Alternatively, you can perform the same steps that scripts does manually. - -Go into the language directory, for the Spanish translations it's at `docs/es/`: - -```console -$ cd docs/es/ -``` - -Then run `mkdocs` in that directory: - -```console -$ mkdocs serve --dev-addr 127.0.0.1:8008 -``` - -/// - -Now you can go to <a href="http://127.0.0.1:8008" class="external-link" target="_blank">http://127.0.0.1:8008</a> and see your changes live. - -You will see that every language has all the pages. But some pages are not translated and have an info box at the top, about the missing translation. - -Now let's say that you want to add a translation for the section [Features](features.md){.internal-link target=_blank}. - -* Copy the file at: - -``` -docs/en/docs/features.md -``` - -* Paste it in exactly the same location but for the language you want to translate, e.g.: - -``` -docs/es/docs/features.md -``` - -/// tip - -Notice that the only change in the path and file name is the language code, from `en` to `es`. - -/// - -If you go to your browser you will see that now the docs show your new section (the info box at the top is gone). ๐ŸŽ‰ - -Now you can translate it all and see how it looks as you save the file. - -#### Don't Translate these Pages - -๐Ÿšจ Don't translate: - -* Files under `reference/` -* `release-notes.md` -* `fastapi-people.md` -* `external-links.md` -* `newsletter.md` -* `management-tasks.md` -* `management.md` -* `contributing.md` - -Some of these files are updated very frequently and a translation would always be behind, or they include the main content from English source files, etc. - #### Request a New Language Let's say that you want to request translations for a language that is not yet translated, not even some pages. For example, Latin. -If there is no discussion for that language, you can start by requesting the new language. For that, you can follow these steps: - +* The first step would be for you to find other 2 people that would be willing to be reviewing translation PRs for that language with you. +* Once there are at least 3 people that would be willing to commit to help maintain that language, you can continue the next steps. * Create a new discussion following the template. -* Get a few native speakers to comment on the discussion and commit to help review translations for that language. +* Tag the other 2 people that will help with the language, and ask them to confirm there they will help. Once there are several people in the discussion, the FastAPI team can evaluate it and can make it an official translation. -Then the docs will be automatically translated using AI, and the team of native speakers can review the translation, and help tweak the AI prompts. +Then the docs will be automatically translated using LLMs, and the team of native speakers can review the translation, and help tweak the LLM prompts. Once there's a new translation, for example if docs are updated or there's a new section, there will be a comment in the same discussion with the link to the new translation to review. - -#### New Language - -/// note - -These steps will be performed by the FastAPI team. - -/// - -Checking the link from above (List of ISO 639-1 codes), you can see that the 2-letter code for Latin is `la`. - -Now you can create a new directory for the new language, running the following script: - -<div class="termy"> - -```console -// Use the command new-lang, pass the language code as a CLI argument -$ python ./scripts/docs.py new-lang la - -Successfully initialized: docs/la -``` - -</div> - -Now you can check in your code editor the newly created directory `docs/la/`. - -That command created a file `docs/la/mkdocs.yml` with a simple config that inherits everything from the `en` version: - -```yaml -INHERIT: ../en/mkdocs.yml -``` - -/// tip - -You could also simply create that file with those contents manually. - -/// - -That command also created a dummy file `docs/la/index.md` for the main page, you can start by translating that one. - -You can continue with the previous instructions for an "Existing Language" for that process. - -You can make the first pull request with those two files, `docs/la/mkdocs.yml` and `docs/la/index.md`. ๐ŸŽ‰ - -#### Preview the result - -As already mentioned above, you can use the `./scripts/docs.py` with the `live` command to preview the results (or `mkdocs serve`). - -Once you are done, you can also test it all as it would look online, including all the other languages. - -To do that, first build all the docs: - -<div class="termy"> - -```console -// Use the command "build-all", this will take a bit -$ python ./scripts/docs.py build-all - -Building docs for: en -Building docs for: es -Successfully built docs for: es -``` - -</div> - -This builds all those independent MkDocs sites for each language, combines them, and generates the final output at `./site/`. - -Then you can serve that with the command `serve`: - -<div class="termy"> - -```console -// Use the command "serve" after running "build-all" -$ python ./scripts/docs.py serve - -Warning: this is a very simple server. For development, use mkdocs serve instead. -This is here only to preview a site with translations already built. -Make sure you run the build-all command first. -Serving at: http://127.0.0.1:8008 -``` - -</div> - -#### Translation specific tips and guidelines - -* Translate only the Markdown documents (`.md`). Do not translate the code examples at `./docs_src`. - -* In code blocks within the Markdown document, translate comments (`# a comment`), but leave the rest unchanged. - -* Do not change anything enclosed in "``" (inline code). - -* In lines starting with `///` translate only the text part after `|`. Leave the rest unchanged. - -* You can translate info boxes like `/// warning` with for example `/// warning | Achtung`. But do not change the word immediately after the `///`, it determines the color of the info box. - -* Do not change the paths in links to images, code files, Markdown documents. - -* However, when a Markdown document is translated, the `#hash-parts` in links to its headings may change. Update these links if possible. - * Search for such links in the translated document using the regex `#[^# ]`. - * Search in all documents already translated into your language for `your-translated-document.md`. For example VS Code has an option "Edit" -> "Find in Files". - * When translating a document, do not "pre-translate" `#hash-parts` that link to headings in untranslated documents. diff --git a/scripts/docs.py b/scripts/docs.py index c350ee8319..20bf1c1688 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -130,14 +130,9 @@ def new_lang(lang: str = typer.Argument(..., callback=lang_callback)): new_path.mkdir() new_config_path: Path = Path(new_path) / mkdocs_name new_config_path.write_text("INHERIT: ../en/mkdocs.yml\n", encoding="utf-8") - new_config_docs_path: Path = new_path / "docs" - new_config_docs_path.mkdir() - en_index_path: Path = en_docs_path / "docs" / "index.md" - new_index_path: Path = new_config_docs_path / "index.md" - en_index_content = en_index_path.read_text(encoding="utf-8") - new_index_content = f"{missing_translation_snippet}\n\n{en_index_content}" - new_index_path.write_text(new_index_content, encoding="utf-8") - typer.secho(f"Successfully initialized: {new_path}", color=typer.colors.GREEN) + new_llm_prompt_path: Path = new_path / "llm-prompt.md" + new_llm_prompt_path.write_text("", encoding="utf-8") + print(f"Successfully initialized: {new_path}") update_languages() From 97aa825422737b8fbafa9ad76390a86b67a88381 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 18:18:58 +0000 Subject: [PATCH 074/110] =?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 7727fa699c..2a2b0d6396 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* ๐Ÿ“ Update docs for contributing with translations. PR [#14701](https://github.com/fastapi/fastapi/pull/14701) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Specify language code for code block. PR [#14656](https://github.com/fastapi/fastapi/pull/14656) by [@YuriiMotov](https://github.com/YuriiMotov). ### Translations From 249a776b70dfc173d4c9b91a3b0c8d9319d0ee14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sun, 11 Jan 2026 11:19:05 -0800 Subject: [PATCH 075/110] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20about=20?= =?UTF-8?q?managing=20translations=20(#14704)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/fastapi-people.md | 22 +----- docs/en/docs/management-tasks.md | 126 ++----------------------------- 2 files changed, 7 insertions(+), 141 deletions(-) diff --git a/docs/en/docs/fastapi-people.md b/docs/en/docs/fastapi-people.md index f2ca26013c..2c07af7647 100644 --- a/docs/en/docs/fastapi-people.md +++ b/docs/en/docs/fastapi-people.md @@ -196,31 +196,11 @@ They have contributed source code, documentation, etc. ๐Ÿ“ฆ There are hundreds of other contributors, you can see them all in the <a href="https://github.com/fastapi/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors page</a>. ๐Ÿ‘ท -## Top Translators - -These are the **Top Translators**. ๐ŸŒ - -These users have created the most Pull Requests with [translations to other languages](contributing.md#translations){.internal-link target=_blank} that have been *merged*. - -<div class="user-list user-list-center"> - -{% for user in (translators.values() | list)[:50] %} - -{% if user.login not in skip_users %} - -<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Translations: {{ user.count }}</div></div> - -{% endif %} - -{% endfor %} - -</div> - ## Top Translation Reviewers These users are the **Top Translation Reviewers**. ๐Ÿ•ต๏ธ -I only speak a few languages (and not very well ๐Ÿ˜…). So, the reviewers are the ones that have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages. +Translation reviewers have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages. <div class="user-list user-list-center"> {% for user in (translation_reviewers.values() | list)[:50] %} diff --git a/docs/en/docs/management-tasks.md b/docs/en/docs/management-tasks.md index aac4d6fe40..68d10a42cc 100644 --- a/docs/en/docs/management-tasks.md +++ b/docs/en/docs/management-tasks.md @@ -106,129 +106,15 @@ This way, we can notice when there are new translations ready, because they have ## Merge Translation PRs -For Spanish, as I'm a native speaker and it's a language close to me, I will give it a final review myself and in most cases tweak the PR a bit before merging it. +Translations are generated automatically with LLMs and scripts. -For the other languages, confirm that: +There's one GitHub Action that can be manually run to add or update translations for a language: <a href="https://github.com/fastapi/fastapi/actions/workflows/translate.yml" class="external-link" target="_blank">`translate.yml`</a>. -* The title is correct following the instructions above. +For these language translation PRs, confirm that: + +* The PR was automated (authored by @tiangolo), not made by another user. * It has the labels `lang-all` and `lang-{lang code}`. -* The PR changes only one Markdown file adding a translation. - * Or in some cases, at most two files, if they are small, for the same language, and people reviewed them. - * If it's the first translation for that language, it will have additional `mkdocs.yml` files, for those cases follow the instructions below. -* The PR doesn't add any additional or extraneous files. -* The translation seems to have a similar structure as the original English file. -* The translation doesn't seem to change the original content, for example with obvious additional documentation sections. -* The translation doesn't use different Markdown structures, for example adding HTML tags when the original didn't have them. -* The "admonition" sections, like `tip`, `info`, etc. are not changed or translated. For example: - -``` -/// tip - -This is a tip. - -/// - -``` - -looks like this: - -/// tip - -This is a tip. - -/// - -...it could be translated as: - -``` -/// tip - -Esto es un consejo. - -/// - -``` - -...but needs to keep the exact `tip` keyword. If it was translated to `consejo`, like: - -``` -/// consejo - -Esto es un consejo. - -/// - -``` - -it would change the style to the default one, it would look like: - -/// consejo - -Esto es un consejo. - -/// - -Those don't have to be translated, but if they are, they need to be written as: - -``` -/// tip | consejo - -Esto es un consejo. - -/// - -``` - -Which looks like: - -/// tip | consejo - -Esto es un consejo. - -/// - -## First Translation PR - -When there's a first translation for a language, it will have a `docs/{lang code}/docs/index.md` translated file and a `docs/{lang code}/mkdocs.yml`. - -For example, for Bosnian, it would be: - -* `docs/bs/docs/index.md` -* `docs/bs/mkdocs.yml` - -The `mkdocs.yml` file will have only the following content: - -```YAML -INHERIT: ../en/mkdocs.yml -``` - -The language code would normally be in the <a href="https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes" class="external-link" target="_blank">ISO 639-1 list of language codes</a>. - -In any case, the language code should be in the file <a href="https://github.com/fastapi/fastapi/blob/master/docs/language_names.yml" class="external-link" target="_blank">docs/language_names.yml</a>. - -There won't be yet a label for the language code, for example, if it was Bosnian, there wouldn't be a `lang-bs`. Before creating the label and adding it to the PR, create the GitHub Discussion: - -* Go to the <a href="https://github.com/fastapi/fastapi/discussions/categories/translations" class="external-link" target="_blank">Translations GitHub Discussions</a> -* Create a new discussion with the title `Bosnian Translations` (or the language name in English) -* A description of: - -```Markdown -## Bosnian translations - -This is the issue to track translations of the docs to Bosnian. ๐Ÿš€ - -Here are the [PRs to review with the label `lang-bs`](https://github.com/fastapi/fastapi/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc+label%3Alang-bs+label%3A%22awaiting-review%22). ๐Ÿค“ -``` - -Update "Bosnian" with the new language. - -And update the search link to point to the new language label that will be created, like `lang-bs`. - -Create and add the label to that new Discussion just created, like `lang-bs`. - -Then go back to the PR, and add the label, like `lang-bs`, and `lang-all` and `awaiting-review`. - -Now the GitHub action will automatically detect the label `lang-bs` and will post in that Discussion that this PR is waiting to be reviewed. +* If the PR is approved by at least one native speaker, you can merge it. ## Review PRs From e9e0419ed0deee736e2b6ef01fc6dea1d6ed987e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 19:19:29 +0000 Subject: [PATCH 076/110] =?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 2a2b0d6396..1df9e63510 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* ๐Ÿ“ Update docs about managing translations. PR [#14704](https://github.com/fastapi/fastapi/pull/14704) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Update docs for contributing with translations. PR [#14701](https://github.com/fastapi/fastapi/pull/14701) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Specify language code for code block. PR [#14656](https://github.com/fastapi/fastapi/pull/14656) by [@YuriiMotov](https://github.com/YuriiMotov). From 7b864acf370f3587cc29eb58f1f897c5c21d139d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sun, 11 Jan 2026 13:19:26 -0800 Subject: [PATCH 077/110] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20for=20ma?= =?UTF-8?q?nagement=20tasks=20(#14705)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/management-tasks.md | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/docs/en/docs/management-tasks.md b/docs/en/docs/management-tasks.md index 68d10a42cc..6f34afb731 100644 --- a/docs/en/docs/management-tasks.md +++ b/docs/en/docs/management-tasks.md @@ -74,7 +74,7 @@ Make sure you use a supported label from the <a href="https://github.com/tiangol * `refactor`: Refactors * This is normally for changes to the internal code that don't change the behavior. Normally it improves maintainability, or enables future features, etc. * `upgrade`: Upgrades - * This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies, normally in `requirements.txt` files or GitHub Action versions should be marked as `internal`, not `upgrade`. + * This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies or GitHub Action versions should be marked as `internal`, not `upgrade`. * `docs`: Docs * Changes in docs. This includes updating the docs, fixing typos. But it doesn't include changes to translations. * You can normally quickly detect it by going to the "Files changed" tab in the PR and checking if the updated file(s) starts with `docs/en/docs`. The original version of the docs is always in English, so in `docs/en/docs`. @@ -118,9 +118,11 @@ For these language translation PRs, confirm that: ## Review PRs -If a PR doesn't explain what it does or why, ask for more information. +* If a PR doesn't explain what it does or why, if it seems like it could be useful, ask for more information. Otherwise, feel free to close it. -A PR should have a specific use case that it is solving. +* If a PR seems to be spam, meaningless, only to change statistics (to appear as "contributor") or similar, you can simply mark it as `invalid`, and it will be automatically closed. + +* A PR should have a specific use case that it is solving. * If the PR is for a feature, it should have docs. * Unless it's a feature we want to discourage, like support for a corner case that we don't want users to use. @@ -140,27 +142,12 @@ Every month, a GitHub Action updates the FastAPI People data. Those PRs look lik If the tests are passing, you can merge it right away. -## External Links PRs - -When people add external links they edit this file <a href="https://github.com/fastapi/fastapi/blob/master/docs/en/data/external_links.yml" class="external-link" target="_blank">external_links.yml</a>. - -* Make sure the new link is in the correct category (e.g. "Podcasts") and language (e.g. "Japanese"). -* A new link should be at the top of its list. -* The link URL should work (it should not return a 404). -* The content of the link should be about FastAPI. -* The new addition should have these fields: - * `author`: The name of the author. - * `link`: The URL with the content. - * `title`: The title of the link (the title of the article, podcast, etc). - -After checking all these things and ensuring the PR has the right labels, you can merge it. - ## Dependabot PRs Dependabot will create PRs to update dependencies for several things, and those PRs all look similar, but some are way more delicate than others. -* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml`, **don't merge it**. ๐Ÿ˜ฑ Let me check it first. There's a good chance that some additional tweaks or updates are needed. -* If the PR updates one of the internal dependencies, for example it's modifying `requirements.txt` files, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. ๐Ÿ˜Ž +* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml` in the main dependencies, **don't merge it**. ๐Ÿ˜ฑ Let me check it first. There's a good chance that some additional tweaks or updates are needed. +* If the PR updates one of the internal dependencies, for example the group `dev` in `pyproject.toml`, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. ๐Ÿ˜Ž ## Mark GitHub Discussions Answers From e63f382b0f189bc1ab76a58eaaf92ce83b775f48 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 21:19:50 +0000 Subject: [PATCH 078/110] =?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 1df9e63510..628eff3485 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* ๐Ÿ“ Update docs for management tasks. PR [#14705](https://github.com/fastapi/fastapi/pull/14705) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Update docs about managing translations. PR [#14704](https://github.com/fastapi/fastapi/pull/14704) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Update docs for contributing with translations. PR [#14701](https://github.com/fastapi/fastapi/pull/14701) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Specify language code for code block. PR [#14656](https://github.com/fastapi/fastapi/pull/14656) by [@YuriiMotov](https://github.com/YuriiMotov). From 1be80f48851845d9e0a6ce8ee6a8b871cd9ff5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Sun, 11 Jan 2026 14:22:58 -0800 Subject: [PATCH 079/110] =?UTF-8?q?=F0=9F=93=9D=20Add=20contribution=20ins?= =?UTF-8?q?tructions=20about=20LLM=20generated=20code=20and=20comments=20a?= =?UTF-8?q?nd=20automated=20tools=20for=20PRs=20(#14706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/issue-manager.yml | 6 ++++- docs/en/docs/contributing.md | 36 +++++++++++++++++++++++++++++ docs/en/docs/management-tasks.md | 2 ++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index f40ec4dc47..2ae588da13 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -41,11 +41,15 @@ jobs: "message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR.", "reminder": { "before": "P3D", - "message": "Heads-up: this will be closed in 3 days unless thereโ€™s new activity." + "message": "Heads-up: this will be closed in 3 days unless there's new activity." } }, "invalid": { "delay": 0, "message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details." + }, + "maybe-ai": { + "delay": 0, + "message": "This was marked as potentially AI generated and will be closed now. If this is an error, please provide additional details, make sure to read the docs about contributing and AI." } } diff --git a/docs/en/docs/contributing.md b/docs/en/docs/contributing.md index be4706f742..a4d896109b 100644 --- a/docs/en/docs/contributing.md +++ b/docs/en/docs/contributing.md @@ -219,3 +219,39 @@ Once there are several people in the discussion, the FastAPI team can evaluate i Then the docs will be automatically translated using LLMs, and the team of native speakers can review the translation, and help tweak the LLM prompts. Once there's a new translation, for example if docs are updated or there's a new section, there will be a comment in the same discussion with the link to the new translation to review. + +## Automated Code and AI + +You are encouraged to use all the tools you want to do your work and contribute as efficiently as possible, this includes AI (LLM) tools, etc. Nevertheless, contributions should have meaningful human intervention, judgement, context, etc. + +If the **human effort** put in a PR, e.g. writing LLM prompts, is **less** than the **effort we would need to put** to **review it**, please **don't** submit the PR. + +Think of it this way: we can already write LLM prompts or run automated tools ourselves, and that would be faster than reviewing external PRs. + +### Closing Automated and AI PRs + +If we see PRs that seem AI generated or automated in similar ways, we'll flag them and close them. + +The same applies to comments and descriptions, please don't copy paste the content generated by an LLM. + +### Human Effort Denial of Service + +Using automated tools and AI to submit PRs or comments that we have to carefully review and handle would be the equivalent of a <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack" class="external-link" target="_blank">Denial-of-service attack</a> on our human effort. + +It would be very little effort from the person submitting the PR (an LLM prompt) that generates a large amount of effort on our side (carefully reviewing code). + +Please don't do that. + +We'll need to block accounts that spam us with repeated automated PRs or comments. + +### Use Tools Wisely + +As Uncle Ben said: + +<blockquote> +With great <strike>power</strike> <strong>tools</strong> comes great responsibility. +</blockquote> + +Avoid inadvertently doing harm. + +You have amazing tools at hand, use them wisely to help effectively. diff --git a/docs/en/docs/management-tasks.md b/docs/en/docs/management-tasks.md index 6f34afb731..fc6e1eb280 100644 --- a/docs/en/docs/management-tasks.md +++ b/docs/en/docs/management-tasks.md @@ -122,6 +122,8 @@ For these language translation PRs, confirm that: * If a PR seems to be spam, meaningless, only to change statistics (to appear as "contributor") or similar, you can simply mark it as `invalid`, and it will be automatically closed. +* If a PR seems to be AI generated, and seems like reviewing it would take more time from you than the time it took to write the prompt, mark it as `maybe-ai`, and it will be automatically closed. + * A PR should have a specific use case that it is solving. * If the PR is for a feature, it should have docs. From a456e92a21d5b94a67d6d0fd070218df09444443 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 22:23:21 +0000 Subject: [PATCH 080/110] =?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 628eff3485..45bc9873ec 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* ๐Ÿ“ Add contribution instructions about LLM generated code and comments and automated tools for PRs. PR [#14706](https://github.com/fastapi/fastapi/pull/14706) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Update docs for management tasks. PR [#14705](https://github.com/fastapi/fastapi/pull/14705) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Update docs about managing translations. PR [#14704](https://github.com/fastapi/fastapi/pull/14704) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Update docs for contributing with translations. PR [#14701](https://github.com/fastapi/fastapi/pull/14701) by [@tiangolo](https://github.com/tiangolo). From a96dd013a4d2ce4f5f758fe51bea19f541743164 Mon Sep 17 00:00:00 2001 From: fcharrier <fcharrie@yahoo.fr> Date: Fri, 16 Jan 2026 11:53:45 +0100 Subject: [PATCH 081/110] =?UTF-8?q?=F0=9F=90=9B=20Fix=20copy=20button=20in?= =?UTF-8?q?=20custom.js=20(#14722)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/js/custom.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/js/custom.js b/docs/en/docs/js/custom.js index 48e95901d5..be326d3029 100644 --- a/docs/en/docs/js/custom.js +++ b/docs/en/docs/js/custom.js @@ -81,8 +81,14 @@ function setupTermynal() { } } saveBuffer(); + const inputCommands = useLines + .filter(line => line.type === "input") + .map(line => line.value) + .join("\n"); + node.textContent = inputCommands; const div = document.createElement("div"); - node.replaceWith(div); + node.style.display = "none"; + node.after(div); const termynal = new Termynal(div, { lineData: useLines, noInit: true, From c597d9cb53415cf81188750ee946d69304922fbb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:54:08 +0000 Subject: [PATCH 082/110] =?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 45bc9873ec..233fdb7434 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* ๐Ÿ› Fix copy button in custom.js. PR [#14722](https://github.com/fastapi/fastapi/pull/14722) by [@fcharrier](https://github.com/fcharrier). * ๐Ÿ“ Add contribution instructions about LLM generated code and comments and automated tools for PRs. PR [#14706](https://github.com/fastapi/fastapi/pull/14706) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Update docs for management tasks. PR [#14705](https://github.com/fastapi/fastapi/pull/14705) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Update docs about managing translations. PR [#14704](https://github.com/fastapi/fastapi/pull/14704) by [@tiangolo](https://github.com/tiangolo). From f317ede223131a2822723602fb3f576f1ca0f5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Fri, 16 Jan 2026 03:54:01 -0800 Subject: [PATCH 083/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20ko=20(add-missing)=20(#14699)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- docs/ko/docs/_llm-test.md | 503 +++++++++++++++++ docs/ko/docs/advanced/additional-responses.md | 247 +++++++++ docs/ko/docs/advanced/behind-a-proxy.md | 466 ++++++++++++++++ docs/ko/docs/advanced/dataclasses.md | 95 ++++ docs/ko/docs/advanced/generate-clients.md | 208 ++++++++ docs/ko/docs/advanced/middleware.md | 97 ++++ docs/ko/docs/advanced/openapi-callbacks.md | 186 +++++++ docs/ko/docs/advanced/openapi-webhooks.md | 55 ++ .../path-operation-advanced-configuration.md | 172 ++++++ .../docs/advanced/security/http-basic-auth.md | 107 ++++ docs/ko/docs/advanced/security/index.md | 19 + .../docs/advanced/security/oauth2-scopes.md | 274 ++++++++++ docs/ko/docs/advanced/settings.md | 302 +++++++++++ docs/ko/docs/alternatives.md | 485 +++++++++++++++++ docs/ko/docs/deployment/concepts.md | 321 +++++++++++ docs/ko/docs/deployment/fastapicloud.md | 65 +++ docs/ko/docs/deployment/https.md | 231 ++++++++ docs/ko/docs/deployment/manually.md | 157 ++++++ .../authentication-error-status-code.md | 17 + docs/ko/docs/how-to/custom-docs-ui-assets.md | 185 +++++++ .../docs/how-to/custom-request-and-route.md | 109 ++++ docs/ko/docs/how-to/extending-openapi.md | 80 +++ docs/ko/docs/how-to/general.md | 39 ++ docs/ko/docs/how-to/graphql.md | 60 +++ docs/ko/docs/how-to/index.md | 13 + ...migrate-from-pydantic-v1-to-pydantic-v2.md | 135 +++++ .../docs/how-to/separate-openapi-schemas.md | 102 ++++ docs/ko/docs/how-to/testing-database.md | 7 + docs/ko/docs/tutorial/bigger-applications.md | 504 ++++++++++++++++++ docs/ko/docs/tutorial/body-updates.md | 100 ++++ .../tutorial/dependencies/sub-dependencies.md | 105 ++++ docs/ko/docs/tutorial/handling-errors.md | 244 +++++++++ docs/ko/docs/tutorial/security/first-steps.md | 203 +++++++ docs/ko/docs/tutorial/security/index.md | 106 ++++ 34 files changed, 5999 insertions(+) create mode 100644 docs/ko/docs/_llm-test.md create mode 100644 docs/ko/docs/advanced/additional-responses.md create mode 100644 docs/ko/docs/advanced/behind-a-proxy.md create mode 100644 docs/ko/docs/advanced/dataclasses.md create mode 100644 docs/ko/docs/advanced/generate-clients.md create mode 100644 docs/ko/docs/advanced/middleware.md create mode 100644 docs/ko/docs/advanced/openapi-callbacks.md create mode 100644 docs/ko/docs/advanced/openapi-webhooks.md create mode 100644 docs/ko/docs/advanced/path-operation-advanced-configuration.md create mode 100644 docs/ko/docs/advanced/security/http-basic-auth.md create mode 100644 docs/ko/docs/advanced/security/index.md create mode 100644 docs/ko/docs/advanced/security/oauth2-scopes.md create mode 100644 docs/ko/docs/advanced/settings.md create mode 100644 docs/ko/docs/alternatives.md create mode 100644 docs/ko/docs/deployment/concepts.md create mode 100644 docs/ko/docs/deployment/fastapicloud.md create mode 100644 docs/ko/docs/deployment/https.md create mode 100644 docs/ko/docs/deployment/manually.md create mode 100644 docs/ko/docs/how-to/authentication-error-status-code.md create mode 100644 docs/ko/docs/how-to/custom-docs-ui-assets.md create mode 100644 docs/ko/docs/how-to/custom-request-and-route.md create mode 100644 docs/ko/docs/how-to/extending-openapi.md create mode 100644 docs/ko/docs/how-to/general.md create mode 100644 docs/ko/docs/how-to/graphql.md create mode 100644 docs/ko/docs/how-to/index.md create mode 100644 docs/ko/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md create mode 100644 docs/ko/docs/how-to/separate-openapi-schemas.md create mode 100644 docs/ko/docs/how-to/testing-database.md create mode 100644 docs/ko/docs/tutorial/bigger-applications.md create mode 100644 docs/ko/docs/tutorial/body-updates.md create mode 100644 docs/ko/docs/tutorial/dependencies/sub-dependencies.md create mode 100644 docs/ko/docs/tutorial/handling-errors.md create mode 100644 docs/ko/docs/tutorial/security/first-steps.md create mode 100644 docs/ko/docs/tutorial/security/index.md diff --git a/docs/ko/docs/_llm-test.md b/docs/ko/docs/_llm-test.md new file mode 100644 index 0000000000..1b828c663e --- /dev/null +++ b/docs/ko/docs/_llm-test.md @@ -0,0 +1,503 @@ +# LLM ํ…Œ์ŠคํŠธ ํŒŒ์ผ { #llm-test-file } + +์ด ๋ฌธ์„œ๋Š” ๋ฌธ์„œ๋ฅผ ๋ฒˆ์—ญํ•˜๋Š” <abbr title="Large Language Model - ๋Œ€๊ทœ๋ชจ ์–ธ์–ด ๋ชจ๋ธ">LLM</abbr>์ด `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์ด ๋” ์ด์ƒ ๋ฒˆ์—ญ์— ๋ณ€๊ฒฝ์„ ๋งŒ๋“ค์ง€ ์•Š๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์ผ๋ฐ˜ ํ”„๋กฌํ”„ํŠธ์™€ ์–ธ์–ด๋ณ„ ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ตœ์„ ์ด๋ผ๋Š” ๋œป์ž…๋‹ˆ๋‹ค(๋•Œ๋•Œ๋กœ ๋ช‡ ๊ฐ€์ง€ seemingly random ๋ณ€๊ฒฝ์„ ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ทธ ์ด์œ ๋Š” <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM์€ ๊ฒฐ์ •๋ก ์  ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ</a>์ž…๋‹ˆ๋‹ค). + +ํ…Œ์ŠคํŠธ: + +## ์ฝ”๋“œ ์Šค๋‹ˆํŽซ { #code-snippets } + +//// tab | ํ…Œ์ŠคํŠธ + +๋‹ค์Œ์€ ์ฝ”๋“œ ์Šค๋‹ˆํŽซ์ž…๋‹ˆ๋‹ค: `foo`. ๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ์€ ๋˜ ๋‹ค๋ฅธ ์ฝ”๋“œ ์Šค๋‹ˆํŽซ์ž…๋‹ˆ๋‹ค: `bar`. ๊ทธ๋ฆฌ๊ณ  ๋˜ ํ•˜๋‚˜: `baz quux`. + +//// + +//// tab | ์ •๋ณด + +์ฝ”๋“œ ์Šค๋‹ˆํŽซ์˜ ๋‚ด์šฉ์€ ๊ทธ๋Œ€๋กœ ๋‘์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +`scripts/translate.py`์˜ ์ผ๋ฐ˜ ํ”„๋กฌํ”„ํŠธ์—์„œ `### Content of code snippets` ์„น์…˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +//// + +## ๋”ฐ์˜ดํ‘œ { #quotes } + +//// tab | ํ…Œ์ŠคํŠธ + +์–ด์ œ ์ œ ์นœ๊ตฌ๊ฐ€ ์ด๋ ‡๊ฒŒ ์ผ์Šต๋‹ˆ๋‹ค: "If you spell incorrectly correctly, you have spelled it incorrectly". ์ด์— ์ €๋Š” ์ด๋ ‡๊ฒŒ ๋‹ตํ–ˆ์Šต๋‹ˆ๋‹ค: "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 +$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u> +<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> 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 | ์ •๋ณด +์ผ๋ถ€ ํ…์ŠคํŠธ +/// + +/// note | ์ฐธ๊ณ  +์ผ๋ถ€ ํ…์ŠคํŠธ +/// + +/// note Technical details | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ +์ผ๋ถ€ ํ…์ŠคํŠธ +/// + +/// check | ํ™•์ธ +์ผ๋ถ€ ํ…์ŠคํŠธ +/// + +/// tip | ํŒ +์ผ๋ถ€ ํ…์ŠคํŠธ +/// + +/// warning | ๊ฒฝ๊ณ  +์ผ๋ถ€ ํ…์ŠคํŠธ +/// + +/// danger | ์œ„ํ—˜ +์ผ๋ถ€ ํ…์ŠคํŠธ +/// + +//// + +//// 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} +* <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">์™ธ๋ถ€ ๋งํฌ</a> +* <a href="https://fastapi.tiangolo.com/css/styles.css" class="external-link" target="_blank">์Šคํƒ€์ผ๋กœ ๊ฐ€๋Š” ๋งํฌ</a> +* <a href="https://fastapi.tiangolo.com/js/logic.js" class="external-link" target="_blank">์Šคํฌ๋ฆฝํŠธ๋กœ ๊ฐ€๋Š” ๋งํฌ</a> +* <a href="https://fastapi.tiangolo.com/img/foo.jpg" class="external-link" target="_blank">์ด๋ฏธ์ง€๋กœ ๊ฐ€๋Š” ๋งํฌ</a> + +๋งํฌ ํ…์ŠคํŠธ๋Š” ๋ฒˆ์—ญ๋˜์–ด์•ผ ํ•˜๊ณ , ๋งํฌ ์ฃผ์†Œ๋Š” ๋ฒˆ์—ญ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€๋ฆฌ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค: + +* <a href="https://fastapi.tiangolo.com/ko/" class="external-link" target="_blank">FastAPI ๋งํฌ</a> + +//// + +//// tab | ์ •๋ณด + +๋งํฌ๋Š” ๋ฒˆ์—ญ๋˜์–ด์•ผ ํ•˜์ง€๋งŒ, ์ฃผ์†Œ๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์™ธ๋Š” FastAPI ๋ฌธ์„œ ํŽ˜์ด์ง€๋กœ ํ–ฅํ•˜๋Š” ์ ˆ๋Œ€ ๋งํฌ์ด๋ฉฐ, ์ด ๊ฒฝ์šฐ ๋ฒˆ์—ญ ํŽ˜์ด์ง€๋กœ ์—ฐ๊ฒฐ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +`scripts/translate.py`์˜ ์ผ๋ฐ˜ ํ”„๋กฌํ”„ํŠธ์—์„œ `### Links` ์„น์…˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +//// + +## HTML "abbr" ์š”์†Œ { #html-abbr-elements } + +//// tab | ํ…Œ์ŠคํŠธ + +์—ฌ๊ธฐ HTML "abbr" ์š”์†Œ๋กœ ๊ฐ์‹ผ ๋ช‡ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค(์ผ๋ถ€๋Š” ์ž„์˜๋กœ ๋งŒ๋“  ๊ฒƒ์ž…๋‹ˆ๋‹ค): + +### abbr๊ฐ€ ์ „์ฒด ๋ฌธ๊ตฌ๋ฅผ ์ œ๊ณต { #the-abbr-gives-a-full-phrase } + +* <abbr title="Getting Things Done - ์ผ์„ ๋๋‚ด๋Š” ๋ฐฉ๋ฒ•๋ก ">GTD</abbr> +* <abbr title="less than - ๋ณด๋‹ค ์ž‘์Œ"><code>lt</code></abbr> +* <abbr title="XML Web Token - XML ์›น ํ† ํฐ">XWT</abbr> +* <abbr title="Parallel Server Gateway Interface - ๋ณ‘๋ ฌ ์„œ๋ฒ„ ๊ฒŒ์ดํŠธ์›จ์ด ์ธํ„ฐํŽ˜์ด์Šค">PSGI</abbr> + +### abbr๊ฐ€ ์„ค๋ช…์„ ์ œ๊ณต { #the-abbr-gives-an-explanation } + +* <abbr title="์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ๋“  ์„œ๋กœ ์—ฐ๊ฒฐ๋˜๊ณ  ํ•จ๊ป˜ ์ž‘๋™ํ•˜๋„๋ก ๊ตฌ์„ฑ๋œ ๋จธ์‹ ๋“ค์˜ ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค.">cluster</abbr> +* <abbr title="์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ ๊ณ„์ธต ์‚ฌ์ด์— ์ˆ˜๋งŽ์€ ์€๋‹‰ ๊ณ„์ธต์„ ๋‘” ์ธ๊ณต ์‹ ๊ฒฝ๋ง์„ ์‚ฌ์šฉํ•˜๋Š” ๋จธ์‹  ๋Ÿฌ๋‹ ๋ฐฉ๋ฒ•์œผ๋กœ, ์ด๋ฅผ ํ†ตํ•ด ํฌ๊ด„์ ์ธ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค">Deep Learning</abbr> + +### abbr๊ฐ€ ์ „์ฒด ๋ฌธ๊ตฌ์™€ ์„ค๋ช…์„ ์ œ๊ณต { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* <abbr title="Mozilla Developer Network - ๋ชจ์งˆ๋ผ ๊ฐœ๋ฐœ์ž ๋„คํŠธ์›Œํฌ: Firefox๋ฅผ ๋งŒ๋“œ๋Š” ์‚ฌ๋žŒ๋“ค์ด ์ž‘์„ฑํ•œ ๊ฐœ๋ฐœ์ž์šฉ ๋ฌธ์„œ">MDN</abbr> +* <abbr title="Input/Output - ์ž…๋ ฅ/์ถœ๋ ฅ: ๋””์Šคํฌ ์ฝ๊ธฐ ๋˜๋Š” ์“ฐ๊ธฐ, ๋„คํŠธ์›Œํฌ ํ†ต์‹ .">I/O</abbr>. + +//// + +//// tab | ์ •๋ณด + +"abbr" ์š”์†Œ์˜ "title" ์†์„ฑ์€ ๋ช‡ ๊ฐ€์ง€ ๊ตฌ์ฒด์ ์ธ ์ง€์นจ์— ๋”ฐ๋ผ ๋ฒˆ์—ญ๋ฉ๋‹ˆ๋‹ค. + +๋ฒˆ์—ญ์—์„œ๋Š”(์˜์–ด ๋‹จ์–ด๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด) ์ž์ฒด "abbr" ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, LLM์€ ์ด๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. + +`scripts/translate.py`์˜ ์ผ๋ฐ˜ ํ”„๋กฌํ”„ํŠธ์—์„œ `### HTML abbr elements` ์„น์…˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +//// + +## ์ œ๋ชฉ { #headings } + +//// tab | ํ…Œ์ŠคํŠธ + +### ์›น์•ฑ ๊ฐœ๋ฐœํ•˜๊ธฐ - ํŠœํ† ๋ฆฌ์–ผ { #develop-a-webapp-a-tutorial } + +์•ˆ๋…•ํ•˜์„ธ์š”. + +### ํƒ€์ž… ํžŒํŠธ์™€ -์• ๋„ˆํ…Œ์ด์…˜ { #type-hints-and-annotations } + +๋‹ค์‹œ ์•ˆ๋…•ํ•˜์„ธ์š”. + +### super- ๋ฐ subclasses { #super-and-subclasses } + +๋‹ค์‹œ ์•ˆ๋…•ํ•˜์„ธ์š”. + +//// + +//// tab | ์ •๋ณด + +์ œ๋ชฉ์— ๋Œ€ํ•œ ์œ ์ผํ•œ ๊ฐ•ํ•œ ๊ทœ์น™์€, LLM์ด ์ค‘๊ด„ํ˜ธ ์•ˆ์˜ ํ•ด์‹œ ๋ถ€๋ถ„์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•„ ๋งํฌ๊ฐ€ ๊นจ์ง€์ง€ ์•Š๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +`scripts/translate.py`์˜ ์ผ๋ฐ˜ ํ”„๋กฌํ”„ํŠธ์—์„œ `### Headings` ์„น์…˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +์–ธ์–ด๋ณ„ ์ง€์นจ์€ ์˜ˆ๋ฅผ ๋“ค์–ด `docs/de/llm-prompt.md`์˜ `### Headings` ์„น์…˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +//// + +## ๋ฌธ์„œ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์šฉ์–ด { #terms-used-in-the-docs } + +//// tab | ํ…Œ์ŠคํŠธ + +* ๋‹น์‹  +* ๋‹น์‹ ์˜ + +* ์˜ˆ: (e.g.) +* ๋“ฑ (etc.) + +* `int`๋กœ์„œ์˜ `foo` +* `str`๋กœ์„œ์˜ `bar` +* `list`๋กœ์„œ์˜ `baz` + +* ํŠœํ† ๋ฆฌ์–ผ - ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ +* ๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ +* SQLModel ๋ฌธ์„œ +* API ๋ฌธ์„œ +* ์ž๋™ ๋ฌธ์„œ + +* Data Science +* Deep Learning +* Machine Learning +* Dependency Injection +* HTTP Basic authentication +* HTTP Digest +* ISO format +* JSON Schema ํ‘œ์ค€ +* JSON schema +* schema definition +* Password Flow +* Mobile + +* deprecated +* designed +* invalid +* on the fly +* standard +* default +* case-sensitive +* case-insensitive + +* ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„œ๋น™ํ•˜๋‹ค +* ํŽ˜์ด์ง€๋ฅผ ์„œ๋น™ํ•˜๋‹ค + +* ์•ฑ +* ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ + +* ์š”์ฒญ +* ์‘๋‹ต +* ์˜ค๋ฅ˜ ์‘๋‹ต + +* ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ +* ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ +* ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ + +* body +* ์š”์ฒญ body +* ์‘๋‹ต body +* JSON body +* form body +* file body +* ํ•จ์ˆ˜ body + +* parameter +* body parameter +* path parameter +* query parameter +* cookie parameter +* header parameter +* form parameter +* function parameter + +* event +* startup event +* ์„œ๋ฒ„ startup +* shutdown event +* lifespan event + +* handler +* event handler +* exception handler +* ์ฒ˜๋ฆฌํ•˜๋‹ค + +* 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 +* ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆํ•œ๋‹ค +* ์‹œ์Šคํ…œ์ด ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•œ๋‹ค + +* CLI +* command line interface + +* server +* client + +* cloud provider +* cloud service + +* development +* development stages + +* dict +* dictionary +* enumeration +* enum +* enum member + +* encoder +* decoder +* encodeํ•˜๋‹ค +* decodeํ•˜๋‹ค + +* exception +* raiseํ•˜๋‹ค + +* 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ํ•˜๋‹ค + +* 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ํ•˜๋‹ค +* validateํ•˜๋‹ค + +//// + +//// tab | ์ •๋ณด + +์ด๊ฒƒ์€ ๋ฌธ์„œ์—์„œ ๋ณด์ด๋Š” (๋Œ€๋ถ€๋ถ„) ๊ธฐ์ˆ  ์šฉ์–ด์˜ ๋ถˆ์™„์ „ํ•˜๊ณ  ๋น„๊ทœ๋ฒ”์ ์ธ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธ ์„ค๊ณ„์ž๊ฐ€ ์–ด๋–ค ์šฉ์–ด์— ๋Œ€ํ•ด LLM์— ์ถ”๊ฐ€์ ์ธ ๋„์›€์ด ํ•„์š”ํ•œ์ง€ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ข‹์€ ๋ฒˆ์—ญ์„ ๊ณ„์† ๋œ ์ข‹์€ ๋ฒˆ์—ญ์œผ๋กœ ๋˜๋Œ๋ฆด ๋•Œ, ๋˜๋Š” ์–ธ์–ด์—์„œ ์šฉ์–ด์˜ ํ™œ์šฉ/๋ณ€ํ™”๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ๋•Œ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด `docs/de/llm-prompt.md`์˜ `### List of English terms and their preferred German translations` ์„น์…˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +//// diff --git a/docs/ko/docs/advanced/additional-responses.md b/docs/ko/docs/advanced/additional-responses.md new file mode 100644 index 0000000000..a6f51f5b93 --- /dev/null +++ b/docs/ko/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`์—๋Š” `response_model`์ฒ˜๋Ÿผ Pydantic ๋ชจ๋ธ์„ ๋‹ด๋Š” `model` ํ‚ค๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +**FastAPI**๋Š” ๊ทธ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ด JSON Schema๋ฅผ ์ƒ์„ฑํ•˜๊ณ , OpenAPI์˜ ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ์ƒํƒœ ์ฝ”๋“œ `404`์™€ Pydantic ๋ชจ๋ธ `Message`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ ์‘๋‹ต์„ ์„ ์–ธํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} + +/// note | ์ฐธ๊ณ  + +`JSONResponse`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. + +/// + +/// info | ์ •๋ณด + +`model` ํ‚ค๋Š” OpenAPI์˜ ์ผ๋ถ€๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. + +**FastAPI**๋Š” ์—ฌ๊ธฐ์—์„œ Pydantic ๋ชจ๋ธ์„ ๊ฐ€์ ธ์™€ JSON Schema๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ๋„ฃ์Šต๋‹ˆ๋‹ค. + +์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* ๊ฐ’์œผ๋กœ ๋˜ ๋‹ค๋ฅธ JSON ๊ฐ์ฒด(`dict`)๋ฅผ ๊ฐ€์ง€๋Š” `content` ํ‚ค ์•ˆ์—: + * ๋ฏธ๋””์–ด ํƒ€์ž…(์˜ˆ: `application/json`)์„ ํ‚ค๋กœ ๊ฐ€์ง€๋ฉฐ, ๊ฐ’์œผ๋กœ ๋˜ ๋‹ค๋ฅธ JSON ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๊ณ : + * `schema` ํ‚ค๊ฐ€ ์žˆ๊ณ , ๊ทธ ๊ฐ’์ด ๋ชจ๋ธ์—์„œ ์ƒ์„ฑ๋œ JSON Schema์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์ž…๋‹ˆ๋‹ค. + * **FastAPI**๋Š” ์ด๋ฅผ ์ง์ ‘ ํฌํ•จํ•˜๋Š” ๋Œ€์‹ , OpenAPI์˜ ๋‹ค๋ฅธ ์œ„์น˜์— ์žˆ๋Š” ์ „์—ญ JSON Schemas๋ฅผ ์ฐธ์กฐํ•˜๋„๋ก ์—ฌ๊ธฐ์—์„œ reference๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ทธ JSON Schema๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ๋” ๋‚˜์€ ์ฝ”๋“œ ์ƒ์„ฑ ๋„๊ตฌ ๋“ฑ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +์ด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ๋Œ€ํ•ด 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` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด ๋™์ผํ•œ ์ฃผ์š” ์‘๋‹ต์— ๋Œ€ํ•ด ๋‹ค๋ฅธ ๋ฏธ๋””์–ด ํƒ€์ž…์„ ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ JSON ๊ฐ์ฒด(๋ฏธ๋””์–ด ํƒ€์ž… `application/json`) ๋˜๋Š” PNG ์ด๋ฏธ์ง€(๋ฏธ๋””์–ด ํƒ€์ž… `image/png`)๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•ด `image/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` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํฌํ•จํ•ด ์—ฌ๋Ÿฌ ์œ„์น˜์˜ ์‘๋‹ต ์ •๋ณด๋ฅผ ๊ฒฐํ•ฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ธฐ๋ณธ ์ƒํƒœ ์ฝ”๋“œ `200`(๋˜๋Š” ํ•„์š”ํ•˜๋‹ค๋ฉด ์ปค์Šคํ…€ ์ฝ”๋“œ)์„ ์‚ฌ์šฉํ•˜์—ฌ `response_model`์„ ์„ ์–ธํ•˜๊ณ , ๊ทธ์™€ ๋™์ผํ•œ ์‘๋‹ต์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ `responses`์—์„œ OpenAPI ์Šคํ‚ค๋งˆ์— ์ง์ ‘ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +**FastAPI**๋Š” `responses`์˜ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ์œ ์ง€ํ•˜๊ณ , ๋ชจ๋ธ์˜ JSON Schema์™€ ๊ฒฐํ•ฉํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ณ  ์ปค์Šคํ…€ `description`์„ ๊ฐ€์ง„ ์ƒํƒœ ์ฝ”๋“œ `404` ์‘๋‹ต์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋˜ํ•œ `response_model`์„ ์‚ฌ์šฉํ•˜๋Š” ์ƒํƒœ ์ฝ”๋“œ `200` ์‘๋‹ต์„ ์„ ์–ธํ•˜๋˜, ์ปค์Šคํ…€ `example`์„ ํฌํ•จํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} + +์ด ๋ชจ๋“  ๋‚ด์šฉ์€ OpenAPI์— ๊ฒฐํ•ฉ๋˜์–ด ํฌํ•จ๋˜๊ณ , API ๋ฌธ์„œ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค: + +<img src="/img/tutorial/additional-responses/image01.png"> + +## ๋ฏธ๋ฆฌ ์ •์˜๋œ ์‘๋‹ต๊ณผ ์ปค์Šคํ…€ ์‘๋‹ต ๊ฒฐํ•ฉํ•˜๊ธฐ { #combine-predefined-responses-and-custom-ones } + +์—ฌ๋Ÿฌ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ์ ์šฉ๋˜๋Š” ๋ฏธ๋ฆฌ ์ •์˜๋œ ์‘๋‹ต์ด ํ•„์š”ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ฐ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋งˆ๋‹ค ํ•„์š”ํ•œ ์ปค์Šคํ…€ ์‘๋‹ต๊ณผ ๊ฒฐํ•ฉํ•˜๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฐ ๊ฒฝ์šฐ Python์˜ `dict` โ€œunpackingโ€ ๊ธฐ๋ฒ•์ธ `**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 ์‚ฌ์–‘์˜ ๋‹ค์Œ ์„น์…˜์„ ํ™•์ธํ•˜์„ธ์š”: + +* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">OpenAPI Responses Object</a>: `Response Object`๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. +* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">OpenAPI Response Object</a>: `responses` ํŒŒ๋ผ๋ฏธํ„ฐ ์•ˆ์˜ ๊ฐ ์‘๋‹ต์— ์ด๊ฒƒ์˜ ์–ด๋–ค ํ•ญ๋ชฉ์ด๋“  ์ง์ ‘ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. `description`, `headers`, `content`(์—ฌ๊ธฐ์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฏธ๋””์–ด ํƒ€์ž…๊ณผ JSON Schema๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค), `links` ๋“ฑ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/advanced/behind-a-proxy.md b/docs/ko/docs/advanced/behind-a-proxy.md new file mode 100644 index 0000000000..92bddac51a --- /dev/null +++ b/docs/ko/docs/advanced/behind-a-proxy.md @@ -0,0 +1,466 @@ +# ํ”„๋ก์‹œ ๋’ค์—์„œ ์‹คํ–‰ํ•˜๊ธฐ { #behind-a-proxy } + +๋งŽ์€ ๊ฒฝ์šฐ FastAPI ์•ฑ ์•ž๋‹จ์— Traefik์ด๋‚˜ Nginx ๊ฐ™์€ **ํ”„๋ก์‹œ(proxy)**๋ฅผ ๋‘๊ณ  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +์ด๋Ÿฐ ํ”„๋ก์‹œ๋Š” HTTPS ์ธ์ฆ์„œ ์ฒ˜๋ฆฌ ๋“ฑ ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋‹ด๋‹นํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ํ”„๋ก์‹œ ์ „๋‹ฌ ํ—ค๋” { #proxy-forwarded-headers } + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•ž๋‹จ์˜ **ํ”„๋ก์‹œ**๋Š” ๋ณดํ†ต **์„œ๋ฒ„**๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „์—, ํ•ด๋‹น ์š”์ฒญ์ด ํ”„๋ก์‹œ์— ์˜ํ•ด **์ „๋‹ฌ(forwarded)**๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ์„œ๋ฒ„๊ฐ€ ์•Œ ์ˆ˜ ์žˆ๋„๋ก ๋ช‡๋ช‡ ํ—ค๋”๋ฅผ ๋™์ ์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„๋Š” ๋„๋ฉ”์ธ์„ ํฌํ•จํ•œ ์›๋ž˜์˜ (๊ณต๊ฐœ) URL, HTTPS ์‚ฌ์šฉ ์—ฌ๋ถ€ ๋“ฑ ์ •๋ณด๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +**์„œ๋ฒ„** ํ”„๋กœ๊ทธ๋žจ(์˜ˆ: **FastAPI CLI**๋ฅผ ํ†ตํ•ด ์‹คํ–‰๋˜๋Š” **Uvicorn**)์€ ์ด๋Ÿฐ ํ—ค๋”๋ฅผ ํ•ด์„ํ•  ์ˆ˜ ์žˆ๊ณ , ๊ทธ ์ •๋ณด๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋ณด์•ˆ์ƒ, ์„œ๋ฒ„๋Š” ์ž์‹ ์ด ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋ก์‹œ ๋’ค์— ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ชจ๋ฅด๋ฉด ํ•ด๋‹น ํ—ค๋”๋ฅผ ํ•ด์„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +ํ”„๋ก์‹œ ํ—ค๋”๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a> +* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a> +* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a> + +/// + +### ํ”„๋ก์‹œ ์ „๋‹ฌ ํ—ค๋” ํ™œ์„ฑํ™”ํ•˜๊ธฐ { #enable-proxy-forwarded-headers } + +FastAPI CLI๋ฅผ *CLI ์˜ต์…˜* `--forwarded-allow-ips`๋กœ ์‹คํ–‰ํ•˜๊ณ , ์ „๋‹ฌ ํ—ค๋”๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก ์‹ ๋ขฐํ•  IP ์ฃผ์†Œ๋“ค์„ ๋„˜๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +`--forwarded-allow-ips="*"`๋กœ ์„ค์ •ํ•˜๋ฉด ๋“ค์–ด์˜ค๋Š” ๋ชจ๋“  IP๋ฅผ ์‹ ๋ขฐํ•ฉ๋‹ˆ๋‹ค. + +**์„œ๋ฒ„**๊ฐ€ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” **ํ”„๋ก์‹œ** ๋’ค์— ์žˆ๊ณ  ํ”„๋ก์‹œ๋งŒ ์„œ๋ฒ„์— ์ ‘๊ทผํ•œ๋‹ค๋ฉด, ์ด๋Š” ํ•ด๋‹น **ํ”„๋ก์‹œ**์˜ IP๊ฐ€ ๋ฌด์—‡์ด๋“  ๊ฐ„์— ๋ฐ›์•„๋“ค์ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +<div class="termy"> + +```console +$ fastapi run --forwarded-allow-ips="*" + +<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +</div> + +### 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<br/>Host: mysuperapp.com<br/>Path: /items + + Note over Proxy: Proxy adds forwarded headers + + Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items + + Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set) + + Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs + + Proxy->>Client: HTTPS Response +``` + +**ํ”„๋ก์‹œ**๋Š” ์›๋ž˜์˜ ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ณ , **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„**๋กœ ์š”์ฒญ์„ ์ „๋‹ฌํ•˜๊ธฐ ์ „์— ํŠน์ˆ˜ํ•œ *forwarded* ํ—ค๋”(`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 } + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ(prefix)๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ํ”„๋ก์‹œ๋ฅผ ๋‘˜ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Ÿฐ ๊ฒฝ์šฐ `root_path`๋ฅผ ์‚ฌ์šฉํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +`root_path`๋Š” (FastAPI๊ฐ€ Starlette๋ฅผ ํ†ตํ•ด ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š”) ASGI ์‚ฌ์–‘์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. + +`root_path`๋Š” ์ด๋Ÿฌํ•œ ํŠน์ • ์‚ฌ๋ก€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +๋˜ํ•œ ์„œ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งˆ์šดํŠธํ•  ๋•Œ ๋‚ด๋ถ€์ ์œผ๋กœ๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ๊ฐ€ ์ œ๊ฑฐ(stripped)๋˜๋Š” ํ”„๋ก์‹œ๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์€, ์ฝ”๋“œ์—์„œ๋Š” `/app`์— ๊ฒฝ๋กœ๋ฅผ ์„ ์–ธํ•˜์ง€๋งŒ, ์œ„์— ํ•œ ๊ฒน(ํ”„๋ก์‹œ)์„ ์ถ”๊ฐ€ํ•ด **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ `/api/v1` ๊ฐ™์€ ๊ฒฝ๋กœ ์•„๋ž˜์— ๋‘๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ ์›๋ž˜ ๊ฒฝ๋กœ `/app`์€ ์‹ค์ œ๋กœ `/api/v1/app`์—์„œ ์„œ๋น„์Šค๋ฉ๋‹ˆ๋‹ค. + +์ฝ”๋“œ๋Š” ๋ชจ๋‘ `/app`๋งŒ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์ž‘์„ฑ๋˜์–ด ์žˆ๋Š”๋ฐ๋„ ๋ง์ž…๋‹ˆ๋‹ค. + +{* ../../docs_src/behind_a_proxy/tutorial001_py39.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`์—์„œ 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", + // More stuff here + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // More stuff here + } +} +``` + +์ด ์˜ˆ์‹œ์—์„œ "Proxy"๋Š” **Traefik** ๊ฐ™์€ ๊ฒƒ์ด๊ณ , ์„œ๋ฒ„๋Š” **Uvicorn**์œผ๋กœ ์‹คํ–‰๋˜๋Š” FastAPI CLI์ฒ˜๋Ÿผ, FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋Š” ๊ตฌ์„ฑ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### `root_path` ์ œ๊ณตํ•˜๊ธฐ { #providing-the-root-path } + +์ด๋ฅผ ๋‹ฌ์„ฑํ•˜๋ ค๋ฉด ๋‹ค์Œ์ฒ˜๋Ÿผ ์ปค๋งจ๋“œ ๋ผ์ธ ์˜ต์…˜ `--root-path`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<div class="termy"> + +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +</div> + +Hypercorn์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, 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_py39.py hl[8] *} + +๊ทธ ๋‹ค์Œ Uvicorn์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹œ์ž‘ํ•˜๋ฉด: + +<div class="termy"> + +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +</div> + +์‘๋‹ต์€ ๋‹ค์Œ๊ณผ ๋น„์Šทํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: + +```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_py39.py hl[3] *} + +`FastAPI`์— `root_path`๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์€ Uvicorn์ด๋‚˜ Hypercorn์— ์ปค๋งจ๋“œ ๋ผ์ธ ์˜ต์…˜ `--root-path`๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. + +### `root_path`์— ๋Œ€ํ•˜์—ฌ { #about-root-path } + +์„œ๋ฒ„(Uvicorn)๋Š” ๊ทธ `root_path`๋ฅผ ์•ฑ์— ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ ์™ธ์—๋Š” ๋‹ค๋ฅธ ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. + +ํ•˜์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €๋กœ <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>์— ์ ‘์†ํ•˜๋ฉด ์ •์ƒ ์‘๋‹ต์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```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 } + +<a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a>์„ ์‚ฌ์šฉํ•˜๋ฉด, ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ๊ฐ€ ์ œ๊ฑฐ๋˜๋Š” ๊ตฌ์„ฑ์„ ๋กœ์ปฌ์—์„œ ์‰ฝ๊ฒŒ ์‹คํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">Traefik ๋‹ค์šด๋กœ๋“œ</a>๋Š” ๋‹จ์ผ ๋ฐ”์ด๋„ˆ๋ฆฌ์ด๋ฉฐ, ์••์ถ• ํŒŒ์ผ์„ ํ’€๊ณ  ํ„ฐ๋ฏธ๋„์—์„œ ๋ฐ”๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ ๋‹ค์Œ ๋‚ด์šฉ์„ ๊ฐ€์ง„ `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์„ ์‹œ์ž‘ํ•˜์„ธ์š”: + +<div class="termy"> + +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +</div> + +๊ทธ๋ฆฌ๊ณ  `--root-path` ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด ์•ฑ์„ ์‹œ์ž‘ํ•˜์„ธ์š”: + +<div class="termy"> + +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +</div> + +### ์‘๋‹ต ํ™•์ธํ•˜๊ธฐ { #check-the-responses } + +์ด์ œ Uvicorn์˜ ํฌํŠธ๋กœ ๋œ URL์ธ <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>๋กœ ์ ‘์†ํ•˜๋ฉด ์ •์ƒ ์‘๋‹ต์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +/// tip | ํŒ + +`http://127.0.0.1:8000/app`๋กœ ์ ‘๊ทผํ–ˆ๋Š”๋ฐ๋„ `/api/v1`์˜ `root_path`๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์— ์ฃผ์˜ํ•˜์„ธ์š”. ์ด๋Š” ์˜ต์…˜ `--root-path`์—์„œ ๊ฐ€์ ธ์˜จ ๊ฐ’์ž…๋‹ˆ๋‹ค. + +/// + +์ด์ œ Traefik์˜ ํฌํŠธ๊ฐ€ ํฌํ•จ๋˜๊ณ  ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ๊ฐ€ ํฌํ•จ๋œ URL <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>์„ ์—ฌ์„ธ์š”. + +๋™์ผํ•œ ์‘๋‹ต์„ ์–ป์Šต๋‹ˆ๋‹ค: + +```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์ด ์ง์ ‘ ์ œ๊ณตํ•˜๋Š” docs UI๋ฅผ ์‹œ๋„ํ•˜๋ฉด, ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•ด ์ ‘๊ทผ๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/tutorial/behind-a-proxy/image01.png"> + +ํ•˜์ง€๋งŒ ํ”„๋ก์‹œ(ํฌํŠธ `9999`)๋ฅผ ์‚ฌ์šฉํ•ด "๊ณต์‹" URL์ธ `/api/v1/docs`์—์„œ docs UI์— ์ ‘๊ทผํ•˜๋ฉด, ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰ + +<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/tutorial/behind-a-proxy/image02.png"> + +์›ํ•˜๋˜ ๊ทธ๋Œ€๋กœ์ž…๋‹ˆ๋‹ค. โœ”๏ธ + +์ด๋Š” FastAPI๊ฐ€ ์ด `root_path`๋ฅผ ์‚ฌ์šฉํ•ด, OpenAPI์—์„œ ๊ธฐ๋ณธ `server`๋ฅผ `root_path`๊ฐ€ ์ œ๊ณตํ•œ URL๋กœ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +## ์ถ”๊ฐ€ ์„œ๋ฒ„ { #additional-servers } + +/// warning | ๊ฒฝ๊ณ  + +์ด๋Š” ๋” ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค. ๊ฑด๋„ˆ๋›ฐ์–ด๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. + +/// + +๊ธฐ๋ณธ์ ์œผ๋กœ **FastAPI**๋Š” OpenAPI ์Šคํ‚ค๋งˆ์—์„œ `root_path`์˜ URL๋กœ `server`๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์˜ˆ๋ฅผ ๋“ค์–ด ๋™์ผํ•œ docs UI๊ฐ€ ์Šคํ…Œ์ด์ง•๊ณผ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ๋ชจ๋‘์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋„๋ก ํ•˜๋ ค๋ฉด, ๋‹ค๋ฅธ ๋Œ€์•ˆ `servers`๋ฅผ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์‚ฌ์šฉ์ž ์ •์˜ `servers` ๋ฆฌ์ŠคํŠธ๋ฅผ ์ „๋‹ฌํ–ˆ๊ณ  `root_path`(API๊ฐ€ ํ”„๋ก์‹œ ๋’ค์— ์žˆ๊ธฐ ๋•Œ๋ฌธ)๊ฐ€ ์žˆ๋‹ค๋ฉด, **FastAPI**๋Š” ๋ฆฌ์ŠคํŠธ์˜ ๋งจ ์•ž์— ์ด `root_path`๋ฅผ ๊ฐ€์ง„ "server"๋ฅผ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ: + +{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} + +๋‹ค์Œ๊ณผ ๊ฐ™์€ OpenAPI ์Šคํ‚ค๋งˆ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: + +```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 | ํŒ + +`root_path`์—์„œ ๊ฐ€์ ธ์˜จ ๊ฐ’์ธ `/api/v1`์˜ `url` ๊ฐ’์„ ๊ฐ€์ง„, ์ž๋™ ์ƒ์„ฑ๋œ server์— ์ฃผ๋ชฉํ•˜์„ธ์š”. + +/// + +<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>์˜ docs UI์—์„œ๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค: + +<img src="/img/tutorial/behind-a-proxy/image03.png"> + +/// tip | ํŒ + +docs 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_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/ko/docs/advanced/dataclasses.md b/docs/ko/docs/advanced/dataclasses.md new file mode 100644 index 0000000000..92ad5545b3 --- /dev/null +++ b/docs/ko/docs/advanced/dataclasses.md @@ -0,0 +1,95 @@ +# Dataclasses ์‚ฌ์šฉํ•˜๊ธฐ { #using-dataclasses } + +FastAPI๋Š” **Pydantic** ์œ„์— ๊ตฌ์ถ•๋˜์–ด ์žˆ์œผ๋ฉฐ, ์ง€๊ธˆ๊นŒ์ง€๋Š” Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ด ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์„ ์–ธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ๋“œ๋ ธ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ FastAPI๋Š” <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a>๋„ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} + +์ด๋Š” **Pydantic** ๋•๋ถ„์— ์—ฌ์ „ํžˆ ์ง€์›๋˜๋Š”๋ฐ, Pydantic์ด <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">`dataclasses`์— ๋Œ€ํ•œ ๋‚ด๋ถ€ ์ง€์›</a>์„ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ์œ„ ์ฝ”๋“œ์ฒ˜๋Ÿผ Pydantic์„ ๋ช…์‹œ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋”๋ผ๋„, FastAPI๋Š” Pydantic์„ ์‚ฌ์šฉํ•ด ํ‘œ์ค€ dataclasses๋ฅผ Pydantic์˜ dataclasses ๋ณ€ํ˜•์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋ฌผ๋ก  ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ๋„ ๋™์ผํ•˜๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค: + +* ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ +* ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™” +* ๋ฐ์ดํ„ฐ ๋ฌธ์„œํ™” ๋“ฑ + +์ด๋Š” Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ๋•Œ์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ๋กœ๋„ ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” Pydantic์„ ์‚ฌ์šฉํ•ด ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +dataclasses๋Š” Pydantic ๋ชจ๋ธ์ด ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒƒ์„ ํ•  ์ˆ˜๋Š” ์—†๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. + +๊ทธ๋ž˜์„œ ์—ฌ์ „ํžˆ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์ด๋ฏธ ์—ฌ๋Ÿฌ dataclasses๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, ์ด๊ฒƒ์€ FastAPI๋กœ ์›น 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 docs ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค: + +<img src="/img/tutorial/dataclasses/image01.png"> + +## ์ค‘์ฒฉ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์—์„œ Dataclasses ์‚ฌ์šฉํ•˜๊ธฐ { #dataclasses-in-nested-data-structures } + +`dataclasses`๋ฅผ ๋‹ค๋ฅธ ํƒ€์ž… ์• ๋„ˆํ…Œ์ด์…˜๊ณผ ์กฐํ•ฉํ•ด ์ค‘์ฒฉ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ผ๋ถ€ ๊ฒฝ์šฐ์—๋Š” Pydantic ๋ฒ„์ „์˜ `dataclasses`๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ž๋™ ์ƒ์„ฑ๋œ API ๋ฌธ์„œ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฐ ๊ฒฝ์šฐ ํ‘œ์ค€ `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` dataclasses์˜ ๋ฆฌ์ŠคํŠธ๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +4. `Author` dataclass๊ฐ€ `response_model` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +5. ์š”์ฒญ ๋ณธ๋ฌธ์œผ๋กœ dataclasses์™€ ํ•จ๊ป˜ ๋‹ค๋ฅธ ํ‘œ์ค€ ํƒ€์ž… ์• ๋„ˆํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + + ์ด ๊ฒฝ์šฐ์—๋Š” `Item` dataclasses์˜ ๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. + +6. ์—ฌ๊ธฐ์„œ๋Š” dataclasses ๋ฆฌ์ŠคํŠธ์ธ `items`๋ฅผ ํฌํ•จํ•˜๋Š” ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + + FastAPI๋Š” ์—ฌ์ „ํžˆ ๋ฐ์ดํ„ฐ๋ฅผ JSON์œผ๋กœ <abbr title="converting the data to a format that can be transmitted - ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†ก ๊ฐ€๋Šฅํ•œ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ">serializing</abbr>ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +7. ์—ฌ๊ธฐ์„œ `response_model`์€ `Author` dataclasses ๋ฆฌ์ŠคํŠธ์— ๋Œ€ํ•œ ํƒ€์ž… ์• ๋„ˆํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + + ๋‹ค์‹œ ๋งํ•ด, `dataclasses`๋ฅผ ํ‘œ์ค€ ํƒ€์ž… ์• ๋„ˆํ…Œ์ด์…˜๊ณผ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +8. ์ด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๋Š” `async def` ๋Œ€์‹  ์ผ๋ฐ˜ `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 ๋ชจ๋ธ๊ณผ ์กฐํ•ฉํ•˜๊ฑฐ๋‚˜, ์ด๋ฅผ ์ƒ์†ํ•˜๊ฑฐ๋‚˜, ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ชจ๋ธ์— ํฌํ•จํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…๋„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">dataclasses์— ๊ด€ํ•œ Pydantic ๋ฌธ์„œ</a>๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +## ๋ฒ„์ „ { #version } + +์ด ๊ธฐ๋Šฅ์€ FastAPI `0.67.0` ๋ฒ„์ „๋ถ€ํ„ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”– diff --git a/docs/ko/docs/advanced/generate-clients.md b/docs/ko/docs/advanced/generate-clients.md new file mode 100644 index 0000000000..1def3efe12 --- /dev/null +++ b/docs/ko/docs/advanced/generate-clients.md @@ -0,0 +1,208 @@ +# SDK ์ƒ์„ฑํ•˜๊ธฐ { #generating-sdks } + +**FastAPI**๋Š” **OpenAPI** ์‚ฌ์–‘์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฏ€๋กœ, FastAPI์˜ API๋Š” ๋งŽ์€ ๋„๊ตฌ๊ฐ€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ํ‘œ์ค€ ํ˜•์‹์œผ๋กœ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋•๋ถ„์— ์—ฌ๋Ÿฌ ์–ธ์–ด์šฉ ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(<abbr title="Software Development Kits - ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ ํ‚คํŠธ">**SDKs**</abbr>), ์ตœ์‹  **๋ฌธ์„œ**, ๊ทธ๋ฆฌ๊ณ  ์ฝ”๋“œ์™€ ๋™๊ธฐํ™”๋œ **ํ…Œ์ŠคํŠธ** ๋˜๋Š” **์ž๋™ํ™” ์›Œํฌํ”Œ๋กœ**๋ฅผ ์‰ฝ๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๊ฐ€์ด๋“œ์—์„œ๋Š” FastAPI ๋ฐฑ์—”๋“œ์šฉ **TypeScript SDK**๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›๋‹ˆ๋‹ค. + +## ์˜คํ”ˆ ์†Œ์Šค SDK ์ƒ์„ฑ๊ธฐ { #open-source-sdk-generators } + +๋‹ค์–‘ํ•˜๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜์œผ๋กœ <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>๊ฐ€ ์žˆ์œผ๋ฉฐ, **๋‹ค์–‘ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด**๋ฅผ ์ง€์›ํ•˜๊ณ  OpenAPI ์‚ฌ์–‘์œผ๋กœ๋ถ€ํ„ฐ SDK๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +**TypeScript ํด๋ผ์ด์–ธํŠธ**์˜ ๊ฒฝ์šฐ <a href="https://heyapi.dev/" class="external-link" target="_blank">Hey API</a>๋Š” TypeScript ์ƒํƒœ๊ณ„์— ์ตœ์ ํ™”๋œ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ๋ชฉ์ ์— ๋งž๊ฒŒ ์„ค๊ณ„๋œ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค. + +๋” ๋งŽ์€ SDK ์ƒ์„ฑ๊ธฐ๋Š” <a href="https://openapi.tools/#sdk" class="external-link" target="_blank">OpenAPI.Tools</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// 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๋ฅผ ์ง€์›ํ•˜๋Š” ๋ฐ์—๋„ ๊ด€์‹ฌ์ด ์žˆ์Œ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ๐Ÿ™‡ + +์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์„ ์‚ฌ์šฉํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a> +* <a href="https://www.stainless.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a> +* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a> + +์ด ์ค‘ ์ผ๋ถ€๋Š” ์˜คํ”ˆ ์†Œ์Šค์ด๊ฑฐ๋‚˜ ๋ฌด๋ฃŒ ํ‹ฐ์–ด๋ฅผ ์ œ๊ณตํ•˜๋ฏ€๋กœ, ๋น„์šฉ ๋ถ€๋‹ด ์—†์ด ์‚ฌ์šฉํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์ƒ์šฉ SDK ์ƒ์„ฑ๊ธฐ๋„ ์žˆ์œผ๋ฉฐ ์˜จ๋ผ์ธ์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ + +## TypeScript SDK ๋งŒ๋“ค๊ธฐ { #create-a-typescript-sdk } + +๊ฐ„๋‹จํ•œ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์‹œ์ž‘ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} + +*path operation*์—์„œ ์š”์ฒญ ํŽ˜์ด๋กœ๋“œ์™€ ์‘๋‹ต ํŽ˜์ด๋กœ๋“œ์— ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋ธ์„ `Item`, `ResponseMessage` ๋ชจ๋ธ๋กœ ์ •์˜ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. + +### API ๋ฌธ์„œ { #api-docs } + +`/docs`๋กœ ์ด๋™ํ•˜๋ฉด, ์š”์ฒญ์œผ๋กœ ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ์™€ ์‘๋‹ต์œผ๋กœ ๋ฐ›์„ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ **์Šคํ‚ค๋งˆ(schemas)**๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/tutorial/generate-clients/image01.png"> + +์ด ์Šคํ‚ค๋งˆ๋Š” ์•ฑ์—์„œ ๋ชจ๋ธ๋กœ ์„ ์–ธ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ ์ •๋ณด๋Š” ์•ฑ์˜ **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๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +<a href="https://heyapi.dev/openapi-ts/get-started" class="external-link" target="_blank">`@hey-api/openapi-ts` ์„ค์น˜ ๋ฐฉ๋ฒ•</a>๊ณผ <a href="https://heyapi.dev/openapi-ts/output" class="external-link" target="_blank">์ƒ์„ฑ๋œ ๊ฒฐ๊ณผ๋ฌผ</a>์€ ํ•ด๋‹น ์›น์‚ฌ์ดํŠธ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### SDK ์‚ฌ์šฉํ•˜๊ธฐ { #using-the-sdk } + +์ด์ œ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋ฅผ importํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ์ž๋™ ์™„์„ฑ์ด ์ œ๊ณต๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/tutorial/generate-clients/image02.png"> + +๋ณด๋‚ผ ํŽ˜์ด๋กœ๋“œ์— ๋Œ€ํ•ด์„œ๋„ ์ž๋™ ์™„์„ฑ์ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค: + +<img src="/img/tutorial/generate-clients/image03.png"> + +/// tip | ํŒ + +`name`๊ณผ `price`์— ๋Œ€ํ•œ ์ž๋™ ์™„์„ฑ์€ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ `Item` ๋ชจ๋ธ์— ์ •์˜๋œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. + +/// + +์ „์†กํ•˜๋Š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด ์ธ๋ผ์ธ ์˜ค๋ฅ˜๋„ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค: + +<img src="/img/tutorial/generate-clients/image04.png"> + +์‘๋‹ต ๊ฐ์ฒด๋„ ์ž๋™ ์™„์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: + +<img src="/img/tutorial/generate-clients/image05.png"> + +## ํƒœ๊ทธ๊ฐ€ ์žˆ๋Š” FastAPI ์•ฑ { #fastapi-app-with-tags } + +๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ FastAPI ์•ฑ์€ ๋” ์ปค์ง€๊ณ , ์„œ๋กœ ๋‹ค๋ฅธ *path operations* ๊ทธ๋ฃน์„ ๋ถ„๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด **items** ์„น์…˜๊ณผ **users** ์„น์…˜์ด ์žˆ๊ณ , ์ด๋ฅผ ํƒœ๊ทธ๋กœ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} + +### ํƒœ๊ทธ๋กœ TypeScript ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑํ•˜๊ธฐ { #generate-a-typescript-client-with-tags } + +ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” FastAPI ์•ฑ์— ๋Œ€ํ•ด ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด, ์ผ๋ฐ˜์ ์œผ๋กœ ์ƒ์„ฑ๋œ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋„ ํƒœ๊ทธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ถ„๋ฆฌ๋ฉ๋‹ˆ๋‹ค. + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ ํ•ญ๋ชฉ๋“ค์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ •๋ ฌ๋˜๊ณ  ๊ทธ๋ฃนํ™”๋ฉ๋‹ˆ๋‹ค: + +<img src="/img/tutorial/generate-clients/image06.png"> + +์ด ๊ฒฝ์šฐ ๋‹ค์Œ์ด ์žˆ์Šต๋‹ˆ๋‹ค: + +* `ItemsService` +* `UsersService` + +### ํด๋ผ์ด์–ธํŠธ ๋ฉ”์„œ๋“œ ์ด๋ฆ„ { #client-method-names } + +ํ˜„์žฌ `createItemItemsPost` ๊ฐ™์€ ์ƒ์„ฑ๋œ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์€ ๊ทธ๋‹ค์ง€ ๊น”๋”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +...์ด๋Š” ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ๊ธฐ๊ฐ€ ๊ฐ *path operation*์— ๋Œ€ํ•ด OpenAPI ๋‚ด๋ถ€์˜ **operation ID**๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +OpenAPI๋Š” ๋ชจ๋“  *path operations* ์ „์ฒด์—์„œ operation ID๊ฐ€ ๊ฐ๊ฐ ์œ ์ผํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ FastAPI๋Š” operation ID๊ฐ€ ์œ ์ผํ•˜๋„๋ก **ํ•จ์ˆ˜ ์ด๋ฆ„**, **๊ฒฝ๋กœ**, **HTTP method/operation**์„ ์กฐํ•ฉํ•ด 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`๋ฅผ ๋ฐ›์•„ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์—์„œ๋Š” ์ฒซ ๋ฒˆ์งธ ํƒœ๊ทธ(๋Œ€๋ถ€๋ถ„ ํƒœ๊ทธ๋Š” ํ•˜๋‚˜๋งŒ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค)์™€ *path operation* ์ด๋ฆ„(ํ•จ์ˆ˜ ์ด๋ฆ„)์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ ์ด ์ปค์Šคํ…€ ํ•จ์ˆ˜๋ฅผ `generate_unique_id_function` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ **FastAPI**์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} + +### ์ปค์Šคํ…€ Operation ID๋กœ TypeScript ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑํ•˜๊ธฐ { #generate-a-typescript-client-with-custom-operation-ids } + +์ด์ œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋‹ค์‹œ ์ƒ์„ฑํ•˜๋ฉด, ๊ฐœ์„ ๋œ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/tutorial/generate-clients/image07.png"> + +๋ณด์‹œ๋‹ค์‹œํ”ผ, ์ด์ œ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์€ ํƒœ๊ทธ ๋‹ค์Œ์— ํ•จ์ˆ˜ ์ด๋ฆ„์ด ์˜ค๋ฉฐ, URL ๊ฒฝ๋กœ์™€ HTTP operation์˜ ์ •๋ณด๋Š” ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +### ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ๊ธฐ๋ฅผ ์œ„ํ•œ OpenAPI ์‚ฌ์–‘ ์ „์ฒ˜๋ฆฌ { #preprocess-the-openapi-specification-for-the-client-generator } + +์ƒ์„ฑ๋œ ์ฝ”๋“œ์—๋Š” ์—ฌ์ „ํžˆ ์ผ๋ถ€ **์ค‘๋ณต ์ •๋ณด**๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +`ItemsService`(ํƒœ๊ทธ์—์„œ ๊ฐ€์ ธ์˜ด)์— ์ด๋ฏธ **items**๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์–ด ์ด ๋ฉ”์„œ๋“œ๊ฐ€ items์™€ ๊ด€๋ จ๋˜์–ด ์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ฉ”์„œ๋“œ ์ด๋ฆ„์—๋„ ํƒœ๊ทธ ์ด๋ฆ„์ด ์ ‘๋‘์‚ฌ๋กœ ๋ถ™์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜• + +OpenAPI ์ „๋ฐ˜์—์„œ๋Š” operation ID๊ฐ€ **์œ ์ผ**ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ์ด ๋ฐฉ์‹์„ ์œ ์ง€ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์ƒ์„ฑ๋œ ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š”, ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์ง์ „์— OpenAPI operation ID๋ฅผ **์ˆ˜์ •**ํ•ด์„œ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์„ ๋” ๋ณด๊ธฐ ์ข‹๊ณ  **๊น”๋”ํ•˜๊ฒŒ** ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +OpenAPI JSON์„ `openapi.json` ํŒŒ์ผ๋กœ ๋‹ค์šด๋กœ๋“œํ•œ ๋’ค, ์•„๋ž˜์™€ ๊ฐ™์€ ์Šคํฌ๋ฆฝํŠธ๋กœ **์ ‘๋‘์‚ฌ ํƒœ๊ทธ๋ฅผ ์ œ๊ฑฐ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/generate_clients/tutorial004_py39.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 +``` + +์ƒˆ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑํ•œ ํ›„์—๋Š” **๊น”๋”ํ•œ ๋ฉ”์„œ๋“œ ์ด๋ฆ„**์„ ๊ฐ€์ง€๋ฉด์„œ๋„, **์ž๋™ ์™„์„ฑ**, **์ธ๋ผ์ธ ์˜ค๋ฅ˜** ๋“ฑ์€ ๊ทธ๋Œ€๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค: + +<img src="/img/tutorial/generate-clients/image08.png"> + +## ์žฅ์  { #benefits } + +์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ์— ๋Œ€ํ•ด **์ž๋™ ์™„์„ฑ**์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +* ๋ฉ”์„œ๋“œ +* ๋ณธ๋ฌธ(body)์˜ ์š”์ฒญ ํŽ˜์ด๋กœ๋“œ, ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๋“ฑ +* ์‘๋‹ต ํŽ˜์ด๋กœ๋“œ + +๋˜ํ•œ ๋ชจ๋“  ๊ฒƒ์— ๋Œ€ํ•ด **์ธ๋ผ์ธ ์˜ค๋ฅ˜**๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋ฐฑ์—”๋“œ ์ฝ”๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•œ ๋’ค ํ”„๋ก ํŠธ์—”๋“œ๋ฅผ **์žฌ์ƒ์„ฑ(regenerate)**ํ•˜๋ฉด, ์ƒˆ *path operations*๊ฐ€ ๋ฉ”์„œ๋“œ๋กœ ์ถ”๊ฐ€๋˜๊ณ  ๊ธฐ์กด ๊ฒƒ์€ ์ œ๊ฑฐ๋˜๋ฉฐ, ๊ทธ ๋ฐ–์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ๋„ ์ƒ์„ฑ๋œ ์ฝ”๋“œ์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๐Ÿค“ + +์ด๋Š” ๋ฌด์–ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๊ทธ ๋ณ€๊ฒฝ์ด ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—๋„ ์ž๋™์œผ๋กœ **๋ฐ˜์˜**๋œ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ ํด๋ผ์ด์–ธํŠธ๋ฅผ **๋นŒ๋“œ(build)**ํ•˜๋ฉด ์‚ฌ์šฉ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ **๋ถˆ์ผ์น˜(mismatch)**ํ•  ๊ฒฝ์šฐ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ์šด์˜ ํ™˜๊ฒฝ์—์„œ ์ตœ์ข… ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ค๋ฅ˜๊ฐ€ ๋…ธ์ถœ๋œ ๋’ค ๋ฌธ์ œ๋ฅผ ์ถ”์ ํ•˜๋Š” ๋Œ€์‹ , ๊ฐœ๋ฐœ ์‚ฌ์ดํด ์ดˆ๊ธฐ์— **๋งŽ์€ ์˜ค๋ฅ˜๋ฅผ ๋งค์šฐ ๋นจ๋ฆฌ ๊ฐ์ง€**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โœจ diff --git a/docs/ko/docs/advanced/middleware.md b/docs/ko/docs/advanced/middleware.md new file mode 100644 index 0000000000..be2c972a6b --- /dev/null +++ b/docs/ko/docs/advanced/middleware.md @@ -0,0 +1,97 @@ +# ๊ณ ๊ธ‰ Middleware { #advanced-middleware } + +๋ฉ”์ธ ํŠœํ† ๋ฆฌ์–ผ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— [์ปค์Šคํ…€ Middleware](../tutorial/middleware.md){.internal-link target=_blank}๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  [`CORSMiddleware`๋กœ CORS ์ฒ˜๋ฆฌํ•˜๊ธฐ](../tutorial/cors.md){.internal-link target=_blank}๋„ ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค. + +์ด ์„น์…˜์—์„œ๋Š” ๋‹ค๋ฅธ middleware๋“ค์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +## ASGI middleware ์ถ”๊ฐ€ํ•˜๊ธฐ { #adding-asgi-middlewares } + +**FastAPI**๋Š” Starlette๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ  <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr> ์‚ฌ์–‘์„ ๊ตฌํ˜„ํ•˜๋ฏ€๋กœ, ์–ด๋–ค ASGI middleware๋“  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ASGI ์‚ฌ์–‘์„ ๋”ฐ๋ฅด๊ธฐ๋งŒ ํ•˜๋ฉด, FastAPI๋‚˜ Starlette๋ฅผ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ middleware๊ฐ€ ์•„๋‹ˆ์–ด๋„ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. + +์ผ๋ฐ˜์ ์œผ๋กœ ASGI middleware๋Š” ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ASGI ์•ฑ์„ ๋ฐ›๋„๋ก ๊ธฐ๋Œ€ํ•˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. + +๊ทธ๋ž˜์„œ ์„œ๋“œํŒŒํ‹ฐ ASGI middleware ๋ฌธ์„œ์—์„œ๋Š” ์•„๋งˆ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜๋ผ๊ณ  ์•ˆ๋‚ดํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +ํ•˜์ง€๋งŒ FastAPI(์ •ํ™•ํžˆ๋Š” Starlette)๋Š” ๋” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋‚ด๋ถ€ middleware๊ฐ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ฅผ ์œ„ํ•ด(๊ทธ๋ฆฌ๊ณ  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()`๋Š” ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ middleware ํด๋ž˜์Šค๋ฅผ ๋ฐ›๊ณ , ๊ทธ ๋’ค์—๋Š” middleware์— ์ „๋‹ฌํ•  ์ถ”๊ฐ€ ์ธ์ž๋“ค์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. + +## ํ†ตํ•ฉ middleware { #integrated-middlewares } + +**FastAPI**์—๋Š” ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์œ„ํ•œ ์—ฌ๋Ÿฌ middleware๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์—์„œ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” `from starlette.middleware.something import SomethingMiddleware`๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค. + +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž ํŽธ์˜๋ฅผ ์œ„ํ•ด `fastapi.middleware`์— ์—ฌ๋Ÿฌ middleware๋ฅผ ์ œ๊ณตํ•˜์ง€๋งŒ, ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋Œ€๋ถ€๋ถ„์˜ middleware๋Š” Starlette์—์„œ ์ง์ ‘ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. + +/// + +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } + +๋“ค์–ด์˜ค๋Š” ๋ชจ๋“  ์š”์ฒญ์ด `https` ๋˜๋Š” `wss`์—ฌ์•ผ ํ•˜๋„๋ก ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค. + +`http` ๋˜๋Š” `ws`๋กœ ๋“ค์–ด์˜ค๋Š” ๋ชจ๋“  ์š”์ฒญ์€ ๋Œ€์‹  ๋ณด์•ˆ ์Šคํ‚ด์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} + +## `TrustedHostMiddleware` { #trustedhostmiddleware } + +HTTP Host Header ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•˜๊ธฐ ์œ„ํ•ด, ๋“ค์–ด์˜ค๋Š” ๋ชจ๋“  ์š”์ฒญ์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋œ `Host` ํ—ค๋”๊ฐ€ ์žˆ์–ด์•ผ ํ•˜๋„๋ก ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} + +๋‹ค์Œ ์ธ์ž๋“ค์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค: + +* `allowed_hosts` - ํ˜ธ์ŠคํŠธ๋ช…์œผ๋กœ ํ—ˆ์šฉํ•  ๋„๋ฉ”์ธ ์ด๋ฆ„ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค. `*.example.com` ๊ฐ™์€ ์™€์ผ๋“œ์นด๋“œ ๋„๋ฉ”์ธ์œผ๋กœ ์„œ๋ธŒ๋„๋ฉ”์ธ์„ ๋งค์นญํ•˜๋Š” ๊ฒƒ๋„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ํ˜ธ์ŠคํŠธ๋ช…์ด๋“  ํ—ˆ์šฉํ•˜๋ ค๋ฉด `allowed_hosts=["*"]`๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ middleware๋ฅผ ์ƒ๋žตํ•˜์„ธ์š”. +* `www_redirect` - True๋กœ ์„ค์ •ํ•˜๋ฉด, ํ—ˆ์šฉ๋œ ํ˜ธ์ŠคํŠธ์˜ non-www ๋ฒ„์ „์œผ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ www ๋ฒ„์ „์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ `True`์ž…๋‹ˆ๋‹ค. + +๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ฒ€์ฆ๋˜์ง€ ์•Š์œผ๋ฉด `400` ์‘๋‹ต์ด ์ „์†ก๋ฉ๋‹ˆ๋‹ค. + +## `GZipMiddleware` { #gzipmiddleware } + +`Accept-Encoding` ํ—ค๋”์— `"gzip"`์ด ํฌํ•จ๋œ ์–ด๋–ค ์š”์ฒญ์ด๋“  GZip ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + +์ด middleware๋Š” ์ผ๋ฐ˜ ์‘๋‹ต๊ณผ ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} + +๋‹ค์Œ ์ธ์ž๋“ค์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค: + +* `minimum_size` - ๋ฐ”์ดํŠธ ๋‹จ์œ„๋กœ ์ง€์ •ํ•œ ์ตœ์†Œ ํฌ๊ธฐ๋ณด๋‹ค ์ž‘์€ ์‘๋‹ต์€ GZip์œผ๋กœ ์••์ถ•ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ `500`์ž…๋‹ˆ๋‹ค. +* `compresslevel` - GZip ์••์ถ• ์ค‘์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. 1๋ถ€ํ„ฐ 9๊นŒ์ง€์˜ ์ •์ˆ˜์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ `9`์ž…๋‹ˆ๋‹ค. ๊ฐ’์ด ๋‚ฎ์„์ˆ˜๋ก ์••์ถ•์€ ๋” ๋น ๋ฅด์ง€๋งŒ ํŒŒ์ผ ํฌ๊ธฐ๋Š” ๋” ์ปค์ง€๊ณ , ๊ฐ’์ด ๋†’์„์ˆ˜๋ก ์••์ถ•์€ ๋” ๋А๋ฆฌ์ง€๋งŒ ํŒŒ์ผ ํฌ๊ธฐ๋Š” ๋” ์ž‘์•„์ง‘๋‹ˆ๋‹ค. + +## ๋‹ค๋ฅธ middleware { #other-middlewares } + +๋‹ค๋ฅธ ASGI middleware๋„ ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด: + +* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn์˜ `ProxyHeadersMiddleware`</a> +* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a> + +์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋‹ค๋ฅธ middleware๋ฅผ ๋ณด๋ ค๋ฉด <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">Starlette์˜ Middleware ๋ฌธ์„œ</a>์™€ <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome List</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. diff --git a/docs/ko/docs/advanced/openapi-callbacks.md b/docs/ko/docs/advanced/openapi-callbacks.md new file mode 100644 index 0000000000..e4bdea9d6c --- /dev/null +++ b/docs/ko/docs/advanced/openapi-callbacks.md @@ -0,0 +1,186 @@ +# OpenAPI ์ฝœ๋ฐฑ { #openapi-callbacks } + +๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๋งŒ๋“  *external API*(์•„๋งˆ๋„ ๋‹น์‹ ์˜ API๋ฅผ *์‚ฌ์šฉ*ํ•  ๋™์ผํ•œ ๊ฐœ๋ฐœ์ž)๊ฐ€ ์š”์ฒญ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋„๋ก ๋งŒ๋“œ๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ๊ฐ€์ง„ API๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋‹น์‹ ์˜ API ์•ฑ์ด *external API*๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ์ผ์–ด๋‚˜๋Š” ๊ณผ์ •์„ "callback"์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•œ ์†Œํ”„ํŠธ์›จ์–ด๊ฐ€ ๋‹น์‹ ์˜ API๋กœ ์š”์ฒญ์„ ๋ณด๋‚ธ ๋‹ค์Œ, ๋‹น์‹ ์˜ API๊ฐ€ ๋‹ค์‹œ *external API*๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด *๋˜๋Œ๋ ค ํ˜ธ์ถœ*ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค(์•„๋งˆ๋„ ๊ฐ™์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งŒ๋“  API์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค). + +์ด ๊ฒฝ์šฐ, ๊ทธ *external API*๊ฐ€ ์–ด๋–ค ํ˜•ํƒœ์—ฌ์•ผ ํ•˜๋Š”์ง€ ๋ฌธ์„œํ™”ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ๊ฐ€์ ธ์•ผ ํ•˜๋Š”์ง€, ์–ด๋–ค body๋ฅผ ๊ธฐ๋Œ€ํ•˜๋Š”์ง€, ์–ด๋–ค ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š”์ง€ ๋“ฑ์ž…๋‹ˆ๋‹ค. + +## ์ฝœ๋ฐฑ์ด ์žˆ๋Š” ์•ฑ { #an-app-with-callbacks } + +์˜ˆ์‹œ๋กœ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +์ฒญ๊ตฌ์„œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์•ฑ์„ ๊ฐœ๋ฐœํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด์„ธ์š”. + +์ด ์ฒญ๊ตฌ์„œ๋Š” `id`, `title`(์„ ํƒ ์‚ฌํ•ญ), `customer`, `total`์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. + +๋‹น์‹ ์˜ API ์‚ฌ์šฉ์ž(์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž)๋Š” POST ์š”์ฒญ์œผ๋กœ ๋‹น์‹ ์˜ API์—์„œ ์ฒญ๊ตฌ์„œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ ๋‹น์‹ ์˜ API๋Š”(๊ฐ€์ •ํ•ด ๋ณด๋ฉด): + +* ์ฒญ๊ตฌ์„œ๋ฅผ ์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž์˜ ๊ณ ๊ฐ์—๊ฒŒ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. +* ๋ˆ์„ ์ˆ˜๊ธˆํ•ฉ๋‹ˆ๋‹ค. +* API ์‚ฌ์šฉ์ž(์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž)์˜ API๋กœ ๋‹ค์‹œ ์•Œ๋ฆผ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. + * ์ด๋Š” (๋‹น์‹ ์˜ API์—์„œ) ๊ทธ ์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์–ด๋–ค *external API*๋กœ POST ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ฐฉ์‹์œผ๋กœ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค(์ด๊ฒƒ์ด "callback"์ž…๋‹ˆ๋‹ค). + +## ์ผ๋ฐ˜์ ์ธ **FastAPI** ์•ฑ { #the-normal-fastapi-app } + +๋จผ์ € ์ฝœ๋ฐฑ์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์ „, ์ผ๋ฐ˜์ ์ธ API ์•ฑ์ด ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋Š”์ง€ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +`Invoice` body๋ฅผ ๋ฐ›๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์™€, ์ฝœ๋ฐฑ์„ ์œ„ํ•œ URL์„ ๋‹ด๋Š” ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ `callback_url`์ด ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +์ด ๋ถ€๋ถ„์€ ๊ฝค ์ผ๋ฐ˜์ ์ด๋ฉฐ, ๋Œ€๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋Š” ์ด๋ฏธ ์ต์ˆ™ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} + +/// tip | ํŒ + +`callback_url` ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” Pydantic์˜ <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a> ํƒ€์ž…์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +/// + +์œ ์ผํ•˜๊ฒŒ ์ƒˆ๋กœ์šด ๊ฒƒ์€ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์˜ ์ธ์ž๋กœ `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 ์‚ฌ์šฉ์ž(์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž)๊ฐ€ ์ฝœ๋ฐฑ ์š”์ฒญ body๋กœ *๋‹น์‹ ์˜ API*๊ฐ€ ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ ๋“ฑ์— ๋งž์ถฐ *external API*๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„ํ•˜๋„๋ก ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๊ทธ๋ž˜์„œ ๋‹ค์Œ์œผ๋กœ ํ•  ์ผ์€, *๋‹น์‹ ์˜ API*์—์„œ ๋ณด๋‚ด๋Š” ์ฝœ๋ฐฑ์„ ๋ฐ›๊ธฐ ์œ„ํ•ด ๊ทธ *external API*๊ฐ€ ์–ด๋–ค ํ˜•ํƒœ์—ฌ์•ผ ํ•˜๋Š”์ง€ ๋ฌธ์„œํ™”ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๊ทธ ๋ฌธ์„œ๋Š” ๋‹น์‹ ์˜ API์—์„œ `/docs`์˜ Swagger UI์— ํ‘œ์‹œ๋˜๋ฉฐ, ์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž๋“ค์ด *external API*๋ฅผ ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. + +์ด ์˜ˆ์‹œ๋Š” ์ฝœ๋ฐฑ ์ž์ฒด(ํ•œ ์ค„ ์ฝ”๋“œ๋กœ๋„ ๋  ์ˆ˜ ์žˆ์Œ)๋ฅผ ๊ตฌํ˜„ํ•˜์ง€ ์•Š๊ณ , ๋ฌธ์„œํ™” ๋ถ€๋ถ„๋งŒ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์‹ค์ œ ์ฝœ๋ฐฑ์€ ๋‹จ์ง€ HTTP ์š”์ฒญ์ž…๋‹ˆ๋‹ค. + +์ฝœ๋ฐฑ์„ ์ง์ ‘ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>๋‚˜ <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a> ๊ฐ™์€ ๊ฒƒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +## ์ฝœ๋ฐฑ ๋ฌธ์„œํ™” ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ { #write-the-callback-documentation-code } + +์ด ์ฝ”๋“œ๋Š” ์•ฑ์—์„œ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ *external API*๊ฐ€ ์–ด๋–ค ํ˜•ํƒœ์—ฌ์•ผ ํ•˜๋Š”์ง€ *๋ฌธ์„œํ™”*ํ•˜๋Š” ๋ฐ๋งŒ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ **FastAPI**๋กœ API์˜ ์ž๋™ ๋ฌธ์„œ๋ฅผ ์‰ฝ๊ฒŒ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ด๋ฏธ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ๊ทธ์™€ ๊ฐ™์€ ์ง€์‹์„ ์‚ฌ์šฉํ•ด *external API*๊ฐ€ ์–ด๋–ป๊ฒŒ ์ƒ๊ฒจ์•ผ ํ•˜๋Š”์ง€ ๋ฌธ์„œํ™”ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค... ์ฆ‰ ์™ธ๋ถ€ API๊ฐ€ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ(๋“ค)*(๋‹น์‹ ์˜ API๊ฐ€ ํ˜ธ์ถœํ•  ๊ฒƒ๋“ค)์„ ๋งŒ๋“ค์–ด์„œ ๋ง์ž…๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์ฝœ๋ฐฑ์„ ๋ฌธ์„œํ™”ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๋Š”, ์ž์‹ ์ด ๊ทธ *์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž*๋ผ๊ณ  ์ƒ์ƒํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ง€๊ธˆ์€ *๋‹น์‹ ์˜ API*๊ฐ€ ์•„๋‹ˆ๋ผ *external API*๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด ๋ณด์„ธ์š”. + +์ด ๊ด€์ (์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž์˜ ๊ด€์ )์„ ์ž ์‹œ ์ฑ„ํƒํ•˜๋ฉด, ๊ทธ *external API*๋ฅผ ์œ„ํ•ด ํŒŒ๋ผ๋ฏธํ„ฐ, body์šฉ Pydantic ๋ชจ๋ธ, ์‘๋‹ต ๋“ฑ์„ ์–ด๋””์— ๋‘์–ด์•ผ ํ•˜๋Š”์ง€๊ฐ€ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +### ์ฝœ๋ฐฑ `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_model=InvoiceEventReceived`). + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} + +์ผ๋ฐ˜์ ์ธ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์™€์˜ ์ฃผ์š” ์ฐจ์ด์ ์€ 2๊ฐ€์ง€์ž…๋‹ˆ๋‹ค: + +* ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๊ฐ€์งˆ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์•ฑ์€ ์ด ์ฝ”๋“œ๋ฅผ ์ ˆ๋Œ€ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋Š” *external API*๋ฅผ ๋ฌธ์„œํ™”ํ•˜๋Š” ๋ฐ๋งŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•จ์ˆ˜๋Š” ๊ทธ๋ƒฅ `pass`๋งŒ ์žˆ์–ด๋„ ๋ฉ๋‹ˆ๋‹ค. +* *path*์—๋Š” <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a>(์ž์„ธํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜ ์ฐธ๊ณ )์ด ํฌํ•จ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด *๋‹น์‹ ์˜ API*๋กœ ๋ณด๋‚ด์ง„ ์›๋ž˜ ์š”์ฒญ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ์ผ๋ถ€ ๊ฐ’์„ ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์ฝœ๋ฐฑ ๊ฒฝ๋กœ ํ‘œํ˜„์‹ { #the-callback-path-expression } + +์ฝœ๋ฐฑ *path*๋Š” *๋‹น์‹ ์˜ API*๋กœ ๋ณด๋‚ด์ง„ ์›๋ž˜ ์š”์ฒญ์˜ ์ผ๋ถ€๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a>์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ, ๋‹ค์Œ `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`(์ฆ‰ *external API*)๋กœ ์ฝœ๋ฐฑ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค: + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์€ JSON body๋ฅผ ํฌํ•จํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +๋˜ํ•œ ๊ทธ *external API*๋กœ๋ถ€ํ„ฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ JSON body ์‘๋‹ต์„ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค: + +```JSON +{ + "ok": true +} +``` + +/// tip | ํŒ + +์ฝœ๋ฐฑ URL์—๋Š” `callback_url` ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์€ URL(`https://www.external.org/events`)๋ฟ ์•„๋‹ˆ๋ผ, JSON body ์•ˆ์˜ ์ฒญ๊ตฌ์„œ `id`(`2expen51ve`)๋„ ํ•จ๊ป˜ ์‚ฌ์šฉ๋œ๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. + +/// + +### ์ฝœ๋ฐฑ ๋ผ์šฐํ„ฐ ์ถ”๊ฐ€ํ•˜๊ธฐ { #add-the-callback-router } + +์ด ์‹œ์ ์—์„œ, ์œ„์—์„œ ๋งŒ๋“  ์ฝœ๋ฐฑ ๋ผ์šฐํ„ฐ ์•ˆ์— *์ฝœ๋ฐฑ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ(๋“ค)*(์ฆ‰ *external developer*๊ฐ€ *external API*์— ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ๋“ค)์„ ์ค€๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค. + +์ด์ œ *๋‹น์‹ ์˜ API ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ `callbacks` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด, ๊ทธ ์ฝœ๋ฐฑ ๋ผ์šฐํ„ฐ์˜ `.routes` ์†์„ฑ(์‹ค์ œ๋กœ๋Š” routes/*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ `list`)์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} + +/// tip | ํŒ + +`callback=`์— ๋ผ์šฐํ„ฐ ์ž์ฒด(`invoices_callback_router`)๋ฅผ ๋„˜๊ธฐ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, `invoices_callback_router.routes`์ฒ˜๋Ÿผ `.routes` ์†์„ฑ์„ ๋„˜๊ธด๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. + +/// + +### ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ { #check-the-docs } + +์ด์ œ ์•ฑ์„ ์‹คํ–‰ํ•˜๊ณ  <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>๋กœ ์ด๋™ํ•˜์„ธ์š”. + +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ๋Œ€ํ•ด "Callbacks" ์„น์…˜์„ ํฌํ•จํ•œ ๋ฌธ์„œ๊ฐ€ ํ‘œ์‹œ๋˜๋ฉฐ, *external API*๊ฐ€ ์–ด๋–ค ํ˜•ํƒœ์—ฌ์•ผ ํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/tutorial/openapi-callbacks/image01.png"> diff --git a/docs/ko/docs/advanced/openapi-webhooks.md b/docs/ko/docs/advanced/openapi-webhooks.md new file mode 100644 index 0000000000..89cacf7b78 --- /dev/null +++ b/docs/ko/docs/advanced/openapi-webhooks.md @@ -0,0 +1,55 @@ +# OpenAPI Webhooks { #openapi-webhooks } + +์•ฑ์ด ์–ด๋–ค ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ (์š”์ฒญ์„ ๋ณด๋‚ด์„œ) *์‚ฌ์šฉ์ž์˜* ์•ฑ์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ณ , ๋ณดํ†ต ์–ด๋–ค **์ด๋ฒคํŠธ**๋ฅผ **์•Œ๋ฆฌ๊ธฐ** ์œ„ํ•ด ๊ทธ๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ API **์‚ฌ์šฉ์ž**์—๊ฒŒ ์•Œ๋ ค์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ API๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ์ผ๋ฐ˜์ ์ธ ๊ณผ์ • ๋Œ€์‹ , **์—ฌ๋Ÿฌ๋ถ„์˜ API**(๋˜๋Š” ์•ฑ)๊ฐ€ **์‚ฌ์šฉ์ž์˜ ์‹œ์Šคํ…œ**(์‚ฌ์šฉ์ž์˜ API, ์‚ฌ์šฉ์ž์˜ ์•ฑ)์œผ๋กœ **์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค**๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. + +์ด๋ฅผ ๋ณดํ†ต **webhook**์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. + +## Webhooks ๋‹จ๊ณ„ { #webhooks-steps } + +์ผ๋ฐ˜์ ์ธ ๊ณผ์ •์€, ์—ฌ๋Ÿฌ๋ถ„์ด ์ฝ”๋“œ์—์„œ ๋ณด๋‚ผ ๋ฉ”์‹œ์ง€, ์ฆ‰ **์š”์ฒญ ๋ณธ๋ฌธ(body)**์ด ๋ฌด์—‡์ธ์ง€ **์ •์˜**ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๋˜ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์ด ์–ด๋–ค **์‹œ์ **์— ๊ทธ ์š”์ฒญ(๋˜๋Š” ์ด๋ฒคํŠธ)์„ ๋ณด๋‚ผ์ง€๋„ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ๋“  ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  **์‚ฌ์šฉ์ž**๋Š” (์˜ˆ: ์–ด๋”˜๊ฐ€์˜ ์›น ๋Œ€์‹œ๋ณด๋“œ์—์„œ) ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์ด ๊ทธ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผ ํ•  **URL**์„ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ๋“  ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + +webhook์˜ URL์„ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ์‹ค์ œ๋กœ ๊ทธ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ์ฝ”๋“œ์— ๋Œ€ํ•œ ๋ชจ๋“  **๋กœ์ง**์€ ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ๋‹ฌ๋ ค ์žˆ์Šต๋‹ˆ๋‹ค. **์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ**์—์„œ ์›ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. + +## **FastAPI**์™€ OpenAPI๋กœ webhooks ๋ฌธ์„œํ™”ํ•˜๊ธฐ { #documenting-webhooks-with-fastapi-and-openapi } + +**FastAPI**์—์„œ๋Š” OpenAPI๋ฅผ ์‚ฌ์šฉํ•ด, ์ด๋Ÿฌํ•œ webhook์˜ ์ด๋ฆ„, ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์ด ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” HTTP ์ž‘์—… ํƒ€์ž…(์˜ˆ: `POST`, `PUT` ๋“ฑ), ๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ฑ์ด ๋ณด๋‚ผ ์š”์ฒญ **๋ณธ๋ฌธ(body)**์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ **webhook** ์š”์ฒญ์„ ๋ฐ›๊ธฐ ์œ„ํ•ด **์ž์‹ ๋“ค์˜ API๋ฅผ ๊ตฌํ˜„**ํ•˜๊ธฐ๊ฐ€ ํ›จ์”ฌ ์‰ฌ์›Œ์ง€๊ณ , ๊ฒฝ์šฐ์— ๋”ฐ๋ผ์„œ๋Š” ์ž์‹ ์˜ API ์ฝ”๋“œ ์ผ๋ถ€๋ฅผ ์ž๋™ ์ƒ์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +Webhooks๋Š” OpenAPI 3.1.0 ์ด์ƒ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, FastAPI `0.99.0` ์ด์ƒ์—์„œ ์ง€์›๋ฉ๋‹ˆ๋‹ค. + +/// + +## webhooks๊ฐ€ ์žˆ๋Š” ์•ฑ { #an-app-with-webhooks } + +**FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค๋ฉด, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ(์˜ˆ: `@app.webhooks.post()`), *webhooks*๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” `webhooks` ์†์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} + +์—ฌ๋Ÿฌ๋ถ„์ด ์ •์˜ํ•œ webhook์€ **OpenAPI** ์Šคํ‚ค๋งˆ์™€ ์ž๋™ **docs UI**์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +`app.webhooks` ๊ฐ์ฒด๋Š” ์‹ค์ œ๋กœ `APIRouter`์ผ ๋ฟ์ด๋ฉฐ, ์—ฌ๋Ÿฌ ํŒŒ์ผ๋กœ ์•ฑ์„ ๊ตฌ์กฐํ™”ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ํƒ€์ž…์ž…๋‹ˆ๋‹ค. + +/// + +webhook์—์„œ๋Š” ์‹ค์ œ๋กœ(`/items/` ๊ฐ™์€) *๊ฒฝ๋กœ(path)*๋ฅผ ์„ ์–ธํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. ๊ทธ๊ณณ์— ์ „๋‹ฌํ•˜๋Š” ํ…์ŠคํŠธ๋Š” webhook์˜ **์‹๋ณ„์ž**(์ด๋ฒคํŠธ ์ด๋ฆ„)์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด `@app.webhooks.post("new-subscription")`์—์„œ webhook ์ด๋ฆ„์€ `new-subscription`์ž…๋‹ˆ๋‹ค. + +์ด๋Š” **์‚ฌ์šฉ์ž**๊ฐ€ webhook ์š”์ฒญ์„ ๋ฐ›๊ณ  ์‹ถ์€ ์‹ค์ œ **URL ๊ฒฝ๋กœ**๋ฅผ ๋‹ค๋ฅธ ๋ฐฉ์‹(์˜ˆ: ์›น ๋Œ€์‹œ๋ณด๋“œ)์œผ๋กœ ์ •์˜ํ•  ๊ฒƒ์ด๋ผ๊ณ  ๊ธฐ๋Œ€ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +### ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ { #check-the-docs } + +์ด์ œ ์•ฑ์„ ์‹คํ–‰ํ•˜๊ณ  <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>๋กœ ์ด๋™ํ•˜์„ธ์š”. + +๋ฌธ์„œ์— ์ผ๋ฐ˜์ ์ธ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ ๋ณด์ด๊ณ , ์ด์ œ๋Š” ์ผ๋ถ€ **webhooks**๋„ ํ•จ๊ป˜ ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค: + +<img src="/img/tutorial/openapi-webhooks/image01.png"> diff --git a/docs/ko/docs/advanced/path-operation-advanced-configuration.md b/docs/ko/docs/advanced/path-operation-advanced-configuration.md new file mode 100644 index 0000000000..f20fa6d263 --- /dev/null +++ b/docs/ko/docs/advanced/path-operation-advanced-configuration.md @@ -0,0 +1,172 @@ +# ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๊ณ ๊ธ‰ ๊ตฌ์„ฑ { #path-operation-advanced-configuration } + +## OpenAPI operationId { #openapi-operationid } + +/// warning | ๊ฒฝ๊ณ  + +OpenAPI โ€œ์ „๋ฌธ๊ฐ€โ€๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด, ์•„๋งˆ ์ด ๋‚ด์šฉ์€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// + +๋งค๊ฐœ๋ณ€์ˆ˜ `operation_id`๋ฅผ ์‚ฌ์šฉํ•ด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ์‚ฌ์šฉํ•  OpenAPI `operationId`๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ฐ ์ž‘์—…๋งˆ๋‹ค ๊ณ ์œ ํ•˜๋„๋ก ๋ณด์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} + +### *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* ์ด๋ฆ„์„ operationId๋กœ ์‚ฌ์šฉํ•˜๊ธฐ { #using-the-path-operation-function-name-as-the-operationid } + +API์˜ ํ•จ์ˆ˜ ์ด๋ฆ„์„ `operationId`๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ๋ชจ๋“  API๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ `APIRoute.name`์„ ์‚ฌ์šฉํ•ด ๊ฐ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ `operation_id`๋ฅผ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋ชจ๋“  *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์ถ”๊ฐ€ํ•œ ๋’ค์— ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} + +/// tip | ํŒ + +`app.openapi()`๋ฅผ ์ˆ˜๋™์œผ๋กœ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด, ๊ทธ ์ „์— `operationId`๋“ค์„ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +/// + +/// warning | ๊ฒฝ๊ณ  + +์ด๋ ‡๊ฒŒ ํ•  ๊ฒฝ์šฐ, ๊ฐ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ์ด๋ฆ„์ด ๊ณ ์œ ํ•˜๋„๋ก ๋ณด์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +์„œ๋กœ ๋‹ค๋ฅธ ๋ชจ๋“ˆ(ํŒŒ์ด์ฌ ํŒŒ์ผ)์— ์žˆ์–ด๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. + +/// + +## OpenAPI์—์„œ ์ œ์™ธํ•˜๊ธฐ { #exclude-from-openapi } + +์ƒ์„ฑ๋œ OpenAPI ์Šคํ‚ค๋งˆ(๋”ฐ๋ผ์„œ ์ž๋™ ๋ฌธ์„œํ™” ์‹œ์Šคํ…œ)์—์„œ ํŠน์ • *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์ œ์™ธํ•˜๋ ค๋ฉด, `include_in_schema` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `False`๋กœ ์„ค์ •ํ•˜์„ธ์š”: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.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 Extra { #openapi-extra } + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์„ ์–ธํ•˜๋ฉด, **FastAPI**๋Š” OpenAPI ์Šคํ‚ค๋งˆ์— ํฌํ•จ๋  ํ•ด๋‹น *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ ๊ด€๋ จ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +OpenAPI ๋ช…์„ธ์—์„œ๋Š” ์ด๋ฅผ <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Operation Object</a>๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. + +/// + +์—ฌ๊ธฐ์—๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ๋Œ€ํ•œ ๋ชจ๋“  ์ •๋ณด๊ฐ€ ์žˆ์œผ๋ฉฐ, ์ž๋™ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +`tags`, `parameters`, `requestBody`, `responses` ๋“ฑ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +์ด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์ „์šฉ OpenAPI ์Šคํ‚ค๋งˆ๋Š” ๋ณดํ†ต **FastAPI**๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜์ง€๋งŒ, ํ™•์žฅํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์ด๋Š” ์ €์ˆ˜์ค€ ํ™•์žฅ ์ง€์ ์ž…๋‹ˆ๋‹ค. + +์ถ”๊ฐ€ ์‘๋‹ต๋งŒ ์„ ์–ธํ•˜๋ฉด ๋œ๋‹ค๋ฉด, ๋” ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์€ [OpenAPI์˜ ์ถ”๊ฐ€ ์‘๋‹ต](additional-responses.md){.internal-link target=_blank}์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// + +`openapi_extra` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ OpenAPI ์Šคํ‚ค๋งˆ๋ฅผ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### 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_py39.py hl[6] *} + +์ž๋™ API ๋ฌธ์„œ๋ฅผ ์—ด๋ฉด, ํ•ด๋‹น ํŠน์ • *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ ํ•˜๋‹จ์— ํ™•์žฅ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. + +<img src="/img/tutorial/path-operation-advanced-configuration/image01.png"> + +๋˜ํ•œ API์˜ `/openapi.json`์—์„œ ๊ฒฐ๊ณผ OpenAPI๋ฅผ ๋ณด๋ฉด, ํŠน์ • *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ ์ผ๋ถ€๋กœ ํ™•์žฅ์ด ํฌํ•จ๋œ ๊ฒƒ๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```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 ์Šคํ‚ค๋งˆ์™€ ๊นŠ๊ฒŒ ๋ณ‘ํ•ฉ๋ฉ๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ์ž๋™ ์ƒ์„ฑ๋œ ์Šคํ‚ค๋งˆ์— ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด Pydantic๊ณผ ํ•จ๊ป˜ FastAPI์˜ ์ž๋™ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ์ž์ฒด ์ฝ”๋“œ๋กœ ์š”์ฒญ์„ ์ฝ๊ณ  ๊ฒ€์ฆํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, OpenAPI ์Šคํ‚ค๋งˆ์—๋Š” ์—ฌ์ „ํžˆ ๊ทธ ์š”์ฒญ์„ ์ •์˜ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋Ÿด ๋•Œ `openapi_extra`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} + +์ด ์˜ˆ์‹œ์—์„œ๋Š” ์–ด๋–ค Pydantic ๋ชจ๋ธ๋„ ์„ ์–ธํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์‚ฌ์‹ค ์š”์ฒญ ๋ฐ”๋””๋Š” JSON์œผ๋กœ <abbr title="converted from some plain format, like bytes, into Python objects - bytes ๊ฐ™์€ ์ผ๋ฐ˜ ํ˜•์‹์—์„œ Python ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜">parsed</abbr>๋˜์ง€๋„ ์•Š๊ณ , `bytes`๋กœ ์ง์ ‘ ์ฝ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ•จ์ˆ˜ `magic_data_reader()`๊ฐ€ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ๋“  ์ด๋ฅผ ํŒŒ์‹ฑํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ์š”์ฒญ ๋ฐ”๋””์— ๋Œ€ํ•ด ๊ธฐ๋Œ€ํ•˜๋Š” ์Šคํ‚ค๋งˆ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์‚ฌ์šฉ์ž ์ •์˜ OpenAPI ์ฝ˜ํ…์ธ  ํƒ€์ž… { #custom-openapi-content-type } + +๊ฐ™์€ ํŠธ๋ฆญ์„ ์‚ฌ์šฉํ•˜๋ฉด, Pydantic ๋ชจ๋ธ์„ ์ด์šฉํ•ด JSON Schema๋ฅผ ์ •์˜ํ•˜๊ณ  ์ด๋ฅผ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์˜ ์‚ฌ์šฉ์ž ์ •์˜ OpenAPI ์Šคํ‚ค๋งˆ ์„น์…˜์— ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์š”์ฒญ์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž…์ด JSON์ด ์•„๋‹ˆ๋”๋ผ๋„ ์ด๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” Pydantic ๋ชจ๋ธ์—์„œ JSON Schema๋ฅผ ์ถ”์ถœํ•˜๋Š” FastAPI์˜ ํ†ตํ•ฉ ๊ธฐ๋Šฅ๋„, JSON์— ๋Œ€ํ•œ ์ž๋™ ๊ฒ€์ฆ๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์š”์ฒญ ์ฝ˜ํ…์ธ  ํƒ€์ž…์„ JSON์ด ์•„๋‹ˆ๋ผ YAML๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} + +๊ทธ๋Ÿผ์—๋„ ๊ธฐ๋ณธ ํ†ตํ•ฉ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋”๋ผ๋„, YAML๋กœ ๋ฐ›๊ณ ์ž ํ•˜๋Š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ JSON Schema๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด Pydantic ๋ชจ๋ธ์„ ์—ฌ์ „ํžˆ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ ์š”์ฒญ์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ณ , ๋ฐ”๋””๋ฅผ `bytes`๋กœ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” FastAPI๊ฐ€ ์š”์ฒญ ํŽ˜์ด๋กœ๋“œ๋ฅผ JSON์œผ๋กœ ํŒŒ์‹ฑํ•˜๋ ค๊ณ  ์‹œ๋„์กฐ์ฐจ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์ฝ”๋“œ์—์„œ YAML ์ฝ˜ํ…์ธ ๋ฅผ ์ง์ ‘ ํŒŒ์‹ฑํ•œ ๋’ค, ๋‹ค์‹œ ๊ฐ™์€ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ด YAML ์ฝ˜ํ…์ธ ๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} + +/// tip | ํŒ + +์—ฌ๊ธฐ์„œ๋Š” ๊ฐ™์€ Pydantic ๋ชจ๋ธ์„ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ๊ฒ€์ฆํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// diff --git a/docs/ko/docs/advanced/security/http-basic-auth.md b/docs/ko/docs/advanced/security/http-basic-auth.md new file mode 100644 index 0000000000..611aad7956 --- /dev/null +++ b/docs/ko/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" ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๊ฐ’์ด `Basic`์ด๊ณ  ์„ ํƒ์ ์œผ๋กœ `realm` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํฌํ•จํ•˜๋Š” `WWW-Authenticate` ํ—ค๋”๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +์ด๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ํ†ตํ•ฉ ํ”„๋กฌํ”„ํŠธ๋ฅผ ํ‘œ์‹œํ•˜๋„๋ก ์•Œ๋ ค์ค๋‹ˆ๋‹ค. + +๊ทธ๋‹ค์Œ ์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ํ•ด๋‹น ๊ฐ’์„ ํ—ค๋”์— ๋‹ด์•„ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. + +## ๊ฐ„๋‹จํ•œ HTTP Basic Auth { #simple-http-basic-auth } + +* `HTTPBasic`๊ณผ `HTTPBasicCredentials`๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค. +* `HTTPBasic`์„ ์‚ฌ์šฉํ•ด "`security` scheme"์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +* *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ dependency๋กœ ํ•ด๋‹น `security`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +* `HTTPBasicCredentials` ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค: + * ์ „์†ก๋œ `username`๊ณผ `password`๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} + +์ฒ˜์Œ์œผ๋กœ URL์„ ์—ด์–ด๋ณด๋ฉด(๋˜๋Š” ๋ฌธ์„œ์—์„œ "Execute" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด) ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฌผ์–ด๋ด…๋‹ˆ๋‹ค: + +<img src="/img/tutorial/security/image12.png"> + +## ์‚ฌ์šฉ์ž๋ช… ํ™•์ธํ•˜๊ธฐ { #check-the-username } + +๋” ์™„์ „ํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. + +dependency๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•˜์„ธ์š”. + +์ด๋ฅผ ์œ„ํ•ด Python ํ‘œ์ค€ ๋ชจ๋“ˆ <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a>๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + +`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_py39.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 { #timing-attacks } + +๊ทธ๋ ‡๋‹ค๋ฉด "timing attack"์ด๋ž€ ๋ฌด์—‡์ผ๊นŒ์š”? + +๊ณต๊ฒฉ์ž๋“ค์ด ์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ถ”์ธกํ•˜๋ ค๊ณ  ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž๋ช… `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์€ ๋‘ ๋ฌธ์ž์—ด์ด ๊ฐ™์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•„์ฐจ๋ฆฌ๊ธฐ ์ „๊นŒ์ง€ `stanleyjobsox`์™€ `stanleyjobson` ์–‘์ชฝ์˜ `stanleyjobso` ์ „์ฒด๋ฅผ ๋น„๊ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ "Incorrect username or password"๋ผ๊ณ  ์‘๋‹ตํ•˜๊ธฐ๊นŒ์ง€ ์ถ”๊ฐ€๋กœ ๋ช‡ ๋งˆ์ดํฌ๋กœ์ดˆ๊ฐ€ ๋” ๊ฑธ๋ฆด ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +#### ์‘๋‹ต ์‹œ๊ฐ„์€ ๊ณต๊ฒฉ์ž์—๊ฒŒ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค { #the-time-to-answer-helps-the-attackers } + +์ด ์‹œ์ ์—์„œ ์„œ๋ฒ„๊ฐ€ "Incorrect username or password" ์‘๋‹ต์„ ๋ณด๋‚ด๋Š” ๋ฐ ๋ช‡ ๋งˆ์ดํฌ๋กœ์ดˆ ๋” ๊ฑธ๋ ธ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•„์ฑ„๋ฉด, ๊ณต๊ฒฉ์ž๋“ค์€ _๋ฌด์–ธ๊ฐ€_ ๋งž์•˜๋‹ค๋Š” ๊ฒƒ(์ดˆ๊ธฐ ๋ช‡ ๊ธ€์ž๊ฐ€ ๋งž์•˜๋‹ค๋Š” ๊ฒƒ)์„ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  `johndoe`๋ณด๋‹ค๋Š” `stanleyjobsox`์— ๋” ๊ฐ€๊นŒ์šด ๊ฐ’์„ ์‹œ๋„ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +#### "์ „๋ฌธ์ ์ธ" ๊ณต๊ฒฉ { #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_py39.py hl[26:30] *} diff --git a/docs/ko/docs/advanced/security/index.md b/docs/ko/docs/advanced/security/index.md new file mode 100644 index 0000000000..4c7abfacc7 --- /dev/null +++ b/docs/ko/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/ko/docs/advanced/security/oauth2-scopes.md b/docs/ko/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 0000000000..0f90f92ae9 --- /dev/null +++ b/docs/ko/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,274 @@ +# OAuth2 ์Šค์ฝ”ํ”„ { #oauth2-scopes } + +**FastAPI**์—์„œ OAuth2 ์Šค์ฝ”ํ”„๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ํ†ตํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ฅผ ํ†ตํ•ด OAuth2 ํ‘œ์ค€์„ ๋”ฐ๋ฅด๋Š” ๋” ์„ธ๋ฐ€ํ•œ ๊ถŒํ•œ ์‹œ์Šคํ…œ์„ OpenAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(๋ฐ API ๋ฌธ์„œ)์— ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์Šค์ฝ”ํ”„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” OAuth2๋Š” Facebook, Google, GitHub, Microsoft, X(Twitter) ๋“ฑ ๋งŽ์€ ๋Œ€ํ˜• ์ธ์ฆ ์ œ๊ณต์ž๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ์ด๋“ค์€ ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํŠน์ • ๊ถŒํ•œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +Facebook, Google, GitHub, Microsoft, X(Twitter)๋กœ โ€œ๋กœ๊ทธ์ธโ€ํ•  ๋•Œ๋งˆ๋‹ค, ํ•ด๋‹น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์Šค์ฝ”ํ”„๊ฐ€ ์žˆ๋Š” OAuth2๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ์„น์…˜์—์„œ๋Š” **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋™์ผํ•œ โ€œ์Šค์ฝ”ํ”„๊ฐ€ ์žˆ๋Š” OAuth2โ€๋กœ ์ธ์ฆ(Authentication)๊ณผ ์ธ๊ฐ€(Authorization)๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + +/// warning | ๊ฒฝ๊ณ  + +์ด ์„น์…˜์€ ๋‹ค์†Œ ๊ณ ๊ธ‰ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์ด์ œ ๋ง‰ ์‹œ์ž‘ํ–ˆ๋‹ค๋ฉด ๊ฑด๋„ˆ๋›ฐ์–ด๋„ ๋ฉ๋‹ˆ๋‹ค. + +OAuth2 ์Šค์ฝ”ํ”„๊ฐ€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ๊ฒƒ์€ ์•„๋‹ˆ๋ฉฐ, ์ธ์ฆ๊ณผ ์ธ๊ฐ€๋Š” ์›ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์Šค์ฝ”ํ”„๊ฐ€ ์žˆ๋Š” OAuth2๋Š” (OpenAPI์™€ ํ•จ๊ป˜) API ๋ฐ API ๋ฌธ์„œ์— ๊น”๋”ํ•˜๊ฒŒ ํ†ตํ•ฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ํ•ด๋‹น ์Šค์ฝ”ํ”„(๋˜๋Š” ๊ทธ ๋ฐ–์˜ ์–ด๋–ค ๋ณด์•ˆ/์ธ๊ฐ€ ์š”๊ตฌ์‚ฌํ•ญ์ด๋“ )๋Š” ์ฝ”๋“œ์—์„œ ํ•„์š”์— ๋งž๊ฒŒ ์ง์ ‘ ๊ฐ•์ œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +๋งŽ์€ ๊ฒฝ์šฐ ์Šค์ฝ”ํ”„๊ฐ€ ์žˆ๋Š” OAuth2๋Š” ๊ณผํ•œ ์„ ํƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ํ•„์š”ํ•˜๋‹ค๊ณ  ์•Œ๊ณ  ์žˆ๊ฑฐ๋‚˜ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด ๊ณ„์† ์ฝ์–ด๋ณด์„ธ์š”. + +/// + +## OAuth2 ์Šค์ฝ”ํ”„์™€ OpenAPI { #oauth2-scopes-and-openapi } + +OAuth2 ์‚ฌ์–‘์€ โ€œ์Šค์ฝ”ํ”„(scopes)โ€๋ฅผ ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„๋œ ๋ฌธ์ž์—ด ๋ชฉ๋ก์œผ๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + +๊ฐ ๋ฌธ์ž์—ด์˜ ๋‚ด์šฉ์€ ์–ด๋–ค ํ˜•์‹์ด๋“  ๋  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ณต๋ฐฑ์„ ํฌํ•จํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. + +์ด ์Šค์ฝ”ํ”„๋“ค์€ โ€œ๊ถŒํ•œโ€์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. + +OpenAPI(์˜ˆ: API ๋ฌธ์„œ)์—์„œ๋Š” โ€œsecurity schemesโ€๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด security scheme ์ค‘ ํ•˜๋‚˜๊ฐ€ OAuth2๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์Šค์ฝ”ํ”„๋„ ์„ ์–ธํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ฐ โ€œ์Šค์ฝ”ํ”„โ€๋Š” (๊ณต๋ฐฑ ์—†๋Š”) ๋ฌธ์ž์—ด์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. + +๋ณดํ†ต ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํŠน์ • ๋ณด์•ˆ ๊ถŒํ•œ์„ ์„ ์–ธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: + +* `users:read` ๋˜๋Š” `users:write` ๋Š” ํ”ํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. +* `instagram_basic` ๋Š” Facebook/Instagram์—์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +* `https://www.googleapis.com/auth/drive` ๋Š” Google์—์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +OAuth2์—์„œ โ€œ์Šค์ฝ”ํ”„โ€๋Š” ํ•„์š”ํ•œ ํŠน์ • ๊ถŒํ•œ์„ ์„ ์–ธํ•˜๋Š” ๋ฌธ์ž์—ด์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. + +`:` ๊ฐ™์€ ๋‹ค๋ฅธ ๋ฌธ์ž๊ฐ€ ์žˆ๊ฑฐ๋‚˜ URL์ด์–ด๋„ ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฐ ์„ธ๋ถ€์‚ฌํ•ญ์€ ๊ตฌํ˜„์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. + +OAuth2 ์ž…์žฅ์—์„œ๋Š” ๊ทธ์ € ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. + +/// + +## ์ „์ฒด ๊ฐœ์š” { #global-view } + +๋จผ์ €, ๋ฉ”์ธ **ํŠœํ† ๋ฆฌ์–ผ - ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ**์˜ [๋น„๋ฐ€๋ฒˆํ˜ธ(๋ฐ ํ•ด์‹ฑ)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” OAuth2, JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” Bearer](../../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 } + +์ฒซ ๋ฒˆ์งธ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ์ด์ œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์Šค์ฝ”ํ”„ 2๊ฐœ(`me`, `items`)๋กœ OAuth2 ๋ณด์•ˆ ์Šคํ‚ด์„ ์„ ์–ธํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. + +`scopes` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๊ฐ ์Šค์ฝ”ํ”„๋ฅผ ํ‚ค๋กœ ํ•˜๊ณ , ์„ค๋ช…์„ ๊ฐ’์œผ๋กœ ํ•˜๋Š” `dict`๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} + +์ด์ œ ์Šค์ฝ”ํ”„๋ฅผ ์„ ์–ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋กœ๊ทธ์ธ/์ธ๊ฐ€ํ•  ๋•Œ API ๋ฌธ์„œ์— ์Šค์ฝ”ํ”„๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•  ์Šค์ฝ”ํ”„๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: `me`์™€ `items`. + +์ด๋Š” Facebook, Google, GitHub ๋“ฑ์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด์„œ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค: + +<img src="/img/tutorial/security/image11.png"> + +## ์Šค์ฝ”ํ”„๋ฅผ ํฌํ•จํ•œ JWT ํ† ํฐ { #jwt-token-with-scopes } + +์ด์ œ ํ† ํฐ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์ˆ˜์ •ํ•ด, ์š”์ฒญ๋œ ์Šค์ฝ”ํ”„๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. + +์—ฌ์ „ํžˆ ๋™์ผํ•œ `OAuth2PasswordRequestForm`์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ์š”์ฒญ์—์„œ ๋ฐ›์€ ๊ฐ ์Šค์ฝ”ํ”„๋ฅผ ๋‹ด๋Š” `scopes` ์†์„ฑ์ด ์žˆ์œผ๋ฉฐ, ํƒ€์ž…์€ `str`์˜ `list`์ž…๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  JWT ํ† ํฐ์˜ ์ผ๋ถ€๋กœ ์Šค์ฝ”ํ”„๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +/// danger | ์œ„ํ—˜ + +๋‹จ์ˆœํ™”๋ฅผ ์œ„ํ•ด, ์—ฌ๊ธฐ์„œ๋Š” ์š”์ฒญ์œผ๋กœ ๋ฐ›์€ ์Šค์ฝ”ํ”„๋ฅผ ๊ทธ๋Œ€๋กœ ํ† ํฐ์— ์ถ”๊ฐ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋ณด์•ˆ์„ ์œ„ํ•ด, ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ œ๋กœ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ์Šค์ฝ”ํ”„๋งŒ(๋˜๋Š” ๋ฏธ๋ฆฌ ์ •์˜ํ•œ ๊ฒƒ๋งŒ) ์ถ”๊ฐ€ํ•˜๋„๋ก ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} + +## *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์™€ ์˜์กด์„ฑ์—์„œ ์Šค์ฝ”ํ”„ ์„ ์–ธํ•˜๊ธฐ { #declare-scopes-in-path-operations-and-dependencies } + +์ด์ œ `/users/me/items/`์— ๋Œ€ํ•œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ ์Šค์ฝ”ํ”„ `items`๋ฅผ ์š”๊ตฌํ•œ๋‹ค๊ณ  ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ฅผ ์œ„ํ•ด `fastapi`์—์„œ `Security`๋ฅผ importํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +`Security`๋Š” (`Depends`์ฒ˜๋Ÿผ) ์˜์กด์„ฑ์„ ์„ ์–ธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, `Security`๋Š” ์Šค์ฝ”ํ”„(๋ฌธ์ž์—ด) ๋ชฉ๋ก์„ ๋ฐ›๋Š” `scopes` ๋งค๊ฐœ๋ณ€์ˆ˜๋„ ๋ฐ›์Šต๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ, ์˜์กด์„ฑ ํ•จ์ˆ˜ `get_current_active_user`๋ฅผ `Security`์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค(`Depends`๋กœ ํ•  ๋•Œ์™€ ๊ฐ™์€ ๋ฐฉ์‹). + +ํ•˜์ง€๋งŒ ์Šค์ฝ”ํ”„ `list`๋„ ํ•จ๊ป˜ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์Šค์ฝ”ํ”„ ํ•˜๋‚˜๋งŒ: `items`(๋” ๋งŽ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค). + +๋˜ํ•œ ์˜์กด์„ฑ ํ•จ์ˆ˜ `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`์˜ ์„œ๋ธŒํด๋ž˜์Šค์ด๋ฉฐ, ๋‚˜์ค‘์— ๋ณด๊ฒŒ ๋  ์ถ”๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜ ํ•˜๋‚˜๋งŒ ๋” ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ `Depends` ๋Œ€์‹  `Security`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, **FastAPI**๋Š” ๋ณด์•ˆ ์Šค์ฝ”ํ”„๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Œ์„ ์•Œ๊ณ  ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, OpenAPI๋กœ API๋ฅผ ๋ฌธ์„œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ `fastapi`์—์„œ `Query`, `Path`, `Depends`, `Security` ๋“ฑ์„ importํ•  ๋•Œ, ์ด๊ฒƒ๋“ค์€ ์‹ค์ œ๋กœ ํŠน์ˆ˜ํ•œ ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. + +/// + +## `SecurityScopes` ์‚ฌ์šฉํ•˜๊ธฐ { #use-securityscopes } + +์ด์ œ ์˜์กด์„ฑ `get_current_user`๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + +์ด๋Š” ์œ„์˜ ์˜์กด์„ฑ๋“ค์ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +์—ฌ๊ธฐ์—์„œ ์•ž์„œ ๋งŒ๋“  ๋™์ผํ•œ OAuth2 ์Šคํ‚ด์„ ์˜์กด์„ฑ์œผ๋กœ ์„ ์–ธํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: `oauth2_scheme`. + +์ด ์˜์กด์„ฑ ํ•จ์ˆ˜ ์ž์ฒด์—๋Š” ์Šค์ฝ”ํ”„ ์š”๊ตฌ์‚ฌํ•ญ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์—, `oauth2_scheme`์™€ ํ•จ๊ป˜ `Depends`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณด์•ˆ ์Šค์ฝ”ํ”„๋ฅผ ์ง€์ •ํ•  ํ•„์š”๊ฐ€ ์—†์„ ๋•Œ๋Š” `Security`๋ฅผ ์“ธ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. + +๋˜ํ•œ `fastapi.security`์—์„œ importํ•œ `SecurityScopes` ํƒ€์ž…์˜ ํŠน๋ณ„ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. + +์ด `SecurityScopes` ํด๋ž˜์Šค๋Š” `Request`์™€ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค(`Request`๋Š” ์š”์ฒญ ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์–ป๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} + +## `scopes` ์‚ฌ์šฉํ•˜๊ธฐ { #use-the-scopes } + +๋งค๊ฐœ๋ณ€์ˆ˜ `security_scopes`์˜ ํƒ€์ž…์€ `SecurityScopes`์ž…๋‹ˆ๋‹ค. + +์—ฌ๊ธฐ์—๋Š” `scopes` ์†์„ฑ์ด ์žˆ์œผ๋ฉฐ, ์ž๊ธฐ ์ž์‹ ๊ณผ ์ด ํ•จ์ˆ˜๋ฅผ ํ•˜์œ„ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ์˜์กด์„ฑ์ด ์š”๊ตฌํ•˜๋Š” ์Šค์ฝ”ํ”„ ์ „์ฒด๋ฅผ ๋‹ด์€ `list`๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์ฆ‰, ๋ชจ๋“  โ€œdependantsโ€... ๋‹ค์†Œ ํ—ท๊ฐˆ๋ฆด ์ˆ˜ ์žˆ๋Š”๋ฐ, ์•„๋ž˜์—์„œ ๋‹ค์‹œ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. + +`security_scopes` ๊ฐ์ฒด(`SecurityScopes` ํด๋ž˜์Šค)์—๋Š” ๋˜ํ•œ `scope_str` ์†์„ฑ์ด ์žˆ๋Š”๋ฐ, ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„๋œ ๋‹จ์ผ ๋ฌธ์ž์—ด๋กœ ์Šค์ฝ”ํ”„๋“ค์„ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค(์ด๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค). + +๋‚˜์ค‘์— ์—ฌ๋Ÿฌ ์ง€์ ์—์„œ ์žฌ์‚ฌ์šฉ(`raise`)ํ•  ์ˆ˜ ์žˆ๋Š” `HTTPException`์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +์ด ์˜ˆ์™ธ์—๋Š” ํ•„์š”ํ•œ ์Šค์ฝ”ํ”„(์žˆ๋‹ค๋ฉด)๋ฅผ ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„๋œ ๋ฌธ์ž์—ด(`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`์„ raiseํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ฅผ ์œ„ํ•ด Pydantic ๋ชจ๋ธ `TokenData`์— ์ƒˆ ์†์„ฑ `scopes`๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. + +Pydantic์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๋ฉด, ์˜ˆ๋ฅผ ๋“ค์–ด ์Šค์ฝ”ํ”„๊ฐ€ ์ •ํ™•ํžˆ `str`์˜ `list`์ด๊ณ  `username`์ด `str`์ธ์ง€ ๋“ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด `dict`๋‚˜ ๋‹ค๋ฅธ ํ˜•ํƒœ๋ผ๋ฉด, ๋‚˜์ค‘์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์–ด๋А ์‹œ์ ์— ๊นจ์ง€๋ฉด์„œ ๋ณด์•ˆ ์œ„ํ—˜์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋˜ํ•œ ํ•ด๋‹น username์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์—†๋‹ค๋ฉด ์•ž์—์„œ ๋งŒ๋“  ๋™์ผํ•œ ์˜ˆ์™ธ๋ฅผ raiseํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} + +## `scopes` ๊ฒ€์ฆํ•˜๊ธฐ { #verify-the-scopes } + +์ด์ œ ์ด ์˜์กด์„ฑ๊ณผ ๋ชจ๋“  dependant( *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ํฌํ•จ)๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋ชจ๋“  ์Šค์ฝ”ํ”„๊ฐ€, ๋ฐ›์€ ํ† ํฐ์˜ ์Šค์ฝ”ํ”„์— ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด `HTTPException`์„ raiseํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ฅผ ์œ„ํ•ด, ๋ชจ๋“  ์Šค์ฝ”ํ”„๋ฅผ `str`๋กœ ๋‹ด๊ณ  ์žˆ๋Š” `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`์˜ ์š”๊ตฌ ์Šค์ฝ”ํ”„ ๋ชฉ๋ก์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ์ž์ฒด๋„ ์Šค์ฝ”ํ”„ `"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`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. + * `SecurityScopes` ํƒ€์ž…์˜ `security_scopes` ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: + * ์ด `security_scopes` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์œ„์—์„œ ์„ ์–ธ๋œ ๋ชจ๋“  ์Šค์ฝ”ํ”„๋ฅผ ๋‹ด์€ `list`์ธ `scopes` ์†์„ฑ์„ ๊ฐ€์ง€๋ฏ€๋กœ: + * *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* `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` ๋ชฉ๋ก์ด ๋‹ฌ๋ผ์ง„๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +์ด๋Š” ํŠน์ • *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ๋Œ€ํ•œ ์˜์กด์„ฑ ํŠธ๋ฆฌ์—์„œ, ๊ฐ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์™€ ๊ฐ ์˜์กด์„ฑ์— ์„ ์–ธ๋œ `scopes`์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. + +/// + +## `SecurityScopes`์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์„ค๋ช… { #more-details-about-securityscopes } + +`SecurityScopes`๋Š” ์–ด๋А ์ง€์ ์—์„œ๋“ , ๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, โ€œ๋ฃจํŠธโ€ ์˜์กด์„ฑ์—๋งŒ ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. + +`SecurityScopes`๋Š” **ํ•ด๋‹น ํŠน์ •** *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์™€ **ํ•ด๋‹น ํŠน์ •** ์˜์กด์„ฑ ํŠธ๋ฆฌ์— ๋Œ€ํ•ด, ํ˜„์žฌ `Security` ์˜์กด์„ฑ๊ณผ ๋ชจ๋“  dependant์— ์„ ์–ธ๋œ ๋ณด์•ˆ ์Šค์ฝ”ํ”„๋ฅผ ํ•ญ์ƒ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +`SecurityScopes`์—๋Š” dependant๊ฐ€ ์„ ์–ธํ•œ ๋ชจ๋“  ์Šค์ฝ”ํ”„๊ฐ€ ํฌํ•จ๋˜๋ฏ€๋กœ, ์ค‘์•™์˜ ์˜์กด์„ฑ ํ•จ์ˆ˜์—์„œ ํ† ํฐ์ด ํ•„์š”ํ•œ ์Šค์ฝ”ํ”„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ๊ฒ€์ฆํ•œ ๋‹ค์Œ, ์„œ๋กœ ๋‹ค๋ฅธ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ์Šค์ฝ”ํ”„ ์š”๊ตฌ์‚ฌํ•ญ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋“ค์€ ๊ฐ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋งˆ๋‹ค ๋…๋ฆฝ์ ์œผ๋กœ ๊ฒ€์‚ฌ๋ฉ๋‹ˆ๋‹ค. + +## ํ™•์ธํ•˜๊ธฐ { #check-it } + +API ๋ฌธ์„œ๋ฅผ ์—ด๋ฉด, ์ธ์ฆํ•˜๊ณ  ์ธ๊ฐ€ํ•  ์Šค์ฝ”ํ”„๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +<img src="/img/tutorial/security/image11.png"> + +์–ด๋–ค ์Šค์ฝ”ํ”„๋„ ์„ ํƒํ•˜์ง€ ์•Š์œผ๋ฉด โ€œ์ธ์ฆโ€์€ ๋˜์ง€๋งŒ, `/users/me/` ๋˜๋Š” `/users/me/items/`์— ์ ‘๊ทผํ•˜๋ ค๊ณ  ํ•˜๋ฉด ๊ถŒํ•œ์ด ์ถฉ๋ถ„ํ•˜์ง€ ์•Š๋‹ค๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. `/status/`์—๋Š” ์—ฌ์ „ํžˆ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์Šค์ฝ”ํ”„ `me`๋Š” ์„ ํƒํ–ˆ์ง€๋งŒ ์Šค์ฝ”ํ”„ `items`๋Š” ์„ ํƒํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, `/users/me/`์—๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ `/users/me/items/`์—๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ–ˆ๋Š”์ง€์— ๋”ฐ๋ผ, ์ œ3์ž ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ œ๊ณต๋ฐ›์€ ํ† ํฐ์œผ๋กœ ์ด *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋“ค ์ค‘ ํ•˜๋‚˜์— ์ ‘๊ทผํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. + +## ์ œ3์ž ํ†ตํ•ฉ์— ๋Œ€ํ•ด { #about-third-party-integrations } + +์ด ์˜ˆ์ œ์—์„œ๋Š” OAuth2 โ€œpasswordโ€ ํ”Œ๋กœ์šฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ๋ณดํ†ต ์ž์ฒด ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ์žˆ๋Š” ์šฐ๋ฆฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋กœ๊ทธ์ธํ•  ๋•Œ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. + +์šฐ๋ฆฌ๊ฐ€ ์ด๋ฅผ ํ†ต์ œํ•˜๋ฏ€๋กœ `username`๊ณผ `password`๋ฅผ ๋ฐ›๋Š” ๊ฒƒ์„ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์—ฐ๊ฒฐํ•  OAuth2 ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์ฆ‰, Facebook, Google, GitHub ๋“ฑ๊ณผ ๋™๋“ฑํ•œ ์ธ์ฆ ์ œ๊ณต์ž๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ๋‹ค๋ฉด)์„ ๊ตฌ์ถ•ํ•œ๋‹ค๋ฉด, ๋‹ค๋ฅธ ํ”Œ๋กœ์šฐ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +๊ฐ€์žฅ ํ”ํ•œ ๊ฒƒ์€ implicit ํ”Œ๋กœ์šฐ์ž…๋‹ˆ๋‹ค. + +๊ฐ€์žฅ ์•ˆ์ „ํ•œ ๊ฒƒ์€ code ํ”Œ๋กœ์šฐ์ด์ง€๋งŒ, ๋” ๋งŽ์€ ๋‹จ๊ณ„๊ฐ€ ํ•„์š”ํ•ด ๊ตฌํ˜„์ด ๋” ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค. ๋ณต์žกํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋งŽ์€ ์ œ๊ณต์ž๋Š” implicit ํ”Œ๋กœ์šฐ๋ฅผ ๊ถŒ์žฅํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +/// note | ์ฐธ๊ณ  + +์ธ์ฆ ์ œ๊ณต์ž๋งˆ๋‹ค ์ž์‹ ๋“ค์˜ ๋ธŒ๋žœ๋“œ์˜ ์ผ๋ถ€๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, ๊ฐ ํ”Œ๋กœ์šฐ๋ฅผ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์ด๋ฆ„ ๋ถ™์ด๋Š” ๊ฒฝ์šฐ๊ฐ€ ํ”ํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๊ฒฐ๊ตญ, ๋™์ผํ•œ OAuth2 ํ‘œ์ค€์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// + +**FastAPI**๋Š” `fastapi.security.oauth2`์— ์ด๋Ÿฌํ•œ ๋ชจ๋“  OAuth2 ์ธ์ฆ ํ”Œ๋กœ์šฐ๋ฅผ ์œ„ํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. + +## ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ `dependencies`์—์„œ์˜ `Security` { #security-in-decorator-dependencies } + +[๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์˜ ์˜์กด์„ฑ](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์˜ `dependencies` ๋งค๊ฐœ๋ณ€์ˆ˜์— `Depends`์˜ `list`๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ, ๊ฑฐ๊ธฐ์—์„œ `scopes`์™€ ํ•จ๊ป˜ `Security`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/advanced/settings.md b/docs/ko/docs/advanced/settings.md new file mode 100644 index 0000000000..6fa7c644cc --- /dev/null +++ b/docs/ko/docs/advanced/settings.md @@ -0,0 +1,302 @@ +# ์„ค์ •๊ณผ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ { #settings-and-environment-variables } + +๋งŽ์€ ๊ฒฝ์šฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ์™ธ๋ถ€ ์„ค์ •์ด๋‚˜ ๊ตฌ์„ฑ(์˜ˆ: secret key, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž๊ฒฉ ์ฆ๋ช…, ์ด๋ฉ”์ผ ์„œ๋น„์Šค ์ž๊ฒฉ ์ฆ๋ช… ๋“ฑ)์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Ÿฌํ•œ ์„ค์ • ๋Œ€๋ถ€๋ถ„์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค URL์ฒ˜๋Ÿผ ๋ณ€๋™ ๊ฐ€๋Šฅ(๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Œ)ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งŽ์€ ์„ค์ •์€ secret์ฒ˜๋Ÿผ ๋ฏผ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๋•Œ๋ฌธ์— ๋ณดํ†ต ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ฝ์–ด๋“ค์ด๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์ด๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค. + +/// tip | ํŒ + +ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ดํ•ดํ•˜๋ ค๋ฉด [ํ™˜๊ฒฝ ๋ณ€์ˆ˜](../environment-variables.md){.internal-link target=_blank}๋ฅผ ์ฝ์–ด๋ณด์„ธ์š”. + +/// + +## ํƒ€์ž…๊ณผ ๊ฒ€์ฆ { #types-and-validation } + +์ด ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋“ค์€ Python ์™ธ๋ถ€์— ์žˆ์œผ๋ฉฐ ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋žจ ๋ฐ ์‹œ์Šคํ…œ์˜ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„(๊ทธ๋ฆฌ๊ณ  Linux, Windows, macOS ๊ฐ™์€ ์„œ๋กœ ๋‹ค๋ฅธ ์šด์˜์ฒด์ œ์™€๋„)๊ณผ ํ˜ธํ™˜๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ, ํ…์ŠคํŠธ ๋ฌธ์ž์—ด๋งŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ฆ‰, Python์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ๋ถ€ํ„ฐ ์ฝ์–ด์˜จ ์–ด๋–ค ๊ฐ’์ด๋“  `str`์ด ๋˜๋ฉฐ, ๋‹ค๋ฅธ ํƒ€์ž…์œผ๋กœ์˜ ๋ณ€ํ™˜์ด๋‚˜ ๊ฒ€์ฆ์€ ์ฝ”๋“œ์—์„œ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +## Pydantic `Settings` { #pydantic-settings } + +๋‹คํ–‰ํžˆ Pydantic์€ <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>๋ฅผ ํ†ตํ•ด ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ ์˜ค๋Š” ์ด๋Ÿฌํ•œ ์„ค์ •์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ํ›Œ๋ฅญํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +### `pydantic-settings` ์„ค์น˜ํ•˜๊ธฐ { #install-pydantic-settings } + +๋จผ์ € [๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ๋งŒ๋“ค๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, `pydantic-settings` ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”: + +<div class="termy"> + +```console +$ pip install pydantic-settings +---> 100% +``` + +</div> + +๋˜๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ `all` extras๋ฅผ ์„ค์น˜ํ•˜๋ฉด ํ•จ๊ป˜ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค: + +<div class="termy"> + +```console +$ pip install "fastapi[all]" +---> 100% +``` + +</div> + +### `Settings` ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ { #create-the-settings-object } + +Pydantic์—์„œ `BaseSettings`๋ฅผ importํ•˜๊ณ , Pydantic ๋ชจ๋ธ๊ณผ ๋งค์šฐ ๋น„์Šทํ•˜๊ฒŒ ์„œ๋ธŒํด๋ž˜์Šค๋ฅผ ๋งŒ๋“œ์„ธ์š”. + +Pydantic ๋ชจ๋ธ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ, ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜(๊ทธ๋ฆฌ๊ณ  ํ•„์š”ํ•˜๋‹ค๋ฉด ๊ธฐ๋ณธ๊ฐ’)๊ณผ ํ•จ๊ป˜ ํด๋ž˜์Šค ์†์„ฑ์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. + +๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž…, `Field()`๋กœ ์ถ”๊ฐ€ ๊ฒ€์ฆ ๋“ฑ Pydantic ๋ชจ๋ธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋™์ผํ•œ ๊ฒ€์ฆ ๊ธฐ๋Šฅ๊ณผ ๋„๊ตฌ๋ฅผ ๋ชจ๋‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/settings/tutorial001_py39.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_py39.py hl[18:20] *} + +### ์„œ๋ฒ„ ์‹คํ–‰ํ•˜๊ธฐ { #run-the-server } + +๋‹ค์Œ์œผ๋กœ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ๊ตฌ์„ฑ์„ ์ „๋‹ฌํ•˜๋ฉด์„œ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์ฒ˜๋Ÿผ `ADMIN_EMAIL`๊ณผ `APP_NAME`์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<div class="termy"> + +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py + +<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +</div> + +/// tip | ํŒ + +ํ•˜๋‚˜์˜ ๋ช…๋ น์— ์—ฌ๋Ÿฌ env var๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„ํ•˜๊ณ , ๋ชจ๋‘ ๋ช…๋ น ์•ž์— ๋‘์„ธ์š”. + +/// + +๊ทธ๋Ÿฌ๋ฉด `admin_email` ์„ค์ •์€ `"deadpool@example.com"`์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. + +`app_name`์€ `"ChimichangApp"`์ด ๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  `items_per_user`๋Š” ๊ธฐ๋ณธ๊ฐ’ `50`์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. + +## ๋‹ค๋ฅธ ๋ชจ๋“ˆ์˜ ์„ค์ • { #settings-in-another-module } + +[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}์—์„œ ๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ, ์„ค์ •์„ ๋‹ค๋ฅธ ๋ชจ๋“ˆ ํŒŒ์ผ์— ๋„ฃ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด `config.py` ํŒŒ์ผ์„ ๋‹ค์Œ์ฒ˜๋Ÿผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/settings/app01_py39/config.py *} + +๊ทธ๋ฆฌ๊ณ  `main.py` ํŒŒ์ผ์—์„œ ์ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} + +/// tip | ํŒ + +[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}์—์„œ ๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ `__init__.py` ํŒŒ์ผ๋„ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +/// + +## ์˜์กด์„ฑ์—์„œ ์„ค์ • ์‚ฌ์šฉํ•˜๊ธฐ { #settings-in-a-dependency } + +์–ด๋–ค ๊ฒฝ์šฐ์—๋Š” ์–ด๋””์„œ๋‚˜ ์‚ฌ์šฉ๋˜๋Š” ์ „์—ญ `settings` ๊ฐ์ฒด๋ฅผ ๋‘๋Š” ๋Œ€์‹ , ์˜์กด์„ฑ์—์„œ ์„ค์ •์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ํŠนํžˆ ํ…Œ์ŠคํŠธ ์ค‘์— ์œ ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์‚ฌ์šฉ์ž ์ •์˜ ์„ค์ •์œผ๋กœ ์˜์กด์„ฑ์„ overrideํ•˜๊ธฐ๊ฐ€ ๋งค์šฐ ์‰ฝ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +### config ํŒŒ์ผ { #the-config-file } + +์ด์ „ ์˜ˆ์‹œ์—์„œ ์ด์–ด์„œ, `config.py` ํŒŒ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} + +์ด์ œ๋Š” ๊ธฐ๋ณธ ์ธ์Šคํ„ด์Šค `settings = Settings()`๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. + +### ๋ฉ”์ธ ์•ฑ ํŒŒ์ผ { #the-main-app-file } + +์ด์ œ ์ƒˆ๋กœ์šด `config.Settings()`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์˜์กด์„ฑ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} + +/// tip | ํŒ + +`@lru_cache`๋Š” ์กฐ๊ธˆ ๋’ค์— ๋‹ค๋ฃน๋‹ˆ๋‹ค. + +์ง€๊ธˆ์€ `get_settings()`๊ฐ€ ์ผ๋ฐ˜ ํ•จ์ˆ˜๋ผ๊ณ  ๊ฐ€์ •ํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค. + +/// + +๊ทธ ๋‹ค์Œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ์ด๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์š”๊ตฌํ•˜๊ณ , ํ•„์š”ํ•œ ์–ด๋””์„œ๋“  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} + +### ์„ค์ •๊ณผ ํ…Œ์ŠคํŠธ { #settings-and-testing } + +๊ทธ ๋‹ค์Œ, `get_settings`์— ๋Œ€ํ•œ ์˜์กด์„ฑ override๋ฅผ ๋งŒ๋“ค์–ด ํ…Œ์ŠคํŠธ ์ค‘์— ๋‹ค๋ฅธ ์„ค์ • ๊ฐ์ฒด๋ฅผ ์ œ๊ณตํ•˜๊ธฐ๊ฐ€ ๋งค์šฐ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค: + +{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} + +์˜์กด์„ฑ override์—์„œ๋Š” ์ƒˆ `Settings` ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ `admin_email`์˜ ์ƒˆ ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ , ๊ทธ ์ƒˆ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ ๊ทธ๊ฒƒ์ด ์‚ฌ์šฉ๋˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## `.env` ํŒŒ์ผ ์ฝ๊ธฐ { #reading-a-env-file } + +๋งŽ์ด ๋ฐ”๋€” ์ˆ˜ ์žˆ๋Š” ์„ค์ •์ด ๋งŽ๊ณ , ์„œ๋กœ ๋‹ค๋ฅธ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์ด๋ฅผ ํŒŒ์ผ์— ๋„ฃ์–ด ํ™˜๊ฒฝ ๋ณ€์ˆ˜์ธ ๊ฒƒ์ฒ˜๋Ÿผ ์ฝ๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๊ด€ํ–‰์€ ์ถฉ๋ถ„ํžˆ ํ”ํ•ด์„œ ์ด๋ฆ„๋„ ์žˆ๋Š”๋ฐ, ์ด๋Ÿฌํ•œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋“ค์€ ๋ณดํ†ต `.env` ํŒŒ์ผ์— ๋‘๋ฉฐ, ๊ทธ ํŒŒ์ผ์„ "dotenv"๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์ (`.`)์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํŒŒ์ผ์€ Linux, macOS ๊ฐ™์€ Unix ๊ณ„์—ด ์‹œ์Šคํ…œ์—์„œ ์ˆจ๊น€ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ dotenv ํŒŒ์ผ์ด ๊ผญ ๊ทธ ์ •ํ™•ํ•œ ํŒŒ์ผ๋ช…์„ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. + +/// + +Pydantic์€ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ์ด๋Ÿฐ ์œ ํ˜•์˜ ํŒŒ์ผ์—์„œ ์ฝ๋Š” ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +/// 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_py39/config.py hl[9] *} + +/// tip | ํŒ + +`model_config` ์†์„ฑ์€ Pydantic ์„ค์ •์„ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Concepts: Configuration</a>์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. + +/// + +์—ฌ๊ธฐ์„œ๋Š” Pydantic `Settings` ํด๋ž˜์Šค ์•ˆ์— config `env_file`์„ ์ •์˜ํ•˜๊ณ , ์‚ฌ์šฉํ•˜๋ ค๋Š” dotenv ํŒŒ์ผ์˜ ํŒŒ์ผ๋ช…์„ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. + +### `lru_cache`๋กœ `Settings`๋ฅผ ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑํ•˜๊ธฐ { #creating-the-settings-only-once-with-lru-cache } + +๋””์Šคํฌ์—์„œ ํŒŒ์ผ์„ ์ฝ๋Š” ๊ฒƒ์€ ๋ณดํ†ต ๋น„์šฉ์ด ํฐ(๋А๋ฆฐ) ์ž‘์—…์ด๋ฏ€๋กœ, ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ์ฝ๊ธฐ๋ณด๋‹ค๋Š” ํ•œ ๋ฒˆ๋งŒ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋™์ผํ•œ settings ๊ฐ์ฒด๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋งค๋ฒˆ ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜๋ฉด: + +```Python +Settings() +``` + +์ƒˆ `Settings` ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ์ƒ์„ฑ ์‹œ์ ์— `.env` ํŒŒ์ผ์„ ๋‹ค์‹œ ์ฝ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +์˜์กด์„ฑ ํ•จ์ˆ˜๊ฐ€ ๋‹จ์ˆœํžˆ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค๋ฉด: + +```Python +def get_settings(): + return Settings() +``` + +์š”์ฒญ๋งˆ๋‹ค ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋˜๊ณ , ์š”์ฒญ๋งˆ๋‹ค `.env` ํŒŒ์ผ์„ ์ฝ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. โš ๏ธ + +ํ•˜์ง€๋งŒ ์œ„์— `@lru_cache` ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, `Settings` ๊ฐ์ฒด๋Š” ์ตœ์ดˆ ํ˜ธ์ถœ ์‹œ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. โœ”๏ธ + +{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} + +๊ทธ ๋‹ค์Œ ์š”์ฒญ๋“ค์—์„œ ์˜์กด์„ฑ์œผ๋กœ `get_settings()`๊ฐ€ ๋‹ค์‹œ ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค, `get_settings()`์˜ ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด์„œ ์ƒˆ `Settings` ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋Š” ๋Œ€์‹ , ์ฒซ ํ˜ธ์ถœ์—์„œ ๋ฐ˜ํ™˜๋œ ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ ๊ณ„์† ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +#### `lru_cache` Technical Details { #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()`์˜ ๊ฒฝ์šฐ, ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ค ์ธ์ž๋„ ๋ฐ›์ง€ ์•Š์œผ๋ฏ€๋กœ ํ•ญ์ƒ ๊ฐ™์€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฑฐ์˜ ์ „์—ญ ๋ณ€์ˆ˜์ฒ˜๋Ÿผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜์กด์„ฑ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์‰ฝ๊ฒŒ overrideํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +`@lru_cache`๋Š” Python ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ `functools`์— ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ, ์ž์„ธํ•œ ๋‚ด์šฉ์€ <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">`@lru_cache`์— ๋Œ€ํ•œ Python ๋ฌธ์„œ</a>์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ์ •๋ฆฌ { #recap } + +Pydantic Settings๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Pydantic ๋ชจ๋ธ์˜ ๋ชจ๋“  ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ค์ • ๋˜๋Š” ๊ตฌ์„ฑ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +* ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* `.env` ํŒŒ์ผ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* `@lru_cache`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ์š”์ฒญ๋งˆ๋‹ค dotenv ํŒŒ์ผ์„ ๋ฐ˜๋ณตํ•ด์„œ ์ฝ๋Š” ๊ฒƒ์„ ํ”ผํ•˜๋ฉด์„œ๋„, ํ…Œ์ŠคํŠธ ์ค‘์—๋Š” ์ด๋ฅผ overrideํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/alternatives.md b/docs/ko/docs/alternatives.md new file mode 100644 index 0000000000..d8c2df2d8d --- /dev/null +++ b/docs/ko/docs/alternatives.md @@ -0,0 +1,485 @@ +# ๋Œ€์•ˆ, ์˜๊ฐ, ๋น„๊ต { #alternatives-inspiration-and-comparisons } + +**FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ๋“ค, ๋Œ€์•ˆ๊ณผ์˜ ๋น„๊ต, ๊ทธ๋ฆฌ๊ณ  ๊ทธ๋กœ๋ถ€ํ„ฐ ๋ฌด์—‡์„ ๋ฐฐ์› ๋Š”์ง€์— ๋Œ€ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. + +## ์†Œ๊ฐœ { #intro } + +๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์ด์ „ ์ž‘์—…์ด ์—†์—ˆ๋‹ค๋ฉด **FastAPI**๋Š” ์กด์žฌํ•˜์ง€ ์•Š์•˜์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๊ทธ ์ „์— ๋งŒ๋“ค์–ด์ง„ ๋งŽ์€ ๋„๊ตฌ๋“ค์ด **FastAPI**์˜ ํƒ„์ƒ์— ์˜๊ฐ์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. + +์ €๋Š” ์—ฌ๋Ÿฌ ํ•ด ๋™์•ˆ ์ƒˆ๋กœ์šด framework๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ํ”ผํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋จผ์ € **FastAPI**๊ฐ€ ๋‹ค๋ฃจ๋Š” ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์—ฌ๋Ÿฌ ์„œ๋กœ ๋‹ค๋ฅธ framework, plug-in, ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๊ฒฐํ•ด ๋ณด๋ ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์–ด๋А ์‹œ์ ์—๋Š”, ์ด์ „ ๋„๊ตฌ๋“ค์˜ ๊ฐ€์žฅ ์ข‹์€ ์•„์ด๋””์–ด๋ฅผ ๊ฐ€์ ธ์™€ ๊ฐ€๋Šฅํ•œ ์ตœ์„ ์˜ ๋ฐฉ์‹์œผ๋กœ ์กฐํ•ฉํ•˜๊ณ , ์ด์ „์—๋Š” ์กด์žฌํ•˜์ง€ ์•Š์•˜๋˜ ์–ธ์–ด ๊ธฐ๋Šฅ(Python 3.6+ type hints)์„ ํ™œ์šฉํ•ด ์ด ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ ์™ธ์—๋Š” ๋‹ค๋ฅธ ์„ ํƒ์ง€๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. + +## ์ด์ „ ๋„๊ตฌ๋“ค { #previous-tools } + +### <a href="https://www.djangoproject.com/" class="external-link" target="_blank">Django</a> { #django } + +๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” Python framework์ด๋ฉฐ ๋„๋ฆฌ ์‹ ๋ขฐ๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Instagram ๊ฐ™์€ ์‹œ์Šคํ…œ์„ ๋งŒ๋“œ๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +์ƒ๋Œ€์ ์œผ๋กœ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(์˜ˆ: MySQL ๋˜๋Š” PostgreSQL)์™€ ๊ฐ•ํ•˜๊ฒŒ ๊ฒฐํ•ฉ๋˜์–ด ์žˆ์–ด์„œ, NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(์˜ˆ: Couchbase, MongoDB, Cassandra ๋“ฑ)๋ฅผ ์ฃผ ์ €์žฅ ์—”์ง„์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ทธ๋ฆฌ ์‰ฝ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +๋ฐฑ์—”๋“œ์—์„œ HTML์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์ง€, ํ˜„๋Œ€์ ์ธ ํ”„๋ŸฐํŠธ์—”๋“œ(์˜ˆ: React, Vue.js, Angular)๋‚˜ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ(์˜ˆ: <abbr title="Internet of Things - ์‚ฌ๋ฌผ ์ธํ„ฐ๋„ท">IoT</abbr> ๊ธฐ๊ธฐ)์—์„œ ์‚ฌ์šฉ๋˜๋Š” API๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋œ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. + +### <a href="https://www.django-rest-framework.org/" class="external-link" target="_blank">Django REST Framework</a> { #django-rest-framework } + +Django REST framework๋Š” Django๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Web API๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ์œ ์—ฐํ•œ toolkit์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ๊ณ , Django์˜ API ๊ธฐ๋Šฅ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. + +Mozilla, Red Hat, Eventbrite๋ฅผ ํฌํ•จํ•ด ๋งŽ์€ ํšŒ์‚ฌ์—์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +**์ž๋™ API ๋ฌธ์„œํ™”**์˜ ์ดˆ๊ธฐ ์‚ฌ๋ก€ ์ค‘ ํ•˜๋‚˜์˜€๊ณ , ์ด๊ฒƒ์ด ํŠนํžˆ **FastAPI**๋ฅผ "์ฐพ๊ฒŒ ๋œ" ์ฒซ ์•„์ด๋””์–ด ์ค‘ ํ•˜๋‚˜์˜€์Šต๋‹ˆ๋‹ค. + +/// note | ์ฐธ๊ณ  + +Django REST Framework๋Š” Tom Christie๊ฐ€ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. **FastAPI**์˜ ๊ธฐ๋ฐ˜์ด ๋˜๋Š” Starlette์™€ Uvicorn์„ ๋งŒ๋“  ์‚ฌ๋žŒ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. + +/// + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +์ž๋™ API ๋ฌธ์„œํ™” ์›น ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๊ธฐ. + +/// + +### <a href="https://flask.palletsprojects.com" class="external-link" target="_blank">Flask</a> { #flask } + +Flask๋Š” "microframework"๋กœ, Django์— ๊ธฐ๋ณธ์œผ๋กœ ํฌํ•จ๋œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ†ตํ•ฉ์ด๋‚˜ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ๋“ค์„ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +์ด ๋‹จ์ˆœํ•จ๊ณผ ์œ ์—ฐ์„ฑ ๋•๋ถ„์— NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ฃผ ๋ฐ์ดํ„ฐ ์ €์žฅ ์‹œ์Šคํ…œ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ™์€ ์ž‘์—…์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. + +๋งค์šฐ ๋‹จ์ˆœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋น„๊ต์  ์ง๊ด€์ ์œผ๋กœ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ฌธ์„œ๊ฐ€ ์–ด๋–ค ์ง€์ ์—์„œ๋Š” ๋‹ค์†Œ ๊ธฐ์ˆ ์ ์œผ๋กœ ๊นŠ์–ด์ง€๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. + +๋˜ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ, ํ˜น์€ Django์— ๋ฏธ๋ฆฌ ๊ตฌ์ถ•๋˜์–ด ์žˆ๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ๋“ค์ด ๊ผญ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋„ ํ”ํžˆ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์ด๋Ÿฐ ๊ธฐ๋Šฅ๋“ค ์ค‘ ๋‹ค์ˆ˜๋Š” plug-in์œผ๋กœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Ÿฐ ๊ตฌ์„ฑ์š”์†Œ์˜ ๋ถ„๋ฆฌ์™€, ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์ •ํ™•ํžˆ ๋ง๋ถ™์—ฌ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋Š” "microframework"๋ผ๋Š” ์ ์€ ์ œ๊ฐ€ ์œ ์ง€ํ•˜๊ณ  ์‹ถ์—ˆ๋˜ ํ•ต์‹ฌ ํŠน์„ฑ์ด์—ˆ์Šต๋‹ˆ๋‹ค. + +Flask์˜ ๋‹จ์ˆœํ•จ์„ ๊ณ ๋ คํ•˜๋ฉด API๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐ ์ž˜ ๋งž๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์˜€์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์œผ๋กœ ์ฐพ๊ณ ์ž ํ–ˆ๋˜ ๊ฒƒ์€ Flask์šฉ "Django REST Framework"์˜€์Šต๋‹ˆ๋‹ค. + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +micro-framework๊ฐ€ ๋˜๊ธฐ. ํ•„์š”ํ•œ ๋„๊ตฌ์™€ ๊ตฌ์„ฑ์š”์†Œ๋ฅผ ์‰ฝ๊ฒŒ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ. + +๋‹จ์ˆœํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด routing ์‹œ์Šคํ…œ์„ ๊ฐ–๊ธฐ. + +/// + +### <a href="https://requests.readthedocs.io" class="external-link" target="_blank">Requests</a> { #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 *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```Python hl_lines="1" +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +`requests.get(...)`์™€ `@app.get(...)`์˜ ์œ ์‚ฌ์„ฑ์„ ํ™•์ธํ•ด ๋ณด์„ธ์š”. + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +* ๋‹จ์ˆœํ•˜๊ณ  ์ง๊ด€์ ์ธ API๋ฅผ ๊ฐ–๊ธฐ. +* HTTP method ์ด๋ฆ„(operations)์„ ์ง์ ‘, ์ง๊ด€์ ์ด๊ณ  ๋ช…ํ™•ํ•œ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ. +* ํ•ฉ๋ฆฌ์ ์ธ ๊ธฐ๋ณธ๊ฐ’์„ ์ œ๊ณตํ•˜๋˜, ๊ฐ•๋ ฅํ•œ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ. + +/// + +### <a href="https://swagger.io/" class="external-link" target="_blank">Swagger</a> / <a href="https://github.com/OAI/OpenAPI-Specification/" class="external-link" target="_blank">OpenAPI</a> { #swagger-openapi } + +์ œ๊ฐ€ Django REST Framework์—์„œ ๊ฐ€์žฅ ์›ํ–ˆ๋˜ ์ฃผ์š” ๊ธฐ๋Šฅ์€ ์ž๋™ API ๋ฌธ์„œํ™”์˜€์Šต๋‹ˆ๋‹ค. + +๊ทธ ํ›„ JSON(๋˜๋Š” JSON์˜ ํ™•์žฅ์ธ YAML)์„ ์‚ฌ์šฉํ•ด API๋ฅผ ๋ฌธ์„œํ™”ํ•˜๋Š” ํ‘œ์ค€์ธ Swagger๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  Swagger API๋ฅผ ์œ„ํ•œ ์›น ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋„ ์ด๋ฏธ ๋งŒ๋“ค์–ด์ ธ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ API์— ๋Œ€ํ•œ Swagger ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์ด ์›น ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ž๋™์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +์–ด๋А ์‹œ์ ์— Swagger๋Š” Linux Foundation์œผ๋กœ ๋„˜์–ด๊ฐ€ OpenAPI๋กœ ์ด๋ฆ„์ด ๋ฐ”๋€Œ์—ˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ž˜์„œ 2.0 ๋ฒ„์ „์„ ์ด์•ผ๊ธฐํ•  ๋•Œ๋Š” "Swagger"๋ผ๊ณ  ๋งํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๊ณ , 3+ ๋ฒ„์ „์€ "OpenAPI"๋ผ๊ณ  ๋งํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค. + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +์ปค์Šคํ…€ schema ๋Œ€์‹ , API ์‚ฌ์–‘์„ ์œ„ํ•œ ์—ด๋ฆฐ ํ‘œ์ค€์„ ์ฑ„ํƒํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ. + +๋˜ํ•œ ํ‘œ์ค€ ๊ธฐ๋ฐ˜์˜ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค ๋„๊ตฌ๋ฅผ ํ†ตํ•ฉํ•˜๊ธฐ: + +* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> +* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> + +์ด ๋‘ ๊ฐ€์ง€๋Š” ๊ฝค ๋Œ€์ค‘์ ์ด๊ณ  ์•ˆ์ •์ ์ด๊ธฐ ๋•Œ๋ฌธ์— ์„ ํƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ„๋‹จํžˆ ๊ฒ€์ƒ‰ํ•ด๋ณด๋ฉด OpenAPI๋ฅผ ์œ„ํ•œ ๋Œ€์•ˆ UI๊ฐ€ ์ˆ˜์‹ญ ๊ฐ€์ง€๋‚˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(**FastAPI**์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). + +/// + +### Flask REST framework๋“ค { #flask-rest-frameworks } + +Flask REST framework๋Š” ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ์žˆ์ง€๋งŒ, ์‹œ๊ฐ„์„ ๋“ค์—ฌ ์กฐ์‚ฌํ•ด ๋ณธ ๊ฒฐ๊ณผ, ์ƒ๋‹น์ˆ˜๊ฐ€ ์ค‘๋‹จ๋˜์—ˆ๊ฑฐ๋‚˜ ๋ฐฉ์น˜๋˜์–ด ์žˆ์—ˆ๊ณ , ํ•ด๊ฒฐ๋˜์ง€ ์•Š์€ ์—ฌ๋Ÿฌ ์ด์Šˆ ๋•Œ๋ฌธ์— ์ ํ•ฉํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜์Šต๋‹ˆ๋‹ค. + +### <a href="https://marshmallow.readthedocs.io/en/stable/" class="external-link" target="_blank">Marshmallow</a> { #marshmallow } + +API ์‹œ์Šคํ…œ์— ํ•„์š”ํ•œ ์ฃผ์š” ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๋Š” ๋ฐ์ดํ„ฐ "<abbr title="also called marshalling, conversion - ๋งˆ์ƒฌ๋ง, ๋ณ€ํ™˜์ด๋ผ๊ณ ๋„ ํ•ฉ๋‹ˆ๋‹ค">serialization</abbr>"์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์ฝ”๋“œ(Python)์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ๋„คํŠธ์›Œํฌ๋กœ ์ „์†กํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์€ ๊ฐ์ฒด๋ฅผ JSON ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜, `datetime` ๊ฐ์ฒด๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์ž…๋‹ˆ๋‹ค. + +API์— ๋˜ ํ•˜๋‚˜ ํฌ๊ฒŒ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์€ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ์ž…๋‹ˆ๋‹ค. ํŠน์ • ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•œ์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์–ด๋–ค ํ•„๋“œ๊ฐ€ `int`์ธ์ง€, ์ž„์˜์˜ ๋ฌธ์ž์—ด์ด ์•„๋‹Œ์ง€ ํ™•์ธํ•˜๋Š” ์‹์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํŠนํžˆ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. + +๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ์‹œ์Šคํ…œ์ด ์—†๋‹ค๋ฉด, ๋ชจ๋“  ๊ฒ€์‚ฌ๋ฅผ ์ฝ”๋“œ์—์„œ ์ˆ˜๋™์œผ๋กœ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +์ด๋Ÿฐ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด Marshmallow๊ฐ€ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ํ›Œ๋ฅญํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋ฉฐ, ์ €๋„ ์ด์ „์— ๋งŽ์ด ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ Python type hints๊ฐ€ ์กด์žฌํ•˜๊ธฐ ์ „์— ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐ <abbr title="the definition of how data should be formed - ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ์ •์˜">schema</abbr>๋ฅผ ์ •์˜ํ•˜๋ ค๋ฉด Marshmallow๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํŠน์ • ์œ ํ‹ธ๋ฆฌํ‹ฐ์™€ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +๋ฐ์ดํ„ฐ ํƒ€์ž…๊ณผ ๊ฒ€์ฆ์„ ์ œ๊ณตํ•˜๋Š” "schema"๋ฅผ ์ฝ”๋“œ๋กœ ์ •์˜ํ•˜๊ณ , ์ด๋ฅผ ์ž๋™์œผ๋กœ ํ™œ์šฉํ•˜๊ธฐ. + +/// + +### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a> { #webargs } + +API์— ํ•„์š”ํ•œ ๋˜ ๋‹ค๋ฅธ ํฐ ๊ธฐ๋Šฅ์€ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ <abbr title="reading and converting to Python data - ์ฝ์–ด์„œ Python ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ">parsing</abbr>ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +Webargs๋Š” Flask๋ฅผ ํฌํ•จํ•œ ์—ฌ๋Ÿฌ framework ์œ„์—์„œ ์ด๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. + +๋‚ด๋ถ€์ ์œผ๋กœ Marshmallow๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ™์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. + +์•„์ฃผ ํ›Œ๋ฅญํ•œ ๋„๊ตฌ์ด๋ฉฐ, ์ €๋„ **FastAPI**๋ฅผ ๋งŒ๋“ค๊ธฐ ์ „์— ๋งŽ์ด ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +Webargs๋Š” Marshmallow์™€ ๊ฐ™์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. + +/// + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +๋“ค์–ด์˜ค๋Š” ์š”์ฒญ ๋ฐ์ดํ„ฐ์˜ ์ž๋™ ๊ฒ€์ฆ์„ ๊ฐ–๊ธฐ. + +/// + +### <a href="https://apispec.readthedocs.io/en/stable/" class="external-link" target="_blank">APISpec</a> { #apispec } + +Marshmallow์™€ Webargs๋Š” plug-in ํ˜•ํƒœ๋กœ ๊ฒ€์ฆ, parsing, serialization์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋ฌธ์„œํ™”๋Š” ์—ฌ์ „ํžˆ ๋ถ€์กฑํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ APISpec์ด ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ์—ฌ๋Ÿฌ framework๋ฅผ ์œ„ํ•œ plug-in์ด๋ฉฐ(Starlette์šฉ plug-in๋„ ์žˆ์Šต๋‹ˆ๋‹ค). + +์ž‘๋™ ๋ฐฉ์‹์€, ๊ฐ route๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜์˜ docstring ์•ˆ์— YAML ํ˜•์‹์œผ๋กœ schema ์ •์˜๋ฅผ ์ž‘์„ฑํ•˜๊ณ , + +๊ทธ๋กœ๋ถ€ํ„ฐ OpenAPI schema๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +Flask, Starlette, Responder ๋“ฑ์—์„œ ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋‹ค์‹œ, Python ๋ฌธ์ž์—ด ๋‚ด๋ถ€(ํฐ YAML)์—์„œ micro-syntax๋ฅผ ๋‹ค๋ฃจ์–ด์•ผ ํ•œ๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +์—๋””ํ„ฐ๊ฐ€ ์ด๋ฅผ ํฌ๊ฒŒ ๋„์™€์ฃผ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋‚˜ Marshmallow schema๋ฅผ ์ˆ˜์ •ํ•ด๋†“๊ณ  YAML docstring๋„ ๊ฐ™์ด ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์„ ์žŠ์–ด๋ฒ„๋ฆฌ๋ฉด, ์ƒ์„ฑ๋œ schema๋Š” ์˜ค๋ž˜๋œ ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +APISpec์€ Marshmallow์™€ ๊ฐ™์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. + +/// + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +API๋ฅผ ์œ„ํ•œ ์—ด๋ฆฐ ํ‘œ์ค€์ธ OpenAPI๋ฅผ ์ง€์›ํ•˜๊ธฐ. + +/// + +### <a href="https://flask-apispec.readthedocs.io/en/latest/" class="external-link" target="_blank">Flask-apispec</a> { #flask-apispec } + +Flask plug-in์œผ๋กœ, Webargs, Marshmallow, APISpec์„ ๋ฌถ์–ด์ค๋‹ˆ๋‹ค. + +Webargs์™€ Marshmallow์˜ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•ด APISpec์œผ๋กœ OpenAPI schema๋ฅผ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +ํ›Œ๋ฅญํ•œ ๋„๊ตฌ์ธ๋ฐ๋„ ๊ณผ์†Œํ‰๊ฐ€๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋งŽ์€ Flask plug-in๋ณด๋‹ค ํ›จ์”ฌ ๋” ์œ ๋ช…ํ•ด์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๊ฐ€ ๋„ˆ๋ฌด ๊ฐ„๊ฒฐํ•˜๊ณ  ์ถ”์ƒ์ ์ด๋ผ์„œ ๊ทธ๋Ÿด ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๋„๊ตฌ๋Š” Python docstring ๋‚ด๋ถ€์— YAML(๋˜ ๋‹ค๋ฅธ ๋ฌธ๋ฒ•)์„ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. + +Flask + Flask-apispec + Marshmallow + Webargs ์กฐํ•ฉ์€ **FastAPI**๋ฅผ ๋งŒ๋“ค๊ธฐ ์ „๊นŒ์ง€ ์ œ๊ฐ€ ๊ฐ€์žฅ ์ข‹์•„ํ•˜๋˜ ๋ฐฑ์—”๋“œ stack์ด์—ˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์—ฌ๋Ÿฌ Flask full-stack generator๊ฐ€ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ๋“ค์ด ์ง€๊ธˆ๊นŒ์ง€ ์ €(๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ ์™ธ๋ถ€ ํŒ€)๊ฐ€ ์‚ฌ์šฉํ•ด ์˜จ ์ฃผ์š” stack์ž…๋‹ˆ๋‹ค: + +* <a href="https://github.com/tiangolo/full-stack" class="external-link" target="_blank">https://github.com/tiangolo/full-stack</a> +* <a href="https://github.com/tiangolo/full-stack-flask-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchbase</a> +* <a href="https://github.com/tiangolo/full-stack-flask-couchdb" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchdb</a> + +๊ทธ๋ฆฌ๊ณ  ์ด ๋™์ผํ•œ full-stack generator๋“ค์ด [**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank}์˜ ๊ธฐ๋ฐ˜์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +Flask-apispec์€ Marshmallow์™€ ๊ฐ™์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. + +/// + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +serialization๊ณผ validation์„ ์ •์˜ํ•˜๋Š” ๋™์ผํ•œ ์ฝ”๋“œ๋กœ๋ถ€ํ„ฐ OpenAPI schema๋ฅผ ์ž๋™ ์ƒ์„ฑํ•˜๊ธฐ. + +/// + +### <a href="https://nestjs.com/" class="external-link" target="_blank">NestJS</a> (๊ทธ๋ฆฌ๊ณ  <a href="https://angular.io/" class="external-link" target="_blank">Angular</a>) { #nestjs-and-angular } + +์ด๊ฑด Python๋„ ์•„๋‹™๋‹ˆ๋‹ค. NestJS๋Š” Angular์—์„œ ์˜๊ฐ์„ ๋ฐ›์€ JavaScript(TypeScript) NodeJS framework์ž…๋‹ˆ๋‹ค. + +Flask-apispec์œผ๋กœ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๊ณผ ์–ด๋А ์ •๋„ ๋น„์Šทํ•œ ๊ฒƒ์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +Angular 2์—์„œ ์˜๊ฐ์„ ๋ฐ›์€ ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์ด ํ†ตํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ๊ฐ€ ์•„๋Š” ๋‹ค๋ฅธ ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ๋“ค์ฒ˜๋Ÿผ "injectable"์„ ์‚ฌ์ „์— ๋“ฑ๋กํ•ด์•ผ ํ•˜๋ฏ€๋กœ, ์žฅํ™ฉํ•จ๊ณผ ์ฝ”๋“œ ๋ฐ˜๋ณต์ด ๋Š˜์–ด๋‚ฉ๋‹ˆ๋‹ค. + +ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ TypeScript ํƒ€์ž…(Python type hints์™€ ์œ ์‚ฌํ•จ)์œผ๋กœ ์„ค๋ช…๋˜๊ธฐ ๋•Œ๋ฌธ์— ์—๋””ํ„ฐ ์ง€์›์€ ๊ฝค ์ข‹์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ TypeScript ๋ฐ์ดํ„ฐ๋Š” JavaScript๋กœ ์ปดํŒŒ์ผ๋œ ๋’ค์—๋Š” ๋ณด์กด๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ํƒ€์ž…์— ์˜์กดํ•ด ๊ฒ€์ฆ, serialization, ๋ฌธ์„œํ™”๋ฅผ ๋™์‹œ์— ์ •์˜ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์ ๊ณผ ์ผ๋ถ€ ์„ค๊ณ„ ๊ฒฐ์ • ๋•Œ๋ฌธ์—, ๊ฒ€์ฆ/serialization/์ž๋™ schema ์ƒ์„ฑ์„ ํ•˜๋ ค๋ฉด ์—ฌ๋Ÿฌ ๊ณณ์— decorator๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋ฉฐ, ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋งค์šฐ ์žฅํ™ฉํ•ด์ง‘๋‹ˆ๋‹ค. + +์ค‘์ฒฉ ๋ชจ๋ธ์„ ์ž˜ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์š”์ฒญ์˜ JSON body๊ฐ€ ๋‚ด๋ถ€ ํ•„๋“œ๋ฅผ ๊ฐ€์ง„ JSON ๊ฐ์ฒด์ด๊ณ  ๊ทธ ๋‚ด๋ถ€ ํ•„๋“œ๋“ค์ด ๋‹ค์‹œ ์ค‘์ฒฉ๋œ JSON ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ, ์ œ๋Œ€๋กœ ๋ฌธ์„œํ™”ํ•˜๊ณ  ๊ฒ€์ฆํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +Python ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด ๋›ฐ์–ด๋‚œ ์—๋””ํ„ฐ ์ง€์›์„ ์ œ๊ณตํ•˜๊ธฐ. + +๊ฐ•๋ ฅํ•œ ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์„ ๊ฐ–์ถ”๊ธฐ. ์ฝ”๋“œ ๋ฐ˜๋ณต์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ๊ธฐ. + +/// + +### <a href="https://sanic.readthedocs.io/en/latest/" class="external-link" target="_blank">Sanic</a> { #sanic } + +`asyncio` ๊ธฐ๋ฐ˜์˜ ๋งค์šฐ ๋น ๋ฅธ Python framework ์ค‘ ์ดˆ๊ธฐ ์‚ฌ๋ก€์˜€์Šต๋‹ˆ๋‹ค. Flask์™€ ๋งค์šฐ ์œ ์‚ฌํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +๊ธฐ๋ณธ Python `asyncio` ๋ฃจํ”„ ๋Œ€์‹  <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a>๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๋งค์šฐ ๋น ๋ฅด๊ฒŒ ๋งŒ๋“  ์š”์ธ์ž…๋‹ˆ๋‹ค. + +์ด๋Š” Uvicorn๊ณผ Starlette์— ๋ช…ํ™•ํžˆ ์˜๊ฐ์„ ์ฃผ์—ˆ๊ณ , ํ˜„์žฌ ๊ณต๊ฐœ benchmark์—์„œ๋Š” ์ด ๋‘˜์ด Sanic๋ณด๋‹ค ๋” ๋น ๋ฆ…๋‹ˆ๋‹ค. + +/// + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +๋ฏธ์นœ ์„ฑ๋Šฅ์„ ๋‚ผ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ๊ธฐ. + +๊ทธ๋ž˜์„œ **FastAPI**๋Š” Starlette๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. Starlette๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ framework ์ค‘ ๊ฐ€์žฅ ๋น ๋ฅด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค(์„œ๋“œํŒŒํ‹ฐ benchmark๋กœ ํ…Œ์ŠคํŠธ๋จ). + +/// + +### <a href="https://falconframework.org/" class="external-link" target="_blank">Falcon</a> { #falcon } + +Falcon์€ ๋˜ ๋‹ค๋ฅธ ๊ณ ์„ฑ๋Šฅ Python framework๋กœ, ์ตœ์†Œํ•œ์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ๊ณ  Hug ๊ฐ™์€ ๋‹ค๋ฅธ framework์˜ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๋„๋ก ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. + +ํ•จ์ˆ˜๊ฐ€ ๋‘ ๊ฐœ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ(ํ•˜๋‚˜๋Š” "request", ํ•˜๋‚˜๋Š” "response")๋ฅผ ๋ฐ›๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ request์—์„œ ์ผ๋ถ€๋ฅผ "์ฝ๊ณ ", response์— ์ผ๋ถ€๋ฅผ "์ž‘์„ฑ"ํ•ฉ๋‹ˆ๋‹ค. ์ด ์„ค๊ณ„ ๋•Œ๋ฌธ์—, ํ‘œ์ค€ Python type hints๋ฅผ ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์‚ฌ์šฉํ•ด ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ body๋ฅผ ์„ ์–ธํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, serialization, ๋ฌธ์„œํ™”๋Š” ์ž๋™์œผ๋กœ ๋˜์ง€ ์•Š๊ณ  ์ฝ”๋“œ๋กœ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜๋Š” Hug์ฒ˜๋Ÿผ Falcon ์œ„์— framework๋ฅผ ์–น์–ด ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. request ๊ฐ์ฒด ํ•˜๋‚˜์™€ response ๊ฐ์ฒด ํ•˜๋‚˜๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” Falcon์˜ ์„ค๊ณ„์—์„œ ์˜๊ฐ์„ ๋ฐ›์€ ๋‹ค๋ฅธ framework์—์„œ๋„ ๊ฐ™์€ ๊ตฌ๋ถ„์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +ํ›Œ๋ฅญํ•œ ์„ฑ๋Šฅ์„ ์–ป๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ๊ธฐ. + +Hug(= Falcon ๊ธฐ๋ฐ˜)๊ณผ ํ•จ๊ป˜, ํ•จ์ˆ˜์—์„œ `response` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ ์–ธํ•˜๋„๋ก **FastAPI**์— ์˜๊ฐ์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. + +๋‹ค๋งŒ FastAPI์—์„œ๋Š” ์„ ํƒ ์‚ฌํ•ญ์ด๋ฉฐ, ์ฃผ๋กœ ํ—ค๋”, ์ฟ ํ‚ค, ๊ทธ๋ฆฌ๊ณ  ๋Œ€์ฒด status code๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +/// + +### <a href="https://moltenframework.com/" class="external-link" target="_blank">Molten</a> { #molten } + +**FastAPI**๋ฅผ ๋งŒ๋“ค๊ธฐ ์‹œ์ž‘ํ•œ ์ดˆ๊ธฐ ๋‹จ๊ณ„์—์„œ Molten์„ ์•Œ๊ฒŒ ๋˜์—ˆ๊ณ , ๊ฝค ๋น„์Šทํ•œ ์•„์ด๋””์–ด๋ฅผ ๊ฐ–๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค: + +* Python type hints ๊ธฐ๋ฐ˜ +* ์ด ํƒ€์ž…์œผ๋กœ๋ถ€ํ„ฐ ๊ฒ€์ฆ๊ณผ ๋ฌธ์„œํ™” ์ƒ์„ฑ +* ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ + +Pydantic ๊ฐ™์€ ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ/serialization/๋ฌธ์„œํ™”๋ฅผ ํ•˜์ง€ ์•Š๊ณ  ์ž์ฒด ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋Ÿฐ ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ •์˜๋ฅผ ์‰ฝ๊ฒŒ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ๋Š” ์–ด๋ ต์Šต๋‹ˆ๋‹ค. + +์กฐ๊ธˆ ๋” ์žฅํ™ฉํ•œ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ WSGI(ASGI๊ฐ€ ์•„๋‹ˆ๋ผ) ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ, Uvicorn, Starlette, Sanic ๊ฐ™์€ ๋„๊ตฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ณ ์„ฑ๋Šฅ์„ ํ™œ์šฉํ•˜๋„๋ก ์„ค๊ณ„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. + +์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์€ ์˜์กด์„ฑ์„ ์‚ฌ์ „์— ๋“ฑ๋กํ•ด์•ผ ํ•˜๊ณ , ์„ ์–ธ๋œ ํƒ€์ž…์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์˜์กด์„ฑ์„ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํŠน์ • ํƒ€์ž…์„ ์ œ๊ณตํ•˜๋Š” "component"๋ฅผ ๋‘ ๊ฐœ ์ด์ƒ ์„ ์–ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + +Route๋Š” ํ•œ ๊ณณ์—์„œ ์„ ์–ธํ•˜๊ณ , ๋‹ค๋ฅธ ๊ณณ์— ์„ ์–ธ๋œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜ ๋ฐ”๋กœ ์œ„์— ๋‘˜ ์ˆ˜ ์žˆ๋Š” decorator๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ ). ์ด๋Š” Flask(๋ฐ Starlette)๋ณด๋‹ค๋Š” Django ๋ฐฉ์‹์— ๊ฐ€๊น์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ์—์„œ ์ƒ๋Œ€์ ์œผ๋กœ ๊ฐ•ํ•˜๊ฒŒ ๊ฒฐํ•ฉ๋œ ๊ฒƒ๋“ค์„ ๋ถ„๋ฆฌํ•ด ๋†“์Šต๋‹ˆ๋‹ค. + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +๋ชจ๋ธ ์†์„ฑ์˜ "default" ๊ฐ’์œผ๋กœ ๋ฐ์ดํ„ฐ ํƒ€์ž…์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๊ฒ€์ฆ์„ ์ •์˜ํ•˜๊ธฐ. ์ด๋Š” ์—๋””ํ„ฐ ์ง€์›์„ ๊ฐœ์„ ํ•˜๋ฉฐ, ์ด์ „์—๋Š” Pydantic์— ์—†์—ˆ์Šต๋‹ˆ๋‹ค. + +์ด๊ฒƒ์€ ์‹ค์ œ๋กœ Pydantic์˜ ์ผ๋ถ€๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ๊ฐ™์€ ๊ฒ€์ฆ ์„ ์–ธ ์Šคํƒ€์ผ์„ ์ง€์›ํ•˜๋„๋ก ํ•˜๋Š” ๋ฐ ์˜๊ฐ์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค(์ด ๊ธฐ๋Šฅ์€ ์ด์ œ Pydantic์— ์ด๋ฏธ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค). + +/// + +### <a href="https://github.com/hugapi/hug" class="external-link" target="_blank">Hug</a> { #hug } + +Hug๋Š” Python type hints๋ฅผ ์‚ฌ์šฉํ•ด API ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…์„ ์„ ์–ธํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ ์ดˆ๊ธฐ framework ์ค‘ ํ•˜๋‚˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค๋ฅธ ๋„๊ตฌ๋“ค๋„ ๊ฐ™์€ ๋ฐฉ์‹์„ ํ•˜๋„๋ก ์˜๊ฐ์„ ์ค€ ํ›Œ๋ฅญํ•œ ์•„์ด๋””์–ด์˜€์Šต๋‹ˆ๋‹ค. + +ํ‘œ์ค€ Python ํƒ€์ž… ๋Œ€์‹  ์ปค์Šคํ…€ ํƒ€์ž…์„ ์„ ์–ธ์— ์‚ฌ์šฉํ–ˆ์ง€๋งŒ, ์—ฌ์ „ํžˆ ํฐ ์ง„์ „์ด์—ˆ์Šต๋‹ˆ๋‹ค. + +๋˜ํ•œ ์ „์ฒด API๋ฅผ JSON์œผ๋กœ ์„ ์–ธํ•˜๋Š” ์ปค์Šคํ…€ schema๋ฅผ ์ƒ์„ฑํ•œ ์ดˆ๊ธฐ framework ์ค‘ ํ•˜๋‚˜์˜€์Šต๋‹ˆ๋‹ค. + +OpenAPI๋‚˜ JSON Schema ๊ฐ™์€ ํ‘œ์ค€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— Swagger UI ๊ฐ™์€ ๋‹ค๋ฅธ ๋„๊ตฌ์™€ ํ†ตํ•ฉํ•˜๋Š” ๊ฒƒ์€ ์ง๊ด€์ ์ด์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—ญ์‹œ ๋งค์šฐ ํ˜์‹ ์ ์ธ ์•„์ด๋””์–ด์˜€์Šต๋‹ˆ๋‹ค. + +ํฅ๋ฏธ๋กญ๊ณ  ํ”์น˜ ์•Š์€ ๊ธฐ๋Šฅ์ด ํ•˜๋‚˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ framework๋กœ API๋ฟ ์•„๋‹ˆ๋ผ CLI๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋™๊ธฐ์‹ Python ์›น framework์˜ ์ด์ „ ํ‘œ์ค€(WSGI) ๊ธฐ๋ฐ˜์ด์–ด์„œ Websockets์™€ ๋‹ค๋ฅธ ๊ฒƒ๋“ค์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋Š” ์—†์ง€๋งŒ, ์„ฑ๋Šฅ์€ ์—ฌ์ „ํžˆ ๋†’์Šต๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +Hug๋Š” Timothy Crosley๊ฐ€ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. Python ํŒŒ์ผ์—์„œ import๋ฅผ ์ž๋™์œผ๋กœ ์ •๋ ฌํ•˜๋Š” ํ›Œ๋ฅญํ•œ ๋„๊ตฌ์ธ <a href="https://github.com/timothycrosley/isort" class="external-link" target="_blank">`isort`</a>์˜ ์ œ์ž‘์ž์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. + +/// + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ์•„์ด๋””์–ด๋“ค + +Hug๋Š” APIStar์˜ ์ผ๋ถ€์— ์˜๊ฐ์„ ์ฃผ์—ˆ๊ณ , ์ €๋Š” APIStar์™€ ํ•จ๊ป˜ Hug๋ฅผ ๊ฐ€์žฅ ์œ ๋งํ•œ ๋„๊ตฌ ์ค‘ ํ•˜๋‚˜๋กœ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. + +Hug๋Š” Python type hints๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ ์–ธํ•˜๊ณ , API๋ฅผ ์ •์˜ํ•˜๋Š” schema๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋„๋ก **FastAPI**์— ์˜๊ฐ์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. + +Hug๋Š” ํ—ค๋”์™€ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ํ•จ์ˆ˜์— `response` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ ์–ธํ•˜๋„๋ก **FastAPI**์— ์˜๊ฐ์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. + +/// + +### <a href="https://github.com/encode/apistar" class="external-link" target="_blank">APIStar</a> (<= 0.5) { #apistar-0-5 } + +**FastAPI**๋ฅผ ๋งŒ๋“ค๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜๊ธฐ ์ง์ „์— **APIStar** ์„œ๋ฒ„๋ฅผ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฐพ๊ณ  ์žˆ๋˜ ๊ฑฐ์˜ ๋ชจ๋“  ๊ฒƒ์„ ๊ฐ–์ถ”๊ณ  ์žˆ์—ˆ๊ณ  ์„ค๊ณ„๋„ ํ›Œ๋ฅญํ–ˆ์Šต๋‹ˆ๋‹ค. + +NestJS์™€ Molten๋ณด๋‹ค ์•ž์„œ, Python type hints๋ฅผ ์‚ฌ์šฉํ•ด ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ์š”์ฒญ์„ ์„ ์–ธํ•˜๋Š” framework ๊ตฌํ˜„์„ ์ œ๊ฐ€ ์ฒ˜์Œ ๋ณธ ์‚ฌ๋ก€๋“ค ์ค‘ ํ•˜๋‚˜์˜€์Šต๋‹ˆ๋‹ค. Hug์™€ ๊ฑฐ์˜ ๊ฐ™์€ ์‹œ๊ธฐ์— ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ APIStar๋Š” OpenAPI ํ‘œ์ค€์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + +์—ฌ๋Ÿฌ ์œ„์น˜์—์„œ ๋™์ผํ•œ type hints๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž๋™ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, ๋ฐ์ดํ„ฐ serialization, OpenAPI schema ์ƒ์„ฑ์„ ์ œ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค. + +Body schema ์ •์˜๋Š” Pydantic์ฒ˜๋Ÿผ ๋™์ผํ•œ Python type hints๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋Š” ์•Š์•˜๊ณ  Marshmallow์™€ ์กฐ๊ธˆ ๋” ๋น„์Šทํ•ด์„œ ์—๋””ํ„ฐ ์ง€์›์€ ๊ทธ๋งŒํผ ์ข‹์ง€ ์•Š์•˜์ง€๋งŒ, ๊ทธ๋ž˜๋„ APIStar๋Š” ๋‹น์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์„ ์˜ ์„ ํƒ์ง€์˜€์Šต๋‹ˆ๋‹ค. + +๋‹น์‹œ ์ตœ๊ณ ์˜ ์„ฑ๋Šฅ benchmark๋ฅผ ๊ฐ€์กŒ์Šต๋‹ˆ๋‹ค(Starlette์— ์˜ํ•ด์„œ๋งŒ ์ถ”์›”๋จ). + +์ฒ˜์Œ์—๋Š” ์ž๋™ API ๋ฌธ์„œํ™” ์›น UI๊ฐ€ ์—†์—ˆ์ง€๋งŒ, Swagger UI๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. + +์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ๋‹ค๋ฅธ ๋„๊ตฌ๋“ค์ฒ˜๋Ÿผ component์˜ ์‚ฌ์ „ ๋“ฑ๋ก์ด ํ•„์š”ํ–ˆ์ง€๋งŒ, ์—ฌ์ „ํžˆ ํ›Œ๋ฅญํ•œ ๊ธฐ๋Šฅ์ด์—ˆ์Šต๋‹ˆ๋‹ค. + +๋ณด์•ˆ ํ†ตํ•ฉ์ด ์—†์–ด์„œ ์ „์ฒด ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•ด ๋ณผ ์ˆ˜๋Š” ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ Flask-apispec ๊ธฐ๋ฐ˜ full-stack generator๋กœ ๊ฐ–์ถ”๊ณ  ์žˆ๋˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๋Œ€์ฒดํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋Š” pull request๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ œ ๋ฐฑ๋กœ๊ทธ์— ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์ดํ›„ ํ”„๋กœ์ ํŠธ์˜ ์ดˆ์ ์ด ๋ฐ”๋€Œ์—ˆ์Šต๋‹ˆ๋‹ค. + +๋” ์ด์ƒ API web framework๊ฐ€ ์•„๋‹ˆ๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ, ์ œ์ž‘์ž๊ฐ€ Starlette์— ์ง‘์ค‘ํ•ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +์ด์ œ APIStar๋Š” web framework๊ฐ€ ์•„๋‹ˆ๋ผ OpenAPI ์‚ฌ์–‘์„ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ ๋ชจ์Œ์ž…๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +APIStar๋Š” Tom Christie๊ฐ€ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์„ ๋งŒ๋“  ์‚ฌ๋žŒ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค: + +* Django REST Framework +* Starlette(**FastAPI**์˜ ๊ธฐ๋ฐ˜) +* Uvicorn(Starlette์™€ **FastAPI**์—์„œ ์‚ฌ์šฉ) + +/// + +/// check | **FastAPI**์— ์˜๊ฐ์„ ์ค€ ๊ฒƒ + +์กด์žฌํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ. + +๋™์ผํ•œ Python ํƒ€์ž…์œผ๋กœ ์—ฌ๋Ÿฌ ๊ฐ€์ง€(๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, serialization, ๋ฌธ์„œํ™”)๋ฅผ ์„ ์–ธํ•˜๋ฉด์„œ ๋™์‹œ์— ๋›ฐ์–ด๋‚œ ์—๋””ํ„ฐ ์ง€์›์„ ์ œ๊ณตํ•œ๋‹ค๋Š” ์•„์ด๋””์–ด๋Š” ์ œ๊ฐ€ ๋งค์šฐ ํ›Œ๋ฅญํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์˜ค๋žซ๋™์•ˆ ๋น„์Šทํ•œ framework๋ฅผ ์ฐพ์•„ ์—ฌ๋Ÿฌ ๋Œ€์•ˆ์„ ํ…Œ์ŠคํŠธํ•œ ๋์—, APIStar๊ฐ€ ๊ทธ๋•Œ ์ด์šฉ ๊ฐ€๋Šฅํ•œ ์ตœ์„ ์˜ ์„ ํƒ์ง€์˜€์Šต๋‹ˆ๋‹ค. + +๊ทธ ํ›„ APIStar ์„œ๋ฒ„๊ฐ€ ๋”๋Š” ์กด์žฌํ•˜์ง€ ์•Š๊ฒŒ ๋˜๊ณ  Starlette๊ฐ€ ๋งŒ๋“ค์–ด์กŒ๋Š”๋ฐ, ์ด๋Š” ๊ทธ๋Ÿฐ ์‹œ์Šคํ…œ์„ ์œ„ํ•œ ๋” ์ƒˆ๋กญ๊ณ  ๋” ๋‚˜์€ ๊ธฐ๋ฐ˜์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด **FastAPI**๋ฅผ ๋งŒ๋“ค๊ฒŒ ๋œ ์ตœ์ข… ์˜๊ฐ์ด์—ˆ์Šต๋‹ˆ๋‹ค. + +์ €๋Š” **FastAPI**๋ฅผ APIStar์˜ "์ •์‹ ์  ํ›„๊ณ„์ž"๋กœ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋™์‹œ์—, ์ด ๋ชจ๋“  ์ด์ „ ๋„๊ตฌ๋“ค์—์„œ ๋ฐฐ์šด ๊ฒƒ๋“ค์„ ๋ฐ”ํƒ•์œผ๋กœ ๊ธฐ๋Šฅ, typing ์‹œ์Šคํ…œ, ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๋ถ€๋ถ„๋“ค์„ ๊ฐœ์„ ํ•˜๊ณ  ํ™•์žฅํ–ˆ์Šต๋‹ˆ๋‹ค. + +/// + +## **FastAPI**๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ { #used-by-fastapi } + +### <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> { #pydantic } + +Pydantic์€ Python type hints๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, serialization, ๋ฌธ์„œํ™”(JSON Schema ์‚ฌ์šฉ)๋ฅผ ์ •์˜ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. + +๊ทธ ๋•๋ถ„์— ๋งค์šฐ ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค. + +Marshmallow์™€ ๋น„๊ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ benchmark์—์„œ Marshmallow๋ณด๋‹ค ๋น ๋ฆ…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋™์ผํ•œ Python type hints๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฏ€๋กœ ์—๋””ํ„ฐ ์ง€์›๋„ ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค. + +/// check | **FastAPI**๊ฐ€ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ชฉ์  + +๋ชจ๋“  ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, ๋ฐ์ดํ„ฐ serialization, ์ž๋™ ๋ชจ๋ธ ๋ฌธ์„œํ™”(JSON Schema ๊ธฐ๋ฐ˜)๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ. + +๊ทธ ๋‹ค์Œ **FastAPI**๋Š” ๊ทธ JSON Schema ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ OpenAPI์— ํฌํ•จ์‹œํ‚ค๋ฉฐ, ๊ทธ ์™ธ์—๋„ ์—ฌ๋Ÿฌ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. + +/// + +### <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> { #starlette } + +Starlette๋Š” ๊ฒฝ๋Ÿ‰ <abbr title="The new standard for building asynchronous Python web applications - ๋น„๋™๊ธฐ Python ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ์ƒˆ๋กœ์šด ํ‘œ์ค€">ASGI</abbr> framework/toolkit์œผ๋กœ, ๊ณ ์„ฑ๋Šฅ asyncio ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค๊ธฐ์— ์ด์ƒ์ ์ž…๋‹ˆ๋‹ค. + +๋งค์šฐ ๋‹จ์ˆœํ•˜๊ณ  ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค. ์‰ฝ๊ฒŒ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์—ˆ๊ณ , ๋ชจ๋“ˆ์‹ component๋ฅผ ๊ฐ–์Šต๋‹ˆ๋‹ค. + +๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค: + +* ์ •๋ง ์ธ์ƒ์ ์ธ ์„ฑ๋Šฅ. +* WebSocket ์ง€์›. +* ํ”„๋กœ์„ธ์Šค ๋‚ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…. +* ์‹œ์ž‘ ๋ฐ ์ข…๋ฃŒ ์ด๋ฒคํŠธ. +* HTTPX ๊ธฐ๋ฐ˜์˜ ํ…Œ์ŠคํŠธ ํด๋ผ์ด์–ธํŠธ. +* CORS, GZip, Static Files, Streaming responses. +* ์„ธ์…˜ ๋ฐ ์ฟ ํ‚ค ์ง€์›. +* 100% ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€. +* 100% ํƒ€์ž… ์ฃผ์„์ด ๋‹ฌ๋ฆฐ ์ฝ”๋“œ๋ฒ ์ด์Šค. +* ์†Œ์ˆ˜์˜ ํ•„์ˆ˜ ์˜์กด์„ฑ. + +Starlette๋Š” ํ˜„์žฌ ํ…Œ์ŠคํŠธ๋œ Python framework ์ค‘ ๊ฐ€์žฅ ๋น ๋ฆ…๋‹ˆ๋‹ค. ๋‹จ, framework๊ฐ€ ์•„๋‹ˆ๋ผ ์„œ๋ฒ„์ธ Uvicorn์ด ๋” ๋น ๋ฆ…๋‹ˆ๋‹ค. + +Starlette๋Š” ์›น microframework์˜ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์„ ๋ชจ๋‘ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์ž๋™ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, serialization, ๋ฌธ์„œํ™”๋Š” ์ œ๊ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +๊ทธ๊ฒƒ์ด **FastAPI**๊ฐ€ ์œ„์— ์ถ”๊ฐ€ํ•˜๋Š” ํ•ต์‹ฌ ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, ๋ชจ๋‘ Python type hints(Pydantic ์‚ฌ์šฉ)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— ๋”ํ•ด ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ, ๋ณด์•ˆ ์œ ํ‹ธ๋ฆฌํ‹ฐ, OpenAPI schema ์ƒ์„ฑ ๋“ฑ๋„ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +ASGI๋Š” Django ์ฝ”์–ด ํŒ€ ๋ฉค๋ฒ„๋“ค์ด ๊ฐœ๋ฐœ ์ค‘์ธ ์ƒˆ๋กœ์šด "ํ‘œ์ค€"์ž…๋‹ˆ๋‹ค. ์•„์ง "Python ํ‘œ์ค€"(PEP)์€ ์•„๋‹ˆ์ง€๋งŒ, ๊ทธ ๋ฐฉํ–ฅ์œผ๋กœ ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค. + +๊ทธ๋Ÿผ์—๋„ ์ด๋ฏธ ์—ฌ๋Ÿฌ ๋„๊ตฌ์—์„œ "ํ‘œ์ค€"์œผ๋กœ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ƒํ˜ธ์šด์šฉ์„ฑ์„ ํฌ๊ฒŒ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Uvicorn์„ ๋‹ค๋ฅธ ASGI ์„œ๋ฒ„(์˜ˆ: Daphne ๋˜๋Š” Hypercorn)๋กœ ๊ต์ฒดํ•  ์ˆ˜๋„ ์žˆ๊ณ , `python-socketio` ๊ฐ™์€ ASGI ํ˜ธํ™˜ ๋„๊ตฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +/// check | **FastAPI**๊ฐ€ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ชฉ์  + +ํ•ต์‹ฌ ์›น ๋ถ€๋ถ„์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•˜๊ธฐ. ๊ทธ ์œ„์— ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ธฐ. + +`FastAPI` ํด๋ž˜์Šค ์ž์ฒด๋Š” `Starlette` ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ์ƒ์†ํ•ฉ๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ Starlette๋กœ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒƒ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ **FastAPI**๋กœ๋„ ์ง์ ‘ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, **FastAPI**๋Š” ์‚ฌ์‹ค์ƒ Starlette์— ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ๋”ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// + +### <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a> { #uvicorn } + +Uvicorn์€ uvloop๊ณผ httptools๋กœ ๊ตฌ์ถ•๋œ ์ดˆ๊ณ ์† ASGI ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค. + +web framework๊ฐ€ ์•„๋‹ˆ๋ผ ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ฒฝ๋กœ ๊ธฐ๋ฐ˜ routing์„ ์œ„ํ•œ ๋„๊ตฌ๋Š” ์ œ๊ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๊ฒƒ์€ Starlette(๋˜๋Š” **FastAPI**) ๊ฐ™์€ framework๊ฐ€ ์œ„์—์„œ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +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/ko/docs/deployment/concepts.md b/docs/ko/docs/deployment/concepts.md new file mode 100644 index 0000000000..dd7edd1bae --- /dev/null +++ b/docs/ko/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 ๊ฐ™์€ ์™ธ๋ถ€ ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ +* Nginx ๊ฐ™์€ Ingress Controller๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Kubernetes + * ์ธ์ฆ์„œ ๊ฐฑ์‹ ์„ ์œ„ํ•ด 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โ€)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์‹คํ–‰์ด ๋ฉˆ์ถ”๊ณ , ๋” ์ด์ƒ **์•„๋ฌด๊ฒƒ๋„ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค**. +* ์ปดํ“จํ„ฐ์—์„œ ์‹คํ–‰ ์ค‘์ธ ๊ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋’ค์—๋Š” ํ”„๋กœ์„ธ์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ๊ทธ๋žจ, ๊ฐ ์ฐฝ ๋“ฑ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ปดํ“จํ„ฐ๊ฐ€ ์ผœ์ ธ ์žˆ๋Š” ๋™์•ˆ ๋ณดํ†ต ๋งŽ์€ ํ”„๋กœ์„ธ์Šค๊ฐ€ **๋™์‹œ์—** ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +* **๊ฐ™์€ ํ”„๋กœ๊ทธ๋žจ**์˜ **์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค**๊ฐ€ ๋™์‹œ์— ์‹คํ–‰๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์šด์˜์ฒด์ œ์˜ โ€œ์ž‘์—… ๊ด€๋ฆฌ์ž(task manager)โ€๋‚˜ โ€œ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ(system monitor)โ€(๋˜๋Š” ๋น„์Šทํ•œ ๋„๊ตฌ)๋ฅผ ํ™•์ธํ•ด ๋ณด๋ฉด, ์ด๋Ÿฐ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋งŽ์ด ์‹คํ–‰ ์ค‘์ธ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋˜ ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ™์€ ๋ธŒ๋ผ์šฐ์ € ํ”„๋กœ๊ทธ๋žจ(Firefox, Chrome, Edge ๋“ฑ)์„ ์‹คํ–‰ํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ ์žˆ๋Š” ๊ฒƒ๋„ ๋ณด์ผ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. ๋ณดํ†ต ํƒญ๋งˆ๋‹ค ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰ํ•˜๊ณ , ๊ทธ ์™ธ์—๋„ ์ถ”๊ฐ€ ํ”„๋กœ์„ธ์Šค ๋ช‡ ๊ฐœ๊ฐ€ ๋” ์žˆ์Šต๋‹ˆ๋‹ค. + +<img class="shadow" src="/img/deployment/concepts/image01.png"> + +--- + +์ด์ œ **ํ”„๋กœ์„ธ์Šค**์™€ **ํ”„๋กœ๊ทธ๋žจ**์˜ ์ฐจ์ด๋ฅผ ์•Œ์•˜์œผ๋‹ˆ, ๋ฐฐํฌ์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ๊ณ„์†ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +## ์‹œ์ž‘ ์‹œ ์‹คํ–‰ { #running-on-startup } + +๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์›น API๋ฅผ ๋งŒ๋“ค๋ฉด, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์–ธ์ œ๋‚˜ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก **ํ•ญ์ƒ ์‹คํ–‰**๋˜๊ณ  ์ค‘๋‹จ๋˜์ง€ ์•Š๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก  ํŠน์ • ์ƒํ™ฉ์—์„œ๋งŒ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์€ ํŠน๋ณ„ํ•œ ์ด์œ ๊ฐ€ ์žˆ์„ ์ˆ˜๋Š” ์žˆ์ง€๋งŒ, ๋Œ€๋ถ€๋ถ„์€ ์ง€์†์ ์œผ๋กœ ์‹คํ–‰๋˜๋ฉฐ **์‚ฌ์šฉ ๊ฐ€๋Šฅ**ํ•œ ์ƒํƒœ์ด๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. + +### ์›๊ฒฉ ์„œ๋ฒ„์—์„œ { #in-a-remote-server } + +์›๊ฒฉ ์„œ๋ฒ„(ํด๋ผ์šฐ๋“œ ์„œ๋ฒ„, ๊ฐ€์ƒ ๋จธ์‹  ๋“ฑ)๋ฅผ ์„ค์ •ํ•  ๋•Œ, ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ๋ฐฉ๋ฒ•์€ ๋กœ์ปฌ ๊ฐœ๋ฐœ ๋•Œ์ฒ˜๋Ÿผ ์ˆ˜๋™์œผ๋กœ `fastapi run`(Uvicorn์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค)์ด๋‚˜ ๋น„์Šทํ•œ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +์ด ๋ฐฉ์‹์€ ๋™์ž‘ํ•˜๊ณ , **๊ฐœ๋ฐœ ์ค‘์—๋Š”** ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์„œ๋ฒ„์— ๋Œ€ํ•œ ์—ฐ๊ฒฐ์ด ๋Š๊ธฐ๋ฉด, ์‹คํ–‰ ์ค‘์ธ **ํ”„๋กœ์„ธ์Šค**๋„ ์•„๋งˆ ์ข…๋ฃŒ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๋˜ ์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘๋˜๋ฉด(์˜ˆ: ์—…๋ฐ์ดํŠธ ์ดํ›„, ํ˜น์€ ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ดํ›„) ์—ฌ๋Ÿฌ๋ถ„์€ ์•„๋งˆ **์•Œ์•„์ฐจ๋ฆฌ์ง€ ๋ชปํ• ** ๊ฒ๋‹ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ, ํ”„๋กœ์„ธ์Šค๋ฅผ ์ˆ˜๋™์œผ๋กœ ๋‹ค์‹œ ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค๋„ ๋ชจ๋ฅด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด API๋Š” ๊ทธ๋ƒฅ ์ฃฝ์€ ์ƒํƒœ๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ฑ + +### ์‹œ์ž‘ ์‹œ ์ž๋™ ์‹คํ–‰ { #run-automatically-on-startup } + +์ผ๋ฐ˜์ ์œผ๋กœ ์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ(์˜ˆ: Uvicorn)์€ ์„œ๋ฒ„๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์ž๋™์œผ๋กœ ์‹œ์ž‘๋˜๊ณ , **์‚ฌ๋žŒ์˜ ๊ฐœ์ž…** ์—†์ด๋„ FastAPI ์•ฑ์„ ์‹คํ–‰ํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๊ฐ€ ํ•ญ์ƒ ์‹คํ–‰ ์ค‘์ด๋„๋ก(์˜ˆ: FastAPI ์•ฑ์„ ์‹คํ–‰ํ•˜๋Š” Uvicorn) ๊ตฌ์„ฑํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +### ๋ณ„๋„์˜ ํ”„๋กœ๊ทธ๋žจ { #separate-program } + +์ด๋ฅผ ์œ„ํ•ด ๋ณดํ†ต ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘ ์‹œ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•˜๋Š” **๋ณ„๋„์˜ ํ”„๋กœ๊ทธ๋žจ**์„ ๋‘ก๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งŽ์€ ๊ฒฝ์šฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ™์€ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋‚˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋„ ํ•จ๊ป˜ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + +### ์‹œ์ž‘ ์‹œ ์‹คํ–‰์„ ์œ„ํ•œ ๋„๊ตฌ ์˜ˆ์‹œ { #example-tools-to-run-at-startup } + +์ด ์—ญํ• ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* Docker +* Kubernetes +* Docker Compose +* Swarm Mode์˜ Docker +* Systemd +* Supervisor +* ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž๊ฐ€ ์„œ๋น„์Šค ์ผ๋ถ€๋กœ ๋‚ด๋ถ€์ ์œผ๋กœ ์ฒ˜๋ฆฌ +* ๊ธฐํƒ€... + +๋‹ค์Œ ์žฅ์—์„œ ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ๋ฅผ ์ œ๊ณตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. + +## ์žฌ์‹œ์ž‘ { #restarts } + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘ ์‹œ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•˜๊ฒŒ, ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ **์žฌ์‹œ์ž‘**๋˜๋„๋ก ๋ณด์žฅํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +### ์šฐ๋ฆฌ๋Š” ์‹ค์ˆ˜ํ•ฉ๋‹ˆ๋‹ค { #we-make-mistakes } + +์‚ฌ๋žŒ์€ ์–ธ์ œ๋‚˜ **์‹ค์ˆ˜**ํ•ฉ๋‹ˆ๋‹ค. ์†Œํ”„ํŠธ์›จ์–ด์—๋Š” ๊ฑฐ์˜ *ํ•ญ์ƒ* ์—ฌ๊ธฐ์ €๊ธฐ์— ์ˆจ์€ **๋ฒ„๊ทธ**๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ› + +๊ทธ๋ฆฌ๊ณ  ๊ฐœ๋ฐœ์ž๋Š” ๋ฒ„๊ทธ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ณ  ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์ฝ”๋“œ๋ฅผ ๊ณ„์† ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค(์ƒˆ๋กœ์šด ๋ฒ„๊ทธ๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๊ฒ ์ฃ  ๐Ÿ˜…). + +### ์ž‘์€ ์˜ค๋ฅ˜๋Š” ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋จ { #small-errors-automatically-handled } + +FastAPI๋กœ ์›น API๋ฅผ ๋งŒ๋“ค ๋•Œ ์ฝ”๋“œ์— ์˜ค๋ฅ˜๊ฐ€ ์žˆ์œผ๋ฉด, 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 +* Swarm Mode์˜ Docker +* Systemd +* Supervisor +* ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž๊ฐ€ ์„œ๋น„์Šค ์ผ๋ถ€๋กœ ๋‚ด๋ถ€์ ์œผ๋กœ ์ฒ˜๋ฆฌ +* ๊ธฐํƒ€... + +## ๋ณต์ œ - ํ”„๋กœ์„ธ์Šค์™€ ๋ฉ”๋ชจ๋ฆฌ { #replication-processes-and-memory } + +FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Uvicorn์„ ์‹คํ–‰ํ•˜๋Š” `fastapi` ๋ช…๋ น ๊ฐ™์€ ์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ์„ ์‚ฌ์šฉํ•˜๋ฉด, **ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค**๋กœ ์‹คํ–‰ํ•˜๋”๋ผ๋„ ์—ฌ๋Ÿฌ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋งŽ์€ ๊ฒฝ์šฐ, ์—ฌ๋Ÿฌ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋ฅผ ๋™์‹œ์— ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +### ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค - ์›Œ์ปค { #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๋ฅผ ํ”„๋กœ์„ธ์Šค ํ•˜๋‚˜๋กœ ์‹คํ–‰ํ•˜๋ฉด RAM์„ ์ตœ์†Œ 1GB ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  **4๊ฐœ ํ”„๋กœ์„ธ์Šค**(์›Œ์ปค 4๊ฐœ)๋ฅผ ์‹œ์ž‘ํ•˜๋ฉด ๊ฐ๊ฐ 1GB RAM์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ ์ด **4 GB RAM**์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฐ๋ฐ ์›๊ฒฉ ์„œ๋ฒ„๋‚˜ ๊ฐ€์ƒ ๋จธ์‹ ์˜ RAM์ด 3GB๋ฟ์ด๋ผ๋ฉด, 4GB๋ฅผ ๋„˜๊ฒŒ ๋กœ๋“œํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. ๐Ÿšจ + +### ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค - ์˜ˆ์‹œ { #multiple-processes-an-example } + +์ด ์˜ˆ์‹œ์—์„œ๋Š” **Manager Process**๊ฐ€ ๋‘ ๊ฐœ์˜ **Worker Processes**๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. + +์ด Manager Process๋Š” ์•„๋งˆ IP์˜ **ํฌํŠธ**์—์„œ ๋ฆฌ์Šค๋‹ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ํ†ต์‹ ์„ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. + +์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋“ค์ด ์‹ค์ œ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋ฉฐ, **์š”์ฒญ**์„ ๋ฐ›์•„ **์‘๋‹ต**์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ฃผ์š” ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๊ณ , RAM์— ๋ณ€์ˆ˜๋กœ ๋กœ๋“œํ•œ ๋ชจ๋“  ๋‚ด์šฉ์„ ๋‹ด์Šต๋‹ˆ๋‹ค. + +<img src="/img/deployment/concepts/process-ram.drawio.svg"> + +๊ทธ๋ฆฌ๊ณ  ๋ฌผ๋ก  ๊ฐ™์€ ๋จธ์‹ ์—๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์™ธ์—๋„ **๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค**๋“ค์ด ์‹คํ–‰ ์ค‘์ผ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. + +ํฅ๋ฏธ๋กœ์šด ์ ์€ ๊ฐ ํ”„๋กœ์„ธ์Šค์˜ **CPU ์‚ฌ์šฉ๋ฅ **์€ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ํฌ๊ฒŒ **๋ณ€๋™**ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, **๋ฉ”๋ชจ๋ฆฌ(RAM)**๋Š” ๋ณดํ†ต ๋Œ€์ฒด๋กœ **์•ˆ์ •์ **์œผ๋กœ ์œ ์ง€๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๋งค๋ฒˆ ๋น„์Šทํ•œ ์–‘์˜ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” API์ด๊ณ  ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋งŽ๋‹ค๋ฉด, **CPU ์‚ฌ์šฉ๋ฅ **๋„ (๊ธ‰๊ฒฉํžˆ ์˜ค๋ฅด๋‚ด๋ฆฌ๊ธฐ๋ณด๋‹ค๋Š”) *์•ˆ์ •์ ์ผ* ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. + +### ๋ณต์ œ ๋„๊ตฌ์™€ ์ „๋žต ์˜ˆ์‹œ { #examples-of-replication-tools-and-strategies } + +์ด๋ฅผ ๋‹ฌ์„ฑํ•˜๋Š” ์ ‘๊ทผ ๋ฐฉ์‹์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋‹ค์Œ ์žฅ๋“ค์—์„œ Docker์™€ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์„ค๋ช…ํ•  ๋•Œ ๊ตฌ์ฒด์ ์ธ ์ „๋žต์„ ๋” ์•Œ๋ ค๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. + +๊ณ ๋ คํ•ด์•ผ ํ•  ์ฃผ์š” ์ œ์•ฝ์€ **๊ณต๊ฐœ IP**์˜ **ํฌํŠธ**๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” **๋‹จ์ผ** ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ณต์ œ๋œ **ํ”„๋กœ์„ธ์Šค/์›Œ์ปค**๋กœ ํ†ต์‹ ์„ **์ „๋‹ฌ**ํ•  ๋ฐฉ๋ฒ•์ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +๊ฐ€๋Šฅํ•œ ์กฐํ•ฉ๊ณผ ์ „๋žต ๋ช‡ ๊ฐ€์ง€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* `--workers` ์˜ต์…˜์„ ์‚ฌ์šฉํ•œ **Uvicorn** + * ํ•˜๋‚˜์˜ 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 } + +์ด๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด **์‹œ์Šคํ…œ์„ ๋ฐฐํฌํ•˜๋Š” ๋ฐฉ์‹**์— ํฌ๊ฒŒ ์ขŒ์šฐ๋˜๋ฉฐ, ํ”„๋กœ๊ทธ๋žจ์„ ์‹œ์ž‘ํ•˜๋Š” ๋ฐฉ์‹, ์žฌ์‹œ์ž‘ ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ๋“ฑ๊ณผ๋„ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. + +๊ฐ€๋Šฅํ•œ ์•„์ด๋””์–ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* ์•ฑ ์ปจํ…Œ์ด๋„ˆ๋ณด๋‹ค ๋จผ์ € ์‹คํ–‰๋˜๋Š” Kubernetes์˜ โ€œInit Containerโ€ +* ์‚ฌ์ „ ๋‹จ๊ณ„๋ฅผ ์‹คํ–‰ํ•œ ๋‹ค์Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘ํ•˜๋Š” bash ์Šคํฌ๋ฆฝํŠธ + * ์ด bash ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹œ์ž‘/์žฌ์‹œ์ž‘ํ•˜๊ณ , ์˜ค๋ฅ˜๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ๋“ฑ์˜ ๋ฐฉ๋ฒ•๋„ ์—ฌ์ „ํžˆ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์ปจํ…Œ์ด๋„ˆ๋กœ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ๋Š” ๋‹ค์Œ ์žฅ์—์„œ ์ œ๊ณตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค: [์ปจํ…Œ์ด๋„ˆ์—์„œ FastAPI - Docker](docker.md){.internal-link target=_blank}. + +/// + +## ๋ฆฌ์†Œ์Šค ํ™œ์šฉ { #resource-utilization } + +์„œ๋ฒ„๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ์†Œ๋น„ํ•˜๊ฑฐ๋‚˜ **ํ™œ์šฉ(utilize)**ํ•  ์ˆ˜ ์žˆ๋Š” **๋ฆฌ์†Œ์Šค**์ž…๋‹ˆ๋‹ค. CPU์˜ ๊ณ„์‚ฐ ์‹œ๊ฐ„๊ณผ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ RAM ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋Œ€ํ‘œ์ ์ž…๋‹ˆ๋‹ค. + +์‹œ์Šคํ…œ ๋ฆฌ์†Œ์Šค๋ฅผ ์–ผ๋งˆ๋‚˜ ์†Œ๋น„/ํ™œ์šฉํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? โ€œ๋งŽ์ง€ ์•Š๊ฒŒโ€๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ธฐ ์‰ฝ์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” **ํฌ๋ž˜์‹œํ•˜์ง€ ์•Š๋Š” ์„ ์—์„œ ๊ฐ€๋Šฅํ•œ ํ•œ ๋งŽ์ด** ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. + +์„œ๋ฒ„ 3๋Œ€๋ฅผ ๋น„์šฉ์„ ๋‚ด๊ณ  ์“ฐ๊ณ  ์žˆ๋Š”๋ฐ RAM๊ณผ CPU๋ฅผ ์กฐ๊ธˆ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์•„๋งˆ **๋ˆ์„ ๋‚ญ๋น„**ํ•˜๊ณ  ๐Ÿ’ธ, **์„œ๋ฒ„ ์ „๋ ฅ๋„ ๋‚ญ๋น„**ํ•˜๊ณ  ๐ŸŒŽ, ๊ธฐํƒ€ ๋“ฑ๋“ฑ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ ๊ฒฝ์šฐ์—๋Š” ์„œ๋ฒ„๋ฅผ 2๋Œ€๋งŒ ๋‘๊ณ , ๊ฐ ์„œ๋ฒ„์˜ ๋ฆฌ์†Œ์Šค(CPU, ๋ฉ”๋ชจ๋ฆฌ, ๋””์Šคํฌ, ๋„คํŠธ์›Œํฌ ๋Œ€์—ญํญ ๋“ฑ)๋ฅผ ๋” ๋†’์€ ๋น„์œจ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ๋‚˜์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋ฐ˜๋Œ€๋กœ ์„œ๋ฒ„ 2๋Œ€๋ฅผ ๋‘๊ณ  CPU์™€ RAM์„ **100%** ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์–ด๋А ์‹œ์ ์— ํ”„๋กœ์„ธ์Šค ํ•˜๋‚˜๊ฐ€ ๋” ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์š”์ฒญํ•˜๊ฒŒ ๋˜๊ณ , ์„œ๋ฒ„๋Š” ๋””์Šคํฌ๋ฅผ โ€œ๋ฉ”๋ชจ๋ฆฌโ€์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค(์ˆ˜์ฒœ ๋ฐฐ ๋А๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). ๋˜๋Š” ์‹ฌ์ง€์–ด **ํฌ๋ž˜์‹œ**ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜น์€ ์–ด๋–ค ํ”„๋กœ์„ธ์Šค๊ฐ€ ๊ณ„์‚ฐ์„ ํ•ด์•ผ ํ•˜๋Š”๋ฐ CPU๊ฐ€ ๋‹ค์‹œ ๋น„์›Œ์งˆ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ์—๋Š” **์„œ๋ฒ„ ํ•œ ๋Œ€๋ฅผ ์ถ”๊ฐ€**๋กœ ํ™•๋ณดํ•˜๊ณ  ์ผ๋ถ€ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ทธ์ชฝ์—์„œ ์‹คํ–‰ํ•ด, ๋ชจ๋‘๊ฐ€ **์ถฉ๋ถ„ํ•œ RAM๊ณผ CPU ์‹œ๊ฐ„**์„ ๊ฐ–๋„๋ก ํ•˜๋Š” ํŽธ์ด ๋” ๋‚ซ์Šต๋‹ˆ๋‹ค. + +๋˜ ์–ด๋–ค ์ด์œ ๋กœ API ์‚ฌ์šฉ๋Ÿ‰์ด **๊ธ‰์ฆ(spike)**ํ•  ๊ฐ€๋Šฅ์„ฑ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ”์ด๋Ÿด์ด ๋˜์—ˆ๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ์„œ๋น„์Šค๋‚˜ ๋ด‡์ด ์‚ฌ์šฉํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด ์ถ”๊ฐ€ ๋ฆฌ์†Œ์Šค๋ฅผ ํ™•๋ณดํ•ด๋‘๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋ฆฌ์†Œ์Šค ํ™œ์šฉ๋ฅ  ๋ชฉํ‘œ๋กœ **์ž„์˜์˜ ์ˆ˜์น˜**๋ฅผ ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด **50%์—์„œ 90% ์‚ฌ์ด**์ฒ˜๋Ÿผ์š”. ์š”์ ์€, ์ด๋Ÿฐ ๊ฒƒ๋“ค์ด ๋ฐฐํฌ๋ฅผ ์กฐ์ •ํ•  ๋•Œ ์ธก์ •ํ•˜๊ณ  ํŠœ๋‹ํ•˜๋Š” ์ฃผ์š” ์ง€ํ‘œ๊ฐ€ ๋  ๊ฐ€๋Šฅ์„ฑ์ด ํฌ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +`htop` ๊ฐ™์€ ๊ฐ„๋‹จํ•œ ๋„๊ตฌ๋กœ ์„œ๋ฒ„์˜ CPU์™€ RAM ์‚ฌ์šฉ๋Ÿ‰, ๋˜๋Š” ๊ฐ ํ”„๋กœ์„ธ์Šค๋ณ„ ์‚ฌ์šฉ๋Ÿ‰์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜น์€ ์„œ๋ฒ„ ์—ฌ๋Ÿฌ ๋Œ€์— ๋ถ„์‚ฐ๋  ์ˆ˜๋„ ์žˆ๋Š” ๋” ๋ณต์žกํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ์š”์•ฝ { #recap } + +์—ฌ๊ธฐ๊นŒ์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•  ๋•Œ ์—ผ๋‘์— ๋‘์–ด์•ผ ํ•  ์ฃผ์š” ๊ฐœ๋…๋“ค์„ ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค: + +* ๋ณด์•ˆ - HTTPS +* ์‹œ์ž‘ ์‹œ ์‹คํ–‰ +* ์žฌ์‹œ์ž‘ +* ๋ณต์ œ(์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ์ˆ˜) +* ๋ฉ”๋ชจ๋ฆฌ +* ์‹œ์ž‘ ์ „ ์‚ฌ์ „ ๋‹จ๊ณ„ + +์ด ์•„์ด๋””์–ด๋“ค์„ ์ดํ•ดํ•˜๊ณ  ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๋ฉด, ๋ฐฐํฌ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์กฐ์ •ํ•  ๋•Œ ํ•„์š”ํ•œ ์ง๊ด€์„ ์–ป๋Š” ๋ฐ ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿค“ + +๋‹ค์Œ ์„น์…˜์—์„œ๋Š” ๋”ฐ๋ผ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€๋Šฅํ•œ ์ „๋žต์˜ ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ๋ฅผ ์ œ๊ณตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿš€ diff --git a/docs/ko/docs/deployment/fastapicloud.md b/docs/ko/docs/deployment/fastapicloud.md new file mode 100644 index 0000000000..9a830b1579 --- /dev/null +++ b/docs/ko/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +**ํ•œ ๋ฒˆ์˜ ๋ช…๋ น**์œผ๋กœ FastAPI ์•ฑ์„ <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>์— ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์ง์ด๋ผ๋ฉด ๋Œ€๊ธฐ์ž ๋ช…๋‹จ์— ๋“ฑ๋กํ•ด ๋ณด์„ธ์š”. ๐Ÿš€ + +## ๋กœ๊ทธ์ธํ•˜๊ธฐ { #login } + +๋จผ์ € **FastAPI Cloud** ๊ณ„์ •์ด ์ด๋ฏธ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”(๋Œ€๊ธฐ์ž ๋ช…๋‹จ์—์„œ ์ดˆ๋Œ€ํ•ด ๋“œ๋ ธ์„ ๊ฑฐ์˜ˆ์š” ๐Ÿ˜‰). + +๊ทธ๋‹ค์Œ ๋กœ๊ทธ์ธํ•ฉ๋‹ˆ๋‹ค: + +<div class="termy"> + +```console +$ fastapi login + +You are logged in to FastAPI Cloud ๐Ÿš€ +``` + +</div> + +## ๋ฐฐํฌํ•˜๊ธฐ { #deploy } + +์ด์ œ **ํ•œ ๋ฒˆ์˜ ๋ช…๋ น**์œผ๋กœ ์•ฑ์„ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค: + +<div class="termy"> + +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +โœ… Deployment successful! + +๐Ÿ” Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +</div> + +์ด๊ฒŒ ์ „๋ถ€์ž…๋‹ˆ๋‹ค! ์ด์ œ ํ•ด๋‹น URL์—์„œ ์•ฑ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โœจ + +## FastAPI Cloud ์†Œ๊ฐœ { #about-fastapi-cloud } + +**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>**๋Š” **FastAPI**๋ฅผ ๋งŒ๋“  ๋™์ผํ•œ ์ €์ž์™€ ํŒ€์ด ๊ตฌ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค. + +์ตœ์†Œํ•œ์˜ ๋…ธ๋ ฅ์œผ๋กœ API๋ฅผ **๊ตฌ์ถ•**, **๋ฐฐํฌ**, **์ ‘๊ทผ**ํ•˜๋Š” ๊ณผ์ •์„ ๊ฐ„์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. + +FastAPI๋กœ ์•ฑ์„ ๋งŒ๋“ค ๋•Œ์˜ ๋™์ผํ•œ **๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜**์„, ํด๋ผ์šฐ๋“œ์— **๋ฐฐํฌ**ํ•  ๋•Œ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰ + +๋˜ํ•œ ์•ฑ์„ ๋ฐฐํฌํ•  ๋•Œ ๋ณดํ†ต ํ•„์š”ํ•œ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒƒ๋“ค๋„ ์ฒ˜๋ฆฌํ•ด ์ค๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด: + +* HTTPS +* ์š”์ฒญ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž๋™ ์Šค์ผ€์ผ๋งํ•˜๋Š” ๋ณต์ œ(Replication) +* ๋“ฑ + +FastAPI Cloud๋Š” *FastAPI and friends* ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ์˜ ์ฃผ์š” ์Šคํฐ์„œ์ด์ž ์ž๊ธˆ ์ง€์› ์ œ๊ณต์ž์ž…๋‹ˆ๋‹ค. โœจ + +## ๋‹ค๋ฅธ ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด์— ๋ฐฐํฌํ•˜๊ธฐ { #deploy-to-other-cloud-providers } + +FastAPI๋Š” ์˜คํ”ˆ ์†Œ์Šค์ด๋ฉฐ ํ‘œ์ค€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์›ํ•˜๋Š” ์–ด๋–ค ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด์—๋„ FastAPI ์•ฑ์„ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•ด๋‹น ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด์˜ ๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ผ FastAPI ์•ฑ์„ ๋ฐฐํฌํ•˜์„ธ์š”. ๐Ÿค“ + +## ์ž์ฒด ์„œ๋ฒ„์— ๋ฐฐํฌํ•˜๊ธฐ { #deploy-your-own-server } + +๋˜ํ•œ ์ด **Deployment** ๊ฐ€์ด๋“œ์—์„œ ์ดํ›„์— ๋ชจ๋“  ์„ธ๋ถ€์‚ฌํ•ญ์„ ์•Œ๋ ค๋“œ๋ฆด ๊ฑฐ์˜ˆ์š”. ๊ทธ๋ž˜์„œ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ๋Š”์ง€, ๋ฌด์—‡์ด ํ•„์š”ํ•˜๋ฉฐ, ๋ณธ์ธ์˜ ์„œ๋ฒ„๋ฅผ ํฌํ•จํ•ด ์ง์ ‘ FastAPI ์•ฑ์„ ์–ด๋–ป๊ฒŒ ๋ฐฐํฌํ•˜๋Š”์ง€๊นŒ์ง€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿค“ diff --git a/docs/ko/docs/deployment/https.md b/docs/ko/docs/deployment/https.md new file mode 100644 index 0000000000..888ec61592 --- /dev/null +++ b/docs/ko/docs/deployment/https.md @@ -0,0 +1,231 @@ +# HTTPS ์•Œ์•„๋ณด๊ธฐ { #about-https } + +HTTPS๋Š” ๊ทธ๋ƒฅ โ€œ์ผœ์ ธ ์žˆ๊ฑฐ๋‚˜โ€ ์•„๋‹ˆ๋ฉด โ€œ๊บผ์ ธ ์žˆ๋Š”โ€ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ํ›จ์”ฌ ๋” ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค. + +/// tip | ํŒ + +๋ฐ”์˜๊ฑฐ๋‚˜ ๋ณ„๋กœ ์‹ ๊ฒฝ ์“ฐ๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋ฉด, ๋‹ค์Œ ์„น์…˜์—์„œ ๋‹ค์–‘ํ•œ ๊ธฐ๋ฒ•์œผ๋กœ ๋ชจ๋“  ๊ฒƒ์„ ์„ค์ •ํ•˜๋Š” ๋‹จ๊ณ„๋ณ„ ์•ˆ๋‚ด๋ฅผ ๊ณ„์† ๋ณด์„ธ์š”. + +/// + +์†Œ๋น„์ž ๊ด€์ ์—์„œ **HTTPS์˜ ๊ธฐ๋ณธ์„ ๋ฐฐ์šฐ๋ ค๋ฉด** <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. + +์ด์ œ **๊ฐœ๋ฐœ์ž ๊ด€์ **์—์„œ HTTPS๋ฅผ ์ƒ๊ฐํ•  ๋•Œ ์—ผ๋‘์— ๋‘์–ด์•ผ ํ•  ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: + +* HTTPS๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด, **์„œ๋ฒ„**๊ฐ€ **์ œ3์ž**๊ฐ€ ๋ฐœ๊ธ‰ํ•œ **"์ธ์ฆ์„œ(certificates)"**๋ฅผ **๋ณด์œ **ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + * ์ด ์ธ์ฆ์„œ๋Š” ์‹ค์ œ๋กœ ์ œ3์ž๊ฐ€ โ€œ์ƒ์„ฑโ€ํ•ด ์ฃผ๋Š” ๊ฒƒ์ด๊ณ , ์„œ๋ฒ„๊ฐ€ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ œ3์ž๋กœ๋ถ€ํ„ฐ **๋ฐœ๊ธ‰/ํš๋“**ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. +* ์ธ์ฆ์„œ์—๋Š” **์œ ํšจ ๊ธฐ๊ฐ„**์ด ์žˆ์Šต๋‹ˆ๋‹ค. + * ์ฆ‰, **๋งŒ๋ฃŒ**๋ฉ๋‹ˆ๋‹ค. + * ๊ทธ๋ฆฌ๊ณ  ๋‚˜๋ฉด ์ œ3์ž๋กœ๋ถ€ํ„ฐ ๋‹ค์‹œ **๊ฐฑ์‹ **ํ•ด์„œ **์žฌ๋ฐœ๊ธ‰/์žฌํš๋“**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +* ์—ฐ๊ฒฐ์˜ ์•”ํ˜ธํ™”๋Š” **TCP ๋ ˆ๋ฒจ**์—์„œ ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค. + * ์ด๋Š” **HTTP๋ณด๋‹ค ํ•œ ๊ณ„์ธต ์•„๋ž˜**์ž…๋‹ˆ๋‹ค. + * ๋”ฐ๋ผ์„œ **์ธ์ฆ์„œ์™€ ์•”ํ˜ธํ™”** ์ฒ˜๋ฆฌ๋Š” **HTTP ์ด์ „**์— ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. +* **TCP๋Š” "๋„๋ฉ”์ธ"์„ ๋ชจ๋ฆ…๋‹ˆ๋‹ค.** IP ์ฃผ์†Œ๋งŒ ์••๋‹ˆ๋‹ค. + * ์–ด๋–ค **ํŠน์ • ๋„๋ฉ”์ธ**์„ ์š”์ฒญํ–ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋Š” **HTTP ๋ฐ์ดํ„ฐ**์— ๋“ค์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +* **HTTPS ์ธ์ฆ์„œ**๋Š” ํŠน์ • **๋„๋ฉ”์ธ**์„ โ€œ์ธ์ฆโ€ํ•˜์ง€๋งŒ, ํ”„๋กœํ† ์ฝœ๊ณผ ์•”ํ˜ธํ™”๋Š” TCP ๋ ˆ๋ฒจ์—์„œ ์ผ์–ด๋‚˜๋ฉฐ, ์–ด๋–ค ๋„๋ฉ”์ธ์„ ๋‹ค๋ฃจ๋Š”์ง€ **์•Œ๊ธฐ ์ „์—** ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. +* **๊ธฐ๋ณธ์ ์œผ๋กœ** ์ด๋Š” IP ์ฃผ์†Œ ํ•˜๋‚˜๋‹น **HTTPS ์ธ์ฆ์„œ ํ•˜๋‚˜๋งŒ** ๋‘˜ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. + * ์„œ๋ฒ„๊ฐ€ ์•„๋ฌด๋ฆฌ ํฌ๋“ , ๊ทธ ์œ„์— ์˜ฌ๋ฆฐ ๊ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์•„๋ฌด๋ฆฌ ์ž‘๋“  ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค. + * ํ•˜์ง€๋งŒ ์ด์— ๋Œ€ํ•œ **ํ•ด๊ฒฐ์ฑ…**์ด ์žˆ์Šต๋‹ˆ๋‹ค. +* **TLS** ํ”„๋กœํ† ์ฝœ(HTTP ์ด์ „, TCP ๋ ˆ๋ฒจ์—์„œ ์•”ํ˜ธํ™”๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ)์— ๋Œ€ํ•œ **ํ™•์žฅ** ์ค‘์— **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="Server Name Indication - ์„œ๋ฒ„ ์ด๋ฆ„ ํ‘œ์‹œ">SNI</abbr></a>**๋ผ๋Š” ๊ฒƒ์ด ์žˆ์Šต๋‹ˆ๋‹ค. + * ์ด SNI ํ™•์žฅ์„ ์‚ฌ์šฉํ•˜๋ฉด, ๋‹จ์ผ ์„œ๋ฒ„(**๋‹จ์ผ IP ์ฃผ์†Œ**)์—์„œ **์—ฌ๋Ÿฌ HTTPS ์ธ์ฆ์„œ**๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  **์—ฌ๋Ÿฌ HTTPS ๋„๋ฉ”์ธ/์• ํ”Œ๋ฆฌ์ผ€์ด์…˜**์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + * ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ์„œ๋ฒ„์—์„œ **๊ณต๊ฐœ IP ์ฃผ์†Œ**๋กœ ๋ฆฌ์Šค๋‹ํ•˜๋Š” **ํ•˜๋‚˜์˜** ์ปดํฌ๋„ŒํŠธ(ํ”„๋กœ๊ทธ๋žจ)๊ฐ€ ์„œ๋ฒ„์— ์žˆ๋Š” **๋ชจ๋“  HTTPS ์ธ์ฆ์„œ**์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +* ๋ณด์•ˆ ์—ฐ๊ฒฐ์„ ์–ป์€ **์ดํ›„์—๋„**, ํ†ต์‹  ํ”„๋กœํ† ์ฝœ ์ž์ฒด๋Š” **์—ฌ์ „ํžˆ HTTP**์ž…๋‹ˆ๋‹ค. + * **HTTP ํ”„๋กœํ† ์ฝœ**๋กœ ์ „์†ก๋˜๋”๋ผ๋„, ๋‚ด์šฉ์€ **์•”ํ˜ธํ™”**๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +์ผ๋ฐ˜์ ์œผ๋กœ ์„œ๋ฒ„(๋จธ์‹ , ํ˜ธ์ŠคํŠธ ๋“ฑ)์—๋Š” **ํ”„๋กœ๊ทธ๋žจ/HTTP ์„œ๋ฒ„ ํ•˜๋‚˜**๋ฅผ ์‹คํ–‰ํ•ด **HTTPS ๊ด€๋ จ ๋ถ€๋ถ„ ์ „์ฒด**๋ฅผ ๊ด€๋ฆฌํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค: **์•”ํ˜ธํ™”๋œ HTTPS ์š”์ฒญ**์„ ๋ฐ›๊ณ , ๋ณตํ˜ธํ™”๋œ **HTTP ์š”์ฒญ**์„ ๊ฐ™์€ ์„œ๋ฒ„์—์„œ ์‹คํ–‰ ์ค‘์ธ ์‹ค์ œ HTTP ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์ด ๊ฒฝ์šฐ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜)์œผ๋กœ ์ „๋‹ฌํ•˜๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ **HTTP ์‘๋‹ต**์„ ๋ฐ›์•„ ์ ์ ˆํ•œ **HTTPS ์ธ์ฆ์„œ**๋กœ **์•”ํ˜ธํ™”**ํ•œ ๋’ค **HTTPS**๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋‹ค์‹œ ๋ณด๋‚ด๋Š” ์—ญํ• ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์„œ๋ฒ„๋ฅผ ํ”ํžˆ **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">TLS Termination Proxy</a>**๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. + +TLS Termination Proxy๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* Traefik (์ธ์ฆ์„œ ๊ฐฑ์‹ ๋„ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ) +* Caddy (์ธ์ฆ์„œ ๊ฐฑ์‹ ๋„ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ) +* Nginx +* HAProxy + +## Let's Encrypt { #lets-encrypt } + +Let's Encrypt ์ด์ „์—๋Š” ์ด๋Ÿฌํ•œ **HTTPS ์ธ์ฆ์„œ**๊ฐ€ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์ œ3์ž์— ์˜ํ•ด ํŒ๋งค๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +์ธ์ฆ์„œ๋ฅผ ํš๋“ํ•˜๋Š” ๊ณผ์ •์€ ๋ฒˆ๊ฑฐ๋กญ๊ณ , ๊ฝค ๋งŽ์€ ์„œ๋ฅ˜ ์ž‘์—…์ด ํ•„์š”ํ–ˆ์œผ๋ฉฐ, ์ธ์ฆ์„œ๋„ ์ƒ๋‹นํžˆ ๋น„์ŒŒ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๊ทธ ํ›„ **<a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>**๊ฐ€ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” Linux Foundation์˜ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. ํ‘œ์ค€ ์•”ํ˜ธํ•™์  ๋ณด์•ˆ์„ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜๋Š” **HTTPS ์ธ์ฆ์„œ**๋ฅผ **๋ฌด๋ฃŒ๋กœ**, ์ž๋™ํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ์ธ์ฆ์„œ๋“ค์€ ์ˆ˜๋ช…์ด ์งง๊ณ (์•ฝ 3๊ฐœ์›”) ๊ทธ๋ž˜์„œ ์œ ํšจ ๊ธฐ๊ฐ„์ด ์งง์€ ๋งŒํผ **์‹ค์ œ๋กœ ๋ณด์•ˆ์ด ๋” ์ข‹์•„์ง€๊ธฐ๋„** ํ•ฉ๋‹ˆ๋‹ค. + +๋„๋ฉ”์ธ์€ ์•ˆ์ „ํ•˜๊ฒŒ ๊ฒ€์ฆ๋˜๋ฉฐ ์ธ์ฆ์„œ๋Š” ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด๋กœ ์ธํ•ด ์ธ์ฆ์„œ ๊ฐฑ์‹ ๋„ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋ชฉํ‘œ๋Š” ์ธ์ฆ์„œ์˜ ๋ฐœ๊ธ‰๊ณผ ๊ฐฑ์‹ ์„ ์ž๋™ํ™”ํ•˜์—ฌ **๋ฌด๋ฃŒ๋กœ, ์˜๊ตฌํžˆ, ์•ˆ์ „ํ•œ HTTPS**๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +## ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ HTTPS { #https-for-developers } + +๊ฐœ๋ฐœ์ž์—๊ฒŒ ์ค‘์š”ํ•œ ๊ฐœ๋…๋“ค์„ ์ค‘์‹ฌ์œผ๋กœ, HTTPS API๊ฐ€ ๋‹จ๊ณ„๋ณ„๋กœ ์–ด๋–ป๊ฒŒ ๋ณด์ผ ์ˆ˜ ์žˆ๋Š”์ง€ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +### ๋„๋ฉ”์ธ ์ด๋ฆ„ { #domain-name } + +์•„๋งˆ๋„ ์‹œ์ž‘์€ **๋„๋ฉ”์ธ ์ด๋ฆ„**์„ **ํš๋“**ํ•˜๋Š” ๊ฒƒ์ผ ๊ฒ๋‹ˆ๋‹ค. ๊ทธ ๋‹ค์Œ DNS ์„œ๋ฒ„(์•„๋งˆ ๊ฐ™์€ ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด)์—์„œ ์ด๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. + +๋Œ€๊ฐœ ํด๋ผ์šฐ๋“œ ์„œ๋ฒ„(๊ฐ€์ƒ ๋จธ์‹ ) ๊ฐ™์€ ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๊ณ , ๊ฑฐ๊ธฐ์—๋Š” <abbr title="That doesn't change - ๋ณ€ํ•˜์ง€ ์•Š์Œ">fixed</abbr> **๊ณต๊ฐœ IP ์ฃผ์†Œ**๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +DNS ์„œ๋ฒ„(๋“ค)์—์„œ **๋„๋ฉ”์ธ**์ด ์„œ๋ฒ„์˜ **๊ณต๊ฐœ IP ์ฃผ์†Œ**๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋„๋ก ๋ ˆ์ฝ”๋“œ(โ€œ`A record`โ€)๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. + +๋ณดํ†ต์€ ์ฒ˜์Œ ํ•œ ๋ฒˆ, ๋ชจ๋“  ๊ฒƒ์„ ์„ค์ •ํ•  ๋•Œ๋งŒ ์ด ์ž‘์—…์„ ํ•ฉ๋‹ˆ๋‹ค. + +/// tip | ํŒ + +๋„๋ฉ”์ธ ์ด๋ฆ„ ๋ถ€๋ถ„์€ HTTPS๋ณด๋‹ค ํ›จ์”ฌ ์ด์ „ ๋‹จ๊ณ„์ง€๋งŒ, ๋ชจ๋“  ๊ฒƒ์ด ๋„๋ฉ”์ธ๊ณผ IP ์ฃผ์†Œ์— ์˜์กดํ•˜๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ ์–ธ๊ธ‰ํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +### DNS { #dns } + +์ด์ œ ์‹ค์ œ HTTPS ๋ถ€๋ถ„์— ์ง‘์ค‘ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +๋จผ์ € ๋ธŒ๋ผ์šฐ์ €๋Š” **DNS ์„œ๋ฒ„**์— ์งˆ์˜ํ•˜์—ฌ, ์—ฌ๊ธฐ์„œ๋Š” `someapp.example.com`์ด๋ผ๋Š” **๋„๋ฉ”์ธ์— ๋Œ€ํ•œ IP**๊ฐ€ ๋ฌด์—‡์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + +DNS ์„œ๋ฒ„๋Š” ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ํŠน์ • **IP ์ฃผ์†Œ**๋ฅผ ์‚ฌ์šฉํ•˜๋ผ๊ณ  ์•Œ๋ ค์ค๋‹ˆ๋‹ค. ์ด๋Š” DNS ์„œ๋ฒ„์— ์„ค์ •ํ•ด ๋‘”, ์„œ๋ฒ„๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๊ณต๊ฐœ IP ์ฃผ์†Œ์ž…๋‹ˆ๋‹ค. + +<img src="/img/deployment/https/https01.drawio.svg"> + +### TLS ํ•ธ๋“œ์…ฐ์ดํฌ ์‹œ์ž‘ { #tls-handshake-start } + +๊ทธ ๋‹ค์Œ ๋ธŒ๋ผ์šฐ์ €๋Š” **ํฌํŠธ 443**(HTTPS ํฌํŠธ)์—์„œ ํ•ด๋‹น IP ์ฃผ์†Œ์™€ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค. + +ํ†ต์‹ ์˜ ์ฒซ ๋ถ€๋ถ„์€ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์‚ฌ์ด์˜ ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๊ณ , ์‚ฌ์šฉํ•  ์•”ํ˜ธํ™” ํ‚ค ๋“ฑ์„ ๊ฒฐ์ •ํ•˜๋Š” ๊ณผ์ •์ž…๋‹ˆ๋‹ค. + +<img src="/img/deployment/https/https02.drawio.svg"> + +ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๊ฐ€ TLS ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ์ด ๊ณผ์ •์„ **TLS ํ•ธ๋“œ์…ฐ์ดํฌ**๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. + +### SNI ํ™•์žฅ์„ ์‚ฌ์šฉํ•˜๋Š” TLS { #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`์— ๋Œ€ํ•œ ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +<img src="/img/deployment/https/https03.drawio.svg"> + +ํด๋ผ์ด์–ธํŠธ๋Š” ์ด๋ฏธ ํ•ด๋‹น TLS ์ธ์ฆ์„œ๋ฅผ ์ƒ์„ฑํ•œ ์ฃผ์ฒด(์—ฌ๊ธฐ์„œ๋Š” Let's Encrypt์ด์ง€๋งŒ, ์ด๋Š” ๋’ค์—์„œ ๋‹ค์‹œ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค)๋ฅผ **์‹ ๋ขฐ**ํ•˜๋ฏ€๋กœ, ์ธ์ฆ์„œ๊ฐ€ ์œ ํšจํ•œ์ง€ **๊ฒ€์ฆ**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•ด ํด๋ผ์ด์–ธํŠธ์™€ TLS Termination Proxy๋Š” ๋‚˜๋จธ์ง€ **TCP ํ†ต์‹ **์„ ์–ด๋–ป๊ฒŒ **์•”ํ˜ธํ™”ํ• ์ง€ ๊ฒฐ์ •**ํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ์จ **TLS ํ•ธ๋“œ์…ฐ์ดํฌ** ๋‹จ๊ณ„๊ฐ€ ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค. + +์ดํ›„ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๋Š” TLS๊ฐ€ ์ œ๊ณตํ•˜๋Š” **์•”ํ˜ธํ™”๋œ TCP ์—ฐ๊ฒฐ**์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์—ฐ๊ฒฐ์„ ์‚ฌ์šฉํ•ด ์‹ค์ œ **HTTP ํ†ต์‹ **์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๊ฒƒ์ด ๋ฐ”๋กœ **HTTPS**์ž…๋‹ˆ๋‹ค. ์ˆœ์ˆ˜(์•”ํ˜ธํ™”๋˜์ง€ ์•Š์€) TCP ์—ฐ๊ฒฐ ๋Œ€์‹  **์•ˆ์ „ํ•œ TLS ์—ฐ๊ฒฐ** ์•ˆ์—์„œ **HTTP**๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// tip | ํŒ + +ํ†ต์‹ ์˜ ์•”ํ˜ธํ™”๋Š” HTTP ๋ ˆ๋ฒจ์ด ์•„๋‹ˆ๋ผ **TCP ๋ ˆ๋ฒจ**์—์„œ ์ผ์–ด๋‚œ๋‹ค๋Š” ์ ์— ์ฃผ์˜ํ•˜์„ธ์š”. + +/// + +### HTTPS ์š”์ฒญ { #https-request } + +์ด์ œ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„(๊ตฌ์ฒด์ ์œผ๋กœ๋Š” ๋ธŒ๋ผ์šฐ์ €์™€ TLS Termination Proxy)๊ฐ€ **์•”ํ˜ธํ™”๋œ TCP ์—ฐ๊ฒฐ**์„ ๊ฐ–๊ฒŒ ๋˜์—ˆ์œผ๋‹ˆ **HTTP ํ†ต์‹ **์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ํด๋ผ์ด์–ธํŠธ๋Š” **HTTPS ์š”์ฒญ**์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ด๋Š” ์•”ํ˜ธํ™”๋œ TLS ์—ฐ๊ฒฐ์„ ํ†ตํ•ด ์ „๋‹ฌ๋˜๋Š” HTTP ์š”์ฒญ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. + +<img src="/img/deployment/https/https04.drawio.svg"> + +### ์š”์ฒญ ๋ณตํ˜ธํ™” { #decrypt-the-request } + +TLS Termination Proxy๋Š” ํ•ฉ์˜๋œ ์•”ํ˜ธํ™”๋ฅผ ์‚ฌ์šฉํ•ด **์š”์ฒญ์„ ๋ณตํ˜ธํ™”**ํ•˜๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค(์˜ˆ: FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋Š” Uvicorn ํ”„๋กœ์„ธ์Šค)์— **์ผ๋ฐ˜(๋ณตํ˜ธํ™”๋œ) HTTP ์š”์ฒญ**์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. + +<img src="/img/deployment/https/https05.drawio.svg"> + +### HTTP ์‘๋‹ต { #http-response } + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  **์ผ๋ฐ˜(์•”ํ˜ธํ™”๋˜์ง€ ์•Š์€) HTTP ์‘๋‹ต**์„ TLS Termination Proxy๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค. + +<img src="/img/deployment/https/https06.drawio.svg"> + +### HTTPS ์‘๋‹ต { #https-response } + +๊ทธ ๋‹ค์Œ TLS Termination Proxy๋Š” ์ด์ „์— ํ•ฉ์˜ํ•œ ์•”ํ˜ธํ™”( `someapp.example.com` ์ธ์ฆ์„œ๋กœ ์‹œ์ž‘๋œ ๊ฒƒ)๋ฅผ ์‚ฌ์šฉํ•ด **์‘๋‹ต์„ ์•”ํ˜ธํ™”**ํ•˜๊ณ , ๋ธŒ๋ผ์šฐ์ €๋กœ ๋‹ค์‹œ ๋ณด๋ƒ…๋‹ˆ๋‹ค. + +์ดํ›„ ๋ธŒ๋ผ์šฐ์ €๋Š” ์‘๋‹ต์ด ์œ ํšจํ•œ์ง€, ์˜ฌ๋ฐ”๋ฅธ ์•”ํ˜ธํ™” ํ‚ค๋กœ ์•”ํ˜ธํ™”๋˜์—ˆ๋Š”์ง€ ๋“ฑ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ **์‘๋‹ต์„ ๋ณตํ˜ธํ™”**ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + +<img src="/img/deployment/https/https07.drawio.svg"> + +ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €)๋Š” ์•ž์„œ **HTTPS ์ธ์ฆ์„œ**๋กœ ํ•ฉ์˜ํ•œ ์•”ํ˜ธํ™”๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ํ•ด๋‹น ์‘๋‹ต์ด ์˜ฌ๋ฐ”๋ฅธ ์„œ๋ฒ„์—์„œ ์™”๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ { #multiple-applications } + +๊ฐ™์€ ์„œ๋ฒ„(๋˜๋Š” ์—ฌ๋Ÿฌ ์„œ๋ฒ„)์—๋Š” ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค๋ฅธ API ํ”„๋กœ๊ทธ๋žจ์ด๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์ฒ˜๋Ÿผ **์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜**์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํŠน์ • IP์™€ ํฌํŠธ ์กฐํ•ฉ์€ ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ(์˜ˆ์‹œ์—์„œ๋Š” TLS Termination Proxy), ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜/ํ”„๋กœ์„ธ์Šค๋„ **๊ณต๊ฐœ IP์™€ ํฌํŠธ ์กฐํ•ฉ**์„ ๋™์ผํ•˜๊ฒŒ ์“ฐ๋ ค๊ณ ๋งŒ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์„œ๋ฒ„์—์„œ ํ•จ๊ป˜ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +<img src="/img/deployment/https/https08.drawio.svg"> + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด TLS Termination Proxy๊ฐ€ **์—ฌ๋Ÿฌ ๋„๋ฉ”์ธ**์— ๋Œ€ํ•œ HTTPS์™€ ์ธ์ฆ์„œ๋ฅผ **์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜**์— ๋Œ€ํ•ด ์ฒ˜๋ฆฌํ•˜๊ณ , ๊ฐ ๊ฒฝ์šฐ์— ๋งž๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์š”์ฒญ์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์ธ์ฆ์„œ ๊ฐฑ์‹  { #certificate-renewal } + +๋ฏธ๋ž˜์˜ ์–ด๋А ์‹œ์ ์—๋Š” ๊ฐ ์ธ์ฆ์„œ๊ฐ€ **๋งŒ๋ฃŒ**๋ฉ๋‹ˆ๋‹ค(ํš๋“ ํ›„ ์•ฝ 3๊ฐœ์›”). + +๊ทธ ๋‹ค์Œ์—๋Š” ๋˜ ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋žจ(๊ฒฝ์šฐ์— ๋”ฐ๋ผ ๋ณ„๋„ ํ”„๋กœ๊ทธ๋žจ์ผ ์ˆ˜๋„ ์žˆ๊ณ , ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ๊ฐ™์€ TLS Termination Proxy์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค)์ด Let's Encrypt์™€ ํ†ต์‹ ํ•˜์—ฌ ์ธ์ฆ์„œ๋ฅผ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. + +<img src="/img/deployment/https/https.drawio.svg"> + +**TLS ์ธ์ฆ์„œ**๋Š” IP ์ฃผ์†Œ๊ฐ€ ์•„๋‹ˆ๋ผ **๋„๋ฉ”์ธ ์ด๋ฆ„**๊ณผ **์—ฐ๊ฒฐ**๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ์ธ์ฆ์„œ๋ฅผ ๊ฐฑ์‹ ํ•˜๋ ค๋ฉด, ๊ฐฑ์‹  ํ”„๋กœ๊ทธ๋žจ์ด ๊ถŒํ•œ ๊ธฐ๊ด€(Let's Encrypt)์—๊ฒŒ ํ•ด๋‹น ๋„๋ฉ”์ธ์„ ์‹ค์ œ๋กœ **โ€œ์†Œ์œ โ€ํ•˜๊ณ  ์ œ์–ดํ•˜๊ณ  ์žˆ์Œ**์„ **์ฆ๋ช…**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ฅผ ์œ„ํ•ด, ๊ทธ๋ฆฌ๊ณ  ๋‹ค์–‘ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์š”๊ตฌ๋ฅผ ์ˆ˜์šฉํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋„๋ฆฌ ์“ฐ์ด๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* **์ผ๋ถ€ DNS ๋ ˆ์ฝ”๋“œ ์ˆ˜์ •**. + * ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ๊ฐฑ์‹  ํ”„๋กœ๊ทธ๋žจ์ด DNS ์ œ๊ณต์—…์ฒด์˜ API๋ฅผ ์ง€์›ํ•ด์•ผ ํ•˜๋ฏ€๋กœ, ์‚ฌ์šฉํ•˜๋Š” DNS ์ œ๊ณต์—…์ฒด์— ๋”ฐ๋ผ ๊ฐ€๋Šฅํ•  ์ˆ˜๋„, ์•„๋‹ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +* ๋„๋ฉ”์ธ๊ณผ ์—ฐ๊ฒฐ๋œ ๊ณต๊ฐœ IP ์ฃผ์†Œ์—์„œ **์„œ๋ฒ„๋กœ ์‹คํ–‰**(์ ์–ด๋„ ์ธ์ฆ์„œ ๋ฐœ๊ธ‰ ๊ณผ์ • ๋™์•ˆ). + * ์•ž์—์„œ ๋งํ–ˆ๋“ฏ ํŠน์ • IP์™€ ํฌํŠธ์—์„œ๋Š” ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค๋งŒ ๋ฆฌ์Šค๋‹ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + * ์ด๊ฒƒ์ด ๋™์ผํ•œ TLS Termination Proxy๊ฐ€ ์ธ์ฆ์„œ ๊ฐฑ์‹  ๊ณผ์ •๊นŒ์ง€ ์ฒ˜๋ฆฌํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•œ ์ด์œ  ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. + * ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด TLS Termination Proxy๋ฅผ ์ž ์‹œ ์ค‘์ง€ํ•˜๊ณ , ๊ฐฑ์‹  ํ”„๋กœ๊ทธ๋žจ์„ ์‹œ์ž‘ํ•ด ์ธ์ฆ์„œ๋ฅผ ํš๋“ํ•œ ๋‹ค์Œ, TLS Termination Proxy์— ์ธ์ฆ์„œ๋ฅผ ์„ค์ •ํ•˜๊ณ , ๋‹ค์‹œ TLS Termination Proxy๋ฅผ ์žฌ์‹œ์ž‘ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” TLS Termination Proxy๊ฐ€ ๊บผ์ ธ ์žˆ๋Š” ๋™์•ˆ ์•ฑ(๋“ค)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์ด์ƒ์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +์•ฑ์„ ๊ณ„์† ์ œ๊ณตํ•˜๋ฉด์„œ ์ด ๊ฐฑ์‹  ๊ณผ์ •์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„(์˜ˆ: Uvicorn)์—์„œ TLS ์ธ์ฆ์„œ๋ฅผ ์ง์ ‘ ์“ฐ๋Š” ๋Œ€์‹  TLS Termination Proxy๋กœ HTTPS๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” **๋ณ„๋„์˜ ์‹œ์Šคํ…œ**์„ ๋‘๊ณ  ์‹ถ์–ด์ง€๋Š” ์ฃผ์š” ์ด์œ  ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. + +## ํ”„๋ก์‹œ ์ „๋‹ฌ ํ—ค๋” { #proxy-forwarded-headers } + +ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•ด HTTPS๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ, **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„**(์˜ˆ: FastAPI CLI๋ฅผ ํ†ตํ•œ Uvicorn)๋Š” HTTPS ๊ณผ์ •์— ๋Œ€ํ•ด ์•„๋ฌด๊ฒƒ๋„ ์•Œ์ง€ ๋ชปํ•˜๊ณ  **TLS Termination Proxy**์™€๋Š” ์ผ๋ฐ˜ HTTP๋กœ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค. + +์ด **ํ”„๋ก์‹œ**๋Š” ๋ณดํ†ต ์š”์ฒญ์„ **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„**์— ์ „๋‹ฌํ•˜๊ธฐ ์ „์—, ์š”์ฒญ์ด ํ”„๋ก์‹œ์— ์˜ํ•ด **์ „๋‹ฌ(forwarded)**๋˜๊ณ  ์žˆ์Œ์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„๊ฐ€ ์•Œ ์ˆ˜ ์žˆ๋„๋ก ์ผ๋ถ€ HTTP ํ—ค๋”๋ฅผ ์ฆ‰์„์—์„œ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +ํ”„๋ก์‹œ ํ—ค๋”๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a> +* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a> +* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a> + +/// + +๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„**๋Š” ์ž์‹ ์ด ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” **ํ”„๋ก์‹œ** ๋’ค์— ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ชจ๋ฅด๋ฏ€๋กœ, ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ๊ทธ ํ—ค๋”๋“ค์„ ์‹ ๋ขฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„**๊ฐ€ **ํ”„๋ก์‹œ**๊ฐ€ ๋ณด๋‚ธ *forwarded* ํ—ค๋”๋ฅผ ์‹ ๋ขฐํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. FastAPI CLI๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, *CLI Option* `--forwarded-allow-ips`๋ฅผ ์‚ฌ์šฉํ•ด ์–ด๋–ค IP์—์„œ ์˜จ *forwarded* ํ—ค๋”๋ฅผ ์‹ ๋ขฐํ• ์ง€ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„**๊ฐ€ ์‹ ๋ขฐํ•˜๋Š” **ํ”„๋ก์‹œ**๋กœ๋ถ€ํ„ฐ๋งŒ ํ†ต์‹ ์„ ๋ฐ›๋Š”๋‹ค๋ฉด, `--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/ko/docs/deployment/manually.md b/docs/ko/docs/deployment/manually.md new file mode 100644 index 0000000000..e85dd02a32 --- /dev/null +++ b/docs/ko/docs/deployment/manually.md @@ -0,0 +1,157 @@ +# ์„œ๋ฒ„๋ฅผ ์ˆ˜๋™์œผ๋กœ ์‹คํ–‰ํ•˜๊ธฐ { #run-a-server-manually } + +## `fastapi run` ๋ช…๋ น ์‚ฌ์šฉํ•˜๊ธฐ { #use-the-fastapi-run-command } + +์š”์•ฝํ•˜๋ฉด, `fastapi run`์„ ์‚ฌ์šฉํ•ด FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„œ๋น„์Šคํ•˜์„ธ์š”: + +<div class="termy"> + +```console +$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u> + + <span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server ๐Ÿš€ + + Searching for package file structure from directories + with <font color="#3465A4">__init__.py</font> files + Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> ๐Ÿ main.py + + <span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with + the following code: + + <u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u> + + <span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font> + + Logs: + + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>2306215</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C + to quit<b>)</b> +``` + +</div> + +๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์—๋Š” ์ด๊ฒƒ์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ˜Ž + +์˜ˆ๋ฅผ ๋“ค์–ด ์ด ๋ช…๋ น์€ ์ปจํ…Œ์ด๋„ˆ๋‚˜ ์„œ๋ฒ„ ๋“ฑ์—์„œ **FastAPI** ์•ฑ์„ ์‹œ์ž‘ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ASGI ์„œ๋ฒ„ { #asgi-servers } + +์ด์ œ ์กฐ๊ธˆ ๋” ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +FastAPI๋Š” <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr>๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š”, Python ์›น ํ”„๋ ˆ์ž„์›Œํฌ์™€ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ‘œ์ค€์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. FastAPI๋Š” ASGI ์›น ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. + +์›๊ฒฉ ์„œ๋ฒ„ ๋จธ์‹ ์—์„œ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(๋˜๋Š” ๋‹ค๋ฅธ ASGI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜)์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ํ•ต์‹ฌ ์š”์†Œ๋Š” **Uvicorn** ๊ฐ™์€ ASGI ์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ์ž…๋‹ˆ๋‹ค. `fastapi` ๋ช…๋ น์—๋Š” ๊ธฐ๋ณธ์œผ๋กœ ์ด๊ฒƒ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +๋‹ค์Œ์„ ํฌํ•จํ•ด ์—ฌ๋Ÿฌ ๋Œ€์•ˆ์ด ์žˆ์Šต๋‹ˆ๋‹ค: + +* <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a>: ๊ณ ์„ฑ๋Šฅ ASGI ์„œ๋ฒ„. +* <a href="https://hypercorn.readthedocs.io/" class="external-link" target="_blank">Hypercorn</a>: HTTP/2 ๋ฐ Trio ๋“ฑ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ๊ณผ ํ˜ธํ™˜๋˜๋Š” ASGI ์„œ๋ฒ„. +* <a href="https://github.com/django/daphne" class="external-link" target="_blank">Daphne</a>: Django Channels๋ฅผ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ ASGI ์„œ๋ฒ„. +* <a href="https://github.com/emmett-framework/granian" class="external-link" target="_blank">Granian</a>: Python ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ Rust HTTP ์„œ๋ฒ„. +* <a href="https://unit.nginx.org/howto/fastapi/" class="external-link" target="_blank">NGINX Unit</a>: NGINX Unit์€ ๊ฐ€๋ณ๊ณ  ๋‹ค์šฉ๋„๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋Ÿฐํƒ€์ž„์ž…๋‹ˆ๋‹ค. + +## ์„œ๋ฒ„ ๋จธ์‹ ๊ณผ ์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ { #server-machine-and-server-program } + +์ด๋ฆ„์— ๊ด€ํ•ด ๊ธฐ์–ตํ•ด ๋‘˜ ์ž‘์€ ๋””ํ…Œ์ผ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ’ก + +"**server**"๋ผ๋Š” ๋‹จ์–ด๋Š” ๋ณดํ†ต ์›๊ฒฉ/ํด๋ผ์šฐ๋“œ ์ปดํ“จํ„ฐ(๋ฌผ๋ฆฌ ๋˜๋Š” ๊ฐ€์ƒ ๋จธ์‹ )์™€, ๊ทธ ๋จธ์‹ ์—์„œ ์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ๊ทธ๋žจ(์˜ˆ: Uvicorn) ๋‘˜ ๋‹ค๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +์ผ๋ฐ˜์ ์œผ๋กœ "server"๋ฅผ ์ฝ์„ ๋•Œ, ์ด ๋‘ ๊ฐ€์ง€ ์ค‘ ํ•˜๋‚˜๋ฅผ ์˜๋ฏธํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. + +์›๊ฒฉ ๋จธ์‹ ์„ ๊ฐ€๋ฆฌํ‚ฌ ๋•Œ๋Š” **server**๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด์ง€๋งŒ, **machine**, **VM**(virtual machine), **node**๋ผ๊ณ  ๋ถ€๋ฅด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ๋“ค์€ ๋ณดํ†ต Linux๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์›๊ฒฉ ๋จธ์‹ ์˜ ํ•œ ํ˜•ํƒœ๋ฅผ ๋œปํ•˜๋ฉฐ, ๊ทธ๊ณณ์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. + +## ์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ ์„ค์น˜ํ•˜๊ธฐ { #install-the-server-program } + +FastAPI๋ฅผ ์„ค์น˜ํ•˜๋ฉด ํ”„๋กœ๋•์…˜ ์„œ๋ฒ„์ธ Uvicorn์ด ํ•จ๊ป˜ ์„ค์น˜๋˜๋ฉฐ, `fastapi run` ๋ช…๋ น์œผ๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ASGI ์„œ๋ฒ„๋ฅผ ์ˆ˜๋™์œผ๋กœ ์„ค์น˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +[๊ฐ€์ƒ ํ™˜๊ฒฝ](../virtual-environments.md){.internal-link target=_blank}์„ ๋งŒ๋“ค๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, ์„œ๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ค์น˜ํ•˜์„ธ์š”. + +์˜ˆ๋ฅผ ๋“ค์–ด Uvicorn์„ ์„ค์น˜ํ•˜๋ ค๋ฉด: + +<div class="termy"> + +```console +$ pip install "uvicorn[standard]" + +---> 100% +``` + +</div> + +๋‹ค๋ฅธ ์–ด๋–ค ASGI ์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ๋„ ๋น„์Šทํ•œ ๊ณผ์ •์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. + +/// tip | ํŒ + +`standard`๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด Uvicorn์ด ๊ถŒ์žฅ๋˜๋Š” ์ถ”๊ฐ€ ์˜์กด์„ฑ ๋ช‡ ๊ฐ€์ง€๋ฅผ ์„ค์น˜ํ•˜๊ณ  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +์—ฌ๊ธฐ์—๋Š” `asyncio`๋ฅผ ๊ณ ์„ฑ๋Šฅ์œผ๋กœ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ๋Š” ๋“œ๋กญ์ธ ๋Œ€์ฒด์žฌ์ธ `uvloop`๊ฐ€ ํฌํ•จ๋˜๋ฉฐ, ํฐ ๋™์‹œ์„ฑ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +`pip install "fastapi[standard]"` ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ FastAPI๋ฅผ ์„ค์น˜ํ•˜๋ฉด `uvicorn[standard]`๋„ ํ•จ๊ป˜ ์„ค์น˜๋ฉ๋‹ˆ๋‹ค. + +/// + +## ์„œ๋ฒ„ ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ํ•˜๊ธฐ { #run-the-server-program } + +ASGI ์„œ๋ฒ„๋ฅผ ์ˆ˜๋™์œผ๋กœ ์„ค์น˜ํ–ˆ๋‹ค๋ฉด, ๋ณดํ†ต FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž„ํฌํŠธํ•˜๊ธฐ ์œ„ํ•ด ํŠน๋ณ„ํ•œ ํ˜•์‹์˜ import string์„ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: + +<div class="termy"> + +```console +$ uvicorn main:app --host 0.0.0.0 --port 80 + +<span style="color: green;">INFO</span>: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) +``` + +</div> + +/// note | ์ฐธ๊ณ  + +`uvicorn main:app` ๋ช…๋ น์€ ๋‹ค์Œ์„ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค: + +* `main`: ํŒŒ์ผ `main.py`(Python "module"). +* `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/ko/docs/how-to/authentication-error-status-code.md b/docs/ko/docs/how-to/authentication-error-status-code.md new file mode 100644 index 0000000000..47120cae68 --- /dev/null +++ b/docs/ko/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 ๋ช…์„ธ์ธ <a href="https://datatracker.ietf.org/doc/html/rfc7235#section-3.1" class="external-link" target="_blank">RFC 7235</a>, <a href="https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized" class="external-link" target="_blank">RFC 9110</a>๋ฅผ ๋”ฐ๋ผ ์‘๋‹ต์— ํ•ฉ๋ฆฌ์ ์ธ `WWW-Authenticate` ํ—ค๋”๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์–ด๋–ค ์ด์œ ๋กœ๋“  ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ด์ „ ๋™์ž‘์— ์˜์กดํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ๋ณด์•ˆ ํด๋ž˜์Šค์—์„œ `make_not_authenticated_error` ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜์—ฌ ์ด์ „ ๋™์ž‘์œผ๋กœ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ๊ธฐ๋ณธ๊ฐ’์ธ `401 Unauthorized` ์˜ค๋ฅ˜ ๋Œ€์‹  `403 Forbidden` ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” `HTTPBearer`์˜ ์„œ๋ธŒํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} + +/// tip | ํŒ + +ํ•จ์ˆ˜๋Š” ์˜ˆ์™ธ๋ฅผ `raise`ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์˜ˆ์™ธ ์ธ์Šคํ„ด์Šค๋ฅผ `return`ํ•œ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š”(`raise`) ์ž‘์—…์€ ๋‚ด๋ถ€ ์ฝ”๋“œ์˜ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์—์„œ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. + +/// diff --git a/docs/ko/docs/how-to/custom-docs-ui-assets.md b/docs/ko/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 0000000000..d6383c29cb --- /dev/null +++ b/docs/ko/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# ์ปค์Šคํ…€ Docs UI ์ •์  ์—์…‹(์ž์ฒด ํ˜ธ์ŠคํŒ…) { #custom-docs-ui-static-assets-self-hosting } + +API ๋ฌธ์„œ๋Š” **Swagger UI**์™€ **ReDoc**์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ๊ฐ๊ฐ JavaScript์™€ CSS ํŒŒ์ผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋Ÿฌํ•œ ํŒŒ์ผ์€ <abbr title="Content Delivery Network - ์ฝ˜ํ…์ธ  ์ „์†ก ๋„คํŠธ์›Œํฌ: ์ผ๋ฐ˜์ ์œผ๋กœ ์—ฌ๋Ÿฌ ์„œ๋ฒ„๋กœ ๊ตฌ์„ฑ๋˜์–ด JavaScript์™€ CSS ๊ฐ™์€ ์ •์  ํŒŒ์ผ์„ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ๋ณดํ†ต ํด๋ผ์ด์–ธํŠธ์— ๋” ๊ฐ€๊นŒ์šด ์„œ๋ฒ„์—์„œ ํŒŒ์ผ์„ ์ œ๊ณตํ•ด ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.">CDN</abbr>์—์„œ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์ด๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํŠน์ • CDN์„ ์ง€์ •ํ•˜๊ฑฐ๋‚˜ ํŒŒ์ผ์„ ์ง์ ‘ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +## JavaScript์™€ CSS์šฉ ์ปค์Šคํ…€ CDN { #custom-cdn-for-javascript-and-css } + +์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค๋ฅธ <abbr title="Content Delivery Network">CDN</abbr>์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด `https://unpkg.com/`์„ ์‚ฌ์šฉํ•˜๋ ค๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. + +์ด๋Š” ์˜ˆ๋ฅผ ๋“ค์–ด ํŠน์ • ๊ตญ๊ฐ€์—์„œ ์ผ๋ถ€ URL์„ ์ œํ•œํ•˜๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์ž๋™ ๋ฌธ์„œ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ธฐ { #disable-the-automatic-docs } + +์ฒซ ๋ฒˆ์งธ ๋‹จ๊ณ„๋Š” ์ž๋™ ๋ฌธ์„œ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ž๋™ ๋ฌธ์„œ๋Š” ๊ธฐ๋ณธ CDN์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด `FastAPI` ์•ฑ์„ ์ƒ์„ฑํ•  ๋•Œ ํ•ด๋‹น URL์„ `None`์œผ๋กœ ์„ค์ •ํ•˜์„ธ์š”: + +{* ../../docs_src/custom_docs_ui/tutorial001_py39.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_py39.py hl[2:6,11:19,22:24,27:33] *} + +/// tip | ํŒ + +`swagger_ui_redirect`์— ๋Œ€ํ•œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋Š” OAuth2๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋„์›€์ด ๋˜๋Š” ํ—ฌํผ์ž…๋‹ˆ๋‹ค. + +API๋ฅผ OAuth2 provider์™€ ํ†ตํ•ฉํ•˜๋ฉด ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•œ ๋’ค ํš๋“ํ•œ ์ž๊ฒฉ ์ฆ๋ช…์œผ๋กœ API ๋ฌธ์„œ๋กœ ๋‹ค์‹œ ๋Œ์•„์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ OAuth2 ์ธ์ฆ์„ ์‚ฌ์šฉํ•ด API์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +Swagger UI๊ฐ€ ์ด ๊ณผ์ •์„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌํ•ด ์ฃผ์ง€๋งŒ, ์ด๋ฅผ ์œ„ํ•ด ์ด "redirect" ํ—ฌํผ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +/// + +### ํ…Œ์ŠคํŠธ์šฉ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ๋งŒ๋“ค๊ธฐ { #create-a-path-operation-to-test-it } + +์ด์ œ ๋ชจ๋“  ๊ฒƒ์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“œ์„ธ์š”: + +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} + +### ํ…Œ์ŠคํŠธํ•˜๊ธฐ { #test-it } + +์ด์ œ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>์—์„œ ๋ฌธ์„œ์— ์ ‘์†ํ•œ ๋’ค ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋ฉด, ์ƒˆ 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**๋Š” ๋‹ค์Œ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: + +* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a> +* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" class="external-link" target="_blank">`swagger-ui.css`</a> + +**ReDoc**์€ ๋‹ค์Œ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: + +* <a href="https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js" class="external-link" target="_blank">`redoc.standalone.js`</a> + +์ดํ›„ ํŒŒ์ผ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +``` +. +โ”œโ”€โ”€ app +โ”‚ย ย  โ”œโ”€โ”€ __init__.py +โ”‚ย ย  โ”œโ”€โ”€ main.py +โ””โ”€โ”€ static + โ”œโ”€โ”€ redoc.standalone.js + โ”œโ”€โ”€ swagger-ui-bundle.js + โ””โ”€โ”€ swagger-ui.css +``` + +### ์ •์  ํŒŒ์ผ ์ œ๊ณตํ•˜๊ธฐ { #serve-the-static-files } + +* `StaticFiles`๋ฅผ importํ•ฉ๋‹ˆ๋‹ค. +* ํŠน์ • ๊ฒฝ๋กœ์— `StaticFiles()` ์ธ์Šคํ„ด์Šค๋ฅผ "๋งˆ์šดํŠธ(mount)"ํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} + +### ์ •์  ํŒŒ์ผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ { #test-the-static-files } + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘ํ•˜๊ณ  <a href="http://127.0.0.1:8000/static/redoc.standalone.js" class="external-link" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a>๋กœ ์ด๋™ํ•˜์„ธ์š”. + +**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_py39.py hl[9] *} + +### ์ •์  ํŒŒ์ผ์„ ์œ„ํ•œ ์ปค์Šคํ…€ ๋ฌธ์„œ ํฌํ•จํ•˜๊ธฐ { #include-the-custom-docs-for-static-files } + +๊ทธ๋ฆฌ๊ณ  ์ปค์Šคํ…€ CDN์„ ์‚ฌ์šฉํ•  ๋•Œ์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ, ์ด์ œ ์ปค์Šคํ…€ ๋ฌธ์„œ๋ฅผ ์œ„ํ•œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋‹ค์‹œ ํ•œ ๋ฒˆ, 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_py39.py hl[2:6,14:22,25:27,30:36] *} + +/// tip | ํŒ + +`swagger_ui_redirect`์— ๋Œ€ํ•œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋Š” OAuth2๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋„์›€์ด ๋˜๋Š” ํ—ฌํผ์ž…๋‹ˆ๋‹ค. + +API๋ฅผ OAuth2 provider์™€ ํ†ตํ•ฉํ•˜๋ฉด ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•œ ๋’ค ํš๋“ํ•œ ์ž๊ฒฉ ์ฆ๋ช…์œผ๋กœ API ๋ฌธ์„œ๋กœ ๋‹ค์‹œ ๋Œ์•„์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ OAuth2 ์ธ์ฆ์„ ์‚ฌ์šฉํ•ด API์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +Swagger UI๊ฐ€ ์ด ๊ณผ์ •์„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌํ•ด ์ฃผ์ง€๋งŒ, ์ด๋ฅผ ์œ„ํ•ด ์ด "redirect" ํ—ฌํผ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +/// + +### ์ •์  ํŒŒ์ผ ํ…Œ์ŠคํŠธ์šฉ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ๋งŒ๋“ค๊ธฐ { #create-a-path-operation-to-test-static-files } + +์ด์ œ ๋ชจ๋“  ๊ฒƒ์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“œ์„ธ์š”: + +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} + +### ์ •์  ํŒŒ์ผ UI ํ…Œ์ŠคํŠธํ•˜๊ธฐ { #test-static-files-ui } + +์ด์ œ WiFi ์—ฐ๊ฒฐ์„ ๋Š๊ณ  <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>์—์„œ ๋ฌธ์„œ์— ์ ‘์†ํ•œ ๋’ค ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด ๋ณด์„ธ์š”. + +์ธํ„ฐ๋„ท์ด ์—†์–ด๋„ API ๋ฌธ์„œ๋ฅผ ๋ณด๊ณ , API์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/how-to/custom-request-and-route.md b/docs/ko/docs/how-to/custom-request-and-route.md new file mode 100644 index 0000000000..335193bb35 --- /dev/null +++ b/docs/ko/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# ์ปค์Šคํ…€ Request ๋ฐ APIRoute ํด๋ž˜์Šค { #custom-request-and-apiroute-class } + +์ผ๋ถ€ ๊ฒฝ์šฐ์—๋Š” `Request`์™€ `APIRoute` ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋กœ์ง์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํŠนํžˆ, ์ด๋Š” middleware์— ์žˆ๋Š” ๋กœ์ง์˜ ์ข‹์€ ๋Œ€์•ˆ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ฒ˜๋ฆฌ๋˜๊ธฐ ์ „์— ์š”์ฒญ ๋ฐ”๋””๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์กฐ์ž‘ํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. + +/// danger | ์œ„ํ—˜ + +์ด ๊ธฐ๋Šฅ์€ "๊ณ ๊ธ‰" ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. + +**FastAPI**๋ฅผ ์ด์ œ ๋ง‰ ์‹œ์ž‘ํ–ˆ๋‹ค๋ฉด ์ด ์„น์…˜์€ ๊ฑด๋„ˆ๋›ฐ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. + +/// + +## ์‚ฌ์šฉ ์‚ฌ๋ก€ { #use-cases } + +์‚ฌ์šฉ ์‚ฌ๋ก€์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค: + +* JSON์ด ์•„๋‹Œ ์š”์ฒญ ๋ฐ”๋””๋ฅผ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ(์˜ˆ: <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>). +* 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`์ด ์—†์œผ๋ฉด ๋ฐ”๋””๋ฅผ ์••์ถ• ํ•ด์ œํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋™์ผํ•œ route ํด๋ž˜์Šค๊ฐ€ 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`๊ฐ€ ์žˆ๋Š”๋ฐ, ์ด๋Š” ์š”์ฒญ์˜ ๋ฐ”๋””๋ฅผ "๋ฐ›๊ธฐ(receive)" ์œ„ํ•œ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. + +`scope` `dict`์™€ `receive` ํ•จ์ˆ˜๋Š” ๋ชจ๋‘ ASGI ๋ช…์„ธ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์ด ๋‘ ๊ฐ€์ง€, `scope`์™€ `receive`๊ฐ€ ์ƒˆ๋กœ์šด `Request` ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐ ํ•„์š”ํ•œ ๊ฒƒ๋“ค์ž…๋‹ˆ๋‹ค. + +`Request`์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๋ ค๋ฉด <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">Starlette์˜ Requests ๋ฌธ์„œ</a>๋ฅผ ํ™•์ธํ•˜์„ธ์š”. + +/// + +`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/ko/docs/how-to/extending-openapi.md b/docs/ko/docs/how-to/extending-openapi.md new file mode 100644 index 0000000000..d04d6c23e3 --- /dev/null +++ b/docs/ko/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# OpenAPI ํ™•์žฅํ•˜๊ธฐ { #extending-openapi } + +์ƒ์„ฑ๋œ OpenAPI ์Šคํ‚ค๋งˆ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ์„น์…˜์—์„œ ๊ทธ ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +## ์ผ๋ฐ˜์ ์ธ ๊ณผ์ • { #the-normal-process } + +์ผ๋ฐ˜์ ์ธ(๊ธฐ๋ณธ) ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. + +`FastAPI` ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์ธ์Šคํ„ด์Šค)์—๋Š” OpenAPI ์Šคํ‚ค๋งˆ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š” `.openapi()` ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ณผ์ •์—์„œ `/openapi.json`(๋˜๋Š” `openapi_url`์— ์„ค์ •ํ•œ ๊ฒฝ๋กœ)์šฉ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค. + +์ด ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ `.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`: ๋ผ์šฐํŠธ ๋ชฉ๋ก. ๊ฐ๊ฐ ๋“ฑ๋ก๋œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์ž…๋‹ˆ๋‹ค. `app.routes`์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +`summary` ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” OpenAPI 3.1.0 ์ด์ƒ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, FastAPI 0.99.0 ์ด์ƒ์—์„œ ์ง€์›๋ฉ๋‹ˆ๋‹ค. + +/// + +## ๊ธฐ๋ณธ๊ฐ’ ๋ฎ์–ด์“ฐ๊ธฐ { #overriding-the-defaults } + +์œ„ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ, ๋™์ผํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด OpenAPI ์Šคํ‚ค๋งˆ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ•„์š”ํ•œ ๊ฐ ๋ถ€๋ถ„์„ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" class="external-link" target="_blank">์ปค์Šคํ…€ ๋กœ๊ณ ๋ฅผ ํฌํ•จํ•˜๊ธฐ ์œ„ํ•œ ReDoc์˜ OpenAPI ํ™•์žฅ</a>์„ ์ถ”๊ฐ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +### ์ผ๋ฐ˜์ ์ธ **FastAPI** { #normal-fastapi } + +๋จผ์ €, ํ‰์†Œ์ฒ˜๋Ÿผ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ชจ๋‘ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/extending_openapi/tutorial001_py39.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] *} + +### OpenAPI ์Šคํ‚ค๋งˆ ์ˆ˜์ •ํ•˜๊ธฐ { #modify-the-openapi-schema } + +์ด์ œ OpenAPI ์Šคํ‚ค๋งˆ์˜ `info` "object"์— ์ปค์Šคํ…€ `x-logo`๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ReDoc ํ™•์žฅ์„ ๋”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *} + +### OpenAPI ์Šคํ‚ค๋งˆ ์บ์‹œํ•˜๊ธฐ { #cache-the-openapi-schema } + +์ƒ์„ฑํ•œ ์Šคํ‚ค๋งˆ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ "cache"๋กœ `.openapi_schema` ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ API ๋ฌธ์„œ๋ฅผ ์—ด ๋•Œ๋งˆ๋‹ค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์Šคํ‚ค๋งˆ๋ฅผ ๋งค๋ฒˆ ์ƒ์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. + +์Šคํ‚ค๋งˆ๋Š” ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑ๋˜๊ณ , ์ดํ›„ ์š”์ฒญ์—์„œ๋Š” ๊ฐ™์€ ์บ์‹œ๋œ ์Šคํ‚ค๋งˆ๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *} + +### ๋ฉ”์„œ๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๊ธฐ { #override-the-method } + +์ด์ œ `.openapi()` ๋ฉ”์„œ๋“œ๋ฅผ ์ƒˆ ํ•จ์ˆ˜๋กœ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *} + +### ํ™•์ธํ•˜๊ธฐ { #check-it } + +<a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>๋กœ ์ด๋™ํ•˜๋ฉด ์ปค์Šคํ…€ ๋กœ๊ณ (์ด ์˜ˆ์‹œ์—์„œ๋Š” **FastAPI** ๋กœ๊ณ )๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/tutorial/extending-openapi/image01.png"> diff --git a/docs/ko/docs/how-to/general.md b/docs/ko/docs/how-to/general.md new file mode 100644 index 0000000000..a18dc68a21 --- /dev/null +++ b/docs/ko/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 } + +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ํƒœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , ๋ฌธ์„œ 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 } + +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ์ง€์› ์ค‘๋‹จ(deprecate)์œผ๋กœ ํ‘œ์‹œํ•˜๊ณ , ๋ฌธ์„œ 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/ko/docs/how-to/graphql.md b/docs/ko/docs/how-to/graphql.md new file mode 100644 index 0000000000..3cc467eb71 --- /dev/null +++ b/docs/ko/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**์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +* <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> ๐Ÿ“ + * <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">FastAPI์šฉ ๋ฌธ์„œ</a> ์ œ๊ณต +* <a href="https://ariadnegraphql.org/" class="external-link" target="_blank">Ariadne</a> + * <a href="https://ariadnegraphql.org/docs/fastapi-integration" class="external-link" target="_blank">FastAPI์šฉ ๋ฌธ์„œ</a> ์ œ๊ณต +* <a href="https://tartiflette.io/" class="external-link" target="_blank">Tartiflette</a> + * ASGI ํ†ตํ•ฉ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด <a href="https://tartiflette.github.io/tartiflette-asgi/" class="external-link" target="_blank">Tartiflette ASGI</a> ์‚ฌ์šฉ +* <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a> + * <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a> ์‚ฌ์šฉ + +## Strawberry๋กœ GraphQL ์‚ฌ์šฉํ•˜๊ธฐ { #graphql-with-strawberry } + +**GraphQL**๋กœ ์ž‘์—…ํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜ ์ž‘์—…ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, <a href="https://strawberry.rocks/" class="external-link" target="_blank">**Strawberry**</a>๋ฅผ **๊ถŒ์žฅ**ํ•ฉ๋‹ˆ๋‹ค. **FastAPI**์˜ ์„ค๊ณ„์™€ ๊ฐ€์žฅ ๊ฐ€๊น๊ณ , ๋ชจ๋“  ๊ฒƒ์ด **type annotations**์— ๊ธฐ๋ฐ˜ํ•ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ ํ˜ธํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ œ๊ฒŒ ๋ฌป๋Š”๋‹ค๋ฉด ์•„๋งˆ **Strawberry**๋ฅผ ๋จผ์ € ์‹œ๋„ํ•ด ๋ณด๋ผ๊ณ  ์ œ์•ˆํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๋‹ค์Œ์€ Strawberry๋ฅผ FastAPI์™€ ํ†ตํ•ฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ž…๋‹ˆ๋‹ค: + +{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *} + +<a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry ๋ฌธ์„œ</a>์—์„œ Strawberry์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋˜ํ•œ <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">FastAPI์—์„œ Strawberry ์‚ฌ์šฉ</a>์— ๋Œ€ํ•œ ๋ฌธ์„œ๋„ ํ™•์ธํ•ด ๋ณด์„ธ์š”. + +## Starlette์˜ ์˜ˆ์ „ `GraphQLApp` { #older-graphqlapp-from-starlette } + +์ด์ „ ๋ฒ„์ „์˜ Starlette์—๋Š” <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>๊ณผ ํ†ตํ•ฉํ•˜๊ธฐ ์œ„ํ•œ `GraphQLApp` ํด๋ž˜์Šค๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. + +์ด๊ฒƒ์€ Starlette์—์„œ deprecated ๋˜์—ˆ์ง€๋งŒ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋˜ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฐ™์€ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋‹ค๋ฃจ๊ณ  **๊ฑฐ์˜ ๋™์ผํ•œ ์ธํ„ฐํŽ˜์ด์Šค**๋ฅผ ๊ฐ€์ง„ <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a>๋กœ ์‰ฝ๊ฒŒ **๋งˆ์ด๊ทธ๋ ˆ์ด์…˜**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// tip | ํŒ + +GraphQL์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, ์ปค์Šคํ…€ ํด๋ž˜์Šค์™€ ํƒ€์ž… ๋Œ€์‹  type annotations์— ๊ธฐ๋ฐ˜ํ•œ <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a>๋ฅผ ์—ฌ์ „ํžˆ ํ™•์ธํ•ด ๋ณด์‹œ๊ธธ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. + +/// + +## ๋” ์•Œ์•„๋ณด๊ธฐ { #learn-more } + +<a href="https://graphql.org/" class="external-link" target="_blank">๊ณต์‹ GraphQL ๋ฌธ์„œ</a>์—์„œ **GraphQL**์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋˜ํ•œ ์œ„์—์„œ ์„ค๋ช…ํ•œ ๊ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•ด์„œ๋„ ํ•ด๋‹น ๋งํฌ์—์„œ ๋” ์ž์„ธํžˆ ์ฝ์–ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/how-to/index.md b/docs/ko/docs/how-to/index.md new file mode 100644 index 0000000000..9321c488bd --- /dev/null +++ b/docs/ko/docs/how-to/index.md @@ -0,0 +1,13 @@ +# How To - ๋ ˆ์‹œํ”ผ { #how-to-recipes } + +์—ฌ๊ธฐ์—์„œ๋Š” **์—ฌ๋Ÿฌ ์ฃผ์ œ**์— ๋Œ€ํ•œ ๋‹ค์–‘ํ•œ ๋ ˆ์‹œํ”ผ(โ€œhow toโ€ ๊ฐ€์ด๋“œ)๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋Œ€๋ถ€๋ถ„์˜ ์•„์ด๋””์–ด๋Š” ์–ด๋А ์ •๋„ **์„œ๋กœ ๋…๋ฆฝ์ **์ด๋ฉฐ, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ **์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ**์— ์ง์ ‘ ์ ์šฉ๋˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ•™์Šตํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. + +ํ”„๋กœ์ ํŠธ์— ํฅ๋ฏธ๋กญ๊ณ  ์œ ์šฉํ•ด ๋ณด์ด๋Š” ๊ฒƒ์ด ์žˆ๋‹ค๋ฉด ํ™•์ธํ•ด ๋ณด์„ธ์š”. ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด ์•„๋งˆ ๊ฑด๋„ˆ๋›ฐ์–ด๋„ ๋ฉ๋‹ˆ๋‹ค. + +/// tip | ํŒ + +**FastAPI๋ฅผ ๊ตฌ์กฐ์ ์œผ๋กœ ํ•™์Šต**ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด(๊ถŒ์žฅ), ๋Œ€์‹  [ํŠœํ† ๋ฆฌ์–ผ - ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ](../tutorial/index.md){.internal-link target=_blank}๋ฅผ ์žฅ๋ณ„๋กœ ์ฝ์–ด๋ณด์„ธ์š”. + +/// 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 new file mode 100644 index 0000000000..6e528ecaf5 --- /dev/null +++ b/docs/ko/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 ๋ฒ„์ „์—์„œ๋Š” v2๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด, Pydantic v2 ๋‚ด๋ถ€์—์„œ Pydantic v1์„(`pydantic.v1`๋กœ) ๋ถ€๋ถ„์ ์œผ๋กœ ์ง€์›ํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. + +FastAPI 0.126.0 ๋ฒ„์ „์—์„œ๋Š” Pydantic v1 ์ง€์›์„ ์ค‘๋‹จํ–ˆ์ง€๋งŒ, `pydantic.v1`์€ ์ž ์‹œ ๋™์•ˆ ๊ณ„์† ์ง€์›ํ–ˆ์Šต๋‹ˆ๋‹ค. + +/// warning | ๊ฒฝ๊ณ  + +Pydantic ํŒ€์€ **Python 3.14**๋ถ€ํ„ฐ ์ตœ์‹  Python ๋ฒ„์ „์—์„œ Pydantic v1 ์ง€์›์„ ์ค‘๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค. + +์—ฌ๊ธฐ์—๋Š” `pydantic.v1`๋„ ํฌํ•จ๋˜๋ฉฐ, Python 3.14 ์ด์ƒ์—์„œ๋Š” ๋” ์ด์ƒ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +Python์˜ ์ตœ์‹  ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Pydantic v2๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +/// + +Pydantic v1์„ ์‚ฌ์šฉํ•˜๋Š” ์˜ค๋ž˜๋œ FastAPI ์•ฑ์ด ์žˆ๋‹ค๋ฉด, ์—ฌ๊ธฐ์„œ๋Š” ์ด๋ฅผ Pydantic v2๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ์ ์ง„์  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋•๋Š” **FastAPI 0.119.0์˜ ๊ธฐ๋Šฅ**์„ ์†Œ๊ฐœํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. + +## ๊ณต์‹ ๊ฐ€์ด๋“œ { #official-guide } + +Pydantic์—๋Š” v1์—์„œ v2๋กœ์˜ ๊ณต์‹ <a href="https://docs.pydantic.dev/latest/migration/" class="external-link" target="_blank">Migration Guide</a>๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +์—ฌ๊ธฐ์—๋Š” ๋ฌด์—‡์ด ๋ฐ”๋€Œ์—ˆ๋Š”์ง€, ๊ฒ€์ฆ์ด ์ด์ œ ์–ด๋–ป๊ฒŒ ๋” ์ •ํ™•ํ•˜๊ณ  ์—„๊ฒฉํ•ด์กŒ๋Š”์ง€, ๊ฐ€๋Šฅํ•œ ์ฃผ์˜์‚ฌํ•ญ ๋“ฑ๋„ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์„ ๋” ์ž˜ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ์ฝ์–ด๋ณด๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค. + +## ํ…Œ์ŠคํŠธ { #tests } + +์•ฑ์— ๋Œ€ํ•œ [tests](../tutorial/testing.md){.internal-link target=_blank}๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์ง€์†์  ํ†ตํ•ฉ(CI)์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”. + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ๋„ ๋ชจ๋“  ๊ฒƒ์ด ๊ธฐ๋Œ€ํ•œ ๋Œ€๋กœ ๊ณ„์† ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## `bump-pydantic` { #bump-pydantic } + +๋งŽ์€ ๊ฒฝ์šฐ, ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ์—†์ด ์ผ๋ฐ˜์ ์ธ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด Pydantic v1์—์„œ Pydantic v2๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ณผ์ • ๋Œ€๋ถ€๋ถ„์„ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ฐ™์€ Pydantic ํŒ€์ด ์ œ๊ณตํ•˜๋Š” <a href="https://github.com/pydantic/bump-pydantic" class="external-link" target="_blank">`bump-pydantic`</a>๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๋„๊ตฌ๋Š” ๋ณ€๊ฒฝํ•ด์•ผ ํ•˜๋Š” ์ฝ”๋“œ์˜ ๋Œ€๋ถ€๋ถ„์„ ์ž๋™์œผ๋กœ ๋ฐ”๊พธ๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ด์„œ ๋ชจ๋“  ๊ฒƒ์ด ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ž˜ ๋œ๋‹ค๋ฉด ๋์ž…๋‹ˆ๋‹ค. ๐Ÿ˜Ž + +## v2 ์•ˆ์˜ Pydantic v1 { #pydantic-v1-in-v2 } + +Pydantic v2๋Š” Pydantic v1์˜ ๋ชจ๋“  ๊ฒƒ์„ ์„œ๋ธŒ๋ชจ๋“ˆ `pydantic.v1`๋กœ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” Python 3.13๋ณด๋‹ค ๋†’์€ ๋ฒ„์ „์—์„œ๋Š” ๋” ์ด์ƒ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +์ฆ‰, Pydantic v2์˜ ์ตœ์‹  ๋ฒ„์ „์„ ์„ค์น˜ํ•œ ๋’ค, ์ด ์„œ๋ธŒ๋ชจ๋“ˆ์—์„œ ์˜ˆ์ „ Pydantic v1 ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ importํ•˜์—ฌ ์˜ˆ์ „ Pydantic 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๋ถ€ํ„ฐ๋Š” v2๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด, Pydantic v2 ๋‚ด๋ถ€์˜ Pydantic v1์— ๋Œ€ํ•ด์„œ๋„ ๋ถ€๋ถ„์ ์ธ ์ง€์›์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ Pydantic์„ ์ตœ์‹  v2๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๊ณ , import๋ฅผ `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 ๋ชจ๋ธ๊ณผ ํ•จ๊ป˜ `Body`, `Query`, `Form` ๋“ฑ ํŒŒ๋ผ๋ฏธํ„ฐ์šฉ FastAPI ์ „์šฉ ๋„๊ตฌ ์ผ๋ถ€๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด, Pydantic v2๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋งˆ์น  ๋•Œ๊นŒ์ง€ `fastapi.temp_pydantic_v1_params`์—์„œ importํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../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๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๊ณ , ๋ชจ๋“  ๋ชจ๋ธ์˜ import๋ฅผ `pydantic.v1`์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ ๋‹ค์Œ Pydantic v1์—์„œ v2๋กœ ๋ชจ๋ธ์„ ๊ทธ๋ฃน ๋‹จ์œ„๋กœ, ์ ์ง„์ ์ธ ๋‹จ๊ณ„๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‹œ์ž‘ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๐Ÿšถ diff --git a/docs/ko/docs/how-to/separate-openapi-schemas.md b/docs/ko/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 0000000000..055429c26f --- /dev/null +++ b/docs/ko/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**๊ฐ€ ์ƒ๊ธฐ๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. **๊ธฐ๋ณธ๊ฐ’(default value)**์ด ์žˆ๋Š”์ง€ ์—ฌ๋ถ€์— ๋”ฐ๋ผ, ์ž…๋ ฅ์šฉ๊ณผ ์ถœ๋ ฅ์šฉ์œผ๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค. + +์ด๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ํ•„์š”ํ•˜๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +## ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ์„ ์œ„ํ•œ 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` ํ•„๋“œ์— **๋นจ๊ฐ„ ๋ณ„ํ‘œ**๊ฐ€ ์—†๊ณ , ํ•„์ˆ˜๋กœ ํ‘œ์‹œ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<div class="screenshot"> +<img src="/img/tutorial/separate-openapi-schemas/image01.png"> +</div> + +### ์ถœ๋ ฅ์šฉ ๋ชจ๋ธ { #model-for-output } + +ํ•˜์ง€๋งŒ ๊ฐ™์€ ๋ชจ๋ธ์„ ๋‹ค์Œ์ฒ˜๋Ÿผ ์ถœ๋ ฅ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +...`description`์— ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ทธ ํ•„๋“œ์— ๋Œ€ํ•ด **์•„๋ฌด๊ฒƒ๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋”๋ผ๋„** ์—ฌ์ „ํžˆ ๊ทธ **๊ธฐ๋ณธ๊ฐ’**์ด ๋“ค์–ด๊ฐ€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +### ์ถœ๋ ฅ ์‘๋‹ต ๋ฐ์ดํ„ฐ์šฉ ๋ชจ๋ธ { #model-for-output-response-data } + +๋ฌธ์„œ์—์„œ ์ง์ ‘ ๋™์ž‘์‹œ์ผœ ์‘๋‹ต์„ ํ™•์ธํ•ด ๋ณด๋ฉด, ์ฝ”๋“œ๊ฐ€ `description` ํ•„๋“œ ์ค‘ ํ•˜๋‚˜์— ์•„๋ฌด๊ฒƒ๋„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•˜๋”๋ผ๋„ JSON ์‘๋‹ต์—๋Š” ๊ธฐ๋ณธ๊ฐ’(`null`)์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค: + +<div class="screenshot"> +<img src="/img/tutorial/separate-openapi-schemas/image02.png"> +</div> + +์ด๋Š” ํ•ด๋‹น ํ•„๋“œ๊ฐ€ **ํ•ญ์ƒ ๊ฐ’์„ ๊ฐ€์ง„๋‹ค๋Š” ๊ฒƒ**์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ ๊ทธ ๊ฐ’์ด ๋•Œ๋กœ๋Š” `None`(JSON์—์„œ๋Š” `null`)์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ฆ‰, API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ๋Š” ๊ฐ’์ด ์กด์žฌํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ํ•„์š”๊ฐ€ ์—†๊ณ , **ํ•„๋“œ๊ฐ€ ํ•ญ์ƒ ์กด์žฌํ•œ๋‹ค๊ณ  ๊ฐ€์ •**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์–ด๋–ค ๊ฒฝ์šฐ์—๋Š” ๊ธฐ๋ณธ๊ฐ’ `None`์ด ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค. + +์ด๋ฅผ OpenAPI์—์„œ ํ‘œํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์€, ๊ทธ ํ•„๋“œ๋ฅผ **required**๋กœ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•ญ์ƒ ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +์ด ๋•Œ๋ฌธ์—, ํ•˜๋‚˜์˜ ๋ชจ๋ธ์ด๋ผ๋„ **์ž…๋ ฅ์šฉ์ธ์ง€ ์ถœ๋ ฅ์šฉ์ธ์ง€**์— ๋”ฐ๋ผ JSON Schema๊ฐ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +* **์ž…๋ ฅ**์—์„œ๋Š” `description`์ด **ํ•„์ˆ˜๊ฐ€ ์•„๋‹˜** +* **์ถœ๋ ฅ**์—์„œ๋Š” **ํ•„์ˆ˜์ž„** (๊ทธ๋ฆฌ๊ณ  ๊ฐ’์€ `None`์ผ ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, JSON ์šฉ์–ด๋กœ๋Š” `null`) + +### ๋ฌธ์„œ์—์„œ์˜ ์ถœ๋ ฅ์šฉ ๋ชจ๋ธ { #model-for-output-in-docs } + +๋ฌธ์„œ์—์„œ ์ถœ๋ ฅ ๋ชจ๋ธ์„ ํ™•์ธํ•ด ๋ณด๋ฉด, `name`๊ณผ `description` **๋‘˜ ๋‹ค** **๋นจ๊ฐ„ ๋ณ„ํ‘œ**๋กœ **ํ•„์ˆ˜**๋กœ ํ‘œ์‹œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค: + +<div class="screenshot"> +<img src="/img/tutorial/separate-openapi-schemas/image03.png"> +</div> + +### ๋ฌธ์„œ์—์„œ์˜ ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ ๋ชจ๋ธ { #model-for-input-and-output-in-docs } + +๋˜ OpenAPI์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  Schemas(JSON Schemas)๋ฅผ ํ™•์ธํ•ด ๋ณด๋ฉด, `Item-Input` ํ•˜๋‚˜์™€ `Item-Output` ํ•˜๋‚˜, ์ด๋ ‡๊ฒŒ ๋‘ ๊ฐœ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +`Item-Input`์—์„œ๋Š” `description`์ด **ํ•„์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ฉฐ**, ๋นจ๊ฐ„ ๋ณ„ํ‘œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ `Item-Output`์—์„œ๋Š” `description`์ด **ํ•„์ˆ˜์ด๋ฉฐ**, ๋นจ๊ฐ„ ๋ณ„ํ‘œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +<div class="screenshot"> +<img src="/img/tutorial/separate-openapi-schemas/image04.png"> +</div> + +**Pydantic v2**์˜ ์ด ๊ธฐ๋Šฅ ๋•๋ถ„์— API ๋ฌธ์„œ๋Š” ๋” **์ •๋ฐ€**ํ•ด์ง€๊ณ , ์ž๋™ ์ƒ์„ฑ๋œ ํด๋ผ์ด์–ธํŠธ์™€ SDK๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ทธ๊ฒƒ๋“ค๋„ ๋” ์ •๋ฐ€ํ•ด์ ธ์„œ ๋” ๋‚˜์€ **developer experience**์™€ ์ผ๊ด€์„ฑ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ + +## ์Šคํ‚ค๋งˆ๋ฅผ ๋ถ„๋ฆฌํ•˜์ง€ ์•Š๊ธฐ { #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`์€ **ํ•„์ˆ˜๊ฐ€ ์•„๋‹Œ ๊ฒƒ**์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค: + +<div class="screenshot"> +<img src="/img/tutorial/separate-openapi-schemas/image05.png"> +</div> diff --git a/docs/ko/docs/how-to/testing-database.md b/docs/ko/docs/how-to/testing-database.md new file mode 100644 index 0000000000..2d7798d707 --- /dev/null +++ b/docs/ko/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ŠคํŠธํ•˜๊ธฐ { #testing-a-database } + +๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, SQL, SQLModel์— ๋Œ€ํ•ด์„œ๋Š” <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel ๋ฌธ์„œ</a>์—์„œ ํ•™์Šตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ + +<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">FastAPI์—์„œ SQLModel์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋ฏธ๋‹ˆ ํŠœํ† ๋ฆฌ์–ผ</a>๋„ ์žˆ์Šต๋‹ˆ๋‹ค. โœจ + +ํ•ด๋‹น ํŠœํ† ๋ฆฌ์–ผ์—๋Š” <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/" class="external-link" target="_blank">SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ŠคํŠธ</a>์— ๋Œ€ํ•œ ์„น์…˜๋„ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž diff --git a/docs/ko/docs/tutorial/bigger-applications.md b/docs/ko/docs/tutorial/bigger-applications.md new file mode 100644 index 0000000000..cfc3900d4c --- /dev/null +++ b/docs/ko/docs/tutorial/bigger-applications.md @@ -0,0 +1,504 @@ +# ๋” ํฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ - ์—ฌ๋Ÿฌ ํŒŒ์ผ { #bigger-applications-multiple-files } + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋‚˜ ์›น 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` ํŒŒ์ผ์ด ์—ฌ๋Ÿฌ ๊ฐœ ์žˆ์Šต๋‹ˆ๋‹ค: ๊ฐ ๋””๋ ‰ํ„ฐ๋ฆฌ ๋˜๋Š” ํ•˜์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ํ•˜๋‚˜์”ฉ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ํŒŒ์ผ๋“ค์ด ํ•œ ํŒŒ์ผ์˜ ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฅธ ํŒŒ์ผ๋กœ importํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด `app/main.py`์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ค„์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +``` +from app.routers import items +``` + +/// + +* `app` ๋””๋ ‰ํ„ฐ๋ฆฌ์—๋Š” ๋ชจ๋“  ๊ฒƒ์ด ๋“ค์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋น„์–ด ์žˆ๋Š” ํŒŒ์ผ `app/__init__.py`๊ฐ€ ์žˆ์–ด "Python package"(โ€œPython modulesโ€์˜ ๋ชจ์Œ)์ธ `app`์ด ๋ฉ๋‹ˆ๋‹ค. +* `app/main.py` ํŒŒ์ผ์ด ์žˆ์Šต๋‹ˆ๋‹ค. Python package(`__init__.py` ํŒŒ์ผ์ด ์žˆ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ) ์•ˆ์— ์žˆ์œผ๋ฏ€๋กœ, ์ด package์˜ "module"์ž…๋‹ˆ๋‹ค: `app.main`. +* `app/dependencies.py` ํŒŒ์ผ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. `app/main.py`์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ "module"์ž…๋‹ˆ๋‹ค: `app.dependencies`. +* `app/routers/` ํ•˜์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ์žˆ๊ณ , ์—ฌ๊ธฐ์— ๋˜ `__init__.py` ํŒŒ์ผ์ด ์žˆ์œผ๋ฏ€๋กœ "Python subpackage"์ž…๋‹ˆ๋‹ค: `app.routers`. +* `app/routers/items.py` ํŒŒ์ผ์€ `app/routers/` package ์•ˆ์— ์žˆ์œผ๋ฏ€๋กœ, submodule์ž…๋‹ˆ๋‹ค: `app.routers.items`. +* `app/routers/users.py`๋„ ๋™์ผํ•˜๊ฒŒ ๋˜ ๋‹ค๋ฅธ submodule์ž…๋‹ˆ๋‹ค: `app.routers.users`. +* `app/internal/` ํ•˜์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ๋„ ์žˆ๊ณ  ์—ฌ๊ธฐ์— `__init__.py`๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ๋˜ ๋‹ค๋ฅธ "Python subpackage"์ž…๋‹ˆ๋‹ค: `app.internal`. +* ๊ทธ๋ฆฌ๊ณ  `app/internal/admin.py` ํŒŒ์ผ์€ ๋˜ ๋‹ค๋ฅธ submodule์ž…๋‹ˆ๋‹ค: `app.internal.admin`. + +<img src="/img/tutorial/bigger-applications/package.drawio.svg"> + +๊ฐ™์€ ํŒŒ์ผ ๊ตฌ์กฐ์— ์ฃผ์„์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +```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`์˜ submodule์ด๋ผ๊ณ  ํ•ด๋ด…์‹œ๋‹ค. + +์ฝ”๋“œ๋ฅผ ์ •๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ์ž์™€ ๊ด€๋ จ๋œ *path operations*๋ฅผ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ์™€ ๋ถ„๋ฆฌํ•ด ๋‘๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์ด๊ฒƒ์€ ์—ฌ์ „ํžˆ ๊ฐ™์€ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜/์›น API์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค(๊ฐ™์€ "Python Package"์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค). + +`APIRouter`๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๋‹น ๋ชจ๋“ˆ์˜ *path operations*๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### `APIRouter` importํ•˜๊ธฐ { #import-apirouter } + +`FastAPI` ํด๋ž˜์Šค์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ importํ•˜๊ณ  "instance"๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/bigger_applications/app_an_py39/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_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} + +`APIRouter`๋Š” "๋ฏธ๋‹ˆ `FastAPI`" ํด๋ž˜์Šค๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋™์ผํ•œ ์˜ต์…˜๋“ค์ด ๋ชจ๋‘ ์ง€์›๋ฉ๋‹ˆ๋‹ค. + +๋™์ผํ•œ `parameters`, `responses`, `dependencies`, `tags` ๋“ฑ๋“ฑ. + +/// tip | ํŒ + +์ด ์˜ˆ์‹œ์—์„œ๋Š” ๋ณ€์ˆ˜ ์ด๋ฆ„์ด `router`์ด์ง€๋งŒ, ์›ํ•˜๋Š” ์ด๋ฆ„์œผ๋กœ ์ง€์–ด๋„ ๋ฉ๋‹ˆ๋‹ค. + +/// + +์ด์ œ ์ด `APIRouter`๋ฅผ ๋ฉ”์ธ `FastAPI` ์•ฑ์— ํฌํ•จ(include)ํ•  ๊ฒƒ์ด์ง€๋งŒ, ๋จผ์ € dependencies์™€ ๋‹ค๋ฅธ `APIRouter` ํ•˜๋‚˜๋ฅผ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +## Dependencies { #dependencies } + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์—ฌ๋Ÿฌ ์œ„์น˜์—์„œ ์‚ฌ์šฉ๋˜๋Š” dependencies๊ฐ€ ์ผ๋ถ€ ํ•„์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ž˜์„œ ์ด๋ฅผ ๋ณ„๋„์˜ `dependencies` ๋ชจ๋“ˆ(`app/dependencies.py`)์— ๋‘ก๋‹ˆ๋‹ค. + +์ด์ œ ๊ฐ„๋‹จํ•œ dependency๋ฅผ ์‚ฌ์šฉํ•ด ์ปค์Šคํ…€ `X-Token` ํ—ค๋”๋ฅผ ์ฝ์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} + +/// tip | ํŒ + +์ด ์˜ˆ์‹œ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์ž„์˜๋กœ ๋งŒ๋“  ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์‹ค์ œ ์ƒํ™ฉ์—์„œ๋Š” ํ†ตํ•ฉ๋œ [Security ์œ ํ‹ธ๋ฆฌํ‹ฐ](security/index.md){.internal-link target=_blank}๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +## `APIRouter`๊ฐ€ ์žˆ๋Š” ๋˜ ๋‹ค๋ฅธ ๋ชจ๋“ˆ { #another-module-with-apirouter } + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ "items"๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ „์šฉ endpoint๋“ค๋„ `app/routers/items.py` ๋ชจ๋“ˆ์— ์žˆ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค. + +์—ฌ๊ธฐ์—๋Š” ๋‹ค์Œ์— ๋Œ€ํ•œ *path operations*๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: + +* `/items/` +* `/items/{item_id}` + +๊ตฌ์กฐ๋Š” `app/routers/users.py`์™€ ์™„์ „ํžˆ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ์กฐ๊ธˆ ๋” ๋˜‘๋˜‘ํ•˜๊ฒŒ, ์ฝ”๋“œ๋ฅผ ์•ฝ๊ฐ„ ๋‹จ์ˆœํ™”ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. + +์ด ๋ชจ๋“ˆ์˜ ๋ชจ๋“  *path operations*์—๋Š” ๋‹ค์Œ์ด ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค: + +* ๊ฒฝ๋กœ `prefix`: `/items`. +* `tags`: (ํƒœ๊ทธ ํ•˜๋‚˜: `items`). +* ์ถ”๊ฐ€ `responses`. +* `dependencies`: ๋ชจ๋‘ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  `X-Token` dependency๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ๊ฐ *path operation*๋งˆ๋‹ค ๋งค๋ฒˆ ๋ชจ๋‘ ์ถ”๊ฐ€ํ•˜๋Š” ๋Œ€์‹ , `APIRouter`์— ํ•œ ๋ฒˆ์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/bigger_applications/app_an_py39/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`์ž…๋‹ˆ๋‹ค. + +๋˜ํ•œ ์ด router์— ํฌํ•จ๋œ ๋ชจ๋“  *path operations*์— ์ ์šฉ๋  `tags` ๋ชฉ๋ก๊ณผ ์ถ”๊ฐ€ `responses`๋„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  router์˜ ๋ชจ๋“  *path operations*์— ์ถ”๊ฐ€๋  `dependencies` ๋ชฉ๋ก๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•ด๋‹น ๊ฒฝ๋กœ๋“ค๋กœ ๋“ค์–ด์˜ค๋Š” ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ์‹คํ–‰/ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค. + +/// tip | ํŒ + +[*path operation decorator์˜ dependencies*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, *path operation function*์— ์–ด๋–ค ๊ฐ’๋„ ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +/// + +์ตœ์ข…์ ์œผ๋กœ item ๊ฒฝ๋กœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* `/items/` +* `/items/{item_id}` + +...์˜๋„ํ•œ ๊ทธ๋Œ€๋กœ์ž…๋‹ˆ๋‹ค. + +* ๋‹จ์ผ ๋ฌธ์ž์—ด `"items"`๋ฅผ ํฌํ•จํ•˜๋Š” ํƒœ๊ทธ ๋ชฉ๋ก์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. + * ์ด "tags"๋Š” ์ž๋™ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ ์‹œ์Šคํ…œ(OpenAPI ์‚ฌ์šฉ)์— ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. +* ๋ชจ๋‘ ๋ฏธ๋ฆฌ ์ •์˜๋œ `responses`๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. +* ์ด ๋ชจ๋“  *path operations*๋Š” ์‹คํ–‰๋˜๊ธฐ ์ „์— `dependencies` ๋ชฉ๋ก์ด ํ‰๊ฐ€/์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + * ํŠน์ • *path operation*์— dependencies๋ฅผ ์ถ”๊ฐ€๋กœ ์„ ์–ธํ•˜๋ฉด **๊ทธ๊ฒƒ๋“ค๋„ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค**. + * router dependencies๊ฐ€ ๋จผ์ € ์‹คํ–‰๋˜๊ณ , ๊ทธ ๋‹ค์Œ์— [decorator์˜ `dependencies`](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, ๊ทธ๋ฆฌ๊ณ  ์ผ๋ฐ˜ ํŒŒ๋ผ๋ฏธํ„ฐ dependencies๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + * [`scopes`๊ฐ€ ์žˆ๋Š” `Security` dependencies](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// tip | ํŒ + +`APIRouter`์— `dependencies`๋ฅผ ๋‘๋Š” ๊ฒƒ์€ ์˜ˆ๋ฅผ ๋“ค์–ด ์ „์ฒด *path operations* ๊ทธ๋ฃน์— ์ธ์ฆ์„ ์š”๊ตฌํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ์— ๊ฐœ๋ณ„์ ์œผ๋กœ dependencies๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. + +/// + +/// check | ํ™•์ธ + +`prefix`, `tags`, `responses`, `dependencies` ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” (๋‹ค๋ฅธ ๋งŽ์€ ๊ฒฝ์šฐ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ) ์ฝ”๋“œ ์ค‘๋ณต์„ ํ”ผํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” **FastAPI**์˜ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. + +/// + +### dependencies importํ•˜๊ธฐ { #import-the-dependencies } + +์ด ์ฝ”๋“œ๋Š” ๋ชจ๋“ˆ `app.routers.items`, ํŒŒ์ผ `app/routers/items.py`์— ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  dependency ํ•จ์ˆ˜๋Š” ๋ชจ๋“ˆ `app.dependencies`, ํŒŒ์ผ `app/dependencies.py`์—์„œ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ž˜์„œ dependencies์— ๋Œ€ํ•ด `..`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ƒ๋Œ€ import๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} + +#### ์ƒ๋Œ€ import๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๋ฐฉ์‹ { #how-relative-imports-work } + +/// tip | ํŒ + +import๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๋ฐฉ์‹์„ ์™„๋ฒฝํžˆ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด, ์•„๋ž˜ ๋‹ค์Œ ์„น์…˜์œผ๋กœ ๋„˜์–ด๊ฐ€์„ธ์š”. + +/// + +๋‹ค์Œ๊ณผ ๊ฐ™์ด ์  ํ•˜๋‚˜ `.`๋ฅผ ์“ฐ๋ฉด: + +```Python +from .dependencies import get_token_header +``` + +์˜๋ฏธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* ์ด ๋ชจ๋“ˆ(ํŒŒ์ผ `app/routers/items.py`)์ด ์†ํ•œ ๊ฐ™์€ package(๋””๋ ‰ํ„ฐ๋ฆฌ `app/routers/`)์—์„œ ์‹œ์ž‘ํ•ด์„œ... +* `dependencies` ๋ชจ๋“ˆ(๊ฐ€์ƒ์˜ ํŒŒ์ผ `app/routers/dependencies.py`)์„ ์ฐพ๊ณ ... +* ๊ทธ ์•ˆ์—์„œ ํ•จ์ˆ˜ `get_token_header`๋ฅผ importํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๊ทธ ํŒŒ์ผ์€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. dependencies๋Š” `app/dependencies.py` ํŒŒ์ผ์— ์žˆ์Šต๋‹ˆ๋‹ค. + +์šฐ๋ฆฌ ์•ฑ/ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ๋‹ค์‹œ ๋– ์˜ฌ๋ ค ๋ณด์„ธ์š”: + +<img src="/img/tutorial/bigger-applications/package.drawio.svg"> + +--- + +๋‹ค์Œ์ฒ˜๋Ÿผ ์  ๋‘ ๊ฐœ `..`๋ฅผ ์“ฐ๋ฉด: + +```Python +from ..dependencies import get_token_header +``` + +์˜๋ฏธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* ์ด ๋ชจ๋“ˆ(ํŒŒ์ผ `app/routers/items.py`)์ด ์†ํ•œ ๊ฐ™์€ package(๋””๋ ‰ํ„ฐ๋ฆฌ `app/routers/`)์—์„œ ์‹œ์ž‘ํ•ด์„œ... +* ์ƒ์œ„ package(๋””๋ ‰ํ„ฐ๋ฆฌ `app/`)๋กœ ์˜ฌ๋ผ๊ฐ€๊ณ ... +* ๊ทธ ์•ˆ์—์„œ `dependencies` ๋ชจ๋“ˆ(ํŒŒ์ผ `app/dependencies.py`)์„ ์ฐพ๊ณ ... +* ๊ทธ ์•ˆ์—์„œ ํ•จ์ˆ˜ `get_token_header`๋ฅผ importํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰ + +--- + +๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์  ์„ธ ๊ฐœ `...`๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด: + +```Python +from ...dependencies import get_token_header +``` + +์˜๋ฏธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* ์ด ๋ชจ๋“ˆ(ํŒŒ์ผ `app/routers/items.py`)์ด ์†ํ•œ ๊ฐ™์€ package(๋””๋ ‰ํ„ฐ๋ฆฌ `app/routers/`)์—์„œ ์‹œ์ž‘ํ•ด์„œ... +* ์ƒ์œ„ package(๋””๋ ‰ํ„ฐ๋ฆฌ `app/`)๋กœ ์˜ฌ๋ผ๊ฐ€๊ณ ... +* ๊ทธ package์˜ ์ƒ์œ„๋กœ ๋˜ ์˜ฌ๋ผ๊ฐ€๋Š”๋ฐ(์ƒ์œ„ package๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค, `app`์ด ์ตœ์ƒ์œ„์ž…๋‹ˆ๋‹ค ๐Ÿ˜ฑ)... +* ๊ทธ ์•ˆ์—์„œ `dependencies` ๋ชจ๋“ˆ(ํŒŒ์ผ `app/dependencies.py`)์„ ์ฐพ๊ณ ... +* ๊ทธ ์•ˆ์—์„œ ํ•จ์ˆ˜ `get_token_header`๋ฅผ importํ•ฉ๋‹ˆ๋‹ค. + +์ด๋Š” `app/` ์œ„์ชฝ์˜ ์–ด๋–ค package(์ž์‹ ์˜ `__init__.py` ํŒŒ์ผ ๋“ฑ์„ ๊ฐ€์ง„)์— ๋Œ€ํ•œ ์ฐธ์กฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ๊ทธ๋Ÿฐ ๊ฒƒ์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด ์˜ˆ์‹œ์—์„œ๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๐Ÿšจ + +์ด์ œ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์•Œ์•˜์œผ๋‹ˆ, ์•ฑ์ด ์–ผ๋งˆ๋‚˜ ๋ณต์žกํ•˜๋“  ์ƒ๋Œ€ import๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ + +### ์ปค์Šคํ…€ `tags`, `responses`, `dependencies` ์ถ”๊ฐ€ํ•˜๊ธฐ { #add-some-custom-tags-responses-and-dependencies } + +`APIRouter`์— ์ด๋ฏธ prefix `/items`์™€ `tags=["items"]`๋ฅผ ์ถ”๊ฐ€ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ *path operation*์— ์ด๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ํŠน์ • *path operation*์—๋งŒ ์ ์šฉ๋  _์ถ”๊ฐ€_ `tags`๋ฅผ ๋”ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ๊ทธ *path operation* ์ „์šฉ์˜ ์ถ”๊ฐ€ `responses`๋„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} + +/// tip | ํŒ + +์ด ๋งˆ์ง€๋ง‰ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๋Š” `["items", "custom"]` ํƒœ๊ทธ ์กฐํ•ฉ์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋ฌธ์„œ์—๋Š” `404`์šฉ ์‘๋‹ต๊ณผ `403`์šฉ ์‘๋‹ต, ๋‘ ๊ฐ€์ง€ ๋ชจ๋‘๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. + +/// + +## ๋ฉ”์ธ `FastAPI` { #the-main-fastapi } + +์ด์ œ `app/main.py` ๋ชจ๋“ˆ์„ ๋ด…์‹œ๋‹ค. + +์—ฌ๊ธฐ์—์„œ `FastAPI` ํด๋ž˜์Šค๋ฅผ importํ•˜๊ณ  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +์ด ํŒŒ์ผ์€ ๋ชจ๋“  ๊ฒƒ์„ ํ•˜๋‚˜๋กœ ์—ฎ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ”์ธ ํŒŒ์ผ์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋Œ€๋ถ€๋ถ„์˜ ๋กœ์ง์ด ๊ฐ์ž์˜ ํŠน์ • ๋ชจ๋“ˆ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, ๋ฉ”์ธ ํŒŒ์ผ์€ ๊ฝค ๋‹จ์ˆœํ•ด์ง‘๋‹ˆ๋‹ค. + +### `FastAPI` importํ•˜๊ธฐ { #import-fastapi } + +ํ‰์†Œ์ฒ˜๋Ÿผ `FastAPI` ํด๋ž˜์Šค๋ฅผ importํ•˜๊ณ  ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +๋˜ํ•œ ๊ฐ `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"] *} + +### `APIRouter` importํ•˜๊ธฐ { #import-the-apirouter } + +์ด์ œ `APIRouter`๊ฐ€ ์žˆ๋Š” ๋‹ค๋ฅธ submodule๋“ค์„ importํ•ฉ๋‹ˆ๋‹ค: + +{* ../../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 package `app`์— ์†ํ•œ submodule๋“ค์ด๋ฏ€๋กœ, ์  ํ•˜๋‚˜ `.`๋ฅผ ์‚ฌ์šฉํ•ด "์ƒ๋Œ€ import"๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### import๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๋ฐฉ์‹ { #how-the-importing-works } + +๋‹ค์Œ ๊ตฌ๋ฌธ์€: + +```Python +from .routers import items, users +``` + +์˜๋ฏธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* ์ด ๋ชจ๋“ˆ(ํŒŒ์ผ `app/main.py`)์ด ์†ํ•œ ๊ฐ™์€ package(๋””๋ ‰ํ„ฐ๋ฆฌ `app/`)์—์„œ ์‹œ์ž‘ํ•ด์„œ... +* subpackage `routers`(๋””๋ ‰ํ„ฐ๋ฆฌ `app/routers/`)๋ฅผ ์ฐพ๊ณ ... +* ๊ทธ ์•ˆ์—์„œ submodule `items`(ํŒŒ์ผ `app/routers/items.py`)์™€ `users`(ํŒŒ์ผ `app/routers/users.py`)๋ฅผ importํ•ฉ๋‹ˆ๋‹ค... + +`items` ๋ชจ๋“ˆ์—๋Š” `router` ๋ณ€์ˆ˜(`items.router`)๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” `app/routers/items.py` ํŒŒ์ผ์—์„œ ๋งŒ๋“  ๊ฒƒ๊ณผ ๋™์ผํ•˜๋ฉฐ `APIRouter` ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  `users` ๋ชจ๋“ˆ๋„ ๊ฐ™์€ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. + +๋‹ค์Œ์ฒ˜๋Ÿผ importํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: + +```Python +from app.routers import items, users +``` + +/// info | ์ •๋ณด + +์ฒซ ๋ฒˆ์งธ ๋ฒ„์ „์€ "์ƒ๋Œ€ import"์ž…๋‹ˆ๋‹ค: + +```Python +from .routers import items, users +``` + +๋‘ ๋ฒˆ์งธ ๋ฒ„์ „์€ "์ ˆ๋Œ€ import"์ž…๋‹ˆ๋‹ค: + +```Python +from app.routers import items, users +``` + +Python Packages์™€ Modules์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๋ ค๋ฉด <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">Modules์— ๋Œ€ํ•œ Python ๊ณต์‹ ๋ฌธ์„œ</a>๋ฅผ ์ฝ์–ด๋ณด์„ธ์š”. + +/// + +### ์ด๋ฆ„ ์ถฉ๋Œ ํ”ผํ•˜๊ธฐ { #avoid-name-collisions } + +submodule `items`๋ฅผ ์ง์ ‘ importํ•˜๊ณ , ๊ทธ ์•ˆ์˜ `router` ๋ณ€์ˆ˜๋งŒ importํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. + +์ด๋Š” submodule `users`์—๋„ `router`๋ผ๋Š” ์ด๋ฆ„์˜ ๋ณ€์ˆ˜๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +๋งŒ์•ฝ ๋‹ค์Œ์ฒ˜๋Ÿผ ์ˆœ์„œ๋Œ€๋กœ importํ–ˆ๋‹ค๋ฉด: + +```Python +from .routers.items import router +from .routers.users import router +``` + +`users`์˜ `router`๊ฐ€ `items`์˜ `router`๋ฅผ ๋ฎ์–ด์จ์„œ ๋™์‹œ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ๊ฐ™์€ ํŒŒ์ผ์—์„œ ๋‘˜ ๋‹ค ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก submodule๋“ค์„ ์ง์ ‘ importํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/bigger_applications/app_an_py39/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"] *} + +/// info | ์ •๋ณด + +`users.router`๋Š” `app/routers/users.py` ํŒŒ์ผ ์•ˆ์˜ `APIRouter`๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +`items.router`๋Š” `app/routers/items.py` ํŒŒ์ผ ์•ˆ์˜ `APIRouter`๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +`app.include_router()`๋กœ ๊ฐ `APIRouter`๋ฅผ ๋ฉ”์ธ `FastAPI` ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ router์˜ ๋ชจ๋“  route๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ผ๋ถ€๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +/// note Technical Details | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +๋‚ด๋ถ€์ ์œผ๋กœ๋Š” `APIRouter`์— ์„ ์–ธ๋œ ๊ฐ *path operation*๋งˆ๋‹ค *path operation*์„ ์‹ค์ œ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +์ฆ‰, ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ๋ชจ๋“  ๊ฒƒ์ด ๋™์ผํ•œ ํ•˜๋‚˜์˜ ์•ฑ์ธ ๊ฒƒ์ฒ˜๋Ÿผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. + +/// + +/// check | ํ™•์ธ + +router๋ฅผ ํฌํ•จ(include)ํ•  ๋•Œ ์„ฑ๋Šฅ์„ ๊ฑฑ์ •ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. + +์ด ์ž‘์—…์€ ๋งˆ์ดํฌ๋กœ์ดˆ ๋‹จ์œ„์ด๋ฉฐ ์‹œ์ž‘ ์‹œ์—๋งŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. โšก + +/// + +### ์ปค์Šคํ…€ `prefix`, `tags`, `responses`, `dependencies`๋กœ `APIRouter` ํฌํ•จํ•˜๊ธฐ { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies } + +์ด์ œ ์กฐ์ง์—์„œ `app/internal/admin.py` ํŒŒ์ผ์„ ๋ฐ›์•˜๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. + +์—ฌ๊ธฐ์—๋Š” ์กฐ์ง์—์„œ ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ ๊ฐ„์— ๊ณต์œ ํ•˜๋Š” ๊ด€๋ฆฌ์ž์šฉ *path operations*๊ฐ€ ์žˆ๋Š” `APIRouter`๊ฐ€ ๋“ค์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ์˜ˆ์‹œ์—์„œ๋Š” ๋งค์šฐ ๋‹จ์ˆœํ•˜๊ฒŒ ๋งŒ๋“ค๊ฒ ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์กฐ์ง ๋‚ด ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์™€ ๊ณต์œ ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†์–ด `prefix`, `dependencies`, `tags` ๋“ฑ์„ `APIRouter`์— ์ง์ ‘ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค: + +{* ../../docs_src/bigger_applications/app_an_py39/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"] *} + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์›๋ž˜ `APIRouter`๋Š” ์ˆ˜์ •๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์กฐ์ง ๋‚ด ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์—์„œ๋„ ๋™์ผํ•œ `app/internal/admin.py` ํŒŒ์ผ์„ ๊ณ„์† ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ฒฐ๊ณผ์ ์œผ๋กœ ์šฐ๋ฆฌ ์•ฑ์—์„œ `admin` ๋ชจ๋“ˆ์˜ ๊ฐ *path operations*๋Š” ๋‹ค์Œ์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: + +* prefix `/admin`. +* tag `admin`. +* dependency `get_token_header`. +* ์‘๋‹ต `418`. ๐Ÿต + +ํ•˜์ง€๋งŒ ์ด๋Š” ์šฐ๋ฆฌ ์•ฑ์—์„œ ๊ทธ `APIRouter`์—๋งŒ ์˜ํ–ฅ์„ ์ฃผ๋ฉฐ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ ์ฝ”๋“œ์—๋Š” ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋“ค์€ ๊ฐ™์€ `APIRouter`๋ฅผ ๋‹ค๋ฅธ ์ธ์ฆ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +### *path operation* ํฌํ•จํ•˜๊ธฐ { #include-a-path-operation } + +*path operations*๋ฅผ `FastAPI` ์•ฑ์— ์ง์ ‘ ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์—ฌ๊ธฐ์„œ๋Š” ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด... ๊ทธ๋ƒฅ ํ•ด๋ด…๋‹ˆ๋‹ค ๐Ÿคท: + +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} + +๊ทธ๋ฆฌ๊ณ  `app.include_router()`๋กœ ์ถ”๊ฐ€ํ•œ ๋‹ค๋ฅธ ๋ชจ๋“  *path operations*์™€ ํ•จ๊ป˜ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +**์ฐธ๊ณ **: ์ด๋Š” ๋งค์šฐ ๊ธฐ์ˆ ์ ์ธ ์„ธ๋ถ€์‚ฌํ•ญ์ด๋ผ ์•„๋งˆ **๊ทธ๋ƒฅ ๊ฑด๋„ˆ๋›ฐ์–ด๋„ ๋ฉ๋‹ˆ๋‹ค**. + +--- + +`APIRouter`๋Š” "mount"๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„๊ณผ ๊ฒฉ๋ฆฌ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +์ด๋Š” OpenAPI ์Šคํ‚ค๋งˆ์™€ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์— ๊ทธ๋“ค์˜ *path operations*๋ฅผ ํฌํ•จ์‹œํ‚ค๊ณ  ์‹ถ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +๋‚˜๋จธ์ง€์™€ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฒฉ๋ฆฌํ•ด "mount"ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, *path operations*๋Š” ์ง์ ‘ ํฌํ•จ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ "clone"(์žฌ์ƒ์„ฑ)๋ฉ๋‹ˆ๋‹ค. + +/// + +## ์ž๋™ API ๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ { #check-the-automatic-api-docs } + +์ด์ œ ์•ฑ์„ ์‹คํ–‰ํ•˜์„ธ์š”: + +<div class="termy"> + +```console +$ fastapi dev app/main.py + +<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +</div> + +๊ทธ๋ฆฌ๊ณ  <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>์—์„œ ๋ฌธ์„œ๋ฅผ ์—ฌ์„ธ์š”. + +์˜ฌ๋ฐ”๋ฅธ ๊ฒฝ๋กœ(๋ฐ prefix)์™€ ์˜ฌ๋ฐ”๋ฅธ ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ด, ๋ชจ๋“  submodule์˜ ๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•œ ์ž๋™ API ๋ฌธ์„œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/tutorial/bigger-applications/image01.png"> + +## ๊ฐ™์€ router๋ฅผ ๋‹ค๋ฅธ `prefix`๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ํฌํ•จํ•˜๊ธฐ { #include-the-same-router-multiple-times-with-different-prefix } + +`.include_router()`๋ฅผ ์‚ฌ์šฉํ•ด *๊ฐ™์€* router๋ฅผ ์„œ๋กœ ๋‹ค๋ฅธ prefix๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ํฌํ•จํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด `/api/v1`๊ณผ `/api/latest`์ฒ˜๋Ÿผ ์„œ๋กœ ๋‹ค๋ฅธ prefix๋กœ ๋™์ผํ•œ API๋ฅผ ๋…ธ์ถœํ•  ๋•Œ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ๊ณ ๊ธ‰ ์‚ฌ์šฉ ๋ฐฉ์‹์ด๋ผ ์‹ค์ œ๋กœ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ํ•„์š”ํ•  ๋•Œ๋ฅผ ์œ„ํ•ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. + +## `APIRouter`์— ๋‹ค๋ฅธ `APIRouter` ํฌํ•จํ•˜๊ธฐ { #include-an-apirouter-in-another } + +`APIRouter`๋ฅผ `FastAPI` ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ, ๋‹ค์Œ์„ ์‚ฌ์šฉํ•ด `APIRouter`๋ฅผ ๋‹ค๋ฅธ `APIRouter`์— ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```Python +router.include_router(other_router) +``` + +`FastAPI` ์•ฑ์— `router`๋ฅผ ํฌํ•จํ•˜๊ธฐ ์ „์— ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋ฉฐ, ๊ทธ๋ž˜์•ผ `other_router`์˜ *path operations*๋„ ํ•จ๊ป˜ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/body-updates.md b/docs/ko/docs/tutorial/body-updates.md new file mode 100644 index 0000000000..3719e1ffab --- /dev/null +++ b/docs/ko/docs/tutorial/body-updates.md @@ -0,0 +1,100 @@ +# Body - ์—…๋ฐ์ดํŠธ { #body-updates } + +## `PUT`์œผ๋กœ ๊ต์ฒด ์—…๋ฐ์ดํŠธํ•˜๊ธฐ { #update-replacing-with-put } + +ํ•ญ๋ชฉ์„ ์—…๋ฐ์ดํŠธํ•˜๋ ค๋ฉด <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a> ์ž‘์—…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +`jsonable_encoder`๋ฅผ ์‚ฌ์šฉํ•ด ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ JSON์œผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์˜ˆ: NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ ์‹œ). ์˜ˆ๋ฅผ ๋“ค์–ด `datetime`์„ `str`๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. + +{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *} + +`PUT`์€ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ **๋Œ€์ฒด**ํ•ด์•ผ ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +### ๋Œ€์ฒด ์‹œ ์ฃผ์˜์‚ฌํ•ญ { #warning-about-replacing } + +์ฆ‰, `PUT`์œผ๋กœ ํ•ญ๋ชฉ `bar`๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฉด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ body๋ฅผ ๋ณด๋‚ธ๋‹ค๋ฉด: + +```Python +{ + "name": "Barz", + "price": 3, + "description": None, +} +``` + +์ด๋ฏธ ์ €์žฅ๋œ ์†์„ฑ `"tax": 20.2`๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ์ž…๋ ฅ ๋ชจ๋ธ์€ `"tax": 10.5`๋ผ๋Š” ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋Š” ๊ทธ โ€œ์ƒˆ๋กœ์šดโ€ `tax` ๊ฐ’ `10.5`๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. + +## `PATCH`๋กœ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ { #partial-updates-with-patch } + +<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> ์ž‘์—…์„ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ *๋ถ€๋ถ„์ ์œผ๋กœ* ์—…๋ฐ์ดํŠธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ์—…๋ฐ์ดํŠธํ•˜๋ ค๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๋ณด๋‚ด๊ณ , ๋‚˜๋จธ์ง€๋Š” ๊ทธ๋Œ€๋กœ ๋‘๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. + +/// 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 } + +์ •๋ฆฌํ•˜๋ฉด, ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๋ฅผ ์ ์šฉํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค: + +* (์„ ํƒ ์‚ฌํ•ญ) `PUT` ๋Œ€์‹  `PATCH`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +* ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. +* ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ Pydantic ๋ชจ๋ธ์— ๋„ฃ์Šต๋‹ˆ๋‹ค. +* ์ž…๋ ฅ ๋ชจ๋ธ์—์„œ ๊ธฐ๋ณธ๊ฐ’์ด ์ œ์™ธ๋œ `dict`๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค(`exclude_unset` ์‚ฌ์šฉ). + * ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ชจ๋ธ์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ด๋ฏธ ์ €์žฅ๋œ ๊ฐ’์„ ๋ฎ์–ด์“ฐ์ง€ ์•Š๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ œ๋กœ ์„ค์ •ํ•œ ๊ฐ’๋งŒ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ์ €์žฅ๋œ ๋ชจ๋ธ์˜ ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“ค๊ณ , ๋ฐ›์€ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๋กœ ํ•ด๋‹น ์†์„ฑ๋“ค์„ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค(`update` ํŒŒ๋ผ๋ฏธํ„ฐ ์‚ฌ์šฉ). +* ๋ณต์‚ฌํ•œ ๋ชจ๋ธ์„ DB์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: `jsonable_encoder` ์‚ฌ์šฉ). + * ์ด๋Š” ๋ชจ๋ธ์˜ `.model_dump()` ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ, JSON์œผ๋กœ ๋ณ€ํ™˜ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์œผ๋กœ ๊ฐ’์ด ํ™•์‹คํžˆ ๋ณ€ํ™˜๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: `datetime` โ†’ `str`). +* ๋ฐ์ดํ„ฐ๋ฅผ DB์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. +* ์—…๋ฐ์ดํŠธ๋œ ๋ชจ๋ธ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} + +/// tip | ํŒ + +๋™์ผํ•œ ๊ธฐ๋ฒ•์„ HTTP `PUT` ์ž‘์—…์—์„œ๋„ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์˜ ์˜ˆ์‹œ๋Š” ์ด๋Ÿฐ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ `PATCH`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +/// + +/// note | ์ฐธ๊ณ  + +์ž…๋ ฅ ๋ชจ๋ธ์€ ์—ฌ์ „ํžˆ ๊ฒ€์ฆ๋œ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. + +๋”ฐ๋ผ์„œ ๋ชจ๋“  ์†์„ฑ์„ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ฐ›์œผ๋ ค๋ฉด, ๋ชจ๋“  ์†์„ฑ์ด optional๋กœ ํ‘œ์‹œ๋œ(๊ธฐ๋ณธ๊ฐ’์„ ๊ฐ€์ง€๊ฑฐ๋‚˜ `None`์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๊ฐ€์ง€๋Š”) ๋ชจ๋ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +**์—…๋ฐ์ดํŠธ**๋ฅผ ์œ„ํ•œ โ€œ๋ชจ๋“  ๊ฐ’์ด optional์ธโ€ ๋ชจ๋ธ๊ณผ, **์ƒ์„ฑ**์„ ์œ„ํ•œ โ€œํ•„์ˆ˜ ๊ฐ’์ด ์žˆ๋Š”โ€ ๋ชจ๋ธ์„ ๊ตฌ๋ถ„ํ•˜๋ ค๋ฉด [์ถ”๊ฐ€ ๋ชจ๋ธ](extra-models.md){.internal-link target=_blank}์— ์„ค๋ช…๋œ ์•„์ด๋””์–ด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// diff --git a/docs/ko/docs/tutorial/dependencies/sub-dependencies.md b/docs/ko/docs/tutorial/dependencies/sub-dependencies.md new file mode 100644 index 0000000000..d81ccf00d0 --- /dev/null +++ b/docs/ko/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_or_cookie_extractor`๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋™์•ˆ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด, ๋จผ์ € `query_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**๋Š” ๊ทธ ํ•˜์œ„ ์˜์กด์„ฑ์„ ์š”์ฒญ๋‹น ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๊ฐ™์€ ์š”์ฒญ์— ๋Œ€ํ•ด ๋™์ผํ•œ ์˜์กด์„ฑ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹ , ๋ฐ˜ํ™˜๊ฐ’์„ <abbr title="๊ณ„์‚ฐ/์ƒ์„ฑ๋œ ๊ฐ’์„ ์ €์žฅํ•ด ๋‘์—ˆ๋‹ค๊ฐ€, ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜์ง€ ์•Š๊ณ  ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ/์‹œ์Šคํ…œ.">"cache"</abbr>์— ์ €์žฅํ•˜๊ณ , ๊ทธ ์š”์ฒญ์—์„œ ํ•ด๋‹น ๊ฐ’์ด ํ•„์š”ํ•œ ๋ชจ๋“  "dependants"์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. + +๊ณ ๊ธ‰ ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ, ๊ฐ™์€ ์š”์ฒญ์—์„œ "cached" ๊ฐ’์„ ์“ฐ๋Š” ๋Œ€์‹  ๋งค ๋‹จ๊ณ„๋งˆ๋‹ค(์•„๋งˆ๋„ ์—ฌ๋Ÿฌ ๋ฒˆ) ์˜์กด์„ฑ์ด ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด, `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 } + +์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ ๊ทธ๋Ÿด๋“ฏํ•œ ์šฉ์–ด๋“ค์„ ์ œ์™ธํ•˜๋ฉด, **Dependency Injection** ์‹œ์Šคํ…œ์€ ๊ฝค ๋‹จ์ˆœํ•ฉ๋‹ˆ๋‹ค. + +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์™€ ๊ฐ™์€ ํ˜•ํƒœ์˜ ํ•จ์ˆ˜๋“ค์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ๋งค์šฐ ๊ฐ•๋ ฅํ•˜๋ฉฐ, ์ž„์˜๋กœ ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ ์˜์กด์„ฑ "๊ทธ๋ž˜ํ”„"(ํŠธ๋ฆฌ)๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์ด ๋‹จ์ˆœํ•œ ์˜ˆ์‹œ๋งŒ ๋ณด๋ฉด ๊ทธ๋‹ค์ง€ ์œ ์šฉํ•ด ๋ณด์ด์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ **๋ณด์•ˆ**์— ๊ด€ํ•œ ์ฑ•ํ„ฐ์—์„œ ์ด๊ฒƒ์ด ์–ผ๋งˆ๋‚˜ ์œ ์šฉํ•œ์ง€ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๋˜ํ•œ ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์ฝ”๋“œ๋ฅผ ์•„๊ปด์ฃผ๋Š”์ง€๋„ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// diff --git a/docs/ko/docs/tutorial/handling-errors.md b/docs/ko/docs/tutorial/handling-errors.md new file mode 100644 index 0000000000..7cc37e80c0 --- /dev/null +++ b/docs/ko/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_py39.py hl[1] *} + +### ์ฝ”๋“œ์—์„œ `HTTPException` ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ { #raise-an-httpexception-in-your-code } + +`HTTPException`์€ API์™€ ๊ด€๋ จ๋œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง„ ์ผ๋ฐ˜์ ์ธ Python ์˜ˆ์™ธ์ž…๋‹ˆ๋‹ค. + +Python ์˜ˆ์™ธ์ด๋ฏ€๋กœ `return` ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ `raise` ํ•ฉ๋‹ˆ๋‹ค. + +์ด๋Š” ๋˜ํ•œ, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜* ๋‚ด๋ถ€์—์„œ ํ˜ธ์ถœํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ์•ˆ์—์„œ `HTTPException`์„ `raise`ํ•˜๋ฉด, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๊ณ  ์ฆ‰์‹œ ํ•ด๋‹น ์š”์ฒญ์ด ์ข…๋ฃŒ๋˜๋ฉฐ `HTTPException`์˜ HTTP ์˜ค๋ฅ˜๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „์†ก๋œ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. + +๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์˜ ์ด์ ์€ ์˜์กด์„ฑ๊ณผ ๋ณด์•ˆ์— ๋Œ€ํ•œ ์„น์…˜์—์„œ ๋” ๋ถ„๋ช…ํ•ด์ง‘๋‹ˆ๋‹ค. + +์ด ์˜ˆ์‹œ์—์„œ๋Š”, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID๋กœ ํ•ญ๋ชฉ์„ ์š”์ฒญํ•˜๋ฉด ์ƒํƒœ ์ฝ”๋“œ `404`๋กœ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค: + +{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *} + +### ๊ฒฐ๊ณผ ์‘๋‹ต { #the-resulting-response } + +ํด๋ผ์ด์–ธํŠธ๊ฐ€ `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("not found" ์˜ค๋ฅ˜)์™€ ๋‹ค์Œ JSON ์‘๋‹ต์„ ๋ฐ›์Šต๋‹ˆ๋‹ค: + +```JSON +{ + "detail": "Item not found" +} +``` + +/// tip | ํŒ + +`HTTPException`์„ ๋ฐœ์ƒ์‹œํ‚ฌ ๋•Œ `detail` ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ `str`๋งŒ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, JSON์œผ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ์–ด๋–ค ๊ฐ’์ด๋“  ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +`dict`, `list` ๋“ฑ์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋“ค์€ **FastAPI**๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด JSON์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +/// + +## ์ปค์Šคํ…€ ํ—ค๋” ์ถ”๊ฐ€ํ•˜๊ธฐ { #add-custom-headers } + +HTTP ์˜ค๋ฅ˜์— ์ปค์Šคํ…€ ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ์œ ์šฉํ•œ ์ƒํ™ฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํŠน์ • ๋ณด์•ˆ ์œ ํ˜•์—์„œ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. + +์•„๋งˆ ์ฝ”๋“œ์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ผ์€ ๊ฑฐ์˜ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๊ณ ๊ธ‰ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ํ•„์š”ํ•˜๋‹ค๋ฉด ์ปค์Šคํ…€ ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *} + +## ์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ ์„ค์น˜ํ•˜๊ธฐ { #install-custom-exception-handlers } + +<a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">Starlette์˜ ๋™์ผํ•œ ์˜ˆ์™ธ ์œ ํ‹ธ๋ฆฌํ‹ฐ</a>๋ฅผ ์‚ฌ์šฉํ•ด ์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์—ฌ๋Ÿฌ๋ถ„(๋˜๋Š” ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)์ด `raise`ํ•  ์ˆ˜ ์žˆ๋Š” ์ปค์Šคํ…€ ์˜ˆ์™ธ `UnicornException`์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์ด ์˜ˆ์™ธ๋ฅผ FastAPI์—์„œ ์ „์—ญ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค. + +`@app.exception_handler()`๋กœ ์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} + +์—ฌ๊ธฐ์„œ `/unicorns/yolo`๋ฅผ ์š”์ฒญํ•˜๋ฉด, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๊ฐ€ `UnicornException`์„ `raise`ํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ `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**์—๋Š” ๋ช‡ ๊ฐ€์ง€ ๊ธฐ๋ณธ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ํ•ธ๋“ค๋Ÿฌ๋“ค์€ `HTTPException`์„ `raise`ํ–ˆ์„ ๋•Œ, ๊ทธ๋ฆฌ๊ณ  ์š”์ฒญ์— ์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ ๊ธฐ๋ณธ JSON ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. + +์ด ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋“ค์„ ์—ฌ๋Ÿฌ๋ถ„์˜ ๊ฒƒ์œผ๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์š”์ฒญ ๊ฒ€์ฆ ์˜ˆ์™ธ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๊ธฐ { #override-request-validation-exceptions } + +์š”์ฒญ์— ์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋˜๋ฉด, **FastAPI**๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ `RequestValidationError`๋ฅผ `raise`ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์ด์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋„ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๋ ค๋ฉด `RequestValidationError`๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , `@app.exception_handler(RequestValidationError)`๋กœ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ฐ์ฝ”๋ ˆ์ดํŠธํ•ด ์‚ฌ์šฉํ•˜์„ธ์š”. + +์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋Š” `Request`์™€ ์˜ˆ์™ธ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/handling_errors/tutorial004_py39.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_py39.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_py39.py hl[14] *} + +์ด์ œ ๋‹ค์Œ์ฒ˜๋Ÿผ ์œ ํšจํ•˜์ง€ ์•Š์€ item์„ ๋ณด๋‚ด๋ณด์„ธ์š”: + +```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` vs Starlette์˜ `HTTPException` { #fastapis-httpexception-vs-starlettes-httpexception } + +**FastAPI**์—๋Š” ์ž์ฒด `HTTPException`์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  **FastAPI**์˜ `HTTPException` ์˜ค๋ฅ˜ ํด๋ž˜์Šค๋Š” Starlette์˜ `HTTPException` ์˜ค๋ฅ˜ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•ฉ๋‹ˆ๋‹ค. + +์œ ์ผํ•œ ์ฐจ์ด๋Š” **FastAPI**์˜ `HTTPException`์€ `detail` ํ•„๋“œ์— JSON์œผ๋กœ ๋ณ€ํ™˜ ๊ฐ€๋Šฅํ•œ ์–ด๋–ค ๋ฐ์ดํ„ฐ๋“  ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๋ฐ˜๋ฉด, Starlette์˜ `HTTPException`์€ ๋ฌธ์ž์—ด๋งŒ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ์ฝ”๋“œ์—์„œ๋Š” ํ‰์†Œ์ฒ˜๋Ÿผ **FastAPI**์˜ `HTTPException`์„ ๊ณ„์† `raise`ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•  ๋•Œ๋Š” Starlette์˜ `HTTPException`์— ๋Œ€ํ•ด ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Starlette ๋‚ด๋ถ€ ์ฝ”๋“œ์˜ ์–ด๋–ค ๋ถ€๋ถ„, ๋˜๋Š” Starlette ํ™•์žฅ/ํ”Œ๋Ÿฌ๊ทธ์ธ์ด Starlette `HTTPException`์„ `raise`ํ•˜๋”๋ผ๋„, ์—ฌ๋Ÿฌ๋ถ„์˜ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ด๋ฅผ ์žก์•„์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ์˜ˆ์‹œ์—์„œ๋Š” ๋™์ผํ•œ ์ฝ”๋“œ์—์„œ ๋‘ `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_py39.py hl[2:5,15,21] *} + +์ด ์˜ˆ์‹œ์—์„œ๋Š” ๋งค์šฐ ํ‘œํ˜„๋ ฅ ์žˆ๋Š” ๋ฉ”์‹œ์ง€๋กœ ์˜ค๋ฅ˜๋ฅผ ์ถœ๋ ฅ๋งŒ ํ•˜๊ณ  ์žˆ์ง€๋งŒ, ์š”์ง€๋Š” ์ดํ•ดํ•˜์…จ์„ ๊ฒ๋‹ˆ๋‹ค. ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•œ ๋’ค ๊ธฐ๋ณธ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ทธ๋Œ€๋กœ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/security/first-steps.md b/docs/ko/docs/tutorial/security/first-steps.md new file mode 100644 index 0000000000..4c9181b31e --- /dev/null +++ b/docs/ko/docs/tutorial/security/first-steps.md @@ -0,0 +1,203 @@ +# ๋ณด์•ˆ - ์ฒซ ๋‹จ๊ณ„ { #security-first-steps } + +์–ด๋–ค ๋„๋ฉ”์ธ์— **backend** API๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์— **frontend**๊ฐ€ ์žˆ๊ฑฐ๋‚˜, ๊ฐ™์€ ๋„๋ฉ”์ธ์˜ ๋‹ค๋ฅธ ๊ฒฝ๋กœ์— ์žˆ๊ฑฐ๋‚˜(๋˜๋Š” ๋ชจ๋ฐ”์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค). + +๊ทธ๋ฆฌ๊ณ  frontend๊ฐ€ **username**๊ณผ **password**๋ฅผ ์‚ฌ์šฉํ•ด backend์— ์ธ์ฆํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•˜๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค. + +**FastAPI**์™€ ํ•จ๊ป˜ **OAuth2**๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ด๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ํ•„์š”ํ•œ ์ž‘์€ ์ •๋ณด ์กฐ๊ฐ๋“ค์„ ์ฐพ๊ธฐ ์œ„ํ•ด ๊ธธ๊ณ  ๊ธด ์ „์ฒด ์ŠคํŽ™์„ ์ฝ๋А๋ผ ์‹œ๊ฐ„์„ ์“ฐ์ง€ ์•Š๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. + +๋ณด์•ˆ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด **FastAPI**๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋„๊ตฌ๋“ค์„ ์‚ฌ์šฉํ•ด ๋ด…์‹œ๋‹ค. + +## ์–ด๋–ป๊ฒŒ ๋ณด์ด๋Š”์ง€ { #how-it-looks } + +๋จผ์ € ์ฝ”๋“œ๋ฅผ ๊ทธ๋ƒฅ ์‚ฌ์šฉํ•ด์„œ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๋ณด๊ณ , ๊ทธ๋‹ค์Œ์— ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์ดํ•ดํ•˜๋Ÿฌ ๋‹ค์‹œ ๋Œ์•„์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค. + +## `main.py` ๋งŒ๋“ค๊ธฐ { #create-main-py } + +์˜ˆ์ œ๋ฅผ ํŒŒ์ผ `main.py`์— ๋ณต์‚ฌํ•˜์„ธ์š”: + +{* ../../docs_src/security/tutorial001_an_py39.py *} + +## ์‹คํ–‰ํ•˜๊ธฐ { #run-it } + +/// info | ์ •๋ณด + +<a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> ํŒจํ‚ค์ง€๋Š” `pip install "fastapi[standard]"` ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ฉด **FastAPI**์™€ ํ•จ๊ป˜ ์ž๋™์œผ๋กœ ์„ค์น˜๋ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ `pip install fastapi` ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜๋ฉด `python-multipart` ํŒจํ‚ค์ง€๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +์ˆ˜๋™์œผ๋กœ ์„ค์น˜ํ•˜๋ ค๋ฉด, [๊ฐ€์ƒ ํ™˜๊ฒฝ](../../virtual-environments.md){.internal-link target=_blank}์„ ๋งŒ๋“ค๊ณ  ํ™œ์„ฑํ™”ํ•œ ๋‹ค์Œ, ์•„๋ž˜๋กœ ์„ค์น˜ํ•˜์„ธ์š”: + +```console +$ pip install python-multipart +``` + +์ด๋Š” **OAuth2**๊ฐ€ `username`๊ณผ `password`๋ฅผ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด "form data"๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +/// + +๋‹ค์Œ์œผ๋กœ ์˜ˆ์ œ๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”: + +<div class="termy"> + +```console +$ fastapi dev main.py + +<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +</div> + +## ํ™•์ธํ•˜๊ธฐ { #check-it } + +๋Œ€ํ™”ํ˜• ๋ฌธ์„œ๋กœ ์ด๋™ํ•˜์„ธ์š”: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. + +๋‹ค์Œ๊ณผ ๋น„์Šทํ•œ ํ™”๋ฉด์ด ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค: + +<img src="/img/tutorial/security/image01.png"> + +/// check | Authorize ๋ฒ„ํŠผ! + +๋ฐ˜์ง์ด๋Š” ์ƒˆ "Authorize" ๋ฒ„ํŠผ์ด ์ด๋ฏธ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์—๋Š” ์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ์— ํด๋ฆญํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์€ ์ž๋ฌผ์‡ ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ํด๋ฆญํ•˜๋ฉด `username`๊ณผ `password`(๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ์„ ํƒ์  ํ•„๋“œ๋“ค)๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์€ ์ธ์ฆ ํผ์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค: + +<img src="/img/tutorial/security/image02.png"> + +/// note | ์ฐธ๊ณ  + +ํผ์— ๋ฌด์—‡์„ ์ž…๋ ฅํ•˜๋“  ์•„์ง์€ ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ณง ์—ฌ๊ธฐ๊นŒ์ง€ ๊ตฌํ˜„ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// + +๋ฌผ๋ก  ์ด๊ฒƒ์€ ์ตœ์ข… ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ frontend๋Š” ์•„๋‹ˆ์ง€๋งŒ, ๋ชจ๋“  API๋ฅผ ๋Œ€ํ™”ํ˜•์œผ๋กœ ๋ฌธ์„œํ™”ํ•˜๋Š” ํ›Œ๋ฅญํ•œ ์ž๋™ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. + +frontend ํŒ€(๊ทธ๊ฒŒ ๋ณธ์ธ์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค)์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์„œ๋“œํŒŒํ‹ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ์‹œ์Šคํ…œ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋™์ผํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋””๋ฒ„๊ทธํ•˜๊ณ , ํ™•์ธํ•˜๊ณ , ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ์ธ์ด ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +## `password` ํ”Œ๋กœ์šฐ { #the-password-flow } + +์ด์ œ ์กฐ๊ธˆ ๋Œ์•„๊ฐ€์„œ ์ด๊ฒƒ๋“ค์ด ๋ฌด์—‡์ธ์ง€ ์ดํ•ดํ•ด ๋ด…์‹œ๋‹ค. + +`password` "flow"๋Š” ๋ณด์•ˆ๊ณผ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด OAuth2์—์„œ ์ •์˜ํ•œ ์—ฌ๋Ÿฌ ๋ฐฉ์‹("flows") ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. + +OAuth2๋Š” backend ๋˜๋Š” API๊ฐ€ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๋Š” ์„œ๋ฒ„์™€ ๋…๋ฆฝ์ ์ผ ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์ด ๊ฒฝ์šฐ์—๋Š” ๊ฐ™์€ **FastAPI** ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด API์™€ ์ธ์ฆ์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ, ๋‹จ์ˆœํ™”๋œ ๊ด€์ ์—์„œ ๋‹ค์‹œ ์ •๋ฆฌํ•ด๋ณด๋ฉด: + +* ์‚ฌ์šฉ์ž๊ฐ€ frontend์—์„œ `username`๊ณผ `password`๋ฅผ ์ž…๋ ฅํ•˜๊ณ  `Enter`๋ฅผ ๋ˆ„๋ฆ…๋‹ˆ๋‹ค. +* frontend(์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰๋จ)๋Š” ํ•ด๋‹น `username`๊ณผ `password`๋ฅผ ์šฐ๋ฆฌ API์˜ ํŠน์ • URL๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค(`tokenUrl="token"`๋กœ ์„ ์–ธ๋จ). +* API๋Š” `username`๊ณผ `password`๋ฅผ ํ™•์ธํ•˜๊ณ  "token"์œผ๋กœ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค(์•„์ง ์•„๋ฌด๊ฒƒ๋„ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค). + * "token"์€ ๋‚˜์ค‘์— ์ด ์‚ฌ์šฉ์ž๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์–ด๋–ค ๋‚ด์šฉ์ด ๋‹ด๊ธด ๋ฌธ์ž์—ด์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. + * ๋ณดํ†ต token์€ ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๋งŒ๋ฃŒ๋˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. + * ๊ทธ๋ž˜์„œ ์‚ฌ์šฉ์ž๋Š” ๋‚˜์ค‘์— ์–ด๋А ์‹œ์ ์—” ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + * ๊ทธ๋ฆฌ๊ณ  token์ด ๋„๋‚œ๋‹นํ•˜๋”๋ผ๋„ ์œ„ํ—˜์ด ๋” ๋‚ฎ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์˜๊ตฌ์ ์œผ๋กœ ํ•ญ์ƒ ๋™์ž‘ํ•˜๋Š” ํ‚ค์™€๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค. +* frontend๋Š” ๊ทธ token์„ ์ž„์‹œ๋กœ ์–ด๋”˜๊ฐ€์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. +* ์‚ฌ์šฉ์ž๊ฐ€ frontend์—์„œ ํด๋ฆญํ•ด์„œ frontend ์›น ์•ฑ์˜ ๋‹ค๋ฅธ ์„น์…˜์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. +* frontend๋Š” API์—์„œ ๋” ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. + * ํ•˜์ง€๋งŒ ๊ทธ ํŠน์ • endpoint์—๋Š” ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + * ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ 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` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” ํด๋ผ์ด์–ธํŠธ(์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰๋˜๋Š” frontend)๊ฐ€ token์„ ๋ฐ›๊ธฐ ์œ„ํ•ด `username`๊ณผ `password`๋ฅผ ๋ณด๋‚ผ URL์ด ๋“ค์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/security/tutorial001_an_py39.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์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ [ํ”„๋ก์‹œ ๋’ค์—์„œ](../../advanced/behind-a-proxy.md){.internal-link target=_blank} ๊ฐ™์€ ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์‚ฌ๋ก€์—์„œ๋„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ณ„์† ๋™์ž‘ํ•˜๋„๋ก ๋ณด์žฅํ•˜๋Š” ๋ฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. + +/// + +์ด ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๊ทธ endpoint / *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*๋ฅผ ๋งŒ๋“ค์ง€๋Š” ์•Š์ง€๋งŒ, URL `/token`์ด ํด๋ผ์ด์–ธํŠธ๊ฐ€ token์„ ์–ป๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•ด์•ผ ํ•  URL์ด๋ผ๊ณ  ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. ์ด ์ •๋ณด๋Š” OpenAPI์— ์‚ฌ์šฉ๋˜๊ณ , ์ด์–ด์„œ ๋Œ€ํ™”ํ˜• API ๋ฌธ์„œ ์‹œ์Šคํ…œ์—์„œ๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +๊ณง ์‹ค์ œ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +์—„๊ฒฉํ•œ "Pythonista"๋ผ๋ฉด `token_url` ๋Œ€์‹  `tokenUrl` ๊ฐ™์€ ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„ ์Šคํƒ€์ผ์ด ๋งˆ์Œ์— ๋“ค์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” OpenAPI ์ŠคํŽ™์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ด๋ฆ„๊ณผ ๋™์ผํ•˜๊ฒŒ ๋งž์ถ˜ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋Ÿฐ ๋ณด์•ˆ ์Šคํ‚ด์— ๋Œ€ํ•ด ๋” ์กฐ์‚ฌํ•ด์•ผ ํ•  ๋•Œ, ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ ๋ถ™์—ฌ ๋„ฃ์–ด ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +`oauth2_scheme` ๋ณ€์ˆ˜๋Š” `OAuth2PasswordBearer`์˜ ์ธ์Šคํ„ด์Šค์ด์ง€๋งŒ, "callable"์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. + +๋‹ค์Œ์ฒ˜๋Ÿผ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```Python +oauth2_scheme(some, parameters) +``` + +๋”ฐ๋ผ์„œ `Depends`์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์‚ฌ์šฉํ•˜๊ธฐ { #use-it } + +์ด์ œ `Depends`๋กœ `oauth2_scheme`๋ฅผ ์˜์กด์„ฑ์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} + +์ด ์˜์กด์„ฑ์€ `str`์„ ์ œ๊ณตํ•˜๊ณ , ๊ทธ ๊ฐ’์€ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์˜ ํŒŒ๋ผ๋ฏธํ„ฐ `token`์— ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค. + +**FastAPI**๋Š” ์ด ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•ด OpenAPI ์Šคํ‚ค๋งˆ(๋ฐ ์ž๋™ API ๋ฌธ์„œ)์— "security scheme"๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +/// info | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +**FastAPI**๋Š” (์˜์กด์„ฑ์— ์„ ์–ธ๋œ) `OAuth2PasswordBearer` ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•ด OpenAPI์—์„œ ๋ณด์•ˆ ์Šคํ‚ด์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” `OAuth2PasswordBearer`๊ฐ€ `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`์ด ๋“ค์–ด ์žˆ๋‹ค๊ณ  ํ™•์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ ์ด๋ฏธ ์‹œ๋„ํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +<img src="/img/tutorial/security/image03.png"> + +์•„์ง token์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜์ง„ ์•Š์ง€๋งŒ, ์ด๊ฒƒ๋งŒ์œผ๋กœ๋„ ์‹œ์ž‘์€ ๋œ ์…ˆ์ž…๋‹ˆ๋‹ค. + +## ์š”์•ฝ { #recap } + +์ฆ‰, ์ถ”๊ฐ€๋กœ 3~4์ค„๋งŒ์œผ๋กœ๋„ ์ด๋ฏธ ์›์‹œ์ ์ธ ํ˜•ํƒœ์˜ ๋ณด์•ˆ์„ ๊ฐ–์ถ”๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. diff --git a/docs/ko/docs/tutorial/security/index.md b/docs/ko/docs/tutorial/security/index.md new file mode 100644 index 0000000000..2320b06571 --- /dev/null +++ b/docs/ko/docs/tutorial/security/index.md @@ -0,0 +1,106 @@ +# ๋ณด์•ˆ { #security } + +๋ณด์•ˆ, ์ธ์ฆ(authentication), ์ธ๊ฐ€(authorization)๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋งค์šฐ ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋ณดํ†ต ๋ณต์žกํ•˜๊ณ  "์–ด๋ ค์šด" ์ฃผ์ œ์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. + +๋งŽ์€ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ์‹œ์Šคํ…œ์—์„œ ๋ณด์•ˆ๊ณผ ์ธ์ฆ๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ๋„ ํฐ ๋…ธ๋ ฅ๊ณผ ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค(๋งŽ์€ ๊ฒฝ์šฐ ์ž‘์„ฑ๋œ ์ „์ฒด ์ฝ”๋“œ์˜ 50% ์ด์ƒ์ด ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค). + +**FastAPI**๋Š” ๋ชจ๋“  ๋ณด์•ˆ ๋ช…์„ธ๋ฅผ ์ „๋ถ€ ๊ณต๋ถ€ํ•˜๊ณ  ๋ฐฐ์šธ ํ•„์š” ์—†์ด, ํ‘œ์ค€์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ **๋ณด์•ˆ(Security)** ์„ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋„๋ก ์—ฌ๋Ÿฌ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋จผ์ €, ๋ช‡ ๊ฐ€์ง€ ์ž‘์€ ๊ฐœ๋…์„ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +## ๊ธ‰ํ•˜์‹ ๊ฐ€์š”? { #in-a-hurry } + +์ด ์šฉ์–ด๋“ค์— ๊ด€์‹ฌ์ด ์—†๊ณ  ์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ธฐ๋ฐ˜ ์ธ์ฆ์„ ์‚ฌ์šฉํ•œ ๋ณด์•ˆ์„ *์ง€๊ธˆ ๋‹น์žฅ* ์ถ”๊ฐ€ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค๋ฉด, ๋‹ค์Œ ์žฅ๋“ค๋กœ ๋„˜์–ด๊ฐ€์„ธ์š”. + +## OAuth2 { #oauth2 } + +OAuth2๋Š” ์ธ์ฆ๊ณผ ์ธ๊ฐ€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•˜๋Š” ๋ช…์„ธ์ž…๋‹ˆ๋‹ค. + +์ƒ๋‹นํžˆ ๋ฐฉ๋Œ€ํ•œ ๋ช…์„ธ์ด๋ฉฐ ์—ฌ๋Ÿฌ ๋ณต์žกํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค. + +"์ œ3์ž"๋ฅผ ์‚ฌ์šฉํ•ด ์ธ์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. + +๋ฐ”๋กœ `"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์—์„œ ๋น„๊ต์  ๋ชจํ˜ธํ•œ ๋ถ€๋ถ„์„ ์ผ๋ถ€ ๊ตฌ์ฒดํ™”ํ•˜์—ฌ ์ƒํ˜ธ ์šด์šฉ์„ฑ์„ ๋†’์ด๋ ค๋Š” ํ™•์žฅ์ž…๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, 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 Foundation์˜ ์ผ๋ถ€). + +**FastAPI**๋Š” **OpenAPI**๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. + +์ด ๋•๋ถ„์— ์—ฌ๋Ÿฌ ์ž๋™ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ ์ธํ„ฐํŽ˜์ด์Šค, ์ฝ”๋“œ ์ƒ์„ฑ ๋“ฑ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +OpenAPI์—๋Š” ์—ฌ๋Ÿฌ ๋ณด์•ˆ "scheme"์„ ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋Ÿฌํ•œ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ ์‹œ์Šคํ…œ์„ ํฌํ•จํ•ด, ํ‘œ์ค€ ๊ธฐ๋ฐ˜ ๋„๊ตฌ๋“ค์„ ๋ชจ๋‘ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +OpenAPI๋Š” ๋‹ค์Œ ๋ณด์•ˆ scheme๋“ค์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค: + +* `apiKey`: ๋‹ค์Œ์—์„œ ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ํ‚ค: + * ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ + * ํ—ค๋” + * ์ฟ ํ‚ค +* `http`: ํ‘œ์ค€ HTTP ์ธ์ฆ ์‹œ์Šคํ…œ, ์˜ˆ: + * `bearer`: `Authorization` ํ—ค๋”์— `Bearer ` + ํ† ํฐ ๊ฐ’์„ ๋„ฃ๋Š” ๋ฐฉ์‹. OAuth2์—์„œ ์œ ๋ž˜ํ–ˆ์Šต๋‹ˆ๋‹ค. + * HTTP Basic ์ธ์ฆ + * HTTP Digest ๋“ฑ +* `oauth2`: ๋ณด์•ˆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ชจ๋“  OAuth2 ๋ฐฉ์‹(์ด๋ฅผ "flow"๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค). + * ์ด flow๋“ค ์ค‘ ์—ฌ๋Ÿฌ ๊ฐœ๋Š” OAuth 2.0 ์ธ์ฆ ์ œ๊ณต์ž(์˜ˆ: Google, Facebook, X (Twitter), GitHub ๋“ฑ)๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค: + * `implicit` + * `clientCredentials` + * `authorizationCode` + * ํ•˜์ง€๋งŒ ๊ฐ™์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ง์ ‘ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์™„๋ฒฝํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํŠน์ • "flow"๋„ ํ•˜๋‚˜ ์žˆ์Šต๋‹ˆ๋‹ค: + * `password`: ๋‹ค์Œ ์žฅ๋“ค์—์„œ ์ด์— ๋Œ€ํ•œ ์˜ˆ์‹œ๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค. +* `openIdConnect`: OAuth2 ์ธ์ฆ ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ํƒ์ƒ‰(discover)ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + * ์ด ์ž๋™ ํƒ์ƒ‰์€ OpenID Connect ๋ช…์„ธ์—์„œ ์ •์˜๋ฉ๋‹ˆ๋‹ค. + + +/// tip | ํŒ + +Google, Facebook, X (Twitter), GitHub ๋“ฑ ๋‹ค๋ฅธ ์ธ์ฆ/์ธ๊ฐ€ ์ œ๊ณต์ž๋ฅผ ํ†ตํ•ฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋ฉฐ ๋น„๊ต์  ์‰ฝ์Šต๋‹ˆ๋‹ค. + +๊ฐ€์žฅ ๋ณต์žกํ•œ ๋ฌธ์ œ๋Š” ๊ทธ๋Ÿฐ ์ธ์ฆ/์ธ๊ฐ€ ์ œ๊ณต์ž ์ž์ฒด๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์ด์ง€๋งŒ, **FastAPI**๋Š” ์–ด๋ ค์šด ์ž‘์—…์„ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•ด ์ฃผ๋ฉด์„œ ์ด๋ฅผ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +/// + +## **FastAPI** ์œ ํ‹ธ๋ฆฌํ‹ฐ { #fastapi-utilities } + +FastAPI๋Š” `fastapi.security` ๋ชจ๋“ˆ์—์„œ ๊ฐ ๋ณด์•ˆ scheme์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์ด๋Ÿฌํ•œ ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋” ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. + +๋‹ค์Œ ์žฅ๋“ค์—์„œ๋Š” **FastAPI**๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•ด API์— ๋ณด์•ˆ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๋˜ํ•œ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ ์‹œ์Šคํ…œ์— ์–ด๋–ป๊ฒŒ ์ž๋™์œผ๋กœ ํ†ตํ•ฉ๋˜๋Š”์ง€๋„ ํ™•์ธํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. From 23bcfa094d9baefbf0150cbcddd1b77ec33a23fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:54:26 +0000 Subject: [PATCH 084/110] =?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 233fdb7434..912f215f09 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update translations for ko (add-missing). PR [#14699](https://github.com/fastapi/fastapi/pull/14699) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for ko (update-outdated). PR [#14589](https://github.com/fastapi/fastapi/pull/14589) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for uk (update-outdated). PR [#14587](https://github.com/fastapi/fastapi/pull/14587) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for es (update-outdated). PR [#14686](https://github.com/fastapi/fastapi/pull/14686) by [@tiangolo](https://github.com/tiangolo). From fb15bba819a71df10fee28891c6c7c29fdf48d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Fri, 16 Jan 2026 03:57:08 -0800 Subject: [PATCH 085/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20LLM=20prompt=20?= =?UTF-8?q?instructions=20file=20for=20French=20(#14618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/fr/llm-prompt.md | 154 ++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 75 deletions(-) diff --git a/docs/fr/llm-prompt.md b/docs/fr/llm-prompt.md index 5ff18c4e8b..228500fe23 100644 --- a/docs/fr/llm-prompt.md +++ b/docs/fr/llm-prompt.md @@ -6,123 +6,127 @@ Language code: fr. ### Grammar to use when talking to the reader -Use the formal grammar (use ยซvousยป instead of ยซtuยป). +Use the formal grammar (use `vous` instead of `tu`). + +Additionally, in instructional sentences, prefer the present tense for obligations: + +- Prefer `vous devez โ€ฆ` over `vous devrez โ€ฆ`, unless the English source explicitly refers to a future requirement. + +- When translating โ€œmake sure (that) โ€ฆ is โ€ฆโ€, prefer the indicative after `vous assurer que` (e.g. `Vous devez vous assurer qu'il est โ€ฆ`) instead of the subjunctive (e.g. `qu'il soit โ€ฆ`). ### Quotes -1) Convert neutral double quotes (ยซ"ยป) and English double typographic quotes (ยซโ€œยป and ยซโ€ยป) to French guillemets (ยซยซยป and ยซยปยป). +- Convert neutral double quotes (`"`) to French guillemets (`ยซ` and `ยป`). -2) In the French docs, guillemets are written without extra spaces: use ยซtexteยป, not ยซ texte ยป. - -3) Do not convert quotes inside code blocks, inline code, paths, URLs, or anything wrapped in backticks. +- Do not convert quotes inside code blocks, inline code, paths, URLs, or anything wrapped in backticks. Examples: - Source (English): +Source (English): - ยซยซยซ - "Hello world" - โ€œHello Universeโ€ - "He said: 'Hello'" - "The module is `__main__`" - ยปยปยป +``` +"Hello world" +โ€œHello Universeโ€ +"He said: 'Hello'" +"The module is `__main__`" +``` - Result (French): +Result (French): - ยซยซยซ - ยซHello worldยป - ยซHello Universeยป - ยซHe said: 'Hello'ยป - ยซThe module is `__main__`ยป - ยปยปยป +``` +"Hello world" +โ€œHello Universeโ€ +"He said: 'Hello'" +"The module is `__main__`" +``` ### Ellipsis -1) Make sure there is a space between an ellipsis and a word following or preceding the ellipsis. +- Make sure there is a space between an ellipsis and a word following or preceding the ellipsis. Examples: - Source (English): +Source (English): - ยซยซยซ - ...as we intended. - ...this would work: - ...etc. - others... - More to come... - ยปยปยป +``` +...as we intended. +...this would work: +...etc. +others... +More to come... +``` - Result (French): +Result (French): - ยซยซยซ - ... comme prรฉvu. - ... cela fonctionnerait : - ... etc. - D'autres ... - La suite ... - ยปยปยป +``` +... comme prรฉvu. +... cela fonctionnerait : +... etc. +D'autres ... +La suite ... +``` -2) This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there. +- This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there. ### Headings -1) Prefer translating headings using the infinitive form (as is common in the existing French docs): ยซCrรฉerโ€ฆยป, ยซUtiliserโ€ฆยป, ยซAjouterโ€ฆยป. +- Prefer translating headings using the infinitive form (as is common in the existing French docs): `Crรฉerโ€ฆ`, `Utiliserโ€ฆ`, `Ajouterโ€ฆ`. -2) For headings that are instructions written in imperative in English (e.g. โ€œGo check โ€ฆโ€), keep them in imperative in French, using the formal grammar (e.g. ยซAllez voir โ€ฆยป). - -3) Keep heading punctuation as in the source. In particular, keep occurrences of literal ยซ - ยป (space-hyphen-space) as ยซ - ยป (the existing French docs use a hyphen here). +- For headings that are instructions written in imperative in English (e.g. `Go check โ€ฆ`), keep them in imperative in French, using the formal grammar (e.g. `Allez voir โ€ฆ`). ### French instructions about technical terms -Do not try to translate everything. In particular, keep common programming terms when that is the established usage in the French docs (e.g. ยซframeworkยป, ยซendpointยป, ยซplug-inยป, ยซpayloadยป). Use French where the existing docs already consistently use French (e.g. ยซrequรชteยป, ยซrรฉponseยป). +Do not try to translate everything. In particular, keep common programming terms (e.g. `framework`, `endpoint`, `plug-in`, `payload`). Keep class names, function names, modules, file names, and CLI commands unchanged. ### List of English terms and their preferred French translations -Below is a list of English terms and their preferred French translations, separated by a colon (ยซ:ยป). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them. +Below is a list of English terms and their preferred French translations, separated by a colon (:). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them. -* ยซ/// note | Technical Detailsยป: ยซ/// note | Dรฉtails techniquesยป -* ยซ/// noteยป: ยซ/// note | Remarqueยป -* ยซ/// tipยป: ยซ/// tip | Astuceยป -* ยซ/// warningยป: ยซ/// warning | Attentionยป -* ยซ/// checkยป: ยซ/// check | vรฉrifierยป -* ยซ/// infoยป: ยซ/// infoยป +- /// note | Technical Detailsยป: /// note | Dรฉtails techniques +- /// note: /// note | Remarque +- /// tip: /// tip | Astuce +- /// warning: /// warning | Alertes +- /// check: /// check | Vรฉrifications +- /// info: /// info -* ยซthe docsยป: ยซles documentsยป -* ยซthe documentationยป: ยซla documentationยป +- the docs: les documents +- the documentation: la documentation -* ยซframeworkยป: ยซframeworkยป (do not translate to ยซcadreยป) -* ยซperformanceยป: ยซperformanceยป +- Exclude from OpenAPI: Exclusion d'OpenAPI -* ยซtype hintsยป: ยซannotations de typeยป -* ยซtype annotationsยป: ยซannotations de typeยป +- framework: framework (do not translate to cadre) +- performance: performance -* ยซautocompleteยป: ยซautocomplรฉtionยป -* ยซautocompletionยป: ยซautocomplรฉtionยป +- type hints: annotations de type +- type annotations: annotations de type -* ยซthe requestยป (what the client sends to the server): ยซla requรชteยป -* ยซthe responseยป (what the server sends back to the client): ยซla rรฉponseยป +- autocomplete: autocomplรฉtion +- autocompletion: autocomplรฉtion -* ยซthe request bodyยป: ยซle corps de la requรชteยป -* ยซthe response bodyยป: ยซle corps de la rรฉponseยป +- the request (what the client sends to the server): la requรชte +- the response (what the server sends back to the client): la rรฉponse -* ยซpath operationยป: ยซopรฉration de cheminยป -* ยซpath operationsยป (plural): ยซopรฉrations de cheminยป -* ยซpath operation functionยป: ยซfonction de cheminยป -* ยซpath operation decoratorยป: ยซdรฉcorateur d'opรฉration de cheminยป +- the request body: le corps de la requรชte +- the response body: le corps de la rรฉponse -* ยซpath parameterยป: ยซparamรจtre de cheminยป -* ยซquery parameterยป: ยซparamรจtre de requรชteยป +- path operation: chemin d'accรจs +- path operations (plural): chemins d'accรจs +- path operation function: fonction de chemin d'accรจs +- path operation decorator: dรฉcorateur de chemin d'accรจs -* ยซthe `Request`ยป: ยซ`Request`ยป (keep as code identifier) -* ยซthe `Response`ยป: ยซ`Response`ยป (keep as code identifier) +- path parameter: paramรจtre de chemin +- query parameter: paramรจtre de requรชte -* ยซdeploymentยป: ยซdรฉploiementยป -* ยซto upgradeยป: ยซmettre ร  niveauยป +- the `Request`: `Request` (keep as code identifier) +- the `Response`: `Response` (keep as code identifier) -* ยซdeprecatedยป: ยซdรฉprรฉciรฉยป -* ยซto deprecateยป: ยซdรฉprรฉcierยป +- deployment: dรฉploiement +- to upgrade: mettre ร  niveau -* ยซcheat sheetยป: ยซaide-mรฉmoireยป -* ยซplug-inยป: ยซplug-inยป +- deprecated: dรฉprรฉciรฉ +- to deprecate: dรฉprรฉcier + +- cheat sheet: aide-mรฉmoire +- plug-in: plug-in From 8fa635c718fe71fa2e761b7fb33978d756e77a45 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:57:32 +0000 Subject: [PATCH 086/110] =?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 912f215f09..404a4e41d0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update LLM prompt instructions file for French. PR [#14618](https://github.com/fastapi/fastapi/pull/14618) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for ko (add-missing). PR [#14699](https://github.com/fastapi/fastapi/pull/14699) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for ko (update-outdated). PR [#14589](https://github.com/fastapi/fastapi/pull/14589) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for uk (update-outdated). PR [#14587](https://github.com/fastapi/fastapi/pull/14587) by [@tiangolo](https://github.com/tiangolo). From 9fec72687f48db48d72bff6ba2aa023fe792206b Mon Sep 17 00:00:00 2001 From: Rafael de Oliveira Marques <rafaelomarques@gmail.com> Date: Fri, 16 Jan 2026 09:27:02 -0300 Subject: [PATCH 087/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20portuguese=20ll?= =?UTF-8?q?m-prompt.md=20(#14702)?= 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: Sofie Van Landeghem <svlandeg@users.noreply.github.com> --- docs/pt/llm-prompt.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/pt/llm-prompt.md b/docs/pt/llm-prompt.md index 3f5208e910..dd72e8d168 100644 --- a/docs/pt/llm-prompt.md +++ b/docs/pt/llm-prompt.md @@ -10,6 +10,26 @@ Keep existing translations as they are if the term is already translated. When translating documentation into Portuguese, use neutral and widely understandable language. Although Portuguese originated in Portugal and has its largest number of speakers in Brazil, it is also an official language in several countries and regions, such as Equatorial Guinea, Mozambique, Angola, Cape Verde, and Sรฃo Tomรฉ and Prรญncipe. Avoid words or expressions that are specific to a single country or region. +Only keep parentheses if they exist in the source text. Do not add parentheses to terms that do not have them. + +### Avoiding Repetition in Translation + +When translating sentences, avoid unnecessary repetition of words or phrases that are implied in context. +- Merge repeated words naturally while keeping the meaning. +- Do **not** introduce extra words to replace repeated phrases unnecessarily. +- Keep translations fluent and concise, but maintain the original meaning. + +**Example:** + +Source: +Let's see how that works and how to change it if you need to do that. + +Avoid translating literally as: +Vamos ver como isso funciona e como alterar isso se vocรช precisar fazer isso. + +Better translation: +Vamos ver como isso funciona e como alterar se vocรช precisar. + --- For the next terms, use the following translations: @@ -22,10 +42,11 @@ For the next terms, use the following translations: * /// note: /// note | Nota * /// tip: /// tip | Dica * /// warning: /// warning | Atenรงรฃo -* (you should): (vocรช deveria) +* you should: vocรช deveria * async context manager: gerenciador de contexto assรญncrono * autocomplete: autocompletar * autocompletion: preenchimento automรกtico +* auto-completion: preenchimento automรกtico * bug: bug * context manager: gerenciador de contexto * cross domain: cross domain (do not translate to "domรญnio cruzado") From 0c7f2b66d769d05df42ea73f85205a3566ba127b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:27:29 +0000 Subject: [PATCH 088/110] =?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 404a4e41d0..d3f80fac93 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update portuguese llm-prompt.md. PR [#14702](https://github.com/fastapi/fastapi/pull/14702) by [@ceb10n](https://github.com/ceb10n). * ๐ŸŒ Update LLM prompt instructions file for French. PR [#14618](https://github.com/fastapi/fastapi/pull/14618) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for ko (add-missing). PR [#14699](https://github.com/fastapi/fastapi/pull/14699) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for ko (update-outdated). PR [#14589](https://github.com/fastapi/fastapi/pull/14589) by [@tiangolo](https://github.com/tiangolo). From 536a5bafe74a77f207b57cf6fb0cf3059121fffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Mon, 19 Jan 2026 12:40:17 -0800 Subject: [PATCH 089/110] =?UTF-8?q?=F0=9F=94=A7=20Update=20sponsors,=20Lam?= =?UTF-8?q?bdaTest=20changes=20to=20TestMu=20AI=20(#14734)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/data/sponsors.yml | 6 +++--- docs/en/docs/img/sponsors/testmu.png | Bin 0 -> 5725 bytes 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 docs/en/docs/img/sponsors/testmu.png diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index 50b1145301..c0bdb15f63 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -65,9 +65,9 @@ bronze: # - url: https://testdriven.io/courses/tdd-fastapi/ # title: Learn to build high-quality web apps with best practices # img: https://fastapi.tiangolo.com/img/sponsors/testdriven.svg - - url: https://lambdatest.com/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage - title: LambdaTest, AI-Powered Cloud-based Test Orchestration Platform - img: https://fastapi.tiangolo.com/img/sponsors/lambdatest.png + - url: https://www.testmu.ai/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage + title: TestMu AI. The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering. + img: https://fastapi.tiangolo.com/img/sponsors/testmu.png - url: https://requestly.com/fastapi title: All-in-one platform to Test, Mock and Intercept APIs. Built for speed, privacy and offline support. img: https://fastapi.tiangolo.com/img/sponsors/requestly.png diff --git a/docs/en/docs/img/sponsors/testmu.png b/docs/en/docs/img/sponsors/testmu.png new file mode 100644 index 0000000000000000000000000000000000000000..5603b04faed5e917bd0ea78f1684ba3c7a747718 GIT binary patch literal 5725 zcmd6L_cvT`w6`7vBf5#+iOyGzE?S}l(Pc#RE_x4%8iPa-MvdO0M2kK~8-hsG=msN1 z57FHx>-`7b-|n5Y&YZQ*oO#;bpR#wnfu1HgDU=ik2Zvl+OWhFM_rYaHOa#7LK6VU% z8;O^exepEw1?u00JK$Mn2Ocu|J}~n&@^tVGc=o~`Cm<j|*xAF?=lL@)dtuKPj)>j+ zP#hfEEp2rb<G}2loDc(J?Abt<V~y2X{jfE$4uJqJyN<enu^X2KqU2?QDS@jf+^-?E zE^+Fo)VQ3UWM86$Qv;)Id()&vbp{V%KbMBs5I1e2dLk(?zA(LrVB7F+=M`!xmsP}Z z*4joT`6%3Bl=UL}Z`g@!>{ag3)7-z&_{!ZjqU!N$&bPpwh=N-56}#<+myBe0Py}CX z;uv^rh}3#TaU~cyVn{w9Da9Ujlf}e6!*dW-C;MM~^{wpiodNF@EjJ~|Bc4>$H5V0$ zOw#tzeRcwe&tJxlJUl$4wjnefPa%clo4#H8x52qbj+jrE84Iwdjw^|!q6u>Jq@9*f z(Tx{BW@lyjSYJ~*HU3oTvqw7PCB?-lo0z1Jh)10N$YZ+3R9k(aLL!l2xm|$=9HTk% z)7Z1gN>kL>*jT~d7Gxla8MQu~k+b{PIp0{|;9}|K>Hc!Zi*?fLljbh+A?X2cv(FCX zmseN(7b6Jix`4e!l`|G$Jbe6vQTaUs5j<65|DB)F=jXw(S8nqycV2wSzL}q&Ph^(& zml4uiO?*C@E!)eoxVT7h(e`3JK^GQreU#@uRa0JGcXv#AaAd@9=n7h)R&4k-E{=FB z1#M?C92^|{Zg<xM`CocQ1~mkdtkQRfkx^R`3hxeh{CM1`OEZaOI75Wj;d5rflABcO zlS-50;bfD_;!tB#Q+Xkc(vC>RF(Nb?ZPwz&Zg1J<F9iOIQtXG{jeY-~=5iZ$1NCBh zLoX6zQO)9YRSJK^gQ~Y5CRh+n!qocyGW<Q)3|UxM(C2Txi6uFe<&=|yf|pC&?n=3B zW<W9@#mvuJWh;a<!7y}m6E6SJ30X*TJ*PxH@b#6XqN1wGMTkDbTU}lCI^LRmmZUT2 zb8lIs^AxYKu`w(+z|XHQkx_!r(#VKfKtRClyt=Z|M<;Ylj-Q`D?#&yuprD|x?a^F? zyDKXzYLt`vbzUo~EnmKvE0h`w)H#iF@GKYDO@FfX^7j696l!3QqTD?>IeCg<y>a@n zmm?pLO}X$nIX3oIx(IC16ZaksY5Um*!rzfA*S@{IT{K|V3Ss~K`}bP=VY-IqW{8pT zRFDT@=+4jo5Lit(l)Dxzab+m&9{#yv5{4}mtxOz=w!>q$Qw|r@DmLV!A;o!#v|%tX z@~<r_E`B!G<lYjnmy7b_(aVnO?d@exblJ>Q43q0mWK^5Au&^k*b6i_1`t^et{>|xt zQjGll1guFnDl#L3zCW2Yd4J#Q^x^_FG6K)TxH>x{t1X)i%+2GIk|<)Y`UVC##l@+v zudgpJ3rs4bH42enZw8MaNA~w?bW#j>pRS8Upd*@ia;vHY8yXuQH|Z;$^?KX~89q2T z*!{`6jVXZ@$E3;(*VNQBc8G|8fQ^&$Rejp~_xU9y{SF=7-O5F!rAAGwtG1yxSAQu6 zoZtNZ?N&iJJ~<h!5OQMkqeM5fD;FapXxT)@tC!922Nf(TiVML3kq7DXHS53IHSLF_ zJt)))7CX=-b{woA2_^SfXpOF~7pI}8FDxq~CVGXeP>NTNa{f`WQmS;t5AuSNk&y%j zgDH~qI7xmiEW~g2T+(>(fG{B;!R!2wv#+0DBu1uU*0Xx)1mtIyPB8oH<(azY-?opX zYEm&WYin!ywY8MVtV&HGu%4bCQO8lHwspbRuU`|l3c9<y`x9RF+!i6;a+ZXa63Yk* zl7WPJU)|o$L`X<z*5t;hk<2nOC*<j3`CLeioZ3st+t2TTo*qTIpm|hpuQ~{?3L`x| zJs6ISi;Dmt=YyP|00#%h{=oq2?bqGjOdF#)Zvf2LWo6A%z7X6GvN=6J@0p&a|9284 zk20ek7itvb=i|ub=%omb-v#^Jl3*_yQxsr-4OK{j!~6dH`r*Ctl@YOoi*uV!FyFSW zJ@Xt2Paj^7nsJ9)k~QUWf{*|XILISmmkB);%|al&57)G-tlQF+usb{O#6+_8o9hd` zv)xFx#kVn~FO3EI5T2d!@VnBsL+|-b`dq6=d*i9=o}q}=Ha18JvQkoB>LR4rxw#*B zjk-zQ{{BEVNeuw>@bIwK1{sBf8H+%n&@xFK5Y*<y;NjiI;Gm#qzrGj0H@gdk=Ei?9 z)T~Ps{@WfiHDwa6c)7oViZU7QiN4LeD^5nJ7lI$)>kG7Ie`FB*mQliiO+-XLWz`&H z8%P03Y3Wy#c&x0f02FKj0tq)_1Ox;&3$1=F{yR_*?$cJ+=}!+HJtDiwq>LIG(s`ee zfw{iAz`;>cQsUy`V&mb7`HRJ3WBs<LRz8m@qUd3v$Y}^U`2#aE2HGV+)a5iLs#<v? zXM3F&6i_%3W*`~s{*OQ4@J87_DW8o;>JJ_~ZldPZv*}Bq{dlI^3-BZ*mHrO^`_8Fp zX=!C;W%>P#wzjsS-mB`NXX_a#XeD7O$?3)jLdJKKvcA5)N(#R(sjW>O92X@ORnHtT zJ0Cwkas?IZeZ2JqY!DwOG&GcwnHes<7>Ex7x_o4#nZy*|-hMyy>Uer{Yb(m4x?ey1 zka{Z~W?_-wKG#$La<RI)8YfpWlA4ODHu%`3B7%;VHu~3(Qp`fY(z^Q_Iw4gL4`JF1 zpigHrg5ClC{ub{3is6?Gj$^rf!y;W@2i8|sBD1rz6$#z6FE?ieBTo0(DVStl(%v(f z>8u;0e3&6z%p&FRnei3{)3VZCG9o6$P=-_<-PJv~m9VCX$&?Yb=yLD;IN+Wa%iXxP zlF;gnI}k_VhK}bGWsmXlBCfV!7lKOQ06$J|mmk{O+uLnTl!r~l$HlRg>g9ag+B^>K z)N^yIVv7NEs(4jgQbKCW$H$jHR0ZpLx-20nIZ>hH`sLd|GAl1TJ9|+<L1g{(^mOX- zvQ1_9Dh7iFkt#m~BXNBC^a(Fy%xV)rPo-*hcGmyKKb2RXZen<72s*Kc92-lXn4Em9 z=(*Ug4Q3+fLM+NI{@q>R#Kpx8J7*cPaw<KEbai#D14PcG6h6OUs3i98?OPRv91`}K z&yEnChY#5^hO1cucIT0?tl?V6__U;i;U}N-a9>VF7L6Bba)PD0+}pT3JK_@-)^K*_ zkN6Ydi~vt1m)Fp#??=Ro?cmwjOfKkbv|Z0)M@Zh#{IPP-PUCdh6fMr>QQl4Ot6LC0 zOKWRp9UUD$O9KN+hp}9RH8Bg+FZYG4EU4RTJ)w9e*{@%}K90ujpeM?W2%8Qt14chc zr#8PNevK-IOwP>2jgIPV4dmtKQWP7;$HWjdG&Ozf=wRO2*=dQmQH0*TdvJZRXQT^z zC^0unLr2FUFONuu16GE0T8S3ZP*FuSG)Pu>Jb6Mpng@X6O-^W7Zd~i7Qu<?M@GWOl z{cFn5^IxSO<O76naGJi7u-*^!nqjq2`)=L=V7M5vr!ctwqW$`O`y+jwVL1(P8b+r5 z>SPZ8ke-f?C<&84?zDe~&Epgjf_G^~Mn<0YiG<^(qF>*Fu;c^s28{d7NfPKEOZe%^ zE#oIoRw`>cq2DDt0iLZ|yl_7|jI6rX)zxWfYa>C12U!LO%l%iTNsI(QKR7*jK2G)& z&MZ#{f?nbV#v&#r?*5u7LAai#!`)yZq>;#goa52WdR<n=2gW83YFA*aA20Fjx=N=% zs51YoE@TVbV5uH$OIzD3jLe4-hu<@G_#ip7baX!MzBLwzwx|Z)rvHsd=ir>59CPQz zuKYh?0s=B!0}HUQQV@f}XPP}(4ULS>q~U-{Ej>G;eXyfQJMm||ad!sM=z>)b7nh#8 z=L0cZB?TN35~)<2`*u%*F#Yhbsb33M-T?uc4h|bfoHjTu!N&ssjuU7|rM`F_Tbd3b zgiSo|k8F<Kz;u`IFRI0`zt(mR4z`DDXkrK?_7TtaQ6zSCH3}reKWhb4`*g%J{6~$| z`n7?TRU$|qmtd#!Kff5HJ(vXU-4k>FDT!<tE;lMYZ?x=F_Sz7P1rBFpEKj7ia?<5d zrg$-|6A)MI&RjDFr1mK3n6R4S((|)OfwZB5>k4+Y%E^yyZHU&Gwtzj`o9iq5!|CyH z<n0fXQDaIsOwu0n%7%ti;eQHQ*(D`uK&1DNkE3;p0e{8l6n}ijPeV!w=1NXTAaz^n z4D+IQ;v7|rrFeF<F%rdJ9IzO4>z?VSqTZrOm&ayio3#<H_&G_EZ6S{7<T3n4C2Zp2 z-}yT^I62>DXS?}S@;YK9abu&Sk7ad8?q|X-50WDUwZu2y++1Rnq;9Fk_$=yz#g>=H zZ{6BH4t85;o!s5<q>3lFXHu0<^x0#nlYAMy%cu|rB_jeq(yFMa5R;L;PELjx3(x{S zUf$bF1`%65o`zu&M*x4ava+YkuOKmQ6E~aVg=WA9H+Gq6Xb>r0{FXqz_*&C-{c|^X zD;IbV5<}^o;%Gd5e==`6z(-Mn5}MN47@4Fk3v(irI{z|o=9epwh+6ZFANooU08MKo zF*#2!{hs}Nuyk|XJa!ajPeo1$Aa^t({eDIR4vlVUk!c%-pirnf=N~*<BZg*XaT1QB zY(hfhN;hX?L2Y`asCa5#0~k!iDMo;XR9#)Ydv5M-d3kxe0tW{ksD3Dfq!@^sJr;Vp zZmv9CP(3Lrl$W~!U9U<@c|9c%Bd5c#>+dxa=#32rkpDW$2L}gI+bylFa_&PsAdoek z=K}U^fJgj)&#V~GYnL0ZHg{bKaj%-f!e|Q$3Q(;FuZ(!!GtW+p0=BKPY)-l#{AbW( z_VhHcRM+29{v0t_7kFT6E1p?)sF}>d4a{iR6fjNP@&UU=TVTd|;KuP@u3@o8*hf6n znNsdqBP~j&%lInHX*zv*&FO<;!GD}8JI{62HmyB=HaM%7{vi2O$P4gSx{9MrLmEAz zPEl?wpq1<7;=&;&CT4<Jmr1g7bi8fLZf6tERB2j6XElI)b&L6vNX9Vw><CNvcipt> zoc4*HWIWT!=I(BPXV}F)nl~@b<sB;yuoVQHou2InL{l}eWbIU*$NJBJvjO}8=z{2B zx?sW9d`C#e9`t=$+G8vzHKp8UUIODntkGdFo;lff2!+S!Y}7WXiwX-<bar;`9~`Ve zz$dom>TrGdbOYhZXSr8x0%4Y$*mD+2MRRTP5CT&J=m1d8YKDdm7gZvYES0&r`QY%d z&ro23YC-oq?uTXDqEI+Rb!yYMg$4N4#a|;&S5RWvq2E12sNOv(S_f=u1FFx!gFbq~ zpzbkoAt9l`v9ST5S%dnrAegH=%}f2j1wRI*L3%nJIQGDh5KM8#Q&krifkuhKai?CO zPU9031k317Yd=3fVsi5J;r5fCu3kPqsOa1D2BxOBs!eW3t48MMvvF~AKl@&!ZES@d z%_(f9K0ZE<3SZCq&ow10OXoPXva+(8N5D+2W*UP@R?>L!qJFZ=)YbJ~5{trCO3fmZ zoG<Yab_|<pB$I?{4?4;>seBA7lBOmppd+1M=0ZvnBdO@=HHBtyUT#m<U{?FyTpjwH z{5GF&3rHSX%>p&y)9^EUq?%_LLlckh{}ca1u)h=Y6LVKpd^Q#q7A_?v*f%bY*OZ0h zT0iIJv|U{**<hI|yt)ej1AAP2d~t_gJQn0sBvL6=Tie>SpFijP;<rs#UQ@$sZEYPT z4(c~9F|oJ*Buqn2PVV<5H?eHkc>_w<X<7S|m%DrQ6BsygE~WvJt(kvQ0+g-&)5?;n z^<3vh3*dUHbN93(5Z|U+rNACozj>DD=POf=0Y?^FOma0rK=xA5toEg(l$3?gg&-*d z4haPX&s?)7udc4Hf6a81NhLRMf$ZMi-gpr%jahDUP1Ue#V)mkniUgqA*;-swLwV{P z?0|@OeN^J3ufYhjt$vltT5>r_MMXtsLVIy9aZ><uyx5xHZxgdD0~;fH6<L3**{!Ik zxOTELXM6?xI28+vV*Zk$q2arH*fk2GM9IpUhqdemeFxyfa((^%Nrx0G@raZ|!@|%N zPbvq|sl3{1YOid(hE*LMdA|Tjnr#n~h)+m>HYkCUc5!t@1E8TnP~)tGo3}*7#ozs2 zXv>&y4>H<UYZj%%?fUcmVNpj%jzGjp4<V@R#rbgY^-Uyqem1@V4F}6B&pV(K0qn~= zz;G#P_e@ENy3RS5zkYp$e6@LcfHASWlf*<C9c9%X`1a6IJW~rOXUgr<a^d+M0Ao$o zu-Bj>McM`LJz*Q&0221kQo42AN;_9A9Jc|!ss*eV*vC6}?nHtY0|Nt<1=3V&rVASz zX+bY$-6z3a_sHxrEj`_~=QRm~gag^0zHkQ0NpcnJ%E`@5U00V}*t*ruhqYv{>xKm( z?b)9J3y&9j-mhG2NK)TcQBh$d%1|5f*TV%`3cSC5*FRAV4_Cy)!wa<3*4ECu*&C`C zD97gi0M%Aiy3oCQYGGjto#$Kk67Nb=h_vtH-B(axPt*BVKLeCbOig`kXrM8zc?xv& zV?V|3ox(}^PYZ1UMomaJJx5W7l)OBV5BSY%#G!x2H7D}Z?a6u>y-vHP_iZvI9FrEp zQjG=DCYhlimH5quo>*Hea6McX1j%!HdTJom@iaLW!g?~70jd+wk^+4a+{{P)w`au5 zCSjoKw2Z~x(%0A5=^hywxw;+;2P(*^kw{sUAkIJ*c)DM}R*7Cz*#GmVh~R90MGb+# zoSmN=LMUD#ZT_xQ84KK@p&<|v5do8J`pPvlG~`!T!|5|Y&w`sY#QRV0+?=JC5<nJF z<5lvTH*ak0mR?kM?13%`)o&^}hFlBkEau-f1IrYp{}pU+ZPj`%DI)Bd!c@WB16~%? z7UjmISw$VrE-t|p2pb88vAcSp|A=G=pXvPef1xA!|JM?|!CyMycQV|oIRdR=9PI~s K>Yr3?qW%ZABJ|Aw literal 0 HcmV?d00001 From db5441eba1d4c3bee11dac239a685e3c0630cda5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:40:38 +0000 Subject: [PATCH 090/110] =?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 d3f80fac93..8b338f60d0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -30,6 +30,7 @@ hide: ### Internal +* ๐Ÿ”ง Update sponsors, LambdaTest changes to TestMu AI. PR [#14734](https://github.com/fastapi/fastapi/pull/14734) by [@tiangolo](https://github.com/tiangolo). * โฌ† Bump actions/cache from 4 to 5. PR [#14511](https://github.com/fastapi/fastapi/pull/14511) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump actions/upload-artifact from 5 to 6. PR [#14525](https://github.com/fastapi/fastapi/pull/14525) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump actions/download-artifact from 6 to 7. PR [#14526](https://github.com/fastapi/fastapi/pull/14526) by [@dependabot[bot]](https://github.com/apps/dependabot). From 463a3a24d7aee142bede2bcdd0ef542646b1fe3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Mon, 19 Jan 2026 12:55:32 -0800 Subject: [PATCH 091/110] =?UTF-8?q?=F0=9F=94=A7=20Update=20sponsors:=20rem?= =?UTF-8?q?ove=20Requestly=20(#14735)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/data/sponsors.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index c0bdb15f63..f8085b4523 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -68,6 +68,3 @@ bronze: - url: https://www.testmu.ai/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage title: TestMu AI. The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering. img: https://fastapi.tiangolo.com/img/sponsors/testmu.png - - url: https://requestly.com/fastapi - title: All-in-one platform to Test, Mock and Intercept APIs. Built for speed, privacy and offline support. - img: https://fastapi.tiangolo.com/img/sponsors/requestly.png From ad6b2901a6ab9350c55f261b06dfd1dc363e1fde Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:56:10 +0000 Subject: [PATCH 092/110] =?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 8b338f60d0..4cc8bf0188 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -30,6 +30,7 @@ hide: ### Internal +* ๐Ÿ”ง Update sponsors: remove Requestly. PR [#14735](https://github.com/fastapi/fastapi/pull/14735) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update sponsors, LambdaTest changes to TestMu AI. PR [#14734](https://github.com/fastapi/fastapi/pull/14734) by [@tiangolo](https://github.com/tiangolo). * โฌ† Bump actions/cache from 4 to 5. PR [#14511](https://github.com/fastapi/fastapi/pull/14511) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump actions/upload-artifact from 5 to 6. PR [#14525](https://github.com/fastapi/fastapi/pull/14525) by [@dependabot[bot]](https://github.com/apps/dependabot). From 6afb15c518e7c151c02639b3704424477997da87 Mon Sep 17 00:00:00 2001 From: Kader Miyanyedi <48386782+Kadermiyanyedi@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:34:03 +0300 Subject: [PATCH 093/110] =?UTF-8?q?=F0=9F=8C=90=20Improve=20LLM=20prompt?= =?UTF-8?q?=20for=20Turkish=20translations=20(#14728)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tr/llm-prompt.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/tr/llm-prompt.md b/docs/tr/llm-prompt.md index 297b0a0e6c..2ba922ec59 100644 --- a/docs/tr/llm-prompt.md +++ b/docs/tr/llm-prompt.md @@ -4,10 +4,16 @@ Translate to Turkish (Tรผrkรงe). Language code: tr. +### Core principle + +Don't translate word-by-word. Rewrite naturally in Turkish as if writing the doc from scratch. Preserve meaning, but prioritize fluency over literal accuracy. + ### Grammar and tone - Use instructional Turkish, consistent with existing Turkish docs. -- Use imperative/guide language when appropriate (e.g. โ€œaรงalฤฑmโ€, โ€œgidinโ€, โ€œkopyalayalฤฑmโ€). +- Use imperative/guide language (e.g. "aรงalฤฑm", "gidin", "kopyalayalฤฑm", "bir bakalฤฑm"). +- Avoid filler words and overly long sentences. +- Ensure sentences make sense in Turkish context โ€” adjust structure, conjunctions, and verb forms as needed for natural flow (e.g. use "Ancak" instead of "Ve" when connecting contrasting sentences, use "-maktadฤฑr/-mektedir" for formal statements). ### Headings @@ -15,13 +21,23 @@ Language code: tr. ### Quotes -- Alฤฑntฤฑ stili mevcut Tรผrkรงe dokรผmanlarla tutarlฤฑ tutun (genellikle metin iรงinde ASCII tฤฑrnak iลŸaretleri kullanฤฑlฤฑr). -- Satฤฑr iรงi kod, kod bloklarฤฑ, URL'ler veya dosya yollarฤฑ iรงindeki tฤฑrnak iลŸaretlerini asla deฤŸiลŸtirmeyin. +- Keep quote style consistent with existing Turkish docs (typically ASCII quotes in text). +- Never modify quotes inside inline code, code blocks, URLs, or file paths. ### Ellipsis -- รœรง nokta (...) stili mevcut Tรผrkรงe dokรผmanlarla tutarlฤฑ tutun. -- Kod, URL veya CLI รถrneklerindeki `...` ifadesini asla deฤŸiลŸtirmeyin. +- Keep ellipsis style (`...`) consistent with existing Turkish docs. +- Never modify `...` in code, URLs, or CLI examples. + +### Consistency + +- Use the same translation for the same term throughout the document. +- If you translate a concept one way, keep it consistent across all occurrences. + +### Links and references + +- Never modify link syntax like `{.internal-link target=_blank}`. +- Keep markdown link structure intact: `[text](url){.internal-link}`. ### Preferred translations / glossary From 7443bc7a4684b3d1754c0ab59bb9e878bac96a6b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 20:34:36 +0000 Subject: [PATCH 094/110] =?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 4cc8bf0188..8d97900394 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Improve LLM prompt for Turkish translations. PR [#14728](https://github.com/fastapi/fastapi/pull/14728) by [@Kadermiyanyedi](https://github.com/Kadermiyanyedi). * ๐ŸŒ Update portuguese llm-prompt.md. PR [#14702](https://github.com/fastapi/fastapi/pull/14702) by [@ceb10n](https://github.com/ceb10n). * ๐ŸŒ Update LLM prompt instructions file for French. PR [#14618](https://github.com/fastapi/fastapi/pull/14618) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for ko (add-missing). PR [#14699](https://github.com/fastapi/fastapi/pull/14699) by [@tiangolo](https://github.com/tiangolo). From 0ab68a762f5fbf86777a45afc8df7059e07a0a7d Mon Sep 17 00:00:00 2001 From: "hy.lee" <rurouni24@gmail.com> Date: Wed, 21 Jan 2026 05:37:04 +0900 Subject: [PATCH 095/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20Korean=20LLM=20?= =?UTF-8?q?prompt=20(#14740)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ko/llm-prompt.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ko/llm-prompt.md b/docs/ko/llm-prompt.md index df807c9496..533160eab9 100644 --- a/docs/ko/llm-prompt.md +++ b/docs/ko/llm-prompt.md @@ -8,6 +8,7 @@ Language code: ko. - Use polite, instructional Korean (e.g. ํ•ฉ๋‹ˆ๋‹ค/ํ•˜์„ธ์š” style). - Keep the tone consistent with the existing Korean FastAPI docs. +- Do not translate โ€œYouโ€ literally as โ€œ๋‹น์‹ โ€. Use โ€œ์—ฌ๋Ÿฌ๋ถ„โ€ where appropriate, or omit the subject if it sounds more natural in Korean. ### Headings From 7faa7089d665ad9e47a73d4a55457a23ba5cf643 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 20:37:28 +0000 Subject: [PATCH 096/110] =?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 8d97900394..7c5460f1df 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update Korean LLM prompt. PR [#14740](https://github.com/fastapi/fastapi/pull/14740) by [@hard-coders](https://github.com/hard-coders). * ๐ŸŒ Improve LLM prompt for Turkish translations. PR [#14728](https://github.com/fastapi/fastapi/pull/14728) by [@Kadermiyanyedi](https://github.com/Kadermiyanyedi). * ๐ŸŒ Update portuguese llm-prompt.md. PR [#14702](https://github.com/fastapi/fastapi/pull/14702) by [@ceb10n](https://github.com/ceb10n). * ๐ŸŒ Update LLM prompt instructions file for French. PR [#14618](https://github.com/fastapi/fastapi/pull/14618) by [@tiangolo](https://github.com/tiangolo). From 2d459e4845e8de9e080039d8f3ab2f1fd2e774a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Tue, 20 Jan 2026 12:40:17 -0800 Subject: [PATCH 097/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20pt=20(update-outdated)=20(#14724)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- docs/pt/docs/_llm-test.md | 48 +++++------ .../path-operation-advanced-configuration.md | 64 ++++---------- docs/pt/docs/advanced/settings.md | 44 ---------- ...migrate-from-pydantic-v1-to-pydantic-v2.md | 22 ++--- .../docs/how-to/separate-openapi-schemas.md | 8 +- docs/pt/docs/index.md | 86 ++++++++++--------- docs/pt/docs/tutorial/bigger-applications.md | 84 +++++++++--------- docs/pt/docs/tutorial/body-updates.md | 42 +++------ docs/pt/docs/tutorial/body.md | 18 ++-- docs/pt/docs/tutorial/extra-models.md | 26 ++---- .../tutorial/query-params-str-validations.md | 34 +++----- docs/pt/docs/tutorial/response-model.md | 14 --- docs/pt/docs/tutorial/schema-extra-example.md | 36 ++------ 13 files changed, 188 insertions(+), 338 deletions(-) diff --git a/docs/pt/docs/_llm-test.md b/docs/pt/docs/_llm-test.md index 3da5e8a71d..b59292f47c 100644 --- a/docs/pt/docs/_llm-test.md +++ b/docs/pt/docs/_llm-test.md @@ -1,8 +1,8 @@ # Arquivo de teste de LLM { #llm-test-file } -Este documento testa se o <abbr title="Large Language Model โ€“ Modelo de Linguagem de Grande Porte">LLM</abbr>, que traduz a documentaรงรฃo, entende o `general_prompt` em `scripts/translate.py` e o prompt especรญfico do idioma em `docs/{language code}/llm-prompt.md`. O prompt especรญfico do idioma รฉ anexado ao `general_prompt`. +Este documento testa se o <abbr title="Large Language Model - Modelo de Linguagem de Grande Porte">LLM</abbr>, que traduz a documentaรงรฃo, entende o `general_prompt` em `scripts/translate.py` e o prompt especรญfico do idioma em `docs/{language code}/llm-prompt.md`. O prompt especรญfico do idioma รฉ anexado ao `general_prompt`. -Os testes adicionados aqui serรฃo vistos por todos os autores dos prompts especรญficos de idioma. +Os testes adicionados aqui serรฃo vistos por todos os designers dos prompts especรญficos de idioma. Use da seguinte forma: @@ -23,7 +23,7 @@ Este รฉ um trecho de cรณdigo: `foo`. E este รฉ outro trecho de cรณdigo: `bar`. E //// -//// tab | Informaรงรตes +//// tab | Informaรงรฃo O conteรบdo dos trechos de cรณdigo deve ser deixado como estรก. @@ -45,9 +45,9 @@ O LLM provavelmente vai traduzir isso errado. O interessante รฉ apenas se ele ma //// -//// tab | Informaรงรตes +//// tab | Informaรงรฃo -O autor do prompt pode escolher se deseja converter aspas neutras em aspas tipogrรกficas. Tambรฉm รฉ aceitรกvel deixรก-las como estรฃo. +O designer do prompt pode escolher se quer converter aspas neutras em aspas tipogrรกficas. Tambรฉm รฉ aceitรกvel deixรก-las como estรฃo. Veja, por exemplo, a seรงรฃo `### Quotes` em `docs/de/llm-prompt.md`. @@ -67,7 +67,7 @@ Pesado: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you ha //// -//// tab | Informaรงรตes +//// tab | Informaรงรฃo ... No entanto, as aspas dentro de trechos de cรณdigo devem permanecer como estรฃo. @@ -95,24 +95,24 @@ $ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid ...e outro exemplo de cรณdigo de console... ```console -// Crie um diretรณrio "Code" +// Criar um diretรณrio "Code" $ mkdir code -// Entre nesse diretรณrio +// Mudar para esse diretรณrio $ cd code ``` ...e um exemplo de cรณdigo Python... ```Python -wont_work() # Isto nรฃo vai funcionar ๐Ÿ˜ฑ -works(foo="bar") # Isto funciona ๐ŸŽ‰ +wont_work() # This won't work ๐Ÿ˜ฑ +works(foo="bar") # This works ๐ŸŽ‰ ``` ...e รฉ isso. //// -//// tab | Informaรงรตes +//// tab | Informaรงรฃo O cรณdigo em blocos de cรณdigo nรฃo deve ser modificado, com exceรงรฃo dos comentรกrios. @@ -154,7 +154,7 @@ Algum texto //// -//// tab | Informaรงรตes +//// tab | Informaรงรฃo Abas e blocos `Info`/`Note`/`Warning`/etc. devem ter a traduรงรฃo do seu tรญtulo adicionada apรณs uma barra vertical (`|`). @@ -181,7 +181,7 @@ O texto do link deve ser traduzido, o endereรงo do link deve apontar para a trad //// -//// tab | Informaรงรตes +//// tab | Informaรงรฃo Os links devem ser traduzidos, mas seus endereรงos devem permanecer inalterados. Uma exceรงรฃo sรฃo links absolutos para pรกginas da documentaรงรฃo do FastAPI. Nesse caso, devem apontar para a traduรงรฃo. @@ -197,10 +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 } -* <abbr title="Getting Things Done โ€“ Fazer as Coisas">GTD</abbr> -* <abbr title="menos que"><code>lt</code></abbr> -* <abbr title="XML Web Token โ€“ Token Web XML">XWT</abbr> -* <abbr title="Parallel Server Gateway Interface โ€“ Interface de Gateway de Servidor Paralelo">PSGI</abbr> +* <abbr title="Getting Things Done">GTD</abbr> +* <abbr title="less than - menos que"><code>lt</code></abbr> +* <abbr title="XML Web Token">XWT</abbr> +* <abbr title="Parallel Server Gateway Interface - Interface de Gateway de Servidor Paralelo">PSGI</abbr> ### O abbr fornece uma explicaรงรฃo { #the-abbr-gives-an-explanation } @@ -209,12 +209,12 @@ 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 } -* <abbr title="Mozilla Developer Network โ€“ Rede de Desenvolvedores da Mozilla: documentaรงรฃo para desenvolvedores, escrita pelo pessoal do Firefox">MDN</abbr> -* <abbr title="Input/Output โ€“ Entrada/Saรญda: leitura ou escrita em disco, comunicaรงรตes de rede.">I/O</abbr>. +* <abbr title="Mozilla Developer Network: documentaรงรฃo para desenvolvedores, escrita pelo pessoal do Firefox">MDN</abbr> +* <abbr title="Input/Output: leitura ou escrita em disco, comunicaรงรตes de rede.">I/O</abbr>. //// -//// tab | Informaรงรตes +//// tab | Informaรงรฃo Os atributos "title" dos elementos "abbr" sรฃo traduzidos seguindo algumas instruรงรตes especรญficas. @@ -228,7 +228,7 @@ Veja a seรงรฃo `### HTML abbr elements` no prompt geral em `scripts/translate.py //// tab | Teste -### Desenvolver uma aplicaรงรฃo web - um tutorial { #develop-a-webapp-a-tutorial } +### Desenvolver uma webapp - um tutorial { #develop-a-webapp-a-tutorial } Olรก. @@ -242,7 +242,7 @@ Olรก novamente. //// -//// tab | Informaรงรตes +//// tab | Informaรงรฃo A รบnica regra rรญgida para tรญtulos รฉ que o LLM deixe a parte do hash dentro de chaves inalterada, o que garante que os links nรฃo quebrem. @@ -494,9 +494,9 @@ Para algumas instruรงรตes especรญficas do idioma, veja, por exemplo, a seรงรฃo ` //// -//// tab | Informaรงรตes +//// tab | Informaรงรฃo -Esta รฉ uma lista nรฃo completa e nรฃo normativa de termos (principalmente) tรฉcnicos vistos na documentaรงรฃo. Pode ser รบtil para o autor do prompt descobrir para quais termos o LLM precisa de uma ajudinha. Por exemplo, quando ele continua revertendo uma boa traduรงรฃo para uma traduรงรฃo subรณtima. Ou quando tem problemas para conjugar/declinar um termo no seu idioma. +Esta รฉ uma lista nรฃo completa e nรฃo normativa de termos (principalmente) tรฉcnicos vistos na documentaรงรฃo. Pode ser รบtil para o designer do prompt descobrir para quais termos o LLM precisa de uma ajudinha. Por exemplo, quando ele continua revertendo uma boa traduรงรฃo para uma traduรงรฃo subรณtima. Ou quando tem problemas para conjugar/declinar um termo no seu idioma. Veja, por exemplo, a seรงรฃo `### List of English terms and their preferred German translations` em `docs/de/llm-prompt.md`. diff --git a/docs/pt/docs/advanced/path-operation-advanced-configuration.md b/docs/pt/docs/advanced/path-operation-advanced-configuration.md index e1c3e5ab89..b3af116a28 100644 --- a/docs/pt/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/pt/docs/advanced/path-operation-advanced-configuration.md @@ -10,7 +10,7 @@ Se vocรช nรฃo รฉ um "especialista" no OpenAPI, vocรช provavelmente nรฃo precisa Vocรช pode definir o `operationId` do OpenAPI que serรก utilizado na sua *operaรงรฃo de rota* com o parรขmetro `operation_id`. -Vocรช precisa ter certeza que ele รฉ รบnico para cada operaรงรฃo. +Vocรช deveria ter certeza que ele รฉ รบnico para cada operaรงรฃo. {* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} @@ -18,13 +18,13 @@ Vocรช precisa ter certeza que ele รฉ รบnico para cada operaรงรฃo. Se vocรช quiser utilizar o nome das funรงรตes da sua API como `operationId`s, vocรช pode iterar sobre todos esses nomes e sobrescrever o `operation_id` em cada *operaรงรฃo de rota* utilizando o `APIRoute.name` dela. -Vocรช deve fazer isso depois de adicionar todas as suas *operaรงรตes de rota*. +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] *} /// tip | Dica -Se vocรช chamar `app.openapi()` manualmente, os `operationId`s devem ser atualizados antes dessa chamada. +Se vocรช chamar `app.openapi()` manualmente, vocรช deveria atualizar os `operationId`s antes dessa chamada. /// @@ -44,11 +44,11 @@ Para excluir uma *operaรงรฃo de rota* do esquema OpenAPI gerado (e por consequรช ## Descriรงรฃo avanรงada a partir de docstring { #advanced-description-from-docstring } -Vocรช pode limitar as linhas utilizadas a partir de uma docstring de uma *funรงรฃo de operaรงรฃo de rota* para o OpenAPI. +Vocรช pode limitar as linhas utilizadas a partir da docstring de uma *funรงรฃo de operaรงรฃo de rota* para o OpenAPI. -Adicionar um `\f` (um caractere de escape para alimentaรงรฃo de formulรกrio) faz com que o **FastAPI** restrinja a saรญda utilizada pelo OpenAPI atรฉ esse ponto. +Adicionar um `\f` (um caractere de escape para "form feed") faz com que o **FastAPI** trunque a saรญda usada para o OpenAPI atรฉ esse ponto. -Ele nรฃo serรก mostrado na documentaรงรฃo, mas outras ferramentas (como o Sphinx) serรฃo capazes de utilizar o resto do texto. +Ele nรฃo serรก mostrado na documentaรงรฃo, mas outras ferramentas (como o Sphinx) serรฃo capazes de utilizar o resto. {* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} @@ -131,70 +131,38 @@ E se vocรช olhar o esquema OpenAPI resultante (na rota `/openapi.json` da sua AP ### Esquema de *operaรงรฃo de rota* do OpenAPI personalizado { #custom-openapi-path-operation-schema } -O dicionรกrio em `openapi_extra` vai ter todos os seus nรญveis mesclados dentro do esquema OpenAPI gerado automaticamente para a *operaรงรฃo de rota*. +O dicionรกrio em `openapi_extra` vai ser mesclado profundamente com o esquema OpenAPI gerado automaticamente para a *operaรงรฃo de rota*. -Entรฃo, vocรช pode adicionar dados extras para o esquema gerado automaticamente. +Entรฃo, vocรช pode adicionar dados extras ao esquema gerado automaticamente. -Por exemplo, vocรช poderia optar por ler e validar a requisiรงรฃo com seu prรณprio cรณdigo, sem utilizar funcionalidades automatizadas do FastAPI com o Pydantic, mas vocรช ainda pode quere definir a requisiรงรฃo no esquema OpenAPI. +Por exemplo, vocรช poderia decidir ler e validar a requisiรงรฃo com seu prรณprio cรณdigo, sem usar as funcionalidades automรกticas do FastAPI com o Pydantic, mas ainda assim querer definir a requisiรงรฃo no esquema OpenAPI. Vocรช pode fazer isso com `openapi_extra`: {* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.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 <abbr title="convertido de um formato plano, como bytes, para objetos Python">analisado</abbr> 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 <abbr title="converted from some plain format, like bytes, into Python objects - convertido de algum formato simples, como bytes, em objetos Python">analisado</abbr> como JSON, ele รฉ lido diretamente como `bytes`, e a funรงรฃo `magic_data_reader()` seria a responsรกvel por analisar ele de alguma forma. De toda forma, nรณs podemos declarar o esquema esperado para o corpo da requisiรงรฃo. ### Tipo de conteรบdo do OpenAPI personalizado { #custom-openapi-content-type } -Utilizando esse mesmo truque, vocรช pode utilizar um modelo Pydantic para definir o JSON Schema que รฉ entรฃo incluรญdo na seรงรฃo do esquema personalizado do OpenAPI na *operaรงรฃo de rota*. +Utilizando esse mesmo truque, vocรช pode usar um modelo Pydantic para definir o JSON Schema que รฉ entรฃo incluรญdo na seรงรฃo do esquema personalizado do OpenAPI na *operaรงรฃo de rota*. -E vocรช pode fazer isso atรฉ mesmo quando os dados da requisiรงรฃo nรฃo seguem o formato JSON. +E vocรช pode fazer isso atรฉ mesmo quando o tipo de dados na requisiรงรฃo nรฃo รฉ JSON. -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 do JSON. Na verdade, estamos declarando o tipo do conteรบdo da requisiรงรฃo como YAML, em vez de JSON: - -//// tab | Pydantic v2 +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] *} -//// +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. -//// tab | Pydantic v1 +Entรฃo utilizamos a requisiรงรฃo diretamente e extraรญmos o corpo como `bytes`. Isso significa que o FastAPI nรฃo vai sequer tentar analisar o payload da requisiรงรฃo como JSON. -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} - -//// - -/// info | Informaรงรฃo - -Na versรฃo 1 do Pydantic, o mรฉtodo para obter o JSON Schema de um modelo รฉ `Item.schema()`, na versรฃo 2 do Pydantic, o mรฉtodo รฉ `Item.model_json_schema()`. - -/// - -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 no formato YAML. - -Entรฃo utilizamos a requisiรงรฃo diretamente, e extraรญmos o corpo como `bytes`. Isso significa que o FastAPI nรฃo vai sequer tentar analisar o corpo da requisiรงรฃo como JSON. - -E entรฃo no nosso cรณdigo, nรณs analisamos o conteรบdo YAML diretamente, e estamos utilizando o mesmo modelo Pydantic para validar o conteรบdo YAML: - -//// tab | Pydantic v2 +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] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} - -//// - -/// info | Informaรงรฃo - -Na versรฃo 1 do Pydantic, o mรฉtodo para analisar e validar um objeto era `Item.parse_obj()`, na versรฃo 2 do Pydantic, o mรฉtodo รฉ chamado de `Item.model_validate()`. - -/// - /// tip | Dica Aqui reutilizamos o mesmo modelo do Pydantic. diff --git a/docs/pt/docs/advanced/settings.md b/docs/pt/docs/advanced/settings.md index 6f5b7feae7..28411269bb 100644 --- a/docs/pt/docs/advanced/settings.md +++ b/docs/pt/docs/advanced/settings.md @@ -46,12 +46,6 @@ $ pip install "fastapi[all]" </div> -/// info | Informaรงรฃo - -No Pydantic v1 ele vinha incluรญdo no pacote principal. Agora รฉ distribuรญdo como um pacote independente para que vocรช possa optar por instalรก-lo ou nรฃo, caso nรฃo precise dessa funcionalidade. - -/// - ### Criar o objeto `Settings` { #create-the-settings-object } Importe `BaseSettings` do Pydantic e crie uma subclasse, muito parecido com um modelo do Pydantic. @@ -60,24 +54,8 @@ 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()`. -//// tab | Pydantic v2 - {* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} -//// - -//// tab | Pydantic v1 - -/// info | Informaรงรฃo - -No Pydantic v1 vocรช importaria `BaseSettings` diretamente de `pydantic` em vez de `pydantic_settings`. - -/// - -{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *} - -//// - /// tip | Dica Se vocรช quer algo rรกpido para copiar e colar, nรฃo use este exemplo, use o รบltimo abaixo. @@ -215,8 +193,6 @@ APP_NAME="ChimichangApp" E entรฃo atualizar seu `config.py` com: -//// tab | Pydantic v2 - {* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip | Dica @@ -225,26 +201,6 @@ O atributo `model_config` รฉ usado apenas para configuraรงรฃo do Pydantic. Vocรช /// -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} - -/// tip | Dica - -A classe `Config` รฉ usada apenas para configuraรงรฃo do Pydantic. Vocรช pode ler mais em <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>. - -/// - -//// - -/// info | Informaรงรฃo - -Na versรฃo 1 do Pydantic a configuraรงรฃo era feita em uma classe interna `Config`, na versรฃo 2 do Pydantic รฉ feita em um atributo `model_config`. Esse atributo recebe um `dict`, e para ter autocompletar e erros inline vocรช pode importar e usar `SettingsConfigDict` para definir esse `dict`. - -/// - Aqui definimos a configuraรงรฃo `env_file` dentro da sua classe `Settings` do Pydantic e definimos o valor como o nome do arquivo dotenv que queremos usar. ### Criando o `Settings` apenas uma vez com `lru_cache` { #creating-the-settings-only-once-with-lru-cache } diff --git a/docs/pt/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/pt/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md index 2a2659a03d..0995e10285 100644 --- a/docs/pt/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md +++ b/docs/pt/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -2,21 +2,23 @@ Se vocรช tem uma aplicaรงรฃo FastAPI antiga, pode estar usando o Pydantic versรฃo 1. -O FastAPI tem suporte ao Pydantic v1 ou v2 desde a versรฃo 0.100.0. +O FastAPI versรฃo 0.100.0 tinha suporte ao Pydantic v1 ou v2. Ele usaria aquele que vocรช tivesse instalado. -Se vocรช tiver o Pydantic v2 instalado, ele serรก utilizado. Se, em vez disso, tiver o Pydantic v1, serรก ele que serรก utilizado. +O FastAPI versรฃo 0.119.0 introduziu suporte parcial ao Pydantic v1 a partir de dentro do Pydantic v2 (como `pydantic.v1`), para facilitar a migraรงรฃo para o v2. -O Pydantic v1 estรก agora descontinuado e o suporte a ele serรก removido nas prรณximas versรตes do FastAPI, vocรช deveria migrar para o Pydantic v2. Assim, vocรช terรก as funcionalidades, melhorias e correรงรตes mais recentes. +O FastAPI 0.126.0 removeu o suporte ao Pydantic v1, enquanto ainda oferece suporte a `pydantic.v1` por mais algum tempo. /// warning | Atenรงรฃo -Alรฉm disso, a equipe do Pydantic interrompeu o suporte ao Pydantic v1 para as versรตes mais recentes do Python, a partir do **Python 3.14**. +A equipe do Pydantic interrompeu o suporte ao Pydantic v1 para as versรตes mais recentes do Python, a partir do **Python 3.14**. + +Isso inclui `pydantic.v1`, que nรฃo รฉ mais suportado no Python 3.14 e superiores. Se quiser usar as funcionalidades mais recentes do Python, vocรช precisarรก garantir que usa o Pydantic v2. /// -Se vocรช tem uma aplicaรงรฃo FastAPI antiga com Pydantic v1, aqui vou mostrar como migrรก-la para o Pydantic v2 e as **novas funcionalidades no FastAPI 0.119.0** para ajudar em uma migraรงรฃo gradual. +Se vocรช tem uma aplicaรงรฃo FastAPI antiga com Pydantic v1, aqui vou mostrar como migrรก-la para o Pydantic v2, e as **funcionalidades no FastAPI 0.119.0** para ajudar em uma migraรงรฃo gradual. ## Guia oficial { #official-guide } @@ -44,7 +46,7 @@ Depois disso, vocรช pode rodar os testes e verificar se tudo funciona. Se funcio ## Pydantic v1 no v2 { #pydantic-v1-in-v2 } -O Pydantic v2 inclui tudo do Pydantic v1 como um submรณdulo `pydantic.v1`. +O Pydantic v2 inclui tudo do Pydantic v1 como um submรณdulo `pydantic.v1`. Mas isso nรฃo รฉ mais suportado em versรตes acima do Python 3.13. Isso significa que vocรช pode instalar a versรฃo mais recente do Pydantic v2 e importar e usar os componentes antigos do Pydantic v1 a partir desse submรณdulo, como se tivesse o Pydantic v1 antigo instalado. @@ -66,7 +68,7 @@ Tenha em mente que, como a equipe do Pydantic nรฃo oferece mais suporte ao Pydan ### Pydantic v1 e v2 na mesma aplicaรงรฃo { #pydantic-v1-and-v2-on-the-same-app } -Nรฃo รฉ suportado pelo Pydantic ter um modelo do Pydantic v2 com campos prรณprios definidos como modelos do Pydantic v1, ou vice-versa. +Nรฃo รฉ **suportado** pelo Pydantic ter um modelo do Pydantic v2 com campos prรณprios definidos como modelos do Pydantic v1, ou vice-versa. ```mermaid graph TB @@ -86,7 +88,7 @@ graph TB style V2Field fill:#f9fff3 ``` -...but, you can have separated models using Pydantic v1 and v2 in the same app. +...mas, vocรช pode ter modelos separados usando Pydantic v1 e v2 na mesma aplicaรงรฃo. ```mermaid graph TB @@ -106,7 +108,7 @@ graph TB style V2Field fill:#f9fff3 ``` -Em alguns casos, รฉ atรฉ possรญvel ter modelos Pydantic v1 e v2 na mesma operaรงรฃo de rota na sua aplicaรงรฃo FastAPI: +Em alguns casos, รฉ atรฉ possรญvel ter modelos Pydantic v1 e v2 na mesma **operaรงรฃo de rota** na sua aplicaรงรฃo FastAPI: {* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} @@ -122,7 +124,7 @@ Se vocรช precisar usar algumas das ferramentas especรญficas do FastAPI para par /// tip | Dica -Primeiro tente com o `bump-pydantic`; se seus testes passarem e isso funcionar, entรฃo vocรช concluiu tudo com um รบnico comando. โœจ +Primeiro tente com o `bump-pydantic`, se seus testes passarem e isso funcionar, entรฃo vocรช concluiu tudo com um รบnico comando. โœจ /// diff --git a/docs/pt/docs/how-to/separate-openapi-schemas.md b/docs/pt/docs/how-to/separate-openapi-schemas.md index 8855934fd9..f757025a09 100644 --- a/docs/pt/docs/how-to/separate-openapi-schemas.md +++ b/docs/pt/docs/how-to/separate-openapi-schemas.md @@ -1,8 +1,8 @@ # Esquemas OpenAPI Separados para Entrada e Saรญda ou Nรฃo { #separate-openapi-schemas-for-input-and-output-or-not } -Ao usar **Pydantic v2**, o OpenAPI gerado รฉ um pouco mais exato e **correto** do que antes. ๐Ÿ˜Ž +Desde que o **Pydantic v2** foi lanรงado, o OpenAPI gerado รฉ um pouco mais exato e **correto** do que antes. ๐Ÿ˜Ž -Inclusive, em alguns casos, ele terรก atรฉ **dois JSON Schemas** no OpenAPI para o mesmo modelo Pydantic, para entrada e saรญda, dependendo se eles possuem **valores padrรฃo**. +De fato, em alguns casos, ele terรก atรฉ **dois JSON Schemas** no OpenAPI para o mesmo modelo Pydantic, para entrada e saรญda, dependendo se eles possuem **valores padrรฃo**. Vamos ver como isso funciona e como alterar se for necessรกrio. @@ -95,10 +95,8 @@ O suporte para `separate_input_output_schemas` foi adicionado no FastAPI `0.102. ### Mesmo Esquema para Modelos de Entrada e Saรญda na Documentaรงรฃo { #same-schema-for-input-and-output-models-in-docs } -E agora haverรก um รบnico esquema para entrada e saรญda para o modelo, apenas `Item`, e `description` **nรฃo serรก obrigatรณrio**: +E agora haverรก um รบnico esquema para entrada e saรญda para o modelo, apenas `Item`, e ele terรก `description` como **nรฃo obrigatรณrio**: <div class="screenshot"> <img src="/img/tutorial/separate-openapi-schemas/image05.png"> </div> - -Esse รฉ o mesmo comportamento do Pydantic v1. ๐Ÿค“ diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md index 0428c3a798..4e3be586da 100644 --- a/docs/pt/docs/index.md +++ b/docs/pt/docs/index.md @@ -40,8 +40,8 @@ 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 _IDEs_. <abbr title="tambรฉm conhecido como autocompletar, preenchimento automรกtico, IntelliSense">Preenchimento automรกtico</abbr> em todos os lugares. Menos tempo debugando. -* **Fรกcil**: Projetado para ser fรกcil de aprender e usar. Menos tempo lendo documentaรงรฃo. +* **Intuitivo**: Grande suporte a editores. <abbr title="tambรฉm conhecido como auto-complete, autocompletion, IntelliSense">Completaรงรฃo</abbr> 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. * **Baseado em padrรตes**: Baseado em (e totalmente compatรญvel com) os padrรตes abertos para APIs: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (anteriormente conhecido como Swagger) e <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>. @@ -73,7 +73,7 @@ Os recursos chave sรฃo: ## Opiniรตes { #opinions } -"*[...] Estou usando **FastAPI** muito esses dias. [...] Estou na verdade planejando utilizar ele em todos os times de **serviรงos _Machine Learning_ na Microsoft**. Alguns deles estรฃo sendo integrados no _core_ do produto **Windows** e alguns produtos **Office**.*" +"_[...] Estou usando **FastAPI** muito esses dias. [...] Estou na verdade planejando utilizar ele em todos os times de **serviรงos ML na Microsoft**. Alguns deles estรฃo sendo integrados no _core_ do produto **Windows** e alguns produtos **Office**._" <div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div> @@ -91,39 +91,45 @@ Os recursos chave sรฃo: --- -"*Estou extremamente entusiasmado com o **FastAPI**. ร‰ tรฃo divertido!*" +"_Estou muito entusiasmado com o **FastAPI**. ร‰ tรฃo divertido!_" -<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcaster</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div> +<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> apresentador do podcast</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div> --- -"*Honestamente, o que vocรช construiu parece super sรณlido e rebuscado. De muitas formas, eu queria que o **Hug** fosse assim - รฉ realmente inspirador ver alguรฉm que construiu ele.*" +"_Honestamente, o que vocรช construiu parece super sรณlido e refinado. De muitas formas, รฉ o que eu queria que o **Hug** fosse - รฉ realmente inspirador ver alguรฉm construir isso._" <div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong>criador do<a href="https://github.com/hugapi/hug" target="_blank">Hug</a></strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div> --- -"*Se vocรช estรก procurando aprender um **_framework_ moderno** para construir aplicaรงรตes _REST_, dรช uma olhada no **FastAPI** [...] ร‰ rรกpido, fรกcil de usar e fรกcil de aprender [...]*" +"_Se vocรช estรก procurando aprender um **framework moderno** para construir APIs REST, dรช uma olhada no **FastAPI** [...] ร‰ rรกpido, fรกcil de usar e fรกcil de aprender [...]_" -"*Nรณs trocamos nossas **APIs** por **FastAPI** [...] Acredito que vocรชs gostarรฃo dele [...]*" +"_Nรณs trocamos nossas **APIs** por **FastAPI** [...] Acredito que vocรช gostarรก dele [...]_" <div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong>fundadores da <a href="https://explosion.ai" target="_blank">Explosion AI</a> - criadores da <a href="https://spacy.io" target="_blank">spaCy</a></strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div> --- -"_Se alguรฉm estiver procurando construir uma API Python para produรงรฃo, eu recomendaria fortemente o **FastAPI**. Ele รฉ **lindamente projetado**, **simples de usar** e **altamente escalรกvel**. Ele se tornou um **componente chave** para a nossa estratรฉgia API first de desenvolvimento e estรก impulsionando diversas automaรงรตes e serviรงos, como o nosso Virtual TAC Engineer._" +"_Se alguรฉm estiver procurando construir uma API Python para produรงรฃo, eu recomendaria fortemente o **FastAPI**. Ele รฉ **lindamente projetado**, **simples de usar** e **altamente escalรกvel**, e se tornou um **componente chave** para a nossa estratรฉgia de desenvolvimento API first, impulsionando diversas automaรงรตes e serviรงos, como o nosso Virtual TAC Engineer._" <div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div> --- +## Mini documentรกrio do FastAPI { #fastapi-mini-documentary } + +Hรก um <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">mini documentรกrio do FastAPI</a> lanรงado no fim de 2025, vocรช pode assisti-lo online: + +<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a> + ## **Typer**, o FastAPI das interfaces de linhas de comando { #typer-the-fastapi-of-clis } <a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a> -Se vocรช estiver construindo uma aplicaรงรฃo <abbr title="Command Line Interface โ€“ Interface de Linha de Comando">CLI</abbr> para ser utilizada em um terminal ao invรฉs de uma aplicaรงรฃo web, dรช uma olhada no <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>. +Se vocรช estiver construindo uma aplicaรงรฃo <abbr title="Command Line Interface - Interface de Linha de Comando">CLI</abbr> para ser utilizada no terminal ao invรฉs de uma API web, dรช uma olhada no <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>. -**Typer** รฉ o irmรฃo menor do FastAPI. E seu propรณsito รฉ ser o **FastAPI das _CLIs_**. โŒจ๏ธ ๐Ÿš€ +**Typer** รฉ o irmรฃo menor do FastAPI. E seu propรณsito รฉ ser o **FastAPI das CLIs**. โŒจ๏ธ ๐Ÿš€ ## Requisitos { #requirements } @@ -255,10 +261,10 @@ Vocรช verรก a resposta JSON como: Vocรช acabou de criar uma API que: -* Recebe requisiรงรตes HTTP nas _rotas_ `/` e `/items/{item_id}`. -* Ambas _rotas_ fazem <em>operaรงรตes</em> `GET` (tambรฉm conhecido como _mรฉtodos_ HTTP). -* A _rota_ `/items/{item_id}` tem um _parรขmetro de rota_ `item_id` que deve ser um `int`. -* A _rota_ `/items/{item_id}` tem um _parรขmetro query_ `q` `str` opcional. +* Recebe requisiรงรตes HTTP nos _paths_ `/` e `/items/{item_id}`. +* Ambos _paths_ fazem <em>operaรงรตes</em> `GET` (tambรฉm conhecido como _mรฉtodos_ HTTP). +* O _path_ `/items/{item_id}` tem um _parรขmetro de path_ `item_id` que deve ser um `int`. +* O _path_ `/items/{item_id}` tem um _parรขmetro query_ `q` `str` opcional. ### Documentaรงรฃo Interativa da API { #interactive-api-docs } @@ -278,7 +284,7 @@ Vocรช verรก a documentaรงรฃo automรกtica alternativa (fornecida por <a href="htt ## Evoluindo o Exemplo { #example-upgrade } -Agora modifique o arquivo `main.py` para receber um corpo para uma requisiรงรฃo `PUT`. +Agora modifique o arquivo `main.py` para receber um corpo de uma requisiรงรฃo `PUT`. Declare o corpo utilizando tipos padrรฃo Python, graรงas ao Pydantic. @@ -334,7 +340,7 @@ Agora vรก para <a href="http://127.0.0.1:8000/docs" class="external-link" target E agora, vรก para <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. -* A documentaรงรฃo alternativa tambรฉm irรก refletir o novo parรขmetro da _query_ e o corpo: +* A documentaรงรฃo alternativa tambรฉm irรก refletir o novo parรขmetro query e o corpo: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) @@ -368,15 +374,15 @@ item: Item * Validaรงรฃo de dados: * Erros automรกticos e claros quando o dado รฉ invรกlido. * Validaรงรฃo atรฉ para objetos JSON profundamente aninhados. -* <abbr title="tambรฉm conhecido como: serializaรงรฃo, parsing, marshalling">Conversรฃo</abbr> de dados de entrada: vindo da rede para dados e tipos Python. Consegue ler: +* <abbr title="tambรฉm conhecido como: serialization, parsing, marshalling">Conversรฃo</abbr> de dados de entrada: vindo da rede para dados e tipos Python. Consegue ler: * JSON. - * Parรขmetros de rota. - * Parรขmetros de _query_ . - * _Cookies_. + * Parรขmetros de path. + * Parรขmetros query. + * Cookies. * Cabeรงalhos. * Formulรกrios. * Arquivos. -* <abbr title="tambรฉm conhecido como: serializaรงรฃo, parsing, marshalling">Conversรฃo</abbr> de dados de saรญda de tipos e dados Python para dados de rede (como JSON): +* <abbr title="tambรฉm conhecido como: serialization, parsing, marshalling">Conversรฃo</abbr> 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`. @@ -390,17 +396,17 @@ item: Item Voltando ao cรณdigo do exemplo anterior, **FastAPI** irรก: -* Validar que existe um `item_id` na rota para requisiรงรตes `GET` e `PUT`. +* Validar que existe um `item_id` no path para requisiรงรตes `GET` e `PUT`. * Validar que `item_id` รฉ do tipo `int` para requisiรงรตes `GET` e `PUT`. - * Se nรฃo รฉ validado, o cliente verรก um รบtil, claro erro. -* Verificar se existe um parรขmetro de _query_ opcional nomeado como `q` (como em `http://127.0.0.1:8000/items/foo?q=somequery`) para requisiรงรตes `GET`. + * Se nรฃo for, o cliente verรก um erro รบtil e claro. +* Verificar se existe um parรขmetro query opcional nomeado como `q` (como em `http://127.0.0.1:8000/items/foo?q=somequery`) para requisiรงรตes `GET`. * Como o parรขmetro `q` รฉ declarado com `= None`, ele รฉ opcional. - * Sem o `None` ele poderia ser obrigatรณrio (como o corpo no caso de `PUT`). + * Sem o `None` ele seria obrigatรณrio (como o corpo no caso de `PUT`). * Para requisiรงรตes `PUT` para `/items/{item_id}`, lerรก o corpo como JSON: * Verifica que tem um atributo obrigatรณrio `name` que deve ser `str`. - * Verifica que tem um atributo obrigatรณrio `price` que deve ser `float`. - * Verifica que tem an atributo opcional `is_offer`, que deve ser `bool`, se presente. - * Tudo isso tambรฉm funciona para objetos JSON profundamente aninhados. + * Verifica que tem um atributo obrigatรณrio `price` que tem que ser um `float`. + * Verifica que tem um atributo opcional `is_offer`, que deve ser um `bool`, se presente. + * Tudo isso tambรฉm funcionaria para objetos JSON profundamente aninhados. * Converter de e para JSON automaticamente. * Documentar tudo com OpenAPI, que poderรก ser usado por: * Sistemas de documentaรงรฃo interativos. @@ -409,7 +415,7 @@ Voltando ao cรณdigo do exemplo anterior, **FastAPI** irรก: --- -Nรณs apenas arranhamos a superfรญcie, mas vocรช jรก tem idรฉia de como tudo funciona. +Nรณs apenas arranhamos a superfรญcie, mas vocรช jรก tem ideia de como tudo funciona. Experimente mudar a seguinte linha: @@ -437,22 +443,22 @@ Para um exemplo mais completo incluindo mais recursos, veja <a href="https://fas **Alerta de Spoiler**: o tutorial - guia do usuรกrio inclui: -* Declaraรงรฃo de **parรขmetetros** de diferentes lugares como: **cabeรงalhos**, **cookies**, **campos de formulรกrios** e **arquivos**. -* Como configurar **Limitaรงรตes de Validaรงรฃo** como `maximum_length` ou `regex`. -* Um poderoso e fรกcil de usar sistema de **<abbr title="tambรฉm conhecido como componentes, recursos, fornecedores, serviรงos, injetรกveis">Injeรงรฃo de Dependรชncia</abbr>**. -* Seguranรงa e autenticaรงรฃo, incluindo suporte para **OAuth2** com autenticaรงรฃo **JWT tokens** e **HTTP Basic**. +* Declaraรงรฃo de **parรขmetros** de diferentes lugares como: **cabeรงalhos**, **cookies**, **campos de formulรกrios** e **arquivos**. +* Como configurar **limitaรงรตes de validaรงรฃo** como `maximum_length` ou `regex`. +* Um poderoso e fรกcil de usar sistema de **<abbr title="tambรฉm conhecido como components, resources, providers, services, injectables">Injeรงรฃo de Dependรชncia</abbr>**. +* 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 <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> e outras bibliotecas. * Muitos recursos extras (graรงas ao Starlette) como: * **WebSockets** - * testes extrememamente fรกceis baseados em HTTPX e `pytest` + * testes extremamente fรกceis baseados em HTTPX e `pytest` * **CORS** * **Cookie Sessions** * ...e mais. ### Implemente sua aplicaรงรฃo (opcional) { #deploy-your-app-optional } -Vocรช pode opcionalmente implantar sua aplicaรงรฃo FastAPI na <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, inscreva-se na lista de espera se ainda nรฃo o fez. ๐Ÿš€ +Vocรช pode opcionalmente implantar sua aplicaรงรฃo FastAPI na <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, vรก e entre na lista de espera se ainda nรฃo o fez. ๐Ÿš€ Se vocรช jรก tem uma conta na **FastAPI Cloud** (nรณs convidamos vocรช da lista de espera ๐Ÿ˜‰), pode implantar sua aplicaรงรฃo com um รบnico comando. @@ -506,7 +512,7 @@ Siga os tutoriais do seu provedor de nuvem para implantar aplicaรงรตes FastAPI c Testes de performance da _Independent TechEmpower_ mostram aplicaรงรตes **FastAPI** rodando sob Uvicorn como <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">um dos _frameworks_ Python mais rรกpidos disponรญveis</a>, somente atrรกs de Starlette e Uvicorn (utilizados internamente pelo FastAPI). (*) -Para entender mais sobre performance, veja a seรงรฃo <a href="https://fastapi.tiangolo.com/pt/benchmarks/" class="internal-link" target="_blank">Comparaรงรตes</a>. +Para entender mais sobre isso, veja a seรงรฃo <a href="https://fastapi.tiangolo.com/pt/benchmarks/" class="internal-link" target="_blank">Comparaรงรตes</a>. ## Dependรชncias { #dependencies } @@ -514,7 +520,7 @@ O FastAPI depende do Pydantic e do Starlette. ### Dependรชncias `standard` { #standard-dependencies } -Quando vocรช instala o FastAPI com `pip install "fastapi[standard]"`, ele vรชm com o grupo `standard` (padrรฃo) de dependรชncias opcionais: +Quando vocรช instala o FastAPI com `pip install "fastapi[standard]"`, ele vem com o grupo `standard` de dependรชncias opcionais: Utilizado pelo Pydantic: @@ -524,7 +530,7 @@ Utilizado pelo Starlette: * <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Obrigatรณrio caso vocรช queira utilizar o `TestClient`. * <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Obrigatรณrio se vocรช quer utilizar a configuraรงรฃo padrรฃo de templates. -* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Obrigatรณrio se vocรช deseja suporte a <abbr title="convertendo a string que vem de uma requisiรงรฃo HTTP em dados Python">"parsing"</abbr> de formulรกrio, com `request.form()`. +* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Obrigatรณrio se vocรช deseja suporte a <abbr title="converting the string that comes from an HTTP request into Python data - convertendo a string que vem de uma requisiรงรฃo HTTP em dados Python">"parsing"</abbr> de formulรกrio, com `request.form()`. Utilizado pelo FastAPI: @@ -547,7 +553,7 @@ Existem algumas dependรชncias adicionais que vocรช pode querer instalar. Dependรชncias opcionais adicionais do Pydantic: * <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - para gerenciamento de configuraรงรตes. -* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - tipos extras para serem utilizados com o Pydantic. +* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - para tipos extras a serem utilizados com o Pydantic. Dependรชncias opcionais adicionais do FastAPI: diff --git a/docs/pt/docs/tutorial/bigger-applications.md b/docs/pt/docs/tutorial/bigger-applications.md index 9dec7b1968..87bd13375a 100644 --- a/docs/pt/docs/tutorial/bigger-applications.md +++ b/docs/pt/docs/tutorial/bigger-applications.md @@ -31,7 +31,7 @@ Digamos que vocรช tenha uma estrutura de arquivos como esta: /// tip | Dica -Existem vรกrios arquivos `__init__.py` presentes em cada diretรณrio ou subdiretรณrio. +Existem vรกrios arquivos `__init__.py`: um em cada diretรณrio ou subdiretรณrio. Isso permite a importaรงรฃo de cรณdigo de um arquivo para outro. @@ -43,32 +43,32 @@ from app.routers import items /// -* O diretรณrio `app` contรฉm todo o cรณdigo da aplicaรงรฃo. Ele possui um arquivo `app/__init__.py` vazio, o que o torna um "pacote Python" (uma coleรงรฃo de "mรณdulos Python"): `app`. -* Dentro dele, o arquivo `app/main.py` estรก localizado em um pacote Python (diretรณrio com `__init__.py`). Portanto, ele รฉ um "mรณdulo" desse pacote: `app.main`. -* Existem tambรฉm um arquivo `app/dependencies.py`, assim como o `app/main.py`, ele รฉ um "mรณdulo": `app.dependencies`. +* O diretรณrio `app` contรฉm tudo. E possui um arquivo vazio `app/__init__.py`, entรฃo ele รฉ um "pacote Python" (uma coleรงรฃo de "mรณdulos Python"): `app`. +* Ele contรฉm um arquivo `app/main.py`. Como estรก dentro de um pacote Python (um diretรณrio com um arquivo `__init__.py`), ele รฉ um "mรณdulo" desse pacote: `app.main`. +* Existe tambรฉm um arquivo `app/dependencies.py`, assim como `app/main.py`, ele รฉ um "mรณdulo": `app.dependencies`. * Hรก um subdiretรณrio `app/routers/` com outro arquivo `__init__.py`, entรฃo ele รฉ um "subpacote Python": `app.routers`. -* O arquivo `app/routers/items.py` estรก dentro de um pacote, `app/routers/`, portanto, รฉ um "submรณdulo": `app.routers.items`. -* O mesmo com `app/routers/users.py`, ele รฉ outro submรณdulo: `app.routers.users`. -* Hรก tambรฉm um subdiretรณrio `app/internal/` com outro arquivo `__init__.py`, entรฃo ele รฉ outro "subpacote Python":`app.internal`. +* O arquivo `app/routers/items.py` estรก dentro de um pacote, `app/routers/`, portanto รฉ um submรณdulo: `app.routers.items`. +* O mesmo com `app/routers/users.py`, ele รฉ outro submรณdulo: `app.routers.users`. +* Hรก tambรฉm um subdiretรณrio `app/internal/` com outro arquivo `__init__.py`, entรฃo ele รฉ outro "subpacote Python": `app.internal`. * E o arquivo `app/internal/admin.py` รฉ outro submรณdulo: `app.internal.admin`. <img src="/img/tutorial/bigger-applications/package.drawio.svg"> A mesma estrutura de arquivos com comentรกrios: -``` +```bash . -โ”œโ”€โ”€ app # "app" รฉ um pacote Python -โ”‚ย ย  โ”œโ”€โ”€ __init__.py # este arquivo torna "app" um "pacote Python" -โ”‚ย ย  โ”œโ”€โ”€ main.py # "main" mรณdulo, e.g. import app.main -โ”‚ย ย  โ”œโ”€โ”€ dependencies.py # "dependencies" mรณdulo, e.g. import app.dependencies -โ”‚ย ย  โ””โ”€โ”€ routers # "routers" รฉ um "subpacote Python" -โ”‚ย ย  โ”‚ โ”œโ”€โ”€ __init__.py # torna "routers" um "subpacote Python" -โ”‚ย ย  โ”‚ โ”œโ”€โ”€ items.py # "items" submรณdulo, e.g. import app.routers.items -โ”‚ย ย  โ”‚ โ””โ”€โ”€ users.py # "users" submรณdulo, e.g. import app.routers.users -โ”‚ย ย  โ””โ”€โ”€ internal # "internal" รฉ um "subpacote Python" -โ”‚ย ย  โ”œโ”€โ”€ __init__.py # torna "internal" um "subpacote Python" -โ”‚ย ย  โ””โ”€โ”€ admin.py # "admin" submรณdulo, e.g. import app.internal.admin +โ”œโ”€โ”€ 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 } @@ -79,11 +79,11 @@ Vocรช quer manter as *operaรงรตes de rota* relacionadas aos seus usuรกrios separ Mas ele ainda faz parte da mesma aplicaรงรฃo/web API **FastAPI** (faz parte do mesmo "pacote Python"). -Vocรช pode criar as *operaรงรตes de rotas* para esse mรณdulo usando o `APIRouter`. +Vocรช pode criar as *operaรงรตes de rota* para esse mรณdulo usando o `APIRouter`. ### Importe `APIRouter` { #import-apirouter } -vocรช o importa e cria uma "instรขncia" da mesma maneira que faria com a classe `FastAPI`: +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"] *} @@ -91,7 +91,7 @@ vocรช o importa e cria uma "instรขncia" da mesma maneira que faria com a classe E entรฃo vocรช o utiliza para declarar suas *operaรงรตes de rota*. -Utilize-o da mesma maneira que utilizaria a classe `FastAPI`: +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"] *} @@ -151,7 +151,7 @@ Entรฃo, em vez de adicionar tudo isso a cada *operaรงรฃo de rota*, podemos adici {* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} -Como o caminho de cada *operaรงรฃo de rota* deve comeรงar com `/`, como em: +Como o path de cada *operaรงรฃo de rota* tem que comeรงar com `/`, como em: ```Python hl_lines="1" @router.get("/{item_id}") @@ -163,9 +163,9 @@ async def read_item(item_id: str): Entรฃo, o prefixo neste caso รฉ `/items`. -Tambรฉm podemos adicionar uma lista de `tags` e `responses` extras que serรฃo aplicadas a todas as *operaรงรตes de rota* incluรญdas neste roteador. +Tambรฉm podemos adicionar uma list de `tags` e `responses` extras que serรฃo aplicadas a todas as *operaรงรตes de rota* incluรญdas neste router. -E podemos adicionar uma lista de `dependencies` que serรฃo adicionadas a todas as *operaรงรตes de rota* no roteador e serรฃo executadas/resolvidas para cada request feita a elas. +E podemos adicionar uma list de `dependencies` que serรฃo adicionadas a todas as *operaรงรตes de rota* no router e serรฃo executadas/resolvidas para cada request feita a elas. /// tip | Dica @@ -173,7 +173,7 @@ Observe que, assim como [dependรชncias em *decoradores de operaรงรฃo de rota*](d /// -O resultado final รฉ que os caminhos dos itens agora sรฃo: +O resultado final รฉ que os paths dos itens agora sรฃo: * `/items/` * `/items/{item_id}` @@ -183,9 +183,9 @@ O resultado final รฉ que os caminhos dos itens agora sรฃo: * Elas serรฃo marcadas com uma lista de tags que contรชm uma รบnica string `"items"`. * Essas "tags" sรฃo especialmente รบteis para os sistemas de documentaรงรฃo interativa automรกtica (usando OpenAPI). * Todas elas incluirรฃo as `responses` predefinidas. -* Todas essas *operaรงรตes de rota* terรฃo a lista de `dependencies` avaliada/executada antes delas. +* Todas essas *operaรงรตes de rota* terรฃo a list de `dependencies` avaliada/executada antes delas. * Se vocรช tambรฉm declarar dependรชncias em uma *operaรงรฃo de rota* especรญfica, **elas tambรฉm serรฃo executadas**. - * As dependรชncias do roteador sรฃo executadas primeiro, depois as [`dependencies` no decorador](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} e, em seguida, as dependรชncias de parรขmetros normais. + * As dependรชncias do router sรฃo executadas primeiro, depois as [`dependencies` no decorador](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} e, em seguida, as dependรชncias de parรขmetros normais. * Vocรช tambรฉm pode adicionar [dependรชncias de `Seguranรงa` com `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. /// tip | Dica @@ -246,7 +246,7 @@ from ..dependencies import get_token_header significa: -* Comeรงando no mesmo pacote em que este mรณdulo (o arquivo `app/routers/items.py`) reside (o diretรณrio `app/routers/`)... +* Comeรงando no mesmo pacote em que este mรณdulo (o arquivo `app/routers/items.py`) vive (o diretรณrio `app/routers/`)... * vรก para o pacote pai (o diretรณrio `app/`)... * e lรก, encontre o mรณdulo `dependencies` (o arquivo em `app/dependencies.py`)... * e dele, importe a funรงรฃo `get_token_header`. @@ -283,9 +283,9 @@ Mas ainda podemos adicionar _mais_ `tags` que serรฃo aplicadas a uma *operaรงรฃo /// tip | Dica -Esta รบltima operaรงรฃo de caminho terรก a combinaรงรฃo de tags: `["items", "custom"]`. +Esta รบltima operaรงรฃo de rota terรก a combinaรงรฃo de tags: `["items", "custom"]`. -E tambรฉm terรก ambas as respostas na documentaรงรฃo, uma para `404` e uma para `403`. +E tambรฉm terรก ambas as responses na documentaรงรฃo, uma para `404` e uma para `403`. /// @@ -325,7 +325,7 @@ from .routers import items, users significa: -* Comeรงando no mesmo pacote em que este mรณdulo (o arquivo `app/main.py`) reside (o diretรณrio `app/`)... +* Comeรงando no mesmo pacote em que este mรณdulo (o arquivo `app/main.py`) vive (o diretรณrio `app/`)... * procure o subpacote `routers` (o diretรณrio em `app/routers/`)... * e dele, importe o submรณdulo `items` (o arquivo em `app/routers/items.py`) e `users` (o arquivo em `app/routers/users.py`)... @@ -376,7 +376,7 @@ Entรฃo, para poder usar ambos no mesmo arquivo, importamos os submรณdulos direta {* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} -### Inclua os `APIRouter`s para `usuรกrios` e `itens` { #include-the-apirouters-for-users-and-items } +### 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`: @@ -392,7 +392,7 @@ E `items.router` contรฉm o `APIRouter` dentro do arquivo `app/routers/items.py`. Com `app.include_router()` podemos adicionar cada `APIRouter` ao aplicativo principal `FastAPI`. -Ele incluirรก todas as rotas daquele roteador como parte dele. +Ele incluirรก todas as rotas daquele router como parte dele. /// note | Detalhes Tรฉcnicos @@ -404,7 +404,7 @@ Entรฃo, nos bastidores, ele realmente funcionarรก como se tudo fosse o mesmo apl /// check | Verifique -Vocรช nรฃo precisa se preocupar com desempenho ao incluir roteadores. +Vocรช nรฃo precisa se preocupar com desempenho ao incluir routers. Isso levarรก microssegundos e sรณ acontecerรก na inicializaรงรฃo. @@ -453,7 +453,7 @@ e funcionarรก corretamente, junto com todas as outras *operaรงรตes de rota* adic /// note | Detalhes Tรฉcnicos Avanรงados -**Observaรงรฃo**: este รฉ um detalhe muito tรฉcnico que vocรช provavelmente pode **simplesmente pular**. +**Nota**: este รฉ um detalhe muito tรฉcnico que vocรช provavelmente pode **simplesmente pular**. --- @@ -479,15 +479,15 @@ $ fastapi dev app/main.py </div> -E abra os documentos em <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. +E abra a documentaรงรฃo em <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. -Vocรช verรก a documentaรงรฃo automรกtica da API, incluindo os caminhos de todos os submรณdulos, usando os caminhos (e prefixos) corretos e as tags corretas: +Vocรช verรก a documentaรงรฃo automรกtica da API, incluindo os paths de todos os submรณdulos, usando os paths (e prefixos) corretos e as tags corretas: <img src="/img/tutorial/bigger-applications/image01.png"> -## Inclua o mesmo roteador vรกrias vezes com `prefix` diferentes { #include-the-same-router-multiple-times-with-different-prefix } +## Inclua o mesmo router vรกrias vezes com `prefix` diferentes { #include-the-same-router-multiple-times-with-different-prefix } -Vocรช tambรฉm pode usar `.include_router()` vรกrias vezes com o *mesmo* roteador usando prefixos diferentes. +Vocรช tambรฉm pode usar `.include_router()` vรกrias vezes com o *mesmo* router usando prefixos diferentes. Isso pode ser รบtil, por exemplo, para expor a mesma API sob prefixos diferentes, por exemplo, `/api/v1` e `/api/latest`. @@ -495,10 +495,10 @@ Esse รฉ um uso avanรงado que vocรช pode nรฃo precisar, mas estรก lรก caso precis ## Inclua um `APIRouter` em outro { #include-an-apirouter-in-another } -Da mesma forma que vocรช pode incluir um `APIRouter` em um aplicativo `FastAPI`, vocรช pode incluir um `APIRouter` em outro `APIRouter` usando: +Da mesma forma que vocรช pode incluir um `APIRouter` em uma aplicaรงรฃo `FastAPI`, vocรช pode incluir um `APIRouter` em outro `APIRouter` usando: ```Python router.include_router(other_router) ``` -Certifique-se de fazer isso antes de incluir `router` no aplicativo `FastAPI`, para que as *operaรงรตes de rota* de `other_router` tambรฉm sejam incluรญdas. +Certifique-se de fazer isso antes de incluir `router` na aplicaรงรฃo `FastAPI`, para que as *operaรงรตes de rota* de `other_router` tambรฉm sejam incluรญdas. diff --git a/docs/pt/docs/tutorial/body-updates.md b/docs/pt/docs/tutorial/body-updates.md index 67bf684925..95f89c8d23 100644 --- a/docs/pt/docs/tutorial/body-updates.md +++ b/docs/pt/docs/tutorial/body-updates.md @@ -1,6 +1,6 @@ # Corpo - Atualizaรงรตes { #body-updates } -## Atualizaรงรฃo de dados existentes com `PUT` { #update-replacing-with-put } +## Atualizaรงรฃo substituindo com `PUT` { #update-replacing-with-put } Para atualizar um item, vocรช pode usar a operaรงรฃo <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a>. @@ -22,13 +22,13 @@ Isso significa que, se vocรช quiser atualizar o item `bar` usando `PUT` com um c } ``` -Como ele nรฃo inclui o atributo jรก armazenado `"tax": 20.2`, o modelo de entrada assumiria o valor padrรฃo de `"tax": 10.5`. +como ele nรฃo inclui o atributo jรก armazenado `"tax": 20.2`, o modelo de entrada assumiria o valor padrรฃo de `"tax": 10.5`. E os dados seriam salvos com esse "novo" `tax` de `10.5`. ## Atualizaรงรตes parciais com `PATCH` { #partial-updates-with-patch } -Vocรช tambรฉm pode usar a operaรงรฃo <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> para atualizar parcialmente os dados. +Vocรช tambรฉm pode usar a operaรงรฃo <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> para atualizar dados *parcialmente*. Isso significa que vocรช pode enviar apenas os dados que deseja atualizar, deixando o restante intacto. @@ -40,25 +40,17 @@ E muitas equipes usam apenas `PUT`, mesmo para atualizaรงรตes parciais. Vocรช รฉ **livre** para usรก-los como preferir, **FastAPI** nรฃo impรตe restriรงรตes. -Mas este guia te dรก uma ideia de como eles sรฃo destinados a serem usados. +Mas este guia mostra, mais ou menos, como eles sรฃo destinados a serem usados. /// ### Usando o parรขmetro `exclude_unset` do Pydantic { #using-pydantics-exclude-unset-parameter } -Se vocรช quiser receber atualizaรงรตes parciais, รฉ muito รบtil usar o parรขmetro `exclude_unset` no mรฉtodo `.model_dump()` do modelo do Pydantic. +Se vocรช quiser receber atualizaรงรตes parciais, รฉ muito รบtil usar o parรขmetro `exclude_unset` no `.model_dump()` do modelo do Pydantic. Como `item.model_dump(exclude_unset=True)`. -/// info | Informaรงรฃo - -No Pydantic v1, o mรฉtodo que era chamado `.dict()` e foi descontinuado (mas ainda suportado) no Pydantic v2. Agora, deve-se usar o mรฉtodo `.model_dump()`. - -Os exemplos aqui usam `.dict()` para compatibilidade com o Pydantic v1, mas vocรช deve usar `.model_dump()` a partir do Pydantic v2. - -/// - -Isso gera um `dict` com apenas os dados definidos ao criar o modelo `item`, excluindo os valores padrรฃo. +Isso geraria um `dict` com apenas os dados que foram definidos ao criar o modelo `item`, excluindo os valores padrรฃo. Entรฃo, vocรช pode usar isso para gerar um `dict` com apenas os dados definidos (enviados na solicitaรงรฃo), omitindo valores padrรฃo: @@ -68,31 +60,23 @@ Entรฃo, vocรช pode usar isso para gerar um `dict` com apenas os dados definidos Agora, vocรช pode criar uma cรณpia do modelo existente usando `.model_copy()`, e passar o parรขmetro `update` com um `dict` contendo os dados para atualizar. -/// info | Informaรงรฃo - -No Pydantic v1, o mรฉtodo era chamado `.copy()`, ele foi descontinuado (mas ainda suportado) no Pydantic v2, e renomeado para `.model_copy()`. - -Os exemplos aqui usam `.copy()` para compatibilidade com o Pydantic v1, mas vocรช deve usar `.model_copy()` com o Pydantic v2. - -/// - Como `stored_item_model.model_copy(update=update_data)`: {* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} ### Recapitulando as atualizaรงรตes parciais { #partial-updates-recap } -Resumindo, para aplicar atualizaรงรตes parciais vocรช pode: +Resumindo, para aplicar atualizaรงรตes parciais vocรช deveria: * (Opcionalmente) usar `PATCH` em vez de `PUT`. * Recuperar os dados armazenados. * Colocar esses dados em um modelo do Pydantic. * Gerar um `dict` sem valores padrรฃo a partir do modelo de entrada (usando `exclude_unset`). - * Dessa forma, vocรช pode atualizar apenas os valores definidos pelo usuรกrio, em vez de substituir os valores jรก armazenados com valores padrรฃo em seu modelo. + * Dessa forma, vocรช pode atualizar apenas os valores realmente definidos pelo usuรกrio, em vez de substituir valores jรก armazenados por valores padrรฃo do modelo. * Criar uma cรณpia do modelo armazenado, atualizando seus atributos com as atualizaรงรตes parciais recebidas (usando o parรขmetro `update`). -* Converter o modelo copiado em algo que possa ser armazenado no seu banco de dados (por exemplo, usando o `jsonable_encoder`). - * Isso รฉ comparรกvel ao uso do mรฉtodo `.model_dump()`, mas garante (e converte) os valores para tipos de dados que possam ser convertidos em JSON, por exemplo, `datetime` para `str`. -* Salvar os dados no seu banco de dados. +* Converter o modelo copiado em algo que possa ser armazenado no seu BD (por exemplo, usando o `jsonable_encoder`). + * Isso รฉ comparรกvel a usar o mรฉtodo `.model_dump()` do modelo novamente, mas garante (e converte) os valores para tipos de dados que possam ser convertidos em JSON, por exemplo, `datetime` para `str`. +* Salvar os dados no seu BD. * Retornar o modelo atualizado. {* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} @@ -109,8 +93,8 @@ Mas o exemplo aqui usa `PATCH` porque foi criado para esses casos de uso. Observe que o modelo de entrada ainda รฉ validado. -Portanto, se vocรช quiser receber atualizaรงรตes parciais que possam omitir todos os atributos, precisarรก ter um modelo com todos os atributos marcados como opcionais (com valores padrรฃo ou `None`). +Portanto, se vocรช quiser receber atualizaรงรตes parciais que possam omitir todos os atributos, vocรช precisa ter um modelo com todos os atributos marcados como opcionais (com valores padrรฃo ou `None`). -Para distinguir os modelos com todos os valores opcionais para **atualizaรงรตes** e modelos com valores obrigatรณrios para **criaรงรฃo**, vocรช pode usar as ideias descritas em [Modelos Adicionais](extra-models.md){.internal-link target=_blank}. +Para distinguir entre os modelos com todos os valores opcionais para **atualizaรงรตes** e modelos com valores obrigatรณrios para **criaรงรฃo**, vocรช pode usar as ideias descritas em [Modelos Adicionais](extra-models.md){.internal-link target=_blank}. /// diff --git a/docs/pt/docs/tutorial/body.md b/docs/pt/docs/tutorial/body.md index 1330f4458f..669334439a 100644 --- a/docs/pt/docs/tutorial/body.md +++ b/docs/pt/docs/tutorial/body.md @@ -10,11 +10,11 @@ Para declarar um corpo da **requisiรงรฃo**, vocรช utiliza os modelos do <a href= /// info | Informaรงรฃo -Para enviar dados, vocรช deve usar um dos: `POST` (o mais comum), `PUT`, `DELETE` ou `PATCH`. +Para enviar dados, vocรช deveria usar um dos: `POST` (o mais comum), `PUT`, `DELETE` ou `PATCH`. Enviar um corpo em uma requisiรงรฃo `GET` nรฃo tem um comportamento definido nas especificaรงรตes, porรฉm รฉ suportado pelo FastAPI, apenas para casos de uso bem complexos/extremos. -Como รฉ desencorajado, a documentaรงรฃo interativa com Swagger UI nรฃo irรก mostrar a documentaรงรฃo para o corpo da requisiรงรฃo para um `GET`, e proxies que intermediarem podem nรฃo suportar o corpo da requisiรงรฃo. +Como รฉ desencorajado, a documentaรงรฃo interativa com Swagger UI nรฃo irรก mostrar a documentaรงรฃo para o corpo da requisiรงรฃo ao usar `GET`, e proxies intermediรกrios podem nรฃo suportรก-lo. /// @@ -32,7 +32,8 @@ Utilize os tipos Python padrรฃo para todos os atributos: {* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} -Assim como quando declaramos parรขmetros de consulta, quando um atributo do modelo possui um valor padrรฃo, ele se torna opcional. Caso contrรกrio, se torna obrigatรณrio. Use `None` para tornรก-lo opcional. + +Assim como quando declaramos parรขmetros de consulta, quando um atributo do modelo possui um valor padrรฃo, ele nรฃo รฉ obrigatรณrio. Caso contrรกrio, รฉ obrigatรณrio. Use `None` para tornรก-lo apenas opcional. Por exemplo, o modelo acima declara um JSON "`object`" (ou `dict` no Python) como esse: @@ -66,7 +67,7 @@ Para adicionรก-lo ร  sua *operaรงรฃo de rota*, declare-o da mesma maneira que vo Apenas com essa declaraรงรฃo de tipos do Python, o **FastAPI** irรก: -* Ler o corpo da requisiรงรฃo como um JSON. +* Ler o corpo da requisiรงรฃo como JSON. * Converter os tipos correspondentes (se necessรกrio). * Validar os dados. * Se algum dado for invรกlido, irรก retornar um erro bem claro, indicando exatamente onde e o que estava incorreto. @@ -127,14 +128,6 @@ Dentro da funรงรฃo, vocรช pode acessar todos os atributos do objeto do modelo di {* ../../docs_src/body/tutorial002_py310.py *} -/// info | Informaรงรฃo - -No Pydantic v1 o mรฉtodo se chamava `.dict()`, ele foi descontinuado (mas ainda รฉ suportado) no Pydantic v2, e renomeado para `.model_dump()`. - -Os exemplos aqui usam `.dict()` para compatibilidade com o Pydantic v1, mas vocรช deve usar `.model_dump()` se puder usar o Pydantic v2. - -/// - ## Corpo da requisiรงรฃo + parรขmetros de rota { #request-body-path-parameters } Vocรช pode declarar parรขmetros de rota e corpo da requisiรงรฃo ao mesmo tempo. @@ -143,6 +136,7 @@ O **FastAPI** irรก reconhecer que os parรขmetros da funรงรฃo que combinam com pa {* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} + ## Corpo da requisiรงรฃo + parรขmetros de rota + parรขmetros de consulta { #request-body-path-query-parameters } Vocรช tambรฉm pode declarar parรขmetros de **corpo**, **rota** e **consulta**, ao mesmo tempo. diff --git a/docs/pt/docs/tutorial/extra-models.md b/docs/pt/docs/tutorial/extra-models.md index c0d22df573..24eafce015 100644 --- a/docs/pt/docs/tutorial/extra-models.md +++ b/docs/pt/docs/tutorial/extra-models.md @@ -22,21 +22,13 @@ Aqui estรก uma ideia geral de como os modelos poderiam parecer com seus campos d {* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} -/// info | Informaรงรฃo +### Sobre `**user_in.model_dump()` { #about-user-in-model-dump } -No Pydantic v1 o mรฉtodo se chamava `.dict()`, ele foi descontinuado (mas ainda รฉ suportado) no Pydantic v2 e renomeado para `.model_dump()`. - -Os exemplos aqui usam `.dict()` por compatibilidade com o Pydantic v1, mas vocรช deve usar `.model_dump()` se puder usar o Pydantic v2. - -/// - -### Sobre `**user_in.dict()` { #about-user-in-dict } - -#### O `.dict()` do Pydantic { #pydantics-dict } +#### O `.model_dump()` do Pydantic { #pydantics-model-dump } `user_in` รฉ um modelo Pydantic da classe `UserIn`. -Os modelos Pydantic possuem um mรฉtodo `.dict()` que retorna um `dict` com os dados do modelo. +Os modelos Pydantic possuem um mรฉtodo `.model_dump()` que retorna um `dict` com os dados do modelo. Entรฃo, se criarmos um objeto Pydantic `user_in` como: @@ -47,7 +39,7 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com e depois chamarmos: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() ``` agora temos um `dict` com os dados na variรกvel `user_dict` (รฉ um `dict` em vez de um objeto de modelo Pydantic). @@ -103,20 +95,20 @@ UserInDB( #### Um modelo Pydantic a partir do conteรบdo de outro { #a-pydantic-model-from-the-contents-of-another } -Como no exemplo acima, obtivemos o `user_dict` a partir do `user_in.dict()`, este cรณdigo: +Como no exemplo acima, obtivemos o `user_dict` a partir do `user_in.model_dump()`, este cรณdigo: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() UserInDB(**user_dict) ``` seria equivalente a: ```Python -UserInDB(**user_in.dict()) +UserInDB(**user_in.model_dump()) ``` -...porque `user_in.dict()` รฉ um `dict`, e depois fazemos o Python "desembrulhรก-lo" passando-o para `UserInDB` precedido por `**`. +...porque `user_in.model_dump()` รฉ um `dict`, e depois fazemos o Python "desembrulhรก-lo" passando-o para `UserInDB` precedido por `**`. Entรฃo, obtemos um modelo Pydantic a partir dos dados em outro modelo Pydantic. @@ -125,7 +117,7 @@ Entรฃo, obtemos um modelo Pydantic a partir dos dados em outro modelo Pydantic. E, entรฃo, adicionando o argumento de palavra-chave extra `hashed_password=hashed_password`, como em: ```Python -UserInDB(**user_in.dict(), hashed_password=hashed_password) +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) ``` ...acaba sendo como: diff --git a/docs/pt/docs/tutorial/query-params-str-validations.md b/docs/pt/docs/tutorial/query-params-str-validations.md index 5ec1b1b55e..c93a941e52 100644 --- a/docs/pt/docs/tutorial/query-params-str-validations.md +++ b/docs/pt/docs/tutorial/query-params-str-validations.md @@ -33,7 +33,7 @@ Para isso, primeiro importe: O FastAPI adicionou suporte a `Annotated` (e passou a recomendรก-lo) na versรฃo 0.95.0. -Se vocรช tiver uma versรฃo mais antiga, terรก erros ao tentar usar `Annotated`. +Se vocรช tiver uma versรฃo mais antiga, teria erros ao tentar usar `Annotated`. Certifique-se de [Atualizar a versรฃo do FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} para pelo menos 0.95.1 antes de usar `Annotated`. @@ -109,7 +109,7 @@ Agora o FastAPI vai: ## Alternativa (antiga): `Query` como valor padrรฃo { #alternative-old-query-as-the-default-value } -Versรตes anteriores do FastAPI (antes de <abbr title="antes de 2023-03">0.95.0</abbr>) exigiam que vocรช usasse `Query` como valor padrรฃo do seu parรขmetro, em vez de colocรก-lo em `Annotated`. ร‰ muito provรกvel que vocรช veja cรณdigo assim por aรญ, entรฃo vou te explicar. +Versรตes anteriores do FastAPI (antes de <abbr title="before 2023-03 - antes de 2023-03">0.95.0</abbr>) 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 @@ -192,7 +192,7 @@ Vocรช tambรฉm pode adicionar um parรขmetro `min_length`: ## Adicione expressรตes regulares { #add-regular-expressions } -Vocรช pode definir um `pattern` de <abbr title="Uma expressรฃo regular, regex ou regexp รฉ uma sequรชncia de caracteres que define um padrรฃo de busca para strings.">expressรฃo regular</abbr> que o parรขmetro deve corresponder: +Vocรช pode definir um `pattern` de <abbr title="A regular expression, regex or regexp is a sequence of characters that define a search pattern for strings. - Uma expressรฃo regular, regex ou regexp รฉ uma sequรชncia de caracteres que define um padrรฃo de busca para strings.">expressรฃo regular</abbr> que o parรขmetro deve corresponder: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -206,20 +206,6 @@ Se vocรช se sentir perdido com essas ideias de **"expressรฃo regular"**, nรฃo se Agora vocรช sabe que, sempre que precisar delas, pode usรก-las no **FastAPI**. -### Pydantic v1 `regex` em vez de `pattern` { #pydantic-v1-regex-instead-of-pattern } - -Antes da versรฃo 2 do Pydantic e antes do FastAPI 0.100.0, o parรขmetro se chamava `regex` em vez de `pattern`, mas agora estรก descontinuado. - -Vocรช ainda pode ver algum cรณdigo usando isso: - -//// tab | Pydantic v1 - -{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *} - -//// - -Mas saiba que isso estรก descontinuado e deve ser atualizado para usar o novo parรขmetro `pattern`. ๐Ÿค“ - ## Valores padrรฃo { #default-values } Vocรช pode, claro, usar valores padrรฃo diferentes de `None`. @@ -280,7 +266,7 @@ Entรฃo, com uma URL como: http://localhost:8000/items/?q=foo&q=bar ``` -vocรช receberรก os mรบltiplos valores do *parรขmetro 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: @@ -350,7 +336,7 @@ Essas informaรงรตes serรฃo incluรญdas no OpenAPI gerado e usadas pelas interface Tenha em mente que ferramentas diferentes podem ter nรญveis diferentes de suporte ao OpenAPI. -Algumas delas podem ainda nรฃo mostrar todas as informaรงรตes extras declaradas, embora na maioria dos casos o recurso ausente jรก esteja planejado para desenvolvimento. +Algumas delas podem ainda nรฃo mostrar todas as informaรงรตes extras declaradas, embora na maioria dos casos a funcionalidade ausente jรก esteja planejada para desenvolvimento. /// @@ -386,7 +372,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รก <abbr title="obsoleto, recomenda-se nรฃo usรก-lo">descontinuado</abbr>. +Vocรช tem que deixรก-lo por um tempo, pois hรก clientes usando-o, mas quer que a documentaรงรฃo mostre claramente que ele estรก <abbr title="obsolete, recommended not to use it - obsoleto, recomenda-se nรฃo usรก-lo">deprecated</abbr>. Entรฃo passe o parรขmetro `deprecated=True` para `Query`: @@ -416,7 +402,7 @@ O Pydantic tambรฉm tem <a href="https://docs.pydantic.dev/latest/concepts/valida /// -Por exemplo, este validador personalizado verifica se o ID do item comeรงa com `isbn-` para um nรบmero de livro <abbr title="ISBN significa Nรบmero Padrรฃo Internacional de Livro">ISBN</abbr> ou com `imdb-` para um ID de URL de filme <abbr title="IMDB (Internet Movie Database) รฉ um site com informaรงรตes sobre filmes">IMDB</abbr>: +Por exemplo, este validador personalizado verifica se o ID do item comeรงa com `isbn-` para um nรบmero de livro <abbr title="ISBN means International Standard Book Number - ISBN significa Nรบmero Padrรฃo Internacional de Livro">ISBN</abbr> ou com `imdb-` para um ID de URL de filme <abbr title="IMDB (Internet Movie Database) is a website with information about movies - IMDB (Internet Movie Database) รฉ um site com informaรงรตes sobre filmes">IMDB</abbr>: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -428,7 +414,7 @@ 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รช deve 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. @@ -440,7 +426,7 @@ O ponto importante รฉ apenas usar **`AfterValidator` com uma funรงรฃo dentro de --- -Mas se vocรช estรก curioso sobre este exemplo especรญfico e ainda entretido, aqui vรฃo alguns detalhes extras. +Mas se vocรช estiver curioso sobre este exemplo de cรณdigo especรญfico e ainda entretido, aqui vรฃo alguns detalhes extras. #### String com `value.startswith()` { #string-with-value-startswith } @@ -450,7 +436,7 @@ Percebeu? Uma string usando `value.startswith()` pode receber uma tupla, e verif #### Um item aleatรณrio { #a-random-item } -Com `data.items()` obtemos um <abbr title="Algo que podemos iterar com um laรงo for, como uma list, set, etc.">objeto iterรกvel</abbr> com tuplas contendo a chave e o valor de cada item do dicionรกrio. +Com `data.items()` obtemos um <abbr title="Something we can iterate on with a for loop, like a list, set, etc. - Algo que podemos iterar com um laรงo for, como uma list, set, etc.">objeto iterรกvel</abbr> 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())`. diff --git a/docs/pt/docs/tutorial/response-model.md b/docs/pt/docs/tutorial/response-model.md index dc66bb46c4..8a7a712488 100644 --- a/docs/pt/docs/tutorial/response-model.md +++ b/docs/pt/docs/tutorial/response-model.md @@ -252,20 +252,6 @@ Entรฃo, se vocรช enviar uma solicitaรงรฃo para essa *operaรงรฃo de rota* para o /// info | Informaรงรฃo -No Pydantic v1, o mรฉtodo era chamado `.dict()`, ele foi descontinuado (mas ainda suportado) no Pydantic v2 e renomeado para `.model_dump()`. - -Os exemplos aqui usam `.dict()` para compatibilidade com Pydantic v1, mas vocรช deve usar `.model_dump()` em vez disso se puder usar Pydantic v2. - -/// - -/// info | Informaรงรฃo - -O FastAPI usa `.dict()` do modelo Pydantic com <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">seu parรขmetro `exclude_unset`</a> para chegar a isso. - -/// - -/// info | Informaรงรฃo - Vocรช tambรฉm pode usar: * `response_model_exclude_defaults=True` diff --git a/docs/pt/docs/tutorial/schema-extra-example.md b/docs/pt/docs/tutorial/schema-extra-example.md index bddd320cd3..2d62ffd851 100644 --- a/docs/pt/docs/tutorial/schema-extra-example.md +++ b/docs/pt/docs/tutorial/schema-extra-example.md @@ -8,39 +8,17 @@ Aqui estรฃo vรกrias maneiras de fazer isso. Vocรช pode declarar `examples` para um modelo Pydantic que serรฃo adicionados ao JSON Schema gerado. -//// tab | Pydantic v2 - {* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *} - -//// - Essas informaรงรตes extras serรฃo adicionadas como estรฃo ao **JSON Schema** de saรญda para esse modelo e serรฃo usadas na documentaรงรฃo da API. -//// tab | Pydantic v2 - -Na versรฃo 2 do Pydantic, vocรช usaria o atributo `model_config`, que recebe um `dict`, conforme descrito na <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">documentaรงรฃo do Pydantic: Configuration</a>. +Vocรช pode usar o atributo `model_config`, que recebe um `dict`, conforme descrito na <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">documentaรงรฃo do Pydantic: Configuration</a>. Vocรช pode definir `"json_schema_extra"` com um `dict` contendo quaisquer dados adicionais que vocรช queira que apareรงam no JSON Schema gerado, incluindo `examples`. -//// - -//// tab | Pydantic v1 - -Na versรฃo 1 do Pydantic, vocรช usaria uma classe interna `Config` e `schema_extra`, conforme descrito na <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">documentaรงรฃo do Pydantic: Schema customization</a>. - -Vocรช pode definir `schema_extra` com um `dict` contendo quaisquer dados adicionais que vocรช queira que apareรงam no JSON Schema gerado, incluindo `examples`. - -//// - /// tip | Dica -Vocรช pode usar a mesma tรฉcnica para estender o JSON Schema e adicionar suas prรณprias informaรงรตes extras personalizadas. +Vocรช poderia usar a mesma tรฉcnica para estender o JSON Schema e adicionar suas prรณprias informaรงรตes extras personalizadas. Por exemplo, vocรช poderia usรก-la para adicionar metadados para uma interface de usuรกrio de front-end, etc. @@ -50,7 +28,7 @@ Por exemplo, vocรช poderia usรก-la para adicionar metadados para uma interface d O OpenAPI 3.1.0 (usado desde o FastAPI 0.99.0) adicionou suporte a `examples`, que faz parte do padrรฃo **JSON Schema**. -Antes disso, ele suportava apenas a palavraโ€‘chave `example` com um รบnico exemplo. Isso ainda รฉ suportado pelo OpenAPI 3.1.0, mas รฉ descontinuado e nรฃo faz parte do padrรฃo JSON Schema. Portanto, รฉ recomendado migrar de `example` para `examples`. ๐Ÿค“ +Antes disso, ele suportava apenas a palavraโ€‘chave `example` com um รบnico exemplo. Isso ainda รฉ suportado pelo OpenAPI 3.1.0, mas รฉ descontinuado e nรฃo faz parte do padrรฃo JSON Schema. Portanto, vocรช รฉ incentivado a migrar de `example` para `examples`. ๐Ÿค“ Vocรช pode ler mais no final desta pรกgina. @@ -102,7 +80,7 @@ No entanto, <abbr title="2023-08-26">no momento em que isto foi escrito</abbr>, Antes do **JSON Schema** suportar `examples`, o OpenAPI jรก tinha suporte para um campo diferente tambรฉm chamado `examples`. -Esse `examples` especรญfico do OpenAPI vai em outra seรงรฃo da especificaรงรฃo. Ele fica nos **detalhes de cada funรงรฃo de operaรงรฃo de rota**, nรฃo dentro de cada JSON Schema. +Esse `examples` **especรญfico do OpenAPI** vai em outra seรงรฃo da especificaรงรฃo OpenAPI. Ele fica nos **detalhes de cada *operaรงรฃo de rota***, nรฃo dentro de cada JSON Schema. E o Swagger UI tem suportado esse campo `examples` particular hรก algum tempo. Entรฃo, vocรช pode usรก-lo para **mostrar** diferentes **exemplos na UI da documentaรงรฃo**. @@ -189,9 +167,9 @@ Depois, o JSON Schema adicionou um campo <a href="https://json-schema.org/draft/ E entรฃo o novo OpenAPI 3.1.0 passou a se basear na versรฃo mais recente (JSON Schema 2020-12), que incluiu esse novo campo `examples`. -Agora, esse novo campo `examples` tem precedรชncia sobre o antigo (e customizado) campo รบnico `example`, que agora estรก descontinuado. +E agora esse novo campo `examples` tem precedรชncia sobre o antigo campo รบnico (e customizado) `example`, que agora estรก descontinuado. -Esse novo campo `examples` no JSON Schema รฉ **apenas uma `list`** de exemplos, nรฃo um `dict` com metadados extras como nos outros lugares do OpenAPI (descritos acima). +Esse novo campo `examples` no JSON Schema รฉ **apenas uma `list`** de exemplos, nรฃo um dict com metadados extras como nos outros lugares do OpenAPI (descritos acima). /// info | Informaรงรฃo @@ -213,7 +191,7 @@ Mas agora que o FastAPI 0.99.0 e superiores usam o OpenAPI 3.1.0, que usa o JSON ### Swagger UI e `examples` especรญficos do OpenAPI { #swagger-ui-and-openapi-specific-examples } -Como o Swagger UI nรฃo suportava vรกrios exemplos no JSON Schema (em 2023-08-26), os usuรกrios nรฃo tinham uma forma de mostrar vรกrios exemplos na documentaรงรฃo. +Agora, como o Swagger UI nรฃo suportava vรกrios exemplos no JSON Schema (em 2023-08-26), os usuรกrios nรฃo tinham uma forma de mostrar vรกrios exemplos na documentaรงรฃo. Para resolver isso, o FastAPI `0.103.0` **adicionou suporte** para declarar o mesmo antigo campo **especรญfico do OpenAPI** `examples` com o novo parรขmetro `openapi_examples`. ๐Ÿค“ From 50a78bf84091df3f1620d986e36ab4ee3aa1a2d2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 20:40:39 +0000 Subject: [PATCH 098/110] =?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 7c5460f1df..33926e3afc 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update translations for pt (update-outdated). PR [#14724](https://github.com/fastapi/fastapi/pull/14724) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update Korean LLM prompt. PR [#14740](https://github.com/fastapi/fastapi/pull/14740) by [@hard-coders](https://github.com/hard-coders). * ๐ŸŒ Improve LLM prompt for Turkish translations. PR [#14728](https://github.com/fastapi/fastapi/pull/14728) by [@Kadermiyanyedi](https://github.com/Kadermiyanyedi). * ๐ŸŒ Update portuguese llm-prompt.md. PR [#14702](https://github.com/fastapi/fastapi/pull/14702) by [@ceb10n](https://github.com/ceb10n). From 2eb978b87a2227801cc9abaeddd5e27d941af868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Tue, 20 Jan 2026 15:03:07 -0800 Subject: [PATCH 099/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20ru=20(update-outdated)=20(#14693)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ๐ŸŒ Update translations for ru (update-outdated) * ๐ŸŽจ Auto format * Apply suggestions from code review * Apply suggestions from code review 2 * Apply suggestions from code review 3 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- docs/ru/docs/_llm-test.md | 18 +- .../path-operation-advanced-configuration.md | 56 +--- docs/ru/docs/advanced/settings.md | 44 --- ...migrate-from-pydantic-v1-to-pydantic-v2.md | 28 +- .../docs/how-to/separate-openapi-schemas.md | 16 +- docs/ru/docs/index.md | 40 +-- docs/ru/docs/tutorial/bigger-applications.md | 289 +++++++++--------- docs/ru/docs/tutorial/body-updates.md | 34 +-- docs/ru/docs/tutorial/body.md | 22 +- docs/ru/docs/tutorial/extra-models.md | 62 ++-- .../tutorial/query-params-str-validations.md | 30 +- docs/ru/docs/tutorial/response-model.md | 72 ++--- docs/ru/docs/tutorial/schema-extra-example.md | 30 +- 13 files changed, 297 insertions(+), 444 deletions(-) diff --git a/docs/ru/docs/_llm-test.md b/docs/ru/docs/_llm-test.md index 9a15f6bb21..6a0272f3a5 100644 --- a/docs/ru/docs/_llm-test.md +++ b/docs/ru/docs/_llm-test.md @@ -1,8 +1,8 @@ # ะขะตัั‚ะพะฒั‹ะน ั„ะฐะนะป LLM { #llm-test-file } -ะญั‚ะพั‚ ะดะพะบัƒะผะตะฝั‚ ะฟั€ะพะฒะตั€ัะตั‚, ะฟะพะฝะธะผะฐะตั‚ ะปะธ <abbr title="Large Language Model โ€“ ะ‘ะพะปัŒัˆะฐั ัะทั‹ะบะพะฒะฐั ะผะพะดะตะปัŒ">LLM</abbr>, ะฟะตั€ะตะฒะพะดัั‰ะฐั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ, `general_prompt` ะฒ `scripts/translate.py` ะธ ัะทั‹ะบะพะฒะพะน ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน ะฟั€ะพะผะฟั‚ ะฒ `docs/{language code}/llm-prompt.md`. ะฏะทั‹ะบะพะฒะพะน ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน ะฟั€ะพะผะฟั‚ ะดะพะฑะฐะฒะปัะตั‚ัั ะบ `general_prompt`. +ะญั‚ะพั‚ ะดะพะบัƒะผะตะฝั‚ ะฟั€ะพะฒะตั€ัะตั‚, ะฟะพะฝะธะผะฐะตั‚ ะปะธ <abbr title="Large Language Model - ะ‘ะพะปัŒัˆะฐั ัะทั‹ะบะพะฒะฐั ะผะพะดะตะปัŒ">LLM</abbr>, ะฟะตั€ะตะฒะพะดัั‰ะฐั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ, `general_prompt` ะฒ `scripts/translate.py` ะธ ัะทั‹ะบะพะฒะพะน ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน ะฟั€ะพะผะฟั‚ ะฒ `docs/{language code}/llm-prompt.md`. ะฏะทั‹ะบะพะฒะพะน ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน ะฟั€ะพะผะฟั‚ ะดะพะฑะฐะฒะปัะตั‚ัั ะบ `general_prompt`. -ะขะตัั‚ั‹, ะดะพะฑะฐะฒะปะตะฝะฝั‹ะต ะทะดะตััŒ, ัƒะฒะธะดัั‚ ะฒัะต ัะพะทะดะฐั‚ะตะปะธ ัะทั‹ะบะพะฒั‹ั… ะฟั€ะพะผะฟั‚ะพะฒ. +ะขะตัั‚ั‹, ะดะพะฑะฐะฒะปะตะฝะฝั‹ะต ะทะดะตััŒ, ัƒะฒะธะดัั‚ ะฒัะต ัะพะทะดะฐั‚ะตะปะธ ัะทั‹ะบะพะฒั‹ั… ัะฟะตั†ะธั„ะธั‡ะฝั‹ั… ะฟั€ะพะผะฟั‚ะพะฒ. ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต: @@ -11,7 +11,7 @@ * ะŸั€ะพะฒะตั€ัŒั‚ะต, ะฒัั‘ ะปะธ ะฒ ะฟะพั€ัะดะบะต ะฒ ะฟะตั€ะตะฒะพะดะต. * ะŸั€ะธ ะฝะตะพะฑั…ะพะดะธะผะพัั‚ะธ ัƒะปัƒั‡ัˆะธั‚ะต ะฒะฐัˆ ัะทั‹ะบะพะฒะพะน ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน ะฟั€ะพะผะฟั‚, ะพะฑั‰ะธะน ะฟั€ะพะผะฟั‚ ะธะปะธ ะฐะฝะณะปะธะนัะบะธะน ะดะพะบัƒะผะตะฝั‚. * ะ—ะฐั‚ะตะผ ะฒั€ัƒั‡ะฝัƒัŽ ะธัะฟั€ะฐะฒัŒั‚ะต ะพัั‚ะฐะฒัˆะธะตัั ะฟั€ะพะฑะปะตะผั‹ ะฒ ะฟะตั€ะตะฒะพะดะต, ั‡ั‚ะพะฑั‹ ะพะฝ ะฑั‹ะป ั…ะพั€ะพัˆะธะผ. -* ะŸะตั€ะตะฒะตะดะธั‚ะต ะทะฐะฝะพะฒะพ, ะธะผะตั ั…ะพั€ะพัˆะธะน ะฟะตั€ะตะฒะพะด ะฝะฐ ะผะตัั‚ะต. ะ˜ะดะตะฐะปัŒะฝั‹ะผ ั€ะตะทัƒะปัŒั‚ะฐั‚ะพะผ ะฑัƒะดะตั‚ ัะธั‚ัƒะฐั†ะธั, ะบะพะณะดะฐ LLM ะฑะพะปัŒัˆะต ะฝะต ะฒะฝะพัะธั‚ ะธะทะผะตะฝะตะฝะธะน ะฒ ะฟะตั€ะตะฒะพะด. ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะพะฑั‰ะธะน ะฟั€ะพะผะฟั‚ ะธ ะฒะฐัˆ ัะทั‹ะบะพะฒะพะน ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน ะฟั€ะพะผะฟั‚ ะผะฐะบัะธะผะฐะปัŒะฝะพ ั…ะพั€ะพัˆะธ (ะธะฝะพะณะดะฐ ะพะฝ ะฑัƒะดะตั‚ ะดะตะปะฐั‚ัŒ ะฝะตัะบะพะปัŒะบะพ, ะบะฐะทะฐะปะพััŒ ะฑั‹, ัะปัƒั‡ะฐะนะฝั‹ั… ะธะทะผะตะฝะตะฝะธะน, ะฟั€ะธั‡ะธะฝะฐ ะฒ ั‚ะพะผ, ั‡ั‚ะพ <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM โ€” ะฝะตะดะตั‚ะตั€ะผะธะฝะธั€ะพะฒะฐะฝะฝั‹ะต ะฐะปะณะพั€ะธั‚ะผั‹</a>). +* ะŸะตั€ะตะฒะตะดะธั‚ะต ะทะฐะฝะพะฒะพ, ะธะผะตั ั…ะพั€ะพัˆะธะน ะฟะตั€ะตะฒะพะด ะฝะฐ ะผะตัั‚ะต. ะ˜ะดะตะฐะปัŒะฝั‹ะผ ั€ะตะทัƒะปัŒั‚ะฐั‚ะพะผ ะฑัƒะดะตั‚ ัะธั‚ัƒะฐั†ะธั, ะบะพะณะดะฐ LLM ะฑะพะปัŒัˆะต ะฝะต ะฒะฝะพัะธั‚ ะธะทะผะตะฝะตะฝะธะน ะฒ ะฟะตั€ะตะฒะพะด. ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะพะฑั‰ะธะน ะฟั€ะพะผะฟั‚ ะธ ะฒะฐัˆ ัะทั‹ะบะพะฒะพะน ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน ะฟั€ะพะผะฟั‚ ะฝะฐัั‚ะพะปัŒะบะพ ั…ะพั€ะพัˆะธ, ะฝะฐัะบะพะปัŒะบะพ ัั‚ะพ ะฒะพะทะผะพะถะฝะพ (ะธะฝะพะณะดะฐ ะพะฝ ะฑัƒะดะตั‚ ะดะตะปะฐั‚ัŒ ะฝะตัะบะพะปัŒะบะพ, ะบะฐะทะฐะปะพััŒ ะฑั‹, ัะปัƒั‡ะฐะนะฝั‹ั… ะธะทะผะตะฝะตะฝะธะน, ะฟั€ะธั‡ะธะฝะฐ ะฒ ั‚ะพะผ, ั‡ั‚ะพ <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM โ€” ะฝะตะดะตั‚ะตั€ะผะธะฝะธั€ะพะฒะฐะฝะฝั‹ะต ะฐะปะณะพั€ะธั‚ะผั‹</a>). ะขะตัั‚ั‹: @@ -197,10 +197,10 @@ works(foo="bar") # ะญั‚ะพ ั€ะฐะฑะพั‚ะฐะตั‚ ๐ŸŽ‰ ### abbr ะดะฐั‘ั‚ ะฟะพะปะฝัƒัŽ ั€ะฐััˆะธั„ั€ะพะฒะบัƒ { #the-abbr-gives-a-full-phrase } -* <abbr title="Getting Things Done โ€“ ะšะฐะบ ะฟั€ะธะฒะตัั‚ะธ ะดะตะปะฐ ะฒ ะฟะพั€ัะดะพะบ">GTD</abbr> -* <abbr title="less than โ€“ ะผะตะฝัŒัˆะต ั‡ะตะผ"><code>lt</code></abbr> -* <abbr title="XML Web Token โ€“ XML ะฒะตะฑโ€‘ั‚ะพะบะตะฝ">XWT</abbr> -* <abbr title="Parallel Server Gateway Interface โ€“ ะŸะฐั€ะฐะปะปะตะปัŒะฝั‹ะน ัะตั€ะฒะตั€ะฝั‹ะน ะธะฝั‚ะตั€ั„ะตะนั ัˆะปัŽะทะฐ">PSGI</abbr> +* <abbr title="Getting Things Done - ะšะฐะบ ะฟั€ะธะฒะตัั‚ะธ ะดะตะปะฐ ะฒ ะฟะพั€ัะดะพะบ">GTD</abbr> +* <abbr title="less than - ะผะตะฝัŒัˆะต ั‡ะตะผ"><code>lt</code></abbr> +* <abbr title="XML Web Token - XML ะฒะตะฑโ€‘ั‚ะพะบะตะฝ">XWT</abbr> +* <abbr title="Parallel Server Gateway Interface - ะŸะฐั€ะฐะปะปะตะปัŒะฝั‹ะน ัะตั€ะฒะตั€ะฝั‹ะน ะธะฝั‚ะตั€ั„ะตะนั ัˆะปัŽะทะฐ">PSGI</abbr> ### abbr ะดะฐั‘ั‚ ะพะฑัŠััะฝะตะฝะธะต { #the-abbr-gives-an-explanation } @@ -209,8 +209,8 @@ works(foo="bar") # ะญั‚ะพ ั€ะฐะฑะพั‚ะฐะตั‚ ๐ŸŽ‰ ### abbr ะดะฐั‘ั‚ ะฟะพะปะฝัƒัŽ ั€ะฐััˆะธั„ั€ะพะฒะบัƒ ะธ ะพะฑัŠััะฝะตะฝะธะต { #the-abbr-gives-a-full-phrase-and-an-explanation } -* <abbr title="Mozilla Developer Network โ€“ ะกะตั‚ัŒ ั€ะฐะทั€ะฐะฑะพั‚ั‡ะธะบะพะฒ Mozilla: ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั ะดะปั ั€ะฐะทั€ะฐะฑะพั‚ั‡ะธะบะพะฒ, ัะพะทะดะฐะฝะฝะฐั ะบะพะผะฐะฝะดะพะน Firefox">MDN</abbr> -* <abbr title="Input/Output โ€“ ะ’ะฒะพะด/ะ’ั‹ะฒะพะด: ั‡ั‚ะตะฝะธะต ะธะปะธ ะทะฐะฟะธััŒ ะฝะฐ ะดะธัะบ, ัะตั‚ะตะฒะพะต ะฒะทะฐะธะผะพะดะตะนัั‚ะฒะธะต.">I/O</abbr>. +* <abbr title="Mozilla Developer Network - ะกะตั‚ัŒ ั€ะฐะทั€ะฐะฑะพั‚ั‡ะธะบะพะฒ Mozilla: ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั ะดะปั ั€ะฐะทั€ะฐะฑะพั‚ั‡ะธะบะพะฒ, ัะพะทะดะฐะฝะฝะฐั ะบะพะผะฐะฝะดะพะน Firefox">MDN</abbr> +* <abbr title="Input/Output - ะ’ะฒะพะด/ะ’ั‹ะฒะพะด: ั‡ั‚ะตะฝะธะต ะธะปะธ ะทะฐะฟะธััŒ ะฝะฐ ะดะธัะบ, ัะตั‚ะตะฒะพะต ะฒะทะฐะธะผะพะดะตะนัั‚ะฒะธะต.">I/O</abbr>. //// diff --git a/docs/ru/docs/advanced/path-operation-advanced-configuration.md b/docs/ru/docs/advanced/path-operation-advanced-configuration.md index eaf9ad0528..86d3a5b630 100644 --- a/docs/ru/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/ru/docs/advanced/path-operation-advanced-configuration.md @@ -14,7 +14,7 @@ {* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} -### ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะธะผะตะฝะธ ั„ัƒะฝะบั†ะธะธ-ะพะฑั€ะฐะฑะพั‚ั‡ะธะบะฐ ะฟัƒั‚ะธ ะบะฐะบ operationId { #using-the-path-operation-function-name-as-the-operationid } +### ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะธะผะตะฝะธ *ั„ัƒะฝะบั†ะธะธ-ะพะฑั€ะฐะฑะพั‚ั‡ะธะบะฐ ะฟัƒั‚ะธ* ะบะฐะบ operationId { #using-the-path-operation-function-name-as-the-operationid } ะ•ัะปะธ ะฒั‹ ั…ะพั‚ะธั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธะผะตะฝะฐ ั„ัƒะฝะบั†ะธะน ะฒะฐัˆะธั… API ะฒ ะบะฐั‡ะตัั‚ะฒะต `operationId`, ะฒั‹ ะผะพะถะตั‚ะต ะฟั€ะพะนั‚ะธ ะฟะพ ะฒัะตะผ ะธะท ะฝะธั… ะธ ะฟะตั€ะตะพะฟั€ะตะดะตะปะธั‚ัŒ `operation_id` ะบะฐะถะดะพะน *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ั ะฟะพะผะพั‰ัŒัŽ ะธั… `APIRoute.name`. @@ -38,7 +38,7 @@ ## ะ˜ัะบะปัŽั‡ะธั‚ัŒ ะธะท OpenAPI { #exclude-from-openapi } -ะงั‚ะพะฑั‹ ะธัะบะปัŽั‡ะธั‚ัŒ *ะพะฟะตั€ะฐั†ะธัŽ ะฟัƒั‚ะธ* ะธะท ะณะตะฝะตั€ะธั€ัƒะตะผะพะน ัั…ะตะผั‹ OpenAPI (ะฐ ะทะฝะฐั‡ะธั‚, ะธ ะธะท ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ), ะธัะฟะพะปัŒะทัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ `include_in_schema` ะธ ัƒัั‚ะฐะฝะพะฒะธั‚ะต ะตะณะพ ะฒ `False`: +ะงั‚ะพะฑั‹ ะธัะบะปัŽั‡ะธั‚ัŒ *ะพะฟะตั€ะฐั†ะธัŽ ะฟัƒั‚ะธ* ะธะท ะณะตะฝะตั€ะธั€ัƒะตะผะพะน ัั…ะตะผั‹ OpenAPI (ะฐ ะทะฝะฐั‡ะธั‚, ะธ ะธะท ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธั… ัะธัั‚ะตะผ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ), ะธัะฟะพะปัŒะทัƒะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ `include_in_schema` ะธ ัƒัั‚ะฐะฝะพะฒะธั‚ะต ะตะณะพ ะฒ `False`: {* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} @@ -48,7 +48,7 @@ ะ”ะพะฑะฐะฒะปะตะฝะธะต `\f` (ัะบั€ะฐะฝะธั€ะพะฒะฐะฝะฝะพะณะพ ัะธะผะฒะพะปะฐ ยซform feedยป) ะทะฐัั‚ะฐะฒะธั‚ **FastAPI** ะพะฑั€ะตะทะฐั‚ัŒ ั‚ะตะบัั‚, ะธัะฟะพะปัŒะทัƒะตะผั‹ะน ะดะปั OpenAPI, ะฒ ัั‚ะพะน ั‚ะพั‡ะบะต. -ะญั‚ะฐ ั‡ะฐัั‚ัŒ ะฝะต ะฟะพะฟะฐะดั‘ั‚ ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ, ะฝะพ ะดั€ัƒะณะธะต ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ (ะฝะฐะฟั€ะธะผะตั€, Sphinx) ัะผะพะณัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพัั‚ะฐะปัŒะฝะพะต. +ะญั‚ะพ ะฝะต ะพั‚ะพะฑั€ะฐะทะธั‚ัั ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ, ะฝะพ ะดั€ัƒะณะธะต ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ (ะฝะฐะฟั€ะธะผะตั€, Sphinx) ัะผะพะณัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพัั‚ะฐะปัŒะฝะพะต. {* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} @@ -56,7 +56,7 @@ ะ’ั‹, ะฒะตั€ะพัั‚ะฝะพ, ัƒะถะต ะฒะธะดะตะปะธ, ะบะฐะบ ะพะฑัŠัะฒะปัั‚ัŒ `response_model` ะธ `status_code` ะดะปั *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. -ะญั‚ะพ ะพะฟั€ะตะดะตะปัะตั‚ ะผะตั‚ะฐะดะฐะฝะฝั‹ะต ะพะฑ ะพัะฝะพะฒะฝะพะผ ะพั‚ะฒะตั‚ะต *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. +ะญั‚ะพ ะพะฟั€ะตะดะตะปัะตั‚ ะผะตั‚ะฐะดะฐะฝะฝั‹ะต ะพะฑ ะพัะฝะพะฒะฝะพะผ HTTP-ะพั‚ะฒะตั‚ะต *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. ะขะฐะบะถะต ะผะพะถะฝะพ ะพะฑัŠัะฒะปัั‚ัŒ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ะพั‚ะฒะตั‚ั‹ ั ะธั… ะผะพะดะตะปัะผะธ, ัั‚ะฐั‚ัƒั-ะบะพะดะฐะผะธ ะธ ั‚.ะด. @@ -76,7 +76,7 @@ ะขะฐะผ ะตัั‚ัŒ `tags`, `parameters`, `requestBody`, `responses` ะธ ั‚.ะด. -ะญั‚ะฐ ัะฟะตั†ะธั„ะธะบะฐั†ะธั OpenAPI, ัะฟะตั†ะธั„ะธั‡ะฝะฐั ะดะปั *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*, ะพะฑั‹ั‡ะฝะพ ะณะตะฝะตั€ะธั€ัƒะตั‚ัั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ **FastAPI**, ะฝะพ ะฒั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะตั‘ ั€ะฐััˆะธั€ะธั‚ัŒ. +ะญั‚ะฐ ัะฟะตั†ะธั„ะธั‡ะฝะฐั ะดะปั *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ัั…ะตะผะฐ OpenAPI ะพะฑั‹ั‡ะฝะพ ะณะตะฝะตั€ะธั€ัƒะตั‚ัั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ **FastAPI**, ะฝะพ ะฒั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะตั‘ ั€ะฐััˆะธั€ะธั‚ัŒ. /// tip | ะกะพะฒะตั‚ @@ -129,13 +129,13 @@ } ``` -### ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ัั…ะตะผะฐ OpenAPI ะดะปั ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ { #custom-openapi-path-operation-schema } +### ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะฐั ัั…ะตะผะฐ OpenAPI ะดะปั *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* { #custom-openapi-path-operation-schema } -ะกะปะพะฒะฐั€ัŒ ะฒ `openapi_extra` ะฑัƒะดะตั‚ ะพะฑัŠะตะดะธะฝั‘ะฝ ั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝะพะน ัั…ะตะผะพะน OpenAPI ะดะปั *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. +ะกะปะพะฒะฐั€ัŒ ะฒ `openapi_extra` ะฑัƒะดะตั‚ ะณะปัƒะฑะพะบะพ ะพะฑัŠะตะดะธะฝั‘ะฝ ั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝะพะน ัั…ะตะผะพะน OpenAPI ะดะปั *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะฒั‹ ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ะดะฐะฝะฝั‹ะต ะบ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝะพะน ัั…ะตะผะต. -ะะฐะฟั€ะธะผะตั€, ะฒั‹ ะผะพะถะตั‚ะต ั€ะตัˆะธั‚ัŒ ั‡ะธั‚ะฐั‚ัŒ ะธ ะฒะฐะปะธะดะธั€ะพะฒะฐั‚ัŒ ะทะฐะฟั€ะพั ัะฒะพะธะผ ะบะพะดะพะผ, ะฝะต ะธัะฟะพะปัŒะทัƒั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธะต ะฒะพะทะผะพะถะฝะพัั‚ะธ FastAPI ะธ Pydantic, ะฝะพ ะฟั€ะธ ัั‚ะพะผ ะทะฐั…ะพั‚ะธั‚ะต ะพะฟะธัะฐั‚ัŒ ะทะฐะฟั€ะพั ะฒ ัั…ะตะผะต OpenAPI. +ะะฐะฟั€ะธะผะตั€, ะฒั‹ ะผะพะถะตั‚ะต ั€ะตัˆะธั‚ัŒ ั‡ะธั‚ะฐั‚ัŒ ะธ ะฒะฐะปะธะดะธั€ะพะฒะฐั‚ัŒ HTTP-ะทะฐะฟั€ะพั ัะฒะพะธะผ ะบะพะดะพะผ, ะฝะต ะธัะฟะพะปัŒะทัƒั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธะต ะฒะพะทะผะพะถะฝะพัั‚ะธ FastAPI ะธ Pydantic, ะฝะพ ะฟั€ะธ ัั‚ะพะผ ะทะฐั…ะพั‚ะธั‚ะต ะพะฟะธัะฐั‚ัŒ HTTP-ะทะฐะฟั€ะพั ะฒ ัั…ะตะผะต OpenAPI. ะญั‚ะพ ะผะพะถะฝะพ ัะดะตะปะฐั‚ัŒ ั ะฟะพะผะพั‰ัŒัŽ `openapi_extra`: @@ -149,52 +149,20 @@ ะ˜ัะฟะพะปัŒะทัƒั ั‚ะพั‚ ะถะต ะฟั€ะธั‘ะผ, ะฒั‹ ะผะพะถะตั‚ะต ะฒะพัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั Pydantic-ะผะพะดะตะปัŒัŽ, ั‡ั‚ะพะฑั‹ ะพะฟั€ะตะดะตะปะธั‚ัŒ JSON Schema, ะบะพั‚ะพั€ะฐั ะทะฐั‚ะตะผ ะฑัƒะดะตั‚ ะฒะบะปัŽั‡ะตะฝะฐ ะฒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ั€ะฐะทะดะตะป ัั…ะตะผั‹ OpenAPI ะดะปั *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. -ะ˜ ะฒั‹ ะผะพะถะตั‚ะต ัะดะตะปะฐั‚ัŒ ัั‚ะพ, ะดะฐะถะต ะตัะปะธ ั‚ะธะฟ ะดะฐะฝะฝั‹ั… ะฒ ะทะฐะฟั€ะพัะต โ€” ะฝะต JSON. +ะ˜ ะฒั‹ ะผะพะถะตั‚ะต ัะดะตะปะฐั‚ัŒ ัั‚ะพ, ะดะฐะถะต ะตัะปะธ ั‚ะธะฟ ะดะฐะฝะฝั‹ั… ะฒ HTTP-ะทะฐะฟั€ะพัะต โ€” ะฝะต JSON. -ะะฐะฟั€ะธะผะตั€, ะฒ ัั‚ะพะผ ะฟั€ะธะปะพะถะตะฝะธะธ ะผั‹ ะฝะต ะธัะฟะพะปัŒะทัƒะตะผ ะฒัั‚ั€ะพะตะฝะฝัƒัŽ ั„ัƒะฝะบั†ะธะพะฝะฐะปัŒะฝะพัั‚ัŒ FastAPI ะดะปั ะธะทะฒะปะตั‡ะตะฝะธั JSON Schema ะธะท ะผะพะดะตะปะตะน Pydantic, ั€ะฐะฒะฝะพ ะบะฐะบ ะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบัƒัŽ ะฒะฐะปะธะดะฐั†ะธัŽ JSON. ะœั‹ ะพะฑัŠัะฒะปัะตะผ ั‚ะธะฟ ัะพะดะตั€ะถะธะผะพะณะพ ะทะฐะฟั€ะพัะฐ ะบะฐะบ YAML, ะฐ ะฝะต JSON: - -//// tab | Pydantic v2 +ะะฐะฟั€ะธะผะตั€, ะฒ ัั‚ะพะผ ะฟั€ะธะปะพะถะตะฝะธะธ ะผั‹ ะฝะต ะธัะฟะพะปัŒะทัƒะตะผ ะฒัั‚ั€ะพะตะฝะฝัƒัŽ ั„ัƒะฝะบั†ะธะพะฝะฐะปัŒะฝะพัั‚ัŒ FastAPI ะดะปั ะธะทะฒะปะตั‡ะตะฝะธั JSON Schema ะธะท ะผะพะดะตะปะตะน Pydantic, ั€ะฐะฒะฝะพ ะบะฐะบ ะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบัƒัŽ ะฒะฐะปะธะดะฐั†ะธัŽ JSON. ะœั‹ ะพะฑัŠัะฒะปัะตะผ ั‚ะธะฟ ัะพะดะตั€ะถะธะผะพะณะพ HTTP-ะทะฐะฟั€ะพัะฐ ะบะฐะบ YAML, ะฐ ะฝะต JSON: {* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} - -//// - -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั - -ะ’ Pydantic ะฒะตั€ัะธะธ 1 ะผะตั‚ะพะด ะดะปั ะฟะพะปัƒั‡ะตะฝะธั JSON Schema ะผะพะดะตะปะธ ะฝะฐะทั‹ะฒะฐะปัั `Item.schema()`, ะฒ Pydantic ะฒะตั€ัะธะธ 2 ะผะตั‚ะพะด ะฝะฐะทั‹ะฒะฐะตั‚ัั `Item.model_json_schema()`. - -/// - ะขะตะผ ะฝะต ะผะตะฝะตะต, ั…ะพั‚ั ะผั‹ ะฝะต ะธัะฟะพะปัŒะทัƒะตะผ ะฒัั‚ั€ะพะตะฝะฝัƒัŽ ั„ัƒะฝะบั†ะธะพะฝะฐะปัŒะฝะพัั‚ัŒ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ, ะผั‹ ะฒัั‘ ั€ะฐะฒะฝะพ ะธัะฟะพะปัŒะทัƒะตะผ Pydantic-ะผะพะดะตะปัŒ, ั‡ั‚ะพะฑั‹ ะฒั€ัƒั‡ะฝัƒัŽ ัะณะตะฝะตั€ะธั€ะพะฒะฐั‚ัŒ JSON Schema ะดะปั ะดะฐะฝะฝั‹ั…, ะบะพั‚ะพั€ั‹ะต ะผั‹ ั…ะพั‚ะธะผ ะฟะพะปัƒั‡ะธั‚ัŒ ะฒ YAML. -ะ—ะฐั‚ะตะผ ะผั‹ ั€ะฐะฑะพั‚ะฐะตะผ ั ะทะฐะฟั€ะพัะพะผ ะฝะฐะฟั€ัะผัƒัŽ ะธ ะธะทะฒะปะตะบะฐะตะผ ั‚ะตะปะพ ะบะฐะบ `bytes`. ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ FastAPI ะดะฐะถะต ะฝะต ะฟะพะฟั‹ั‚ะฐะตั‚ัั ั€ะฐัะฟะฐั€ัะธั‚ัŒ ะฟะพะปะตะทะฝัƒัŽ ะฝะฐะณั€ัƒะทะบัƒ ะทะฐะฟั€ะพัะฐ ะบะฐะบ JSON. +ะ—ะฐั‚ะตะผ ะผั‹ ั€ะฐะฑะพั‚ะฐะตะผ ั HTTP-ะทะฐะฟั€ะพัะพะผ ะฝะฐะฟั€ัะผัƒัŽ ะธ ะธะทะฒะปะตะบะฐะตะผ ั‚ะตะปะพ ะบะฐะบ `bytes`. ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ FastAPI ะดะฐะถะต ะฝะต ะฟะพะฟั‹ั‚ะฐะตั‚ัั ั€ะฐัะฟะฐั€ัะธั‚ัŒ ะฟะพะปะตะทะฝัƒัŽ ะฝะฐะณั€ัƒะทะบัƒ HTTP-ะทะฐะฟั€ะพัะฐ ะบะฐะบ JSON. -ะ ะทะฐั‚ะตะผ ะฒ ะฝะฐัˆะตะผ ะบะพะดะต ะผั‹ ะฝะฐะฟั€ัะผัƒัŽ ะฟะฐั€ัะธะผ ัั‚ะพั‚ YAML ะธ ัะฝะพะฒะฐ ะธัะฟะพะปัŒะทัƒะตะผ ั‚ัƒ ะถะต Pydantic-ะผะพะดะตะปัŒ ะดะปั ะฒะฐะปะธะดะฐั†ะธะธ YAML-ัะพะดะตั€ะถะธะผะพะณะพ: - -//// tab | Pydantic v2 +ะ ะทะฐั‚ะตะผ ะฒ ะฝะฐัˆะตะผ ะบะพะดะต ะผั‹ ะฝะฐะฟั€ัะผัƒัŽ ะฟะฐั€ัะธะผ ัั‚ะพ ัะพะดะตั€ะถะธะผะพะต YAML ะธ ัะฝะพะฒะฐ ะธัะฟะพะปัŒะทัƒะตะผ ั‚ัƒ ะถะต Pydantic-ะผะพะดะตะปัŒ, ั‡ั‚ะพะฑั‹ ะฒะฐะปะธะดะธั€ะพะฒะฐั‚ัŒ YAML-ัะพะดะตั€ะถะธะผะพะต: {* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} - -//// - -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั - -ะ’ Pydantic ะฒะตั€ัะธะธ 1 ะผะตั‚ะพะด ะดะปั ะฟะฐั€ัะธะฝะณะฐ ะธ ะฒะฐะปะธะดะฐั†ะธะธ ะพะฑัŠะตะบั‚ะฐ ะฝะฐะทั‹ะฒะฐะปัั `Item.parse_obj()`, ะฒ Pydantic ะฒะตั€ัะธะธ 2 ะผะตั‚ะพะด ะฝะฐะทั‹ะฒะฐะตั‚ัั `Item.model_validate()`. - -/// - /// tip | ะกะพะฒะตั‚ ะ—ะดะตััŒ ะผั‹ ะฟะตั€ะตะธัะฟะพะปัŒะทัƒะตะผ ั‚ัƒ ะถะต Pydantic-ะผะพะดะตะปัŒ. diff --git a/docs/ru/docs/advanced/settings.md b/docs/ru/docs/advanced/settings.md index b96ee44a3a..8408faebff 100644 --- a/docs/ru/docs/advanced/settings.md +++ b/docs/ru/docs/advanced/settings.md @@ -46,12 +46,6 @@ $ pip install "fastapi[all]" </div> -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั - -ะ’ Pydantic v1 ะพะฝ ะฒั…ะพะดะธะป ะฒ ะพัะฝะพะฒะฝะพะน ะฟะฐะบะตั‚. ะขะตะฟะตั€ัŒ ะพะฝ ั€ะฐัะฟั€ะพัั‚ั€ะฐะฝัะตั‚ัั ะบะฐะบ ะพั‚ะดะตะปัŒะฝั‹ะน ะฟะฐะบะตั‚, ั‡ั‚ะพะฑั‹ ะฒั‹ ะผะพะณะปะธ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะตะณะพ ั‚ะพะปัŒะบะพ ะฟั€ะธ ะฝะตะพะฑั…ะพะดะธะผะพัั‚ะธ. - -/// - ### ะกะพะทะดะฐะฝะธะต ะพะฑัŠะตะบั‚ะฐ `Settings` { #create-the-settings-object } ะ˜ะผะฟะพั€ั‚ะธั€ัƒะนั‚ะต `BaseSettings` ะธะท Pydantic ะธ ัะพะทะดะฐะนั‚ะต ะฟะพะดะบะปะฐัั, ะพั‡ะตะฝัŒ ะฟะพั…ะพะถะธะน ะฝะฐ Pydanticโ€‘ะผะพะดะตะปัŒ. @@ -60,24 +54,8 @@ $ pip install "fastapi[all]" ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฒัะต ั‚ะต ะถะต ะฒะพะทะผะพะถะฝะพัั‚ะธ ะฒะฐะปะธะดะฐั†ะธะธ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹, ั‡ั‚ะพ ะธ ะดะปั Pydanticโ€‘ะผะพะดะตะปะตะน, ะฝะฐะฟั€ะธะผะตั€ ั€ะฐะทะฝั‹ะต ั‚ะธะฟั‹ ะดะฐะฝะฝั‹ั… ะธ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝัƒัŽ ะฒะฐะปะธะดะฐั†ะธัŽ ั‡ะตั€ะตะท `Field()`. -//// tab | Pydantic v2 - {* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} -//// - -//// tab | Pydantic v1 - -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั - -ะ’ Pydantic v1 ะฒั‹ ะฑั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะปะธ `BaseSettings` ะฝะฐะฟั€ัะผัƒัŽ ะธะท `pydantic`, ะฐ ะฝะต ะธะท `pydantic_settings`. - -/// - -{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *} - -//// - /// tip | ะกะพะฒะตั‚ ะ•ัะปะธ ะฒะฐะผ ะฝัƒะถะฝะพ ั‡ั‚ะพ-ั‚ะพ ะฑั‹ัั‚ั€ะพ ัะบะพะฟะธั€ะพะฒะฐั‚ัŒ ะธ ะฒัั‚ะฐะฒะธั‚ัŒ, ะฝะต ะธัะฟะพะปัŒะทัƒะนั‚ะต ัั‚ะพั‚ ะฟั€ะธะผะตั€ โ€” ะฒะพัะฟะพะปัŒะทัƒะนั‚ะตััŒ ะฟะพัะปะตะดะฝะธะผ ะฝะธะถะต. @@ -215,8 +193,6 @@ APP_NAME="ChimichangApp" ะ—ะฐั‚ะตะผ ะพะฑะฝะพะฒะธั‚ะต ะฒะฐัˆ `config.py` ั‚ะฐะบ: -//// tab | Pydantic v2 - {* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip | ะกะพะฒะตั‚ @@ -225,26 +201,6 @@ APP_NAME="ChimichangApp" /// -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} - -/// tip | ะกะพะฒะตั‚ - -ะšะปะฐัั `Config` ะธัะฟะพะปัŒะทัƒะตั‚ัั ั‚ะพะปัŒะบะพ ะดะปั ะบะพะฝั„ะธะณัƒั€ะฐั†ะธะธ Pydantic. ะŸะพะดั€ะพะฑะฝะตะต ัะผ. <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>. - -/// - -//// - -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั - -ะ’ Pydantic ะฒะตั€ัะธะธ 1 ะบะพะฝั„ะธะณัƒั€ะฐั†ะธั ะทะฐะดะฐะฒะฐะปะฐััŒ ะฒะพ ะฒะฝัƒั‚ั€ะตะฝะฝะตะผ ะบะปะฐััะต `Config`, ะฒ Pydantic ะฒะตั€ัะธะธ 2 โ€” ะฒ ะฐั‚ั€ะธะฑัƒั‚ะต `model_config`. ะญั‚ะพั‚ ะฐั‚ั€ะธะฑัƒั‚ ะฟั€ะธะฝะธะผะฐะตั‚ `dict`, ะธ ั‡ั‚ะพะฑั‹ ะฟะพะปัƒั‡ะธั‚ัŒ ะฐะฒั‚ะพะทะฐะฒะตั€ัˆะตะฝะธะต ะธ ะพัˆะธะฑะบะธ ยซะฝะฐ ะปะตั‚ัƒยป, ะฒั‹ ะผะพะถะตั‚ะต ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `SettingsConfigDict` ะดะปั ะพะฟะธัะฐะฝะธั ัั‚ะพะณะพ `dict`. - -/// - ะ—ะดะตััŒ ะผั‹ ะทะฐะดะฐะตะผ ะฟะฐั€ะฐะผะตั‚ั€ ะบะพะฝั„ะธะณัƒั€ะฐั†ะธะธ `env_file` ะฒะฝัƒั‚ั€ะธ ะฒะฐัˆะตะณะพ ะบะปะฐััะฐ Pydantic `Settings` ะธ ัƒัั‚ะฐะฝะฐะฒะปะธะฒะฐะตะผ ะทะฝะฐั‡ะตะฝะธะต ั€ะฐะฒะฝั‹ะผ ะธะผะตะฝะธ ั„ะฐะนะปะฐ dotenv, ะบะพั‚ะพั€ั‹ะน ั…ะพั‚ะธะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ. ### ะกะพะทะดะฐะฝะธะต `Settings` ั‚ะพะปัŒะบะพ ะพะดะธะฝ ั€ะฐะท ั ะฟะพะผะพั‰ัŒัŽ `lru_cache` { #creating-the-settings-only-once-with-lru-cache } diff --git a/docs/ru/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/ru/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md index 95481bc668..2b47c08f67 100644 --- a/docs/ru/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md +++ b/docs/ru/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -2,21 +2,23 @@ ะ•ัะปะธ ัƒ ะฒะฐั ัั‚ะฐั€ะพะต ะฟั€ะธะปะพะถะตะฝะธะต FastAPI, ะฒะพะทะผะพะถะฝะพ, ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต Pydantic ะฒะตั€ัะธะธ 1. -FastAPI ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะธ Pydantic v1, ะธ v2 ะฝะฐั‡ะธะฝะฐั ั ะฒะตั€ัะธะธ 0.100.0. +FastAPI ะฒะตั€ัะธะธ 0.100.0 ะฟะพะดะดะตั€ะถะธะฒะฐะป ะปะธะฑะพ Pydantic v1, ะปะธะฑะพ v2. ะžะฝ ะธัะฟะพะปัŒะทะพะฒะฐะป ั‚ัƒ ะฒะตั€ัะธัŽ, ะบะพั‚ะพั€ะฐั ะฑั‹ะปะฐ ัƒัั‚ะฐะฝะพะฒะปะตะฝะฐ. -ะ•ัะปะธ ัƒ ะฒะฐั ะฑั‹ะป ัƒัั‚ะฐะฝะพะฒะปะตะฝ Pydantic v2, ะธัะฟะพะปัŒะทะพะฒะฐะปัั ะพะฝ. ะ•ัะปะธ ะฒะผะตัั‚ะพ ัั‚ะพะณะพ ะฑั‹ะป ัƒัั‚ะฐะฝะพะฒะปะตะฝ Pydantic v1 โ€” ะธัะฟะพะปัŒะทะพะฒะฐะปัั ะพะฝ. +FastAPI ะฒะตั€ัะธะธ 0.119.0 ะดะพะฑะฐะฒะธะป ั‡ะฐัั‚ะธั‡ะฝัƒัŽ ะฟะพะดะดะตั€ะถะบัƒ Pydantic v1 ะธะทะฝัƒั‚ั€ะธ Pydantic v2 (ะบะฐะบ `pydantic.v1`), ั‡ั‚ะพะฑั‹ ัƒะฟั€ะพัั‚ะธั‚ัŒ ะผะธะณั€ะฐั†ะธัŽ ะฝะฐ v2. -ะกะตะนั‡ะฐั Pydantic v1 ะพะฑัŠัะฒะปะตะฝ ัƒัั‚ะฐั€ะตะฒัˆะธะผ, ะธ ะฟะพะดะดะตั€ะถะบะฐ ะตะณะพ ะฑัƒะดะตั‚ ัƒะดะฐะปะตะฝะฐ ะฒ ัะปะตะดัƒัŽั‰ะธั… ะฒะตั€ัะธัั… FastAPI, ะฟะพัั‚ะพะผัƒ ะฒะฐะผ ัะปะตะดัƒะตั‚ **ะฟะตั€ะตะนั‚ะธ ะฝะฐ Pydantic v2**. ะขะฐะบ ะฒั‹ ะฟะพะปัƒั‡ะธั‚ะต ะฟะพัะปะตะดะฝะธะต ะฒะพะทะผะพะถะฝะพัั‚ะธ, ัƒะปัƒั‡ัˆะตะฝะธั ะธ ะธัะฟั€ะฐะฒะปะตะฝะธั. +FastAPI 0.126.0 ัƒะฑั€ะฐะป ะฟะพะดะดะตั€ะถะบัƒ Pydantic v1, ะฟั€ะธ ัั‚ะพะผ ะตั‰ั‘ ะฝะตะบะพั‚ะพั€ะพะต ะฒั€ะตะผั ะฟั€ะพะดะพะปะถะฐะป ะฟะพะดะดะตั€ะถะธะฒะฐั‚ัŒ `pydantic.v1`. /// warning | ะŸั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต -ะšั€ะพะผะต ั‚ะพะณะพ, ะบะพะผะฐะฝะดะฐ Pydantic ะฟั€ะตะบั€ะฐั‚ะธะปะฐ ะฟะพะดะดะตั€ะถะบัƒ Pydantic v1 ะดะปั ะฟะพัะปะตะดะฝะธั… ะฒะตั€ัะธะน Python, ะฝะฐั‡ะธะฝะฐั ั **Python 3.14**. +ะšะพะผะฐะฝะดะฐ Pydantic ะฟั€ะตะบั€ะฐั‚ะธะปะฐ ะฟะพะดะดะตั€ะถะบัƒ Pydantic v1 ะดะปั ะฟะพัะปะตะดะฝะธั… ะฒะตั€ัะธะน Python, ะฝะฐั‡ะธะฝะฐั ั **Python 3.14**. + +ะญั‚ะพ ะฒะบะปัŽั‡ะฐะตั‚ `pydantic.v1`, ะบะพั‚ะพั€ั‹ะน ะฑะพะปัŒัˆะต ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั ะฒ Python 3.14 ะธ ะฒั‹ัˆะต. ะ•ัะปะธ ะฒั‹ ั…ะพั‚ะธั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะพัะปะตะดะฝะธะต ะฒะพะทะผะพะถะฝะพัั‚ะธ Python, ะฒะฐะผ ะฝัƒะถะฝะพ ัƒะฑะตะดะธั‚ัŒัั, ั‡ั‚ะพ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต Pydantic v2. /// -ะ•ัะปะธ ัƒ ะฒะฐั ัั‚ะฐั€ะพะต ะฟั€ะธะปะพะถะตะฝะธะต FastAPI ั Pydantic v1, ะทะดะตััŒ ั ะฟะพะบะฐะถัƒ, ะบะฐะบ ะผะธะณั€ะธั€ะพะฒะฐั‚ัŒ ะฝะฐ Pydantic v2, ะธ **ะฝะพะฒั‹ะต ะฒะพะทะผะพะถะฝะพัั‚ะธ ะฒ FastAPI 0.119.0**, ะบะพั‚ะพั€ั‹ะต ะฟะพะผะพะณัƒั‚ ะฒั‹ะฟะพะปะฝะธั‚ัŒ ะฟะพัั‚ะตะฟะตะฝะฝัƒัŽ ะผะธะณั€ะฐั†ะธัŽ. +ะ•ัะปะธ ัƒ ะฒะฐั ัั‚ะฐั€ะพะต ะฟั€ะธะปะพะถะตะฝะธะต FastAPI ั Pydantic v1, ะทะดะตััŒ ั ะฟะพะบะฐะถัƒ, ะบะฐะบ ะผะธะณั€ะธั€ะพะฒะฐั‚ัŒ ะฝะฐ Pydantic v2, ะธ **ะฒะพะทะผะพะถะฝะพัั‚ะธ FastAPI 0.119.0**, ะบะพั‚ะพั€ั‹ะต ะฟะพะผะพะณัƒั‚ ะฒั‹ะฟะพะปะฝะธั‚ัŒ ะฟะพัั‚ะตะฟะตะฝะฝัƒัŽ ะผะธะณั€ะฐั†ะธัŽ. ## ะžั„ะธั†ะธะฐะปัŒะฝะพะต ั€ัƒะบะพะฒะพะดัั‚ะฒะพ { #official-guide } @@ -38,13 +40,13 @@ FastAPI ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะธ Pydantic v1, ะธ v2 ะฝะฐั‡ะธะฝะฐั ั ะฒะตั€ ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ <a href="https://github.com/pydantic/bump-pydantic" class="external-link" target="_blank">`bump-pydantic`</a> ะพั‚ ั‚ะพะน ะถะต ะบะพะผะฐะฝะดั‹ Pydantic. -ะญั‚ะพั‚ ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะฟะพะผะพะถะตั‚ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฒะฝะตัั‚ะธ ะฑะพะปัŒัˆัƒัŽ ั‡ะฐัั‚ัŒ ะฝะตะพะฑั…ะพะดะธะผั‹ั… ะธะทะผะตะฝะตะฝะธะน ะฒ ะบะพะด. +ะญั‚ะพั‚ ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะฟะพะผะพะถะตั‚ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะธะทะผะตะฝะธั‚ัŒ ะฑะพะปัŒัˆัƒัŽ ั‡ะฐัั‚ัŒ ะบะพะดะฐ, ะบะพั‚ะพั€ั‹ะน ะฝัƒะถะฝะพ ะธะทะผะตะฝะธั‚ัŒ. -ะŸะพัะปะต ัั‚ะพะณะพ ะทะฐะฟัƒัั‚ะธั‚ะต ั‚ะตัั‚ั‹ ะธ ะฟั€ะพะฒะตั€ัŒั‚ะต, ั‡ั‚ะพ ะฒัั‘ ั€ะฐะฑะพั‚ะฐะตั‚. ะ•ัะปะธ ะดะฐ โ€” ะฝะฐ ัั‚ะพะผ ะฒัั‘. ๐Ÿ˜Ž +ะŸะพัะปะต ัั‚ะพะณะพ ะฒั‹ ะผะพะถะตั‚ะต ะทะฐะฟัƒัั‚ะธั‚ัŒ ั‚ะตัั‚ั‹ ะธ ะฟั€ะพะฒะตั€ะธั‚ัŒ, ั‡ั‚ะพ ะฒัั‘ ั€ะฐะฑะพั‚ะฐะตั‚. ะ•ัะปะธ ะดะฐ โ€” ะฝะฐ ัั‚ะพะผ ะฒัั‘. ๐Ÿ˜Ž ## Pydantic v1 ะฒ v2 { #pydantic-v1-in-v2 } -Pydantic v2 ะฒะบะปัŽั‡ะฐะตั‚ ะฒัั‘ ะธะท Pydantic v1 ะบะฐะบ ะฟะพะดะผะพะดัƒะปัŒ `pydantic.v1`. +Pydantic v2 ะฒะบะปัŽั‡ะฐะตั‚ ะฒัั‘ ะธะท Pydantic v1 ะบะฐะบ ะฟะพะดะผะพะดัƒะปัŒ `pydantic.v1`. ะะพ ัั‚ะพ ะฑะพะปัŒัˆะต ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั ะฒ ะฒะตั€ัะธัั… Python ะฒั‹ัˆะต 3.13. ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะฒั‹ ะผะพะถะตั‚ะต ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะพัะปะตะดะฝัŽัŽ ะฒะตั€ัะธัŽ Pydantic v2 ะธ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะฐั€ั‹ะต ะบะพะผะฟะพะฝะตะฝั‚ั‹ Pydantic v1 ะธะท ัั‚ะพะณะพ ะฟะพะดะผะพะดัƒะปั ั‚ะฐะบ, ะบะฐะบ ะตัะปะธ ะฑั‹ ัƒ ะฒะฐั ะฑั‹ะป ัƒัั‚ะฐะฝะพะฒะปะตะฝ ัั‚ะฐั€ั‹ะน Pydantic v1. @@ -52,7 +54,7 @@ Pydantic v2 ะฒะบะปัŽั‡ะฐะตั‚ ะฒัั‘ ะธะท Pydantic v1 ะบะฐะบ ะฟะพะดะผะพะดัƒะปัŒ ` ### ะŸะพะดะดะตั€ะถะบะฐ FastAPI ะดะปั Pydantic v1 ะฒะฝัƒั‚ั€ะธ v2 { #fastapi-support-for-pydantic-v1-in-v2 } -ะะฐั‡ะธะฝะฐั ั FastAPI 0.119.0, ะตัั‚ัŒ ั‚ะฐะบะถะต ั‡ะฐัั‚ะธั‡ะฝะฐั ะฟะพะดะดะตั€ะถะบะฐ Pydantic v1 ะฒ ัะพัั‚ะฐะฒะต Pydantic v2, ั‡ั‚ะพะฑั‹ ัƒะฟั€ะพัั‚ะธั‚ัŒ ะผะธะณั€ะฐั†ะธัŽ ะฝะฐ v2. +ะะฐั‡ะธะฝะฐั ั FastAPI 0.119.0, ะตัั‚ัŒ ั‚ะฐะบะถะต ั‡ะฐัั‚ะธั‡ะฝะฐั ะฟะพะดะดะตั€ะถะบะฐ Pydantic v1 ะธะทะฝัƒั‚ั€ะธ Pydantic v2, ั‡ั‚ะพะฑั‹ ัƒะฟั€ะพัั‚ะธั‚ัŒ ะผะธะณั€ะฐั†ะธัŽ ะฝะฐ v2. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะฒั‹ ะผะพะถะตั‚ะต ะพะฑะฝะพะฒะธั‚ัŒ Pydantic ะดะพ ะฟะพัะปะตะดะฝะตะน ะฒะตั€ัะธะธ 2 ะธ ัะผะตะฝะธั‚ัŒ ะธะผะฟะพั€ั‚ั‹ ะฝะฐ ะฟะพะดะผะพะดัƒะปัŒ `pydantic.v1` โ€” ะฒะพ ะผะฝะพะณะธั… ัะปัƒั‡ะฐัั… ะฒัั‘ ะฟั€ะพัั‚ะพ ะทะฐั€ะฐะฑะพั‚ะฐะตั‚. @@ -106,7 +108,7 @@ graph TB style V2Field fill:#f9fff3 ``` -ะ’ ะฝะตะบะพั‚ะพั€ั‹ั… ัะปัƒั‡ะฐัั… ะผะพะถะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธ ะผะพะดะตะปะธ Pydantic v1, ะธ v2 ะฒ ะพะดะฝะพะน ะธ ั‚ะพะน ะถะต ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ (ะพะฑั€ะฐะฑะพั‚ั‡ะธะบะต ะฟัƒั‚ะธ) ะฒะฐัˆะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั FastAPI: +ะ’ ะฝะตะบะพั‚ะพั€ั‹ั… ัะปัƒั‡ะฐัั… ะผะพะถะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธ ะผะพะดะตะปะธ Pydantic v1, ะธ v2 ะฒ ะพะดะฝะพะน ะธ ั‚ะพะน ะถะต **ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ** (ะพะฑั€ะฐะฑะพั‚ั‡ะธะบะต ะฟัƒั‚ะธ) ะฒะฐัˆะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั FastAPI: {* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} @@ -122,12 +124,12 @@ graph TB /// tip | ะกะพะฒะตั‚ -ะกะฝะฐั‡ะฐะปะฐ ะฟะพะฟั€ะพะฑัƒะนั‚ะต `bump-pydantic`. ะ•ัะปะธ ั‚ะตัั‚ั‹ ะฟั€ะพั…ะพะดัั‚ ะธ ะฒัั‘ ั€ะฐะฑะพั‚ะฐะตั‚, ะฒั‹ ัะฟั€ะฐะฒะธะปะธััŒ ะพะดะฝะพะน ะบะพะผะฐะฝะดะพะน. โœจ +ะกะฝะฐั‡ะฐะปะฐ ะฟะพะฟั€ะพะฑัƒะนั‚ะต `bump-pydantic`: ะตัะปะธ ั‚ะตัั‚ั‹ ะฟั€ะพั…ะพะดัั‚ ะธ ะฒัั‘ ั€ะฐะฑะพั‚ะฐะตั‚, ะฒั‹ ัะฟั€ะฐะฒะธะปะธััŒ ะพะดะฝะพะน ะบะพะผะฐะฝะดะพะน. โœจ /// ะ•ัะปะธ `bump-pydantic` ะฝะต ะฟะพะดั…ะพะดะธั‚ ะดะปั ะฒะฐัˆะตะณะพ ัะปัƒั‡ะฐั, ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะพะดะดะตั€ะถะบัƒ ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพะน ั€ะฐะฑะพั‚ั‹ ะผะพะดะตะปะตะน Pydantic v1 ะธ v2 ะฒ ะพะดะฝะพะผ ะฟั€ะธะปะพะถะตะฝะธะธ, ั‡ั‚ะพะฑั‹ ะผะธะณั€ะธั€ะพะฒะฐั‚ัŒ ะฝะฐ Pydantic v2 ะฟะพัั‚ะตะฟะตะฝะฝะพ. -ะกะฝะฐั‡ะฐะปะฐ ะพะฑะฝะพะฒะธั‚ะต Pydantic ะดะพ ะฟะพัะปะตะดะฝะตะน 2-ะน ะฒะตั€ัะธะธ ะธ ะธะทะผะตะฝะธั‚ะต ะธะผะฟะพั€ั‚ั‹ ั‚ะฐะบ, ั‡ั‚ะพะฑั‹ ะฒัะต ะฒะฐัˆะธ ะผะพะดะตะปะธ ะธัะฟะพะปัŒะทะพะฒะฐะปะธ `pydantic.v1`. +ะกะฝะฐั‡ะฐะปะฐ ะฒั‹ ะผะพะถะตั‚ะต ะพะฑะฝะพะฒะธั‚ัŒ Pydantic ะดะพ ะฟะพัะปะตะดะฝะตะน 2-ะน ะฒะตั€ัะธะธ ะธ ะธะทะผะตะฝะธั‚ัŒ ะธะผะฟะพั€ั‚ั‹ ั‚ะฐะบ, ั‡ั‚ะพะฑั‹ ะฒัะต ะฒะฐัˆะธ ะผะพะดะตะปะธ ะธัะฟะพะปัŒะทะพะฒะฐะปะธ `pydantic.v1`. -ะ—ะฐั‚ะตะผ ะฝะฐั‡ะฝะธั‚ะต ะผะธะณั€ะธั€ะพะฒะฐั‚ัŒ ะฒะฐัˆะธ ะผะพะดะตะปะธ ั Pydantic v1 ะฝะฐ v2 ะณั€ัƒะฟะฟะฐะผะธ, ะฟะพัั‚ะฐะฟะฝะพ. ๐Ÿšถ +ะ—ะฐั‚ะตะผ ะฒั‹ ะผะพะถะตั‚ะต ะฝะฐั‡ะฐั‚ัŒ ะผะธะณั€ะธั€ะพะฒะฐั‚ัŒ ะฒะฐัˆะธ ะผะพะดะตะปะธ ั Pydantic v1 ะฝะฐ v2 ะณั€ัƒะฟะฟะฐะผะธ, ะฟะพัั‚ะฐะฟะฝะพ. ๐Ÿšถ diff --git a/docs/ru/docs/how-to/separate-openapi-schemas.md b/docs/ru/docs/how-to/separate-openapi-schemas.md index 5b12140167..8f6c83e7ec 100644 --- a/docs/ru/docs/how-to/separate-openapi-schemas.md +++ b/docs/ru/docs/how-to/separate-openapi-schemas.md @@ -2,7 +2,7 @@ ะŸั€ะธ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ **Pydantic v2** ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝั‹ะน OpenAPI ัั‚ะฐะฝะพะฒะธั‚ัั ั‡ัƒั‚ัŒ ะฑะพะปะตะต ั‚ะพั‡ะฝั‹ะผ ะธ **ะบะพั€ั€ะตะบั‚ะฝั‹ะผ**, ั‡ะตะผ ั€ะฐะฝัŒัˆะต. ๐Ÿ˜Ž -ะะฐ ัะฐะผะพะผ ะดะตะปะต, ะฒ ะฝะตะบะพั‚ะพั€ั‹ั… ัะปัƒั‡ะฐัั… ะฒ OpenAPI ะฑัƒะดะตั‚ ะดะฐะถะต **ะดะฒะต JSON ัั…ะตะผั‹** ะดะปั ะพะดะฝะพะน ะธ ั‚ะพะน ะถะต Pydanticโ€‘ะผะพะดะตะปะธ: ะดะปั ะฒั…ะพะดะฐ ะธ ะดะปั ะฒั‹ั…ะพะดะฐ โ€” ะฒ ะทะฐะฒะธัะธะผะพัั‚ะธ ะพั‚ ะฝะฐะปะธั‡ะธั **ะทะฝะฐั‡ะตะฝะธะน ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ**. +ะะฐ ัะฐะผะพะผ ะดะตะปะต, ะฒ ะฝะตะบะพั‚ะพั€ั‹ั… ัะปัƒั‡ะฐัั… ะฒ OpenAPI ะฑัƒะดะตั‚ ะดะฐะถะต **ะดะฒะต JSON-ัั…ะตะผั‹** ะดะปั ะพะดะฝะพะน ะธ ั‚ะพะน ะถะต Pydanticโ€‘ะผะพะดะตะปะธ: ะดะปั ะฒั…ะพะดะฐ ะธ ะดะปั ะฒั‹ั…ะพะดะฐ โ€” ะฒ ะทะฐะฒะธัะธะผะพัั‚ะธ ะพั‚ ะฝะฐะปะธั‡ะธั **ะทะฝะฐั‡ะตะฝะธะน ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ**. ะŸะพัะผะพั‚ั€ะธะผ, ะบะฐะบ ัั‚ะพ ั€ะฐะฑะพั‚ะฐะตั‚, ะธ ะบะฐะบ ัั‚ะพ ะธะทะผะตะฝะธั‚ัŒ ะฟั€ะธ ะฝะตะพะฑั…ะพะดะธะผะพัั‚ะธ. @@ -34,7 +34,7 @@ {* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} -โ€ฆั‚ะพ, ะฟะพัะบะพะปัŒะบัƒ ัƒ `description` ะตัั‚ัŒ ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ, ะดะฐะถะต ะตัะปะธ ะฒั‹ **ะฝะธั‡ะตะณะพ ะฝะต ะฒะตั€ะฝั‘ั‚ะต** ะดะปั ัั‚ะพะณะพ ะฟะพะปั, ะพะฝะพ ะฒัั‘ ั€ะฐะฒะฝะพ ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ัั‚ะพ **ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ**. +โ€ฆั‚ะพ, ะฟะพัะบะพะปัŒะบัƒ ัƒ `description` ะตัั‚ัŒ ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ, ะตัะปะธ ะฒั‹ **ะฝะธั‡ะตะณะพ ะฝะต ะฒะตั€ะฝั‘ั‚ะต** ะดะปั ัั‚ะพะณะพ ะฟะพะปั, ะพะฝะพ ะฒัั‘ ั€ะฐะฒะฝะพ ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ัั‚ะพ **ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ**. ### ะœะพะดะตะปัŒ ะดะปั ะดะฐะฝะฝั‹ั… ะพั‚ะฒะตั‚ะฐ { #model-for-output-response-data } @@ -46,13 +46,13 @@ ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ัƒ ะฝะตะณะพ **ะฒัะตะณะดะฐ ะฑัƒะดะตั‚ ะบะฐะบะพะตโ€‘ั‚ะพ ะทะฝะฐั‡ะตะฝะธะต**, ะฟั€ะพัั‚ะพ ะธะฝะพะณะดะฐ ัั‚ะพ ะทะฝะฐั‡ะตะฝะธะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ `None` (ะธะปะธ `null` ะฒ JSON). -ะกะปะตะดะพะฒะฐั‚ะตะปัŒะฝะพ, ะบะปะธะตะฝั‚ะฐะผ, ะธัะฟะพะปัŒะทัƒัŽั‰ะธะผ ะฒะฐัˆ API, ะฝะต ะฝัƒะถะฝะพ ะฟั€ะพะฒะตั€ัั‚ัŒ ะฝะฐะปะธั‡ะธะต ัั‚ะพะณะพ ะทะฝะฐั‡ะตะฝะธั: ะพะฝะธ ะผะพะณัƒั‚ **ะธัั…ะพะดะธั‚ัŒ ะธะท ั‚ะพะณะพ, ั‡ั‚ะพ ะฟะพะปะต ะฒัะตะณะดะฐ ะฟั€ะธััƒั‚ัั‚ะฒัƒะตั‚**, ะฐ ะฒ ะฝะตะบะพั‚ะพั€ั‹ั… ัะปัƒั‡ะฐัั… ะธะผะตะตั‚ ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ `None`. +ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะบะปะธะตะฝั‚ะฐะผ, ะธัะฟะพะปัŒะทัƒัŽั‰ะธะผ ะฒะฐัˆ API, ะฝะต ะฝัƒะถะฝะพ ะฟั€ะพะฒะตั€ัั‚ัŒ, ััƒั‰ะตัั‚ะฒัƒะตั‚ ะปะธ ัั‚ะพ ะทะฝะฐั‡ะตะฝะธะต ะธะปะธ ะฝะตั‚: ะพะฝะธ ะผะพะณัƒั‚ **ะธัั…ะพะดะธั‚ัŒ ะธะท ั‚ะพะณะพ, ั‡ั‚ะพ ะฟะพะปะต ะฒัะตะณะดะฐ ะฟั€ะธััƒั‚ัั‚ะฒัƒะตั‚**, ะฝะพ ะฒ ะฝะตะบะพั‚ะพั€ั‹ั… ัะปัƒั‡ะฐัั… ะพะฝะพ ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ `None`. ะ’ OpenAPI ัั‚ะพ ะพะฟะธัั‹ะฒะฐะตั‚ัั ั‚ะตะผ, ั‡ั‚ะพ ะฟะพะปะต ะฟะพะผะตั‡ะฐะตั‚ัั ะบะฐะบ **ะพะฑัะทะฐั‚ะตะปัŒะฝะพะต**, ะฟะพัะบะพะปัŒะบัƒ ะพะฝะพ ะฒัะตะณะดะฐ ะฟั€ะธััƒั‚ัั‚ะฒัƒะตั‚. ะ˜ะทโ€‘ะทะฐ ัั‚ะพะณะพ JSON Schema ะดะปั ะผะพะดะตะปะธ ะผะพะถะตั‚ ะพั‚ะปะธั‡ะฐั‚ัŒัั ะฒ ะทะฐะฒะธัะธะผะพัั‚ะธ ะพั‚ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะดะปั **ะฒั…ะพะดะฐ** ะธะปะธ **ะฒั‹ั…ะพะดะฐ**: -* ะดะปั **ะฒั…ะพะดะฐ** `description` ะฝะต ะฑัƒะดะตั‚ ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ +* ะดะปั **ะฒั…ะพะดะฐ** `description` **ะฝะต ะฑัƒะดะตั‚ ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ** * ะดะปั **ะฒั‹ั…ะพะดะฐ** ะพะฝะพ ะฑัƒะดะตั‚ **ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ** (ะธ ะฟั€ะธ ัั‚ะพะผ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ `None`, ะธะปะธ, ะฒ ั‚ะตั€ะผะธะฝะฐั… JSON, `null`) ### ะ’ั‹ั…ะพะดะฝะฐั ะผะพะดะตะปัŒ ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ { #model-for-output-in-docs } @@ -81,9 +81,9 @@ ะžะดะฝะฐะบะพ ะฑั‹ะฒะฐัŽั‚ ัะปัƒั‡ะฐะธ, ะบะพะณะดะฐ ะฒั‹ ั…ะพั‚ะธั‚ะต ะธะผะตั‚ัŒ **ะพะดะฝัƒ ะธ ั‚ัƒ ะถะต ัั…ะตะผัƒ ะดะปั ะฒั…ะพะดะฐ ะธ ะฒั‹ั…ะพะดะฐ**. -ะ“ะปะฐะฒะฝั‹ะน ัั†ะตะฝะฐั€ะธะน โ€” ะบะพะณะดะฐ ัƒ ะฒะฐั ัƒะถะต ะตัั‚ัŒ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝั‹ะน ะบะปะธะตะฝั‚ัะบะธะน ะบะพะด/SDK, ะธ ะฒั‹ ะฟะพะบะฐ ะฝะต ั…ะพั‚ะธั‚ะต ะพะฑะฝะพะฒะปัั‚ัŒ ะฒะตััŒ ัั‚ะพั‚ ะฐะฒั‚ะพะณะตะฝะตั€ะธั€ัƒะตะผั‹ะน ะบะพะด/SDK (ั€ะฐะฝะพ ะธะปะธ ะฟะพะทะดะฝะพ ะฒั‹ ัั‚ะพ ัะดะตะปะฐะตั‚ะต, ะฝะพ ะฝะต ัะตะนั‡ะฐั). +ะ“ะปะฐะฒะฝั‹ะน ัั†ะตะฝะฐั€ะธะน โ€” ะบะพะณะดะฐ ัƒ ะฒะฐั ัƒะถะต ะตัั‚ัŒ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝั‹ะน ะบะปะธะตะฝั‚ัะบะธะน ะบะพะด/SDK, ะธ ะฒั‹ ะฟะพะบะฐ ะฝะต ั…ะพั‚ะธั‚ะต ะพะฑะฝะพะฒะปัั‚ัŒ ะฒะตััŒ ัั‚ะพั‚ ะฐะฒั‚ะพะณะตะฝะตั€ะธั€ัƒะตะผั‹ะน ะบะปะธะตะฝั‚ัะบะธะน ะบะพะด/SDK, ะฒะตั€ะพัั‚ะฝะพ, ะฒั‹ ะทะฐั…ะพั‚ะธั‚ะต ัะดะตะปะฐั‚ัŒ ัั‚ะพ ะฒ ะบะฐะบะพะน-ั‚ะพ ะผะพะผะตะฝั‚, ะฝะพ, ะฒะพะทะผะพะถะฝะพ, ะฝะต ะฟั€ัะผะพ ัะตะนั‡ะฐั. -ะ’ ั‚ะฐะบะพะผ ัะปัƒั‡ะฐะต ะฒั‹ ะผะพะถะตั‚ะต ะพั‚ะบะปัŽั‡ะธั‚ัŒ ัั‚ัƒ ั„ัƒะฝะบั†ะธะพะฝะฐะปัŒะฝะพัั‚ัŒ ะฒ FastAPI ั ะฟะพะผะพั‰ัŒัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `separate_input_output_schemas=False`. +ะ’ ั‚ะฐะบะพะผ ัะปัƒั‡ะฐะต ะฒั‹ ะผะพะถะตั‚ะต ะพั‚ะบะปัŽั‡ะธั‚ัŒ ัั‚ัƒ ั„ัƒะฝะบั†ะธะพะฝะฐะปัŒะฝะพัั‚ัŒ ะฒ **FastAPI** ั ะฟะพะผะพั‰ัŒัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `separate_input_output_schemas=False`. /// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั @@ -95,10 +95,8 @@ ### ะžะดะฝะฐ ะธ ั‚ะฐ ะถะต ัั…ะตะผะฐ ะดะปั ะฒั…ะพะดะฝะพะน ะธ ะฒั‹ั…ะพะดะฝะพะน ะผะพะดะตะปะตะน ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ { #same-schema-for-input-and-output-models-in-docs } -ะขะตะฟะตั€ัŒ ะดะปั ัั‚ะพะน ะผะพะดะตะปะธ ะฑัƒะดะตั‚ ะพะดะฝะฐ ะพะฑั‰ะฐั ัั…ะตะผะฐ ะธ ะดะปั ะฒั…ะพะดะฐ, ะธ ะดะปั ะฒั‹ั…ะพะดะฐ โ€” ั‚ะพะปัŒะบะพ `Item`, ะธ ะฒ ะฝะตะน `description` ะฑัƒะดะตั‚ **ะฝะต ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ**: +ะ˜ ั‚ะตะฟะตั€ัŒ ะดะปั ะผะพะดะตะปะธ ะฑัƒะดะตั‚ ะพะดะฝะฐ ะพะฑั‰ะฐั ัั…ะตะผะฐ ะธ ะดะปั ะฒั…ะพะดะฐ, ะธ ะดะปั ะฒั‹ั…ะพะดะฐ โ€” ั‚ะพะปัŒะบะพ `Item`, ะธ ะฒ ะฝะตะน `description` ะฑัƒะดะตั‚ **ะฝะต ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ**: <div class="screenshot"> <img src="/img/tutorial/separate-openapi-schemas/image05.png"> </div> - -ะญั‚ะพ ั‚ะพ ะถะต ะฟะพะฒะตะดะตะฝะธะต, ั‡ั‚ะพ ะธ ะฒ Pydantic v1. ๐Ÿค“ diff --git a/docs/ru/docs/index.md b/docs/ru/docs/index.md index b562cbe5bc..02b1c9a286 100644 --- a/docs/ru/docs/index.md +++ b/docs/ru/docs/index.md @@ -5,10 +5,10 @@ </style> <p align="center"> - <a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a> + <a href="https://fastapi.tiangolo.com/ru"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a> </p> <p align="center"> - <em>ะคั€ะตะนะผะฒะพั€ะบ FastAPI: ะฒั‹ัะพะบะฐั ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ, ะฟั€ะพัั‚ ะฒ ะธะทัƒั‡ะตะฝะธะธ, ะฑั‹ัั‚ั€ั‹ะน ะฒ ั€ะฐะทั€ะฐะฑะพั‚ะบะต, ะณะพั‚ะพะฒ ะบ ะฟั€ะพะดะฐะบัˆะฝ</em> + <em>ะคั€ะตะนะผะฒะพั€ะบ FastAPI: ะฒั‹ัะพะบะฐั ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ, ะฟั€ะพัั‚ ะฒ ะธะทัƒั‡ะตะฝะธะธ, ะฟะพะทะฒะพะปัะตั‚ ะฑั‹ัั‚ั€ะพ ะฟะธัะฐั‚ัŒ ะบะพะด, ะณะพั‚ะพะฒ ะบ ะฟั€ะพะดะฐะบัˆะฝ</em> </p> <p align="center"> <a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank"> @@ -40,7 +40,7 @@ FastAPI โ€” ัั‚ะพ ัะพะฒั€ะตะผะตะฝะฝั‹ะน, ะฑั‹ัั‚ั€ั‹ะน (ะฒั‹ัะพะบะพะฟั€ะพะธ * **ะกะบะพั€ะพัั‚ัŒ**: ะžั‡ะตะฝัŒ ะฒั‹ัะพะบะฐั ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ, ะฝะฐ ัƒั€ะพะฒะฝะต **NodeJS** ะธ **Go** (ะฑะปะฐะณะพะดะฐั€ั Starlette ะธ Pydantic). [ะžะดะธะฝ ะธะท ัะฐะผั‹ั… ะฑั‹ัั‚ั€ั‹ั… ะดะพัั‚ัƒะฟะฝั‹ั… ั„ั€ะตะนะผะฒะพั€ะบะพะฒ Python](#performance). * **ะ‘ั‹ัั‚ั€ะพั‚ะฐ ั€ะฐะทั€ะฐะฑะพั‚ะบะธ**: ะฃะฒะตะปะธั‡ัŒั‚ะต ัะบะพั€ะพัั‚ัŒ ั€ะฐะทั€ะฐะฑะพั‚ะบะธ ั„ะธั‡ ะฟั€ะธะผะตั€ะฝะพ ะฝะฐ 200โ€“300%. * * **ะœะตะฝัŒัˆะต ะพัˆะธะฑะพะบ**: ะกะพะบั€ะฐั‚ะธั‚ะต ะฟั€ะธะผะตั€ะฝะพ ะฝะฐ 40% ะบะพะปะธั‡ะตัั‚ะฒะพ ะพัˆะธะฑะพะบ, ะฒั‹ะทะฒะฐะฝะฝั‹ั… ั‡ะตะปะพะฒะตะบะพะผ (ั€ะฐะทั€ะฐะฑะพั‚ั‡ะธะบะพะผ). * -* **ะ˜ะฝั‚ัƒะธั‚ะธะฒะฝะพัั‚ัŒ**: ะžั‚ะปะธั‡ะฝะฐั ะฟะพะดะดะตั€ะถะบะฐ ั€ะตะดะฐะบั‚ะพั€ะฐ ะบะพะดะฐ. <abbr title="ั‚ะฐะบะถะต ะธะทะฒะตัั‚ะฝะพะต ะบะฐะบ: ะฐะฒั‚ะพะดะพะฟะพะปะฝะตะฝะธะต, IntelliSense">ะะฒั‚ะพะทะฐะฒะตั€ัˆะตะฝะธะต</abbr> ะฒะตะทะดะต. ะœะตะฝัŒัˆะต ะฒั€ะตะผะตะฝะธ ะฝะฐ ะพั‚ะปะฐะดะบัƒ. +* **ะ˜ะฝั‚ัƒะธั‚ะธะฒะฝะพัั‚ัŒ**: ะžั‚ะปะธั‡ะฝะฐั ะฟะพะดะดะตั€ะถะบะฐ ั€ะตะดะฐะบั‚ะพั€ะฐ ะบะพะดะฐ. <abbr title="ั‚ะฐะบะถะต ะธะทะฒะตัั‚ะฝะพะต ะบะฐะบ: ะฐะฒั‚ะพะดะพะฟะพะปะฝะตะฝะธะต, ะฐะฒั‚ะพะทะฐะฒะตั€ัˆะตะฝะธะต, IntelliSense">ะะฒั‚ะพะทะฐะฒะตั€ัˆะตะฝะธะต</abbr> ะฒะตะทะดะต. ะœะตะฝัŒัˆะต ะฒั€ะตะผะตะฝะธ ะฝะฐ ะพั‚ะปะฐะดะบัƒ. * **ะŸั€ะพัั‚ะพั‚ะฐ**: ะ ะฐะทั€ะฐะฑะพั‚ะฐะฝ ั‚ะฐะบ, ั‡ั‚ะพะฑั‹ ะตะณะพ ะฑั‹ะปะพ ะปะตะณะบะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธ ะพัะฒะฐะธะฒะฐั‚ัŒ. ะœะตะฝัŒัˆะต ะฒั€ะตะผะตะฝะธ ะฝะฐ ั‡ั‚ะตะฝะธะต ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ. * **ะšั€ะฐั‚ะบะพัั‚ัŒ**: ะœะธะฝะธะผะธะทะธั€ัƒะนั‚ะต ะดัƒะฑะปะธั€ะพะฒะฐะฝะธะต ะบะพะดะฐ. ะะตัะบะพะปัŒะบะพ ะฒะพะทะผะพะถะฝะพัั‚ะตะน ะธะท ะบะฐะถะดะพะณะพ ะพะฑัŠัะฒะปะตะฝะธั ะฟะฐั€ะฐะผะตั‚ั€ะพะฒ. ะœะตะฝัŒัˆะต ะพัˆะธะฑะพะบ. * **ะะฐะดะตะถะฝะพัั‚ัŒ**: ะŸะพะปัƒั‡ะธั‚ะต ะบะพะด, ะณะพั‚ะพะฒั‹ะน ะบ ะฟั€ะพะดะฐะบัˆะฝ. ะก ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะธะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะตะน. @@ -117,6 +117,12 @@ FastAPI โ€” ัั‚ะพ ัะพะฒั€ะตะผะตะฝะฝั‹ะน, ะฑั‹ัั‚ั€ั‹ะน (ะฒั‹ัะพะบะพะฟั€ะพะธ --- +## ะœะธะฝะธ-ะดะพะบัƒะผะตะฝั‚ะฐะปัŒะฝั‹ะน ั„ะธะปัŒะผ ะพ FastAPI { #fastapi-mini-documentary } + +ะ’ ะบะพะฝั†ะต 2025 ะณะพะดะฐ ะฒั‹ัˆะตะป <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">ะผะธะฝะธ-ะดะพะบัƒะผะตะฝั‚ะฐะปัŒะฝั‹ะน ั„ะธะปัŒะผ ะพ FastAPI</a>, ะฒั‹ ะผะพะถะตั‚ะต ะฟะพัะผะพั‚ั€ะตั‚ัŒ ะตะณะพ ะพะฝะปะฐะนะฝ: + +<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a> + ## **Typer**, FastAPI ะดะปั CLI { #typer-the-fastapi-of-clis } <a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a> @@ -257,7 +263,7 @@ INFO: Application startup complete. * ะŸะพะปัƒั‡ะฐะตั‚ HTTP-ะทะฐะฟั€ะพัั‹ ะฟะพ _ะฟัƒั‚ัะผ_ `/` ะธ `/items/{item_id}`. * ะžะฑะฐ _ะฟัƒั‚ะธ_ ะธัะฟะพะปัŒะทัƒัŽั‚ `GET` <em>ะพะฟะตั€ะฐั†ะธะธ</em> (ั‚ะฐะบะถะต ะธะทะฒะตัั‚ะฝั‹ะต ะบะฐะบ HTTP _ะผะตั‚ะพะดั‹_). -* _ะŸัƒั‚ัŒ_ `/items/{item_id}` ะธะผะตะตั‚ _ะฟะฐั€ะฐะผะตั‚ั€ ะฟัƒั‚ะธ_ `item_id`, ะบะพั‚ะพั€ั‹ะน ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ `int`. +* _ะŸัƒั‚ัŒ_ `/items/{item_id}` ะธะผะตะตั‚ _path-ะฟะฐั€ะฐะผะตั‚ั€_ `item_id`, ะบะพั‚ะพั€ั‹ะน ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ `int`. * _ะŸัƒั‚ัŒ_ `/items/{item_id}` ะธะผะตะตั‚ ะฝะตะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะน `str` _ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟั€ะพัะฐ_ `q`. ### ะ˜ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั API { #interactive-api-docs } @@ -278,9 +284,9 @@ INFO: Application startup complete. ## ะŸั€ะธะผะตั€ ะพะฑะฝะพะฒะปะตะฝะธั { #example-upgrade } -ะขะตะฟะตั€ัŒ ะธะทะผะตะฝะธั‚ะต ั„ะฐะนะป `main.py`, ั‡ั‚ะพะฑั‹ ะฟั€ะธะฝะธะผะฐั‚ัŒ ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ ะธะท `PUT` ะทะฐะฟั€ะพัะฐ. +ะขะตะฟะตั€ัŒ ะธะทะผะตะฝะธั‚ะต ั„ะฐะนะป `main.py`, ั‡ั‚ะพะฑั‹ ะฟั€ะธะฝะธะผะฐั‚ัŒ ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ ะธะท `PUT` HTTP-ะทะฐะฟั€ะพัะฐ. -ะžะฑัŠัะฒะธั‚ะต ั‚ะตะปะพ, ะธัะฟะพะปัŒะทัƒั ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะต ั‚ะธะฟั‹ Python, ัะฟะฐัะธะฑะพ Pydantic. +ะžะฑัŠัะฒะธั‚ะต ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ, ะธัะฟะพะปัŒะทัƒั ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะต ั‚ะธะฟั‹ Python, ัะฟะฐัะธะฑะพ Pydantic. ```Python hl_lines="4 9-12 25-27" from typing import Union @@ -318,7 +324,7 @@ def update_item(item_id: int, item: Item): ะŸะตั€ะตะนะดะธั‚ะต ะฝะฐ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. -* ะ˜ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั API ะฑัƒะดะตั‚ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะพะฑะฝะพะฒะปะตะฝะฐ, ะฒะบะปัŽั‡ะฐั ะฝะพะฒะพะต ั‚ะตะปะพ: +* ะ˜ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั API ะฑัƒะดะตั‚ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะพะฑะฝะพะฒะปะตะฝะฐ, ะฒะบะปัŽั‡ะฐั ะฝะพะฒะพะต ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) @@ -334,13 +340,13 @@ def update_item(item_id: int, item: Item): ะขะตะฟะตั€ัŒ ะพั‚ะบั€ะพะนั‚ะต <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. -* ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั ั‚ะฐะบะถะต ะพั‚ั€ะฐะทะธั‚ ะฝะพะฒั‹ะน ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟั€ะพัะฐ ะธ ั‚ะตะปะพ: +* ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั ั‚ะฐะบะถะต ะพั‚ั€ะฐะทะธั‚ ะฝะพะฒั‹ะน ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟั€ะพัะฐ ะธ ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) ### ะŸะพะดะฒะตะดั‘ะผ ะธั‚ะพะณะธ { #recap } -ะ˜ั‚ะฐะบ, ะฒั‹ ะพะฑัŠัะฒะปัะตั‚ะต **ะพะดะธะฝ ั€ะฐะท** ั‚ะธะฟั‹ ะฟะฐั€ะฐะผะตั‚ั€ะพะฒ, ั‚ะตะปะฐ ะทะฐะฟั€ะพัะฐ ะธ ั‚.ะด. ะบะฐะบ ะฟะฐั€ะฐะผะตั‚ั€ั‹ ั„ัƒะฝะบั†ะธะธ. +ะ˜ั‚ะฐะบ, ะฒั‹ ะพะฑัŠัะฒะปัะตั‚ะต **ะพะดะธะฝ ั€ะฐะท** ั‚ะธะฟั‹ ะฟะฐั€ะฐะผะตั‚ั€ะพะฒ, ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ ะธ ั‚.ะด. ะบะฐะบ ะฟะฐั€ะฐะผะตั‚ั€ั‹ ั„ัƒะฝะบั†ะธะธ. ะ’ั‹ ะดะตะปะฐะตั‚ะต ัั‚ะพ ั ะฟะพะผะพั‰ัŒัŽ ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ั… ัะพะฒั€ะตะผะตะฝะฝั‹ั… ั‚ะธะฟะพะฒ Python. @@ -390,13 +396,13 @@ item: Item ะ’ะพะทะฒั€ะฐั‰ะฐัััŒ ะบ ะฟั€ะตะดั‹ะดัƒั‰ะตะผัƒ ะฟั€ะธะผะตั€ัƒ ะบะพะดะฐ, **FastAPI** ะฑัƒะดะตั‚: -* ะ’ะฐะปะธะดะธั€ะพะฒะฐั‚ัŒ ะฝะฐะปะธั‡ะธะต `item_id` ะฒ ะฟัƒั‚ะธ ะดะปั `GET` ะธ `PUT` ะทะฐะฟั€ะพัะพะฒ. -* ะ’ะฐะปะธะดะธั€ะพะฒะฐั‚ัŒ, ั‡ั‚ะพ `item_id` ะธะผะตะตั‚ ั‚ะธะฟ `int` ะดะปั `GET` ะธ `PUT` ะทะฐะฟั€ะพัะพะฒ. +* ะ’ะฐะปะธะดะธั€ะพะฒะฐั‚ัŒ ะฝะฐะปะธั‡ะธะต `item_id` ะฒ ะฟัƒั‚ะธ ะดะปั `GET` ะธ `PUT` HTTP-ะทะฐะฟั€ะพัะพะฒ. +* ะ’ะฐะปะธะดะธั€ะพะฒะฐั‚ัŒ, ั‡ั‚ะพ `item_id` ะธะผะตะตั‚ ั‚ะธะฟ `int` ะดะปั `GET` ะธ `PUT` HTTP-ะทะฐะฟั€ะพัะพะฒ. * ะ•ัะปะธ ัั‚ะพ ะฝะต ั‚ะฐะบ, ะบะปะธะตะฝั‚ ัƒะฒะธะดะธั‚ ะฟะพะปะตะทะฝัƒัŽ ะฟะพะฝัั‚ะฝัƒัŽ ะพัˆะธะฑะบัƒ. -* ะŸั€ะพะฒะตั€ัั‚ัŒ, ะตัั‚ัŒ ะปะธ ะฝะตะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะน ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟั€ะพัะฐ ั ะธะผะตะฝะตะผ `q` (ะฝะฐะฟั€ะธะผะตั€, `http://127.0.0.1:8000/items/foo?q=somequery`) ะดะปั `GET` ะทะฐะฟั€ะพัะพะฒ. +* ะŸั€ะพะฒะตั€ัั‚ัŒ, ะตัั‚ัŒ ะปะธ ะฝะตะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะน ะฟะฐั€ะฐะผะตั‚ั€ ะทะฐะฟั€ะพัะฐ ั ะธะผะตะฝะตะผ `q` (ะฝะฐะฟั€ะธะผะตั€, `http://127.0.0.1:8000/items/foo?q=somequery`) ะดะปั `GET` HTTP-ะทะฐะฟั€ะพัะพะฒ. * ะŸะพัะบะพะปัŒะบัƒ ะฟะฐั€ะฐะผะตั‚ั€ `q` ะพะฑัŠัะฒะปะตะฝ ั `= None`, ะพะฝ ะฝะตะพะฑัะทะฐั‚ะตะปะตะฝ. * ะ‘ะตะท `None` ะพะฝ ะฑั‹ะป ะฑั‹ ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ (ะบะฐะบ ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ ะฒ ัะปัƒั‡ะฐะต ั `PUT`). -* ะ”ะปั `PUT` ะทะฐะฟั€ะพัะพะฒ ะบ `/items/{item_id}` ั‡ะธั‚ะฐั‚ัŒ ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ ะบะฐะบ JSON: +* ะ”ะปั `PUT` HTTP-ะทะฐะฟั€ะพัะพะฒ ะบ `/items/{item_id}` ั‡ะธั‚ะฐั‚ัŒ ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ ะบะฐะบ JSON: * ะŸั€ะพะฒะตั€ัั‚ัŒ, ั‡ั‚ะพ ะตัั‚ัŒ ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะน ะฐั‚ั€ะธะฑัƒั‚ `name`, ะบะพั‚ะพั€ั‹ะน ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ `str`. * ะŸั€ะพะฒะตั€ัั‚ัŒ, ั‡ั‚ะพ ะตัั‚ัŒ ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะน ะฐั‚ั€ะธะฑัƒั‚ `price`, ะบะพั‚ะพั€ั‹ะน ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ `float`. * ะŸั€ะพะฒะตั€ัั‚ัŒ, ั‡ั‚ะพ ะตัั‚ัŒ ะฝะตะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะน ะฐั‚ั€ะธะฑัƒั‚ `is_offer`, ะบะพั‚ะพั€ั‹ะน ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ `bool`, ะตัะปะธ ะพะฝ ะฟั€ะธััƒั‚ัั‚ะฒัƒะตั‚. @@ -435,11 +441,11 @@ item: Item ะ‘ะพะปะตะต ะฟะพะปะฝั‹ะน ะฟั€ะธะผะตั€ ั ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะผะธ ะฒะพะทะผะพะถะฝะพัั‚ัะผะธ ัะผ. ะฒ <a href="https://fastapi.tiangolo.com/ru/tutorial/">ะฃั‡ะตะฑะฝะธะบ - ะ ัƒะบะพะฒะพะดัั‚ะฒะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั</a>. -**ะžัั‚ะพั€ะพะถะฝะพ, ัะฟะพะนะปะตั€**: ัƒั‡ะตะฑะฝะธะบ - ั€ัƒะบะพะฒะพะดัั‚ะฒะพ ะฒะบะปัŽั‡ะฐะตั‚: +**ะžัั‚ะพั€ะพะถะฝะพ, ัะฟะพะนะปะตั€**: ัƒั‡ะตะฑะฝะธะบ - ั€ัƒะบะพะฒะพะดัั‚ะฒะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฒะบะปัŽั‡ะฐะตั‚: * ะžะฑัŠัะฒะปะตะฝะธะต **ะฟะฐั€ะฐะผะตั‚ั€ะพะฒ** ะธะท ะดั€ัƒะณะธั… ะธัั‚ะพั‡ะฝะธะบะพะฒ: **HTTP-ะทะฐะณะพะปะพะฒะบะธ**, **cookies**, **ะฟะพะปั ั„ะพั€ะผั‹** ะธ **ั„ะฐะนะปั‹**. * ะšะฐะบ ะทะฐะดะฐั‚ัŒ **ะพะณั€ะฐะฝะธั‡ะตะฝะธั ะฒะฐะปะธะดะฐั†ะธะธ** ะฒั€ะพะดะต `maximum_length` ะธะปะธ `regex`. -* ะžั‡ะตะฝัŒ ะผะพั‰ะฝัƒัŽ ะธ ะฟั€ะพัั‚ัƒัŽ ะฒ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ ัะธัั‚ะตะผัƒ **<abbr title="ั‚ะฐะบะถะต ะธะทะฒะตัั‚ะฝะฐั ะบะฐะบ: ะบะพะผะฟะพะฝะตะฝั‚ั‹, ั€ะตััƒั€ัั‹, ะฟั€ะพะฒะฐะนะดะตั€ั‹, ัะตั€ะฒะธัั‹, ะธะฝัŠะตะบั†ะธะธ">ะฒะฝะตะดั€ะตะฝะธั ะทะฐะฒะธัะธะผะพัั‚ะตะน</abbr>**. +* ะžั‡ะตะฝัŒ ะผะพั‰ะฝัƒัŽ ะธ ะฟั€ะพัั‚ัƒัŽ ะฒ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ ัะธัั‚ะตะผัƒ **<abbr title="ั‚ะฐะบะถะต ะธะทะฒะตัั‚ะฝะฐ ะบะฐะบ: ะบะพะผะฟะพะฝะตะฝั‚ั‹, ั€ะตััƒั€ัั‹, ะฟั€ะพะฒะฐะนะดะตั€ั‹, ัะตั€ะฒะธัั‹, ะธะฝัŠะตะบั†ะธะธ">ะฒะฝะตะดั€ะตะฝะธั ะทะฐะฒะธัะธะผะพัั‚ะตะน</abbr>**. * ะ‘ะตะทะพะฟะฐัะฝะพัั‚ัŒ ะธ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ, ะฒะบะปัŽั‡ะฐั ะฟะพะดะดะตั€ะถะบัƒ **OAuth2** ั **JWT ั‚ะพะบะตะฝะฐะผะธ** ะธ **HTTP Basic** ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ. * ะ‘ะพะปะตะต ะฟั€ะพะดะฒะธะฝัƒั‚ั‹ะต (ะฝะพ ัั‚ะพะปัŒ ะถะต ะฟั€ะพัั‚ั‹ะต) ะฟั€ะธั‘ะผั‹ ะพะฑัŠัะฒะปะตะฝะธั **ะณะปัƒะฑะพะบะพ ะฒะปะพะถะตะฝะฝั‹ั… JSON-ะผะพะดะตะปะตะน** (ัะฟะฐัะธะฑะพ Pydantic). * ะ˜ะฝั‚ะตะณั€ะฐั†ะธัŽ **GraphQL** ั <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> ะธ ะดั€ัƒะณะธะผะธ ะฑะธะฑะปะธะพั‚ะตะบะฐะผะธ. @@ -524,11 +530,11 @@ FastAPI ะทะฐะฒะธัะธั‚ ะพั‚ Pydantic ะธ Starlette. * <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> โ€” ะพะฑัะทะฐั‚ะตะปะตะฝ, ะตัะปะธ ะฒั‹ ั…ะพั‚ะธั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `TestClient`. * <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> โ€” ะพะฑัะทะฐั‚ะตะปะตะฝ, ะตัะปะธ ะฒั‹ ั…ะพั‚ะธั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะบะพะฝั„ะธะณัƒั€ะฐั†ะธัŽ ัˆะฐะฑะปะพะฝะพะฒ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ. -* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> โ€” ะพะฑัะทะฐั‚ะตะปะตะฝ, ะตัะปะธ ะฒั‹ ั…ะพั‚ะธั‚ะต ะฟะพะดะดะตั€ะถะธะฒะฐั‚ัŒ <abbr title="ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐะฝะธะต ัั‚ั€ะพะบะธ, ะฟะพะปัƒั‡ะตะฝะฝะพะน ะธะท HTTP-ะทะฐะฟั€ะพัะฐ, ะฒ ะดะฐะฝะฝั‹ะต Python">ยซะฟะฐั€ัะธะฝะณยป</abbr> ั„ะพั€ะผ ั‡ะตั€ะตะท `request.form()`. +* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - ะพะฑัะทะฐั‚ะตะปะตะฝ, ะตัะปะธ ะฒั‹ ั…ะพั‚ะธั‚ะต ะฟะพะดะดะตั€ะถะธะฒะฐั‚ัŒ <abbr title="ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐะฝะธะต ัั‚ั€ะพะบะธ, ะฟะพะปัƒั‡ะตะฝะฝะพะน ะธะท HTTP-ะทะฐะฟั€ะพัะฐ, ะฒ ะดะฐะฝะฝั‹ะต Python">ยซะฟะฐั€ัะธะฝะณยป</abbr> ั„ะพั€ะผ ั‡ะตั€ะตะท `request.form()`. ะ˜ัะฟะพะปัŒะทัƒะตั‚ัั FastAPI: -* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> โ€” ัะตั€ะฒะตั€, ะบะพั‚ะพั€ั‹ะน ะทะฐะณั€ัƒะถะฐะตั‚ ะธ ะพะฑัะปัƒะถะธะฒะฐะตั‚ ะฒะฐัˆะต ะฟั€ะธะปะพะถะตะฝะธะต. ะ’ะบะปัŽั‡ะฐะตั‚ `uvicorn[standard]`, ัะพะดะตั€ะถะฐั‰ะธะน ะฝะตะบะพั‚ะพั€ั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ (ะฝะฐะฟั€ะธะผะตั€, `uvloop`), ะฝัƒะถะฝั‹ะต ะดะปั ะฒั‹ัะพะบะพะน ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ. +* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> โ€” ัะตั€ะฒะตั€, ะบะพั‚ะพั€ั‹ะน ะทะฐะณั€ัƒะถะฐะตั‚ ะธ ยซะพั‚ะดะฐั‘ั‚ยป ะฒะฐัˆะต ะฟั€ะธะปะพะถะตะฝะธะต. ะ’ะบะปัŽั‡ะฐะตั‚ `uvicorn[standard]`, ัะพะดะตั€ะถะฐั‰ะธะน ะฝะตะบะพั‚ะพั€ั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ (ะฝะฐะฟั€ะธะผะตั€, `uvloop`), ะฝัƒะถะฝั‹ะต ะดะปั ะฒั‹ัะพะบะพะน ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ. * `fastapi-cli[standard]` โ€” ั‡ั‚ะพะฑั‹ ะฟั€ะตะดะพัั‚ะฐะฒะธั‚ัŒ ะบะพะผะฐะฝะดัƒ `fastapi`. * ะ’ะบะปัŽั‡ะฐะตั‚ `fastapi-cloud-cli`, ะบะพั‚ะพั€ั‹ะน ะฟะพะทะฒะพะปัะตั‚ ั€ะฐะทะฒะตั€ะฝัƒั‚ัŒ ะฒะฐัˆะต ะฟั€ะธะปะพะถะตะฝะธะต FastAPI ะฒ <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>. diff --git a/docs/ru/docs/tutorial/bigger-applications.md b/docs/ru/docs/tutorial/bigger-applications.md index 5e5d6ada94..76304523c9 100644 --- a/docs/ru/docs/tutorial/bigger-applications.md +++ b/docs/ru/docs/tutorial/bigger-applications.md @@ -1,4 +1,4 @@ -# ะ‘ะพะปัŒัˆะธะต ะฟั€ะธะปะพะถะตะฝะธั, ะฒ ะบะพั‚ะพั€ั‹ั… ะผะฝะพะณะพ ั„ะฐะนะปะพะฒ { #bigger-applications-multiple-files } +# ะ‘ะพะปัŒัˆะธะต ะฟั€ะธะปะพะถะตะฝะธั โ€” ะฝะตัะบะพะปัŒะบะพ ั„ะฐะนะปะพะฒ { #bigger-applications-multiple-files } ะŸั€ะธ ะฟะพัั‚ั€ะพะตะฝะธะธ ะฟั€ะธะปะพะถะตะฝะธั ะธะปะธ ะฒะตะฑ-API ะฝะฐะผ ั€ะตะดะบะพ ัƒะดะฐะตั‚ัั ะฟะพะผะตัั‚ะธั‚ัŒ ะฒัั‘ ะฒ ะพะดะธะฝ ั„ะฐะนะป. @@ -31,7 +31,7 @@ /// tip | ะŸะพะดัะบะฐะทะบะฐ -ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ ะฒ ะบะฐะถะดะพะผ ะบะฐั‚ะฐะปะพะณะต ะธ ะฟะพะดะบะฐั‚ะฐะปะพะณะต ะธะผะตะตั‚ัั ั„ะฐะนะป `__init__.py` +ะ•ัั‚ัŒ ะฝะตัะบะพะปัŒะบะพ ั„ะฐะนะปะพะฒ `__init__.py`: ะฟะพ ะพะดะฝะพะผัƒ ะฒ ะบะฐะถะดะพะผ ะบะฐั‚ะฐะปะพะณะต ะธะปะธ ะฟะพะดะบะฐั‚ะฐะปะพะณะต. ะญั‚ะพ ะบะฐะบ ั€ะฐะท ั‚ะพ, ั‡ั‚ะพ ะฟะพะทะฒะพะปัะตั‚ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะบะพะด ะธะท ะพะดะฝะพะณะพ ั„ะฐะนะปะฐ ะฒ ะดั€ัƒะณะพะน. @@ -43,61 +43,63 @@ from app.routers import items /// -* ะ’ัั‘ ะฟะพะผะตั‰ะฐะตั‚ัั ะฒ ะบะฐั‚ะฐะปะพะณะต `app`. ะ’ ะฝั‘ะผ ั‚ะฐะบะถะต ะฝะฐั…ะพะดะธั‚ัั ะฟัƒัั‚ะพะน ั„ะฐะนะป `app/__init__.py`. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, `app` ัะฒะปัะตั‚ัั "Python-ะฟะฐะบะตั‚ะพะผ" (ะบะพะปะปะตะบั†ะธะตะน ะผะพะดัƒะปะตะน Python). -* ะžะฝ ัะพะดะตั€ะถะธั‚ ั„ะฐะนะป `app/main.py`. ะ”ะฐะฝะฝั‹ะน ั„ะฐะนะป ัะฒะปัะตั‚ัั ั‡ะฐัั‚ัŒัŽ ะฟะฐะบะตั‚ะฐ (ั‚.ะต. ะฝะฐั…ะพะดะธั‚ัั ะฒะฝัƒั‚ั€ะธ ะบะฐั‚ะฐะปะพะณะฐ, ัะพะดะตั€ะถะฐั‰ะตะณะพ ั„ะฐะนะป `__init__.py`), ะธ, ัะพะพั‚ะฒะตั‚ัั‚ะฒะตะฝะฝะพ, ะพะฝ ัะฒะปัะตั‚ัั ะผะพะดัƒะปะตะผ ะฟะฐะบะตั‚ะฐ: `app.main`. +* ะ’ัั‘ ะฟะพะผะตั‰ะฐะตั‚ัั ะฒ ะบะฐั‚ะฐะปะพะณะต `app`. ะ’ ะฝั‘ะผ ั‚ะฐะบะถะต ะฝะฐั…ะพะดะธั‚ัั ะฟัƒัั‚ะพะน ั„ะฐะนะป `app/__init__.py`. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, `app` ัะฒะปัะตั‚ัั "Python-ะฟะฐะบะตั‚ะพะผ" (ะบะพะปะปะตะบั†ะธะตะน "Python-ะผะพะดัƒะปะตะน"): `app`. +* ะžะฝ ัะพะดะตั€ะถะธั‚ ั„ะฐะนะป `app/main.py`. ะ”ะฐะฝะฝั‹ะน ั„ะฐะนะป ัะฒะปัะตั‚ัั ั‡ะฐัั‚ัŒัŽ Python-ะฟะฐะบะตั‚ะฐ (ั‚.ะต. ะฝะฐั…ะพะดะธั‚ัั ะฒะฝัƒั‚ั€ะธ ะบะฐั‚ะฐะปะพะณะฐ, ัะพะดะตั€ะถะฐั‰ะตะณะพ ั„ะฐะนะป `__init__.py`), ะธ, ัะพะพั‚ะฒะตั‚ัั‚ะฒะตะฝะฝะพ, ะพะฝ ัะฒะปัะตั‚ัั ะผะพะดัƒะปะตะผ ัั‚ะพะณะพ ะฟะฐะบะตั‚ะฐ: `app.main`. * ะžะฝ ั‚ะฐะบะถะต ัะพะดะตั€ะถะธั‚ ั„ะฐะนะป `app/dependencies.py`, ะบะพั‚ะพั€ั‹ะน ั‚ะฐะบะถะต, ะบะฐะบ ะธ `app/main.py`, ัะฒะปัะตั‚ัั ะผะพะดัƒะปะตะผ: `app.dependencies`. -* ะ—ะดะตััŒ ั‚ะฐะบะถะต ะฝะฐั…ะพะดะธั‚ัั ะฟะพะดะบะฐั‚ะฐะปะพะณ `app/routers/`, ัะพะดะตั€ะถะฐั‰ะธะน `__init__.py`. ะžะฝ ัะฒะปัะตั‚ัั ััƒะฑ-ะฟะฐะบะตั‚ะพะผ: `app.routers`. -* ะคะฐะนะป `app/routers/items.py` ะฝะฐั…ะพะดะธั‚ัั ะฒะฝัƒั‚ั€ะธ ะฟะฐะบะตั‚ะฐ `app/routers/`. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะพะฝ ัะฒะปัะตั‚ัั ััƒะฑ-ะผะพะดัƒะปะตะผ: `app.routers.items`. -* ะขะพั‡ะฝะพ ั‚ะฐะบะถะต `app/routers/users.py` ัะฒะปัะตั‚ัั ะตั‰ั‘ ะพะดะฝะธะผ ััƒะฑ-ะผะพะดัƒะปะตะผ: `app.routers.users`. -* ะŸะพะดะบะฐั‚ะฐะปะพะณ `app/internal/`, ัะพะดะตั€ะถะฐั‰ะธะน ั„ะฐะนะป `__init__.py`, ัะฒะปัะตั‚ัั ะตั‰ั‘ ะพะดะฝะธะผ ััƒะฑ-ะฟะฐะบะตั‚ะพะผ: `app.internal`. -* ะ ั„ะฐะนะป `app/internal/admin.py` ัะฒะปัะตั‚ัั ะตั‰ั‘ ะพะดะฝะธะผ ััƒะฑ-ะผะพะดัƒะปะตะผ: `app.internal.admin`. +* ะ—ะดะตััŒ ั‚ะฐะบะถะต ะฝะฐั…ะพะดะธั‚ัั ะฟะพะดะบะฐั‚ะฐะปะพะณ `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`. <img src="/img/tutorial/bigger-applications/package.drawio.svg"> ะขะฐ ะถะต ัะฐะผะฐั ั„ะฐะนะปะพะฒะฐั ัั‚ั€ัƒะบั‚ัƒั€ะฐ ะฟั€ะธะปะพะถะตะฝะธั, ะฝะพ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ: -``` +```bash . โ”œโ”€โ”€ app # "app" ะฟะฐะบะตั‚ โ”‚ย ย  โ”œโ”€โ”€ __init__.py # ัั‚ะพั‚ ั„ะฐะนะป ะฟั€ะตะฒั€ะฐั‰ะฐะตั‚ "app" ะฒ "Python-ะฟะฐะบะตั‚" โ”‚ย ย  โ”œโ”€โ”€ main.py # ะผะพะดัƒะปัŒ "main", ะฝะฐะฟั€.: import app.main โ”‚ย ย  โ”œโ”€โ”€ dependencies.py # ะผะพะดัƒะปัŒ "dependencies", ะฝะฐะฟั€.: import app.dependencies -โ”‚ย ย  โ””โ”€โ”€ routers # ััƒะฑ-ะฟะฐะบะตั‚ "routers" -โ”‚ย ย  โ”‚ โ”œโ”€โ”€ __init__.py # ะฟั€ะตะฒั€ะฐั‰ะฐะตั‚ "routers" ะฒ ััƒะฑ-ะฟะฐะบะตั‚ -โ”‚ย ย  โ”‚ โ”œโ”€โ”€ items.py # ััƒะฑ-ะผะพะดัƒะปัŒ "items", ะฝะฐะฟั€.: import app.routers.items -โ”‚ย ย  โ”‚ โ””โ”€โ”€ users.py # ััƒะฑ-ะผะพะดัƒะปัŒ "users", ะฝะฐะฟั€.: import app.routers.users -โ”‚ย ย  โ””โ”€โ”€ internal # ััƒะฑ-ะฟะฐะบะตั‚ "internal" -โ”‚ย ย  โ”œโ”€โ”€ __init__.py # ะฟั€ะตะฒั€ะฐั‰ะฐะตั‚ "internal" ะฒ ััƒะฑ-ะฟะฐะบะตั‚ -โ”‚ย ย  โ””โ”€โ”€ admin.py # ััƒะฑ-ะผะพะดัƒะปัŒ "admin", ะฝะฐะฟั€.: import app.internal.admin +โ”‚ย ย  โ””โ”€โ”€ routers # ะฟะพะดะฟะฐะบะตั‚ "routers" +โ”‚ย ย  โ”‚ โ”œโ”€โ”€ __init__.py # ะฟั€ะตะฒั€ะฐั‰ะฐะตั‚ "routers" ะฒ ะฟะพะดะฟะฐะบะตั‚ +โ”‚ย ย  โ”‚ โ”œโ”€โ”€ items.py # ะฟะพะดะผะพะดัƒะปัŒ "items", ะฝะฐะฟั€.: import app.routers.items +โ”‚ย ย  โ”‚ โ””โ”€โ”€ users.py # ะฟะพะดะผะพะดัƒะปัŒ "users", ะฝะฐะฟั€.: import app.routers.users +โ”‚ย ย  โ””โ”€โ”€ internal # ะฟะพะดะฟะฐะบะตั‚ "internal" +โ”‚ย ย  โ”œโ”€โ”€ __init__.py # ะฟั€ะตะฒั€ะฐั‰ะฐะตั‚ "internal" ะฒ ะฟะพะดะฟะฐะบะตั‚ +โ”‚ย ย  โ””โ”€โ”€ admin.py # ะฟะพะดะผะพะดัƒะปัŒ "admin", ะฝะฐะฟั€.: import app.internal.admin ``` ## `APIRouter` { #apirouter } -ะ”ะฐะฒะฐะนั‚ะต ะฟั€ะตะดะฟะพะปะพะถะธะผ, ั‡ั‚ะพ ะดะปั ั€ะฐะฑะพั‚ั‹ ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผะธ ะธัะฟะพะปัŒะทัƒะตั‚ัั ะพั‚ะดะตะปัŒะฝั‹ะน ั„ะฐะนะป (ััƒะฑ-ะผะพะดัƒะปัŒ) `/app/routers/users.py`. +ะ”ะฐะฒะฐะนั‚ะต ะฟั€ะตะดะฟะพะปะพะถะธะผ, ั‡ั‚ะพ ะดะปั ั€ะฐะฑะพั‚ั‹ ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผะธ ะธัะฟะพะปัŒะทัƒะตั‚ัั ะพั‚ะดะตะปัŒะฝั‹ะน ั„ะฐะนะป (ะฟะพะดะผะพะดัƒะปัŒ) `/app/routers/users.py`. -ะ”ะปั ะปัƒั‡ัˆะตะน ะพั€ะณะฐะฝะธะทะฐั†ะธะธ ะฟั€ะธะปะพะถะตะฝะธั, ะฒั‹ ั…ะพั‚ะธั‚ะต ะพั‚ะดะตะปะธั‚ัŒ ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ, ัะฒัะทะฐะฝะฝั‹ะต ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผะธ, ะพั‚ ะพัั‚ะฐะปัŒะฝะพะณะพ ะบะพะดะฐ. +ะ’ั‹ ั…ะพั‚ะธั‚ะต ะพั‚ะดะตะปะธั‚ัŒ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*, ัะฒัะทะฐะฝะฝั‹ะต ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผะธ, ะพั‚ ะพัั‚ะฐะปัŒะฝะพะณะพ ะบะพะดะฐ, ั‡ั‚ะพะฑั‹ ัะพั…ั€ะฐะฝะธั‚ัŒ ะฟะพั€ัะดะพะบ. -ะะพ ั‚ะฐะบ, ั‡ั‚ะพะฑั‹ ัั‚ะธ ะพะฟะตั€ะฐั†ะธะธ ะฟะพ-ะฟั€ะตะถะฝะตะผัƒ ะพัั‚ะฐะฒะฐะปะธััŒ ั‡ะฐัั‚ัŒัŽ **FastAPI** ะฟั€ะธะปะพะถะตะฝะธั/ะฒะตะฑ-API (ั‡ะฐัั‚ัŒัŽ ะพะดะฝะพะณะพ ะฟะฐะบะตั‚ะฐ) +ะะพ ัั‚ะพ ะฒัั‘ ั€ะฐะฒะฝะพ ั‡ะฐัั‚ัŒ ั‚ะพะณะพ ะถะต ะฟั€ะธะปะพะถะตะฝะธั/ะฒะตะฑ-API ะฝะฐ **FastAPI** (ั‡ะฐัั‚ัŒ ั‚ะพะณะพ ะถะต ยซPython-ะฟะฐะบะตั‚ะฐยป). -ะก ะฟะพะผะพั‰ัŒัŽ `APIRouter` ะฒั‹ ะผะพะถะตั‚ะต ัะพะทะดะฐั‚ัŒ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* (*ัะฝะดะฟะพะธะฝั‚ั‹*) ะดะปั ะดะฐะฝะฝะพะณะพ ะผะพะดัƒะปั. +ะก ะฟะพะผะพั‰ัŒัŽ `APIRouter` ะฒั‹ ะผะพะถะตั‚ะต ัะพะทะดะฐั‚ัŒ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะดะปั ัั‚ะพะณะพ ะผะพะดัƒะปั. ### ะ˜ะผะฟะพั€ั‚ `APIRouter` { #import-apirouter } -ะขะพั‡ะฝะพ ั‚ะฐะบะถะต, ะบะฐะบ ะธ ะฒ ัะปัƒั‡ะฐะต ั ะบะปะฐััะพะผ `FastAPI`, ะฒะฐะผ ะฝัƒะถะฝะพ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธ ัะพะทะดะฐั‚ัŒ ะพะฑัŠะตะบั‚ ะบะปะฐััะฐ `APIRouter`. +ะขะพั‡ะฝะพ ั‚ะฐะบ ะถะต, ะบะฐะบ ะธ ะฒ ัะปัƒั‡ะฐะต ั ะบะปะฐััะพะผ `FastAPI`, ะฒะฐะผ ะฝัƒะถะฝะพ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธ ัะพะทะดะฐั‚ัŒ ะตะณะพ ยซัะบะทะตะผะฟะปัั€ยป: {* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} -### ะกะพะทะดะฐะฝะธะต *ัะฝะดะฟะพะธะฝั‚ะพะฒ* ั ะฟะพะผะพั‰ัŒัŽ `APIRouter` { #path-operations-with-apirouter } +### *ะžะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ั `APIRouter` { #path-operations-with-apirouter } -ะ’ ะดะฐะปัŒะฝะตะนัˆะตะผ ะธัะฟะพะปัŒะทัƒะนั‚ะต `APIRouter` ะดะปั ะพะฑัŠัะฒะปะตะฝะธั *ัะฝะดะฟะพะธะฝั‚ะพะฒ*, ั‚ะพั‡ะฝะพ ั‚ะฐะบะถะต, ะบะฐะบ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต ะบะปะฐัั `FastAPI`: +ะ˜ ะทะฐั‚ะตะผ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต ะตะณะพ, ั‡ั‚ะพะฑั‹ ะพะฑัŠัะฒะธั‚ัŒ ะฒะฐัˆะธ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. + +ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะตะณะพ ั‚ะฐะบ ะถะต, ะบะฐะบ ะฒั‹ ะธัะฟะพะปัŒะทะพะฒะฐะปะธ ะฑั‹ ะบะปะฐัั `FastAPI`: {* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} -ะ’ั‹ ะผะพะถะตั‚ะต ะดัƒะผะฐั‚ัŒ ะพะฑ `APIRouter` ะบะฐะบ ะพะฑ "ัƒะผะตะฝัŒัˆะตะฝะฝะพะน ะฒะตั€ัะธะธ" ะบะปะฐััะฐ FastAPI`. +ะ’ั‹ ะผะพะถะตั‚ะต ะดัƒะผะฐั‚ัŒ ะพะฑ `APIRouter` ะบะฐะบ ะพะฑ ยซะผะธะฝะธ-ะบะปะฐััะต `FastAPI`ยป. -`APIRouter` ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะฒัะต ั‚ะต ะถะต ัะฐะผั‹ะต ะพะฟั†ะธะธ. +ะŸะพะดะดะตั€ะถะธะฒะฐัŽั‚ัั ะฒัะต ั‚ะต ะถะต ะพะฟั†ะธะธ. -`APIRouter` ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะฒัะต ั‚ะต ะถะต ัะฐะผั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹, ั‚ะฐะบะธะต ะบะฐะบ `parameters`, `responses`, `dependencies`, `tags`, ะธ ั‚. ะด. +ะ’ัะต ั‚ะต ะถะต `parameters`, `responses`, `dependencies`, `tags` ะธ ั‚.ะด. /// tip | ะŸะพะดัะบะฐะทะบะฐ @@ -105,21 +107,21 @@ from app.routers import items /// -ะœั‹ ัะพะฑะธั€ะฐะตะผัั ะฟะพะดะบะปัŽั‡ะธั‚ัŒ ะดะฐะฝะฝั‹ะน `APIRouter` ะบ ะฝะฐัˆะตะผัƒ ะพัะฝะพะฒะฝะพะผัƒ ะฟั€ะธะปะพะถะตะฝะธัŽ ะฝะฐ `FastAPI`, ะฝะพ ัะฝะฐั‡ะฐะปะฐ ะดะฐะฒะฐะนั‚ะต ะฟั€ะพะฒะตั€ะธะผ ะทะฐะฒะธัะธะผะพัั‚ะธ ะธ ัะพะทะดะฐะดะธะผ ะตั‰ั‘ ะพะดะธะฝ ะผะพะดัƒะปัŒ ั `APIRouter`. +ะœั‹ ัะพะฑะธั€ะฐะตะผัั ะฟะพะดะบะปัŽั‡ะธั‚ัŒ ะดะฐะฝะฝั‹ะน `APIRouter` ะบ ะฝะฐัˆะตะผัƒ ะพัะฝะพะฒะฝะพะผัƒ ะฟั€ะธะปะพะถะตะฝะธัŽ ะฝะฐ `FastAPI`, ะฝะพ ัะฝะฐั‡ะฐะปะฐ ะดะฐะฒะฐะนั‚ะต ะฟั€ะพะฒะตั€ะธะผ ะทะฐะฒะธัะธะผะพัั‚ะธ ะธ ะตั‰ั‘ ะพะดะธะฝ `APIRouter`. ## ะ—ะฐะฒะธัะธะผะพัั‚ะธ { #dependencies } -ะะฐะผ ะฟะพะฝะฐะดะพะฑัั‚ัั ะฝะตะบะพั‚ะพั€ั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ, ะบะพั‚ะพั€ั‹ะต ะผั‹ ะฑัƒะดะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฒ ั€ะฐะทะฝั‹ั… ะผะตัั‚ะฐั… ะฝะฐัˆะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั. +ะœั‹ ะฒะธะดะธะผ, ั‡ั‚ะพ ะฝะฐะผ ะฟะพะฝะฐะดะพะฑัั‚ัั ะฝะตะบะพั‚ะพั€ั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ, ะบะพั‚ะพั€ั‹ะต ะฑัƒะดัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะฒ ะฝะตัะบะพะปัŒะบะธั… ะผะตัั‚ะฐั… ะฟั€ะธะปะพะถะตะฝะธั. -ะœั‹ ะฟะพะผะตัั‚ะธะผ ะธั… ะฒ ะพั‚ะดะตะปัŒะฝั‹ะน ะผะพะดัƒะปัŒ `dependencies` (`app/dependencies.py`). +ะŸะพัั‚ะพะผัƒ ะผั‹ ะฟะพะผะตัั‚ะธะผ ะธั… ะฒ ะพั‚ะดะตะปัŒะฝั‹ะน ะผะพะดัƒะปัŒ `dependencies` (`app/dependencies.py`). -ะขะตะฟะตั€ัŒ ะผั‹ ะฒะพัะฟะพะปัŒะทัƒะตะผัั ะฟั€ะพัั‚ะพะน ะทะฐะฒะธัะธะผะพัั‚ัŒัŽ, ั‡ั‚ะพะฑั‹ ะฟั€ะพั‡ะธั‚ะฐั‚ัŒ ะบะฐัั‚ะพะผะธะทะธั€ะพะฒะฐะฝะฝั‹ะน `X-Token` ะธะท ะทะฐะณะพะปะพะฒะบะฐ: +ะขะตะฟะตั€ัŒ ะผั‹ ะฒะพัะฟะพะปัŒะทัƒะตะผัั ะฟั€ะพัั‚ะพะน ะทะฐะฒะธัะธะผะพัั‚ัŒัŽ, ั‡ั‚ะพะฑั‹ ะฟั€ะพั‡ะธั‚ะฐั‚ัŒ ะบะฐัั‚ะพะผะฝั‹ะน HTTP-ะทะฐะณะพะปะพะฒะพะบ `X-Token`: {* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | ะŸะพะดัะบะฐะทะบะฐ -ะ”ะปั ะฟั€ะพัั‚ะพั‚ั‹ ะผั‹ ะฒะพัะฟะพะปัŒะทะพะฒะฐะปะธััŒ ะฝะตะบะธะผ ะฒะพะพะฑั€ะฐะถะฐะตะผั‹ะผ ะทะฐะณะพะปะพะฒะพะบะพะผ. +ะ”ะปั ะฟั€ะพัั‚ะพั‚ั‹ ะผั‹ ะฒะพัะฟะพะปัŒะทะพะฒะฐะปะธััŒ ะฒั‹ะดัƒะผะฐะฝะฝั‹ะผ ะทะฐะณะพะปะพะฒะบะพะผ. ะ’ ั€ะตะฐะปัŒะฝั‹ั… ัะปัƒั‡ะฐัั… ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะฝะฐะธะปัƒั‡ัˆะธั… ั€ะตะทัƒะปัŒั‚ะฐั‚ะพะฒ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะธะฝั‚ะตะณั€ะธั€ะพะฒะฐะฝะฝั‹ะต [ัƒั‚ะธะปะธั‚ั‹ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ](security/index.md){.internal-link target=_blank}. @@ -127,30 +129,29 @@ from app.routers import items ## ะ•ั‰ั‘ ะพะดะธะฝ ะผะพะดัƒะปัŒ ั `APIRouter` { #another-module-with-apirouter } -ะ”ะฐะฒะฐะนั‚ะต ั‚ะฐะบะถะต ะฟั€ะตะดะฟะพะปะพะถะธะผ, ั‡ั‚ะพ ัƒ ะฒะฐั ะตัั‚ัŒ *ัะฝะดะฟะพะธะฝั‚ั‹*, ะพั‚ะฒะตั‡ะฐัŽั‰ะธะต ะทะฐ ะพะฑั€ะฐะฑะพั‚ะบัƒ "items", ะธ ะพะฝะธ ะฝะฐั…ะพะดัั‚ัั ะฒ ะผะพะดัƒะปะต `app/routers/items.py`. +ะ”ะฐะฒะฐะนั‚ะต ั‚ะฐะบะถะต ะฟั€ะตะดะฟะพะปะพะถะธะผ, ั‡ั‚ะพ ัƒ ะฒะฐั ะตัั‚ัŒ ัะฝะดะฟะพะธะฝั‚ั‹, ะพั‚ะฒะตั‡ะฐัŽั‰ะธะต ะทะฐ ะพะฑั€ะฐะฑะพั‚ะบัƒ ยซitemsยป ะฒ ะฒะฐัˆะตะผ ะฟั€ะธะปะพะถะตะฝะธะธ, ะธ ะพะฝะธ ะฝะฐั…ะพะดัั‚ัั ะฒ ะผะพะดัƒะปะต `app/routers/items.py`. -ะฃ ะฒะฐั ะพะฟั€ะตะดะตะปะตะฝั‹ ัะปะตะดัƒัŽั‰ะธะต *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* (*ัะฝะดะฟะพะธะฝั‚ั‹*): +ะฃ ะฒะฐั ะพะฟั€ะตะดะตะปะตะฝั‹ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะดะปั: * `/items/` * `/items/{item_id}` -ะขัƒั‚ ะฒัั‘ ั‚ะพั‡ะฝะพ ั‚ะฐะบะถะต, ะบะฐะบ ะธ ะฒ ัะธั‚ัƒะฐั†ะธะธ ั `app/routers/users.py`. +ะขัƒั‚ ะฒัั‘ ั‚ะฐ ะถะต ัั‚ั€ัƒะบั‚ัƒั€ะฐ, ะบะฐะบ ะธ ะฒ ัะปัƒั‡ะฐะต ั `app/routers/users.py`. -ะะพ ั‚ะตะฟะตั€ัŒ ะผั‹ ั…ะพั‚ะธะผ ะฟะพัั‚ัƒะฟะธั‚ัŒ ะฝะตะผะฝะพะณะพ ัƒะผะฝะตะต ะธ ัะปะตะณะบะฐ ัƒะฟั€ะพัั‚ะธั‚ัŒ ะบะพะด. +ะะพ ะผั‹ ั…ะพั‚ะธะผ ะฟะพัั‚ัƒะฟะธั‚ัŒ ัƒะผะฝะตะต ะธ ัะปะตะณะบะฐ ัƒะฟั€ะพัั‚ะธั‚ัŒ ะบะพะด. -ะœั‹ ะทะฝะฐะตะผ, ั‡ั‚ะพ ะฒัะต *ัะฝะดะฟะพะธะฝั‚ั‹* ะดะฐะฝะฝะพะณะพ ะผะพะดัƒะปั ะธะผะตัŽั‚ ะฝะตะบะพั‚ะพั€ั‹ะต ะพะฑั‰ะธะต ัะฒะพะนัั‚ะฒะฐ: +ะœั‹ ะทะฝะฐะตะผ, ั‡ั‚ะพ ะฒัะต *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ัั‚ะพะณะพ ะผะพะดัƒะปั ะธะผะตัŽั‚ ะพะดะธะฝะฐะบะพะฒั‹ะต: -* ะŸั€ะตั„ะธะบั ะฟัƒั‚ะธ: `/items`. -* ะขะตะณะธ: (ะพะดะธะฝ ะตะดะธะฝัั‚ะฒะตะฝะฝั‹ะน ั‚ะตะณ: `items`). -* ะ”ะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ะพั‚ะฒะตั‚ั‹ (responses) -* ะ—ะฐะฒะธัะธะผะพัั‚ะธ: ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต ัะพะทะดะฐะฝะฝะพะน ะฝะฐะผะธ ะทะฐะฒะธัะธะผะพัั‚ะธ `X-token` +* `prefix` ะฟัƒั‚ะธ: `/items`. +* `tags`: (ะพะดะธะฝ ะตะดะธะฝัั‚ะฒะตะฝะฝั‹ะน ั‚ะตะณ: `items`). +* ะ”ะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต `responses`. +* `dependencies`: ะฒัะตะผ ะธะผ ะฝัƒะถะฝะฐ ั‚ะฐ ะทะฐะฒะธัะธะผะพัั‚ัŒ `X-Token`, ะบะพั‚ะพั€ัƒัŽ ะผั‹ ัะพะทะดะฐะปะธ. -ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะฒะผะตัั‚ะพ ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะดะพะฑะฐะฒะปัั‚ัŒ ะฒัะต ัั‚ะธ ัะฒะพะนัั‚ะฒะฐ ะฒ ั„ัƒะฝะบั†ะธัŽ ะบะฐะถะดะพะณะพ ะพั‚ะดะตะปัŒะฝะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*, -ะผั‹ ะดะพะฑะฐะฒะธะผ ะธั… ะฒ `APIRouter`. +ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะฒะผะตัั‚ะพ ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะดะพะฑะฐะฒะปัั‚ัŒ ะฒัั‘ ัั‚ะพ ะฒ ะบะฐะถะดัƒัŽ *ะพะฟะตั€ะฐั†ะธัŽ ะฟัƒั‚ะธ*, ะผั‹ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ัั‚ะพ ะฒ `APIRouter`. {* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} -ะขะฐะบ ะบะฐะบ ะบะฐะถะดั‹ะน *ัะฝะดะฟะพะธะฝั‚* ะฝะฐั‡ะธะฝะฐะตั‚ัั ั ัะธะผะฒะพะปะฐ `/`: +ะขะฐะบ ะบะฐะบ ะฟัƒั‚ัŒ ะบะฐะถะดะพะน *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะดะพะปะถะตะฝ ะฝะฐั‡ะธะฝะฐั‚ัŒัั ั `/`, ะบะฐะบ ะทะดะตััŒ: ```Python hl_lines="1" @router.get("/{item_id}") @@ -162,73 +163,74 @@ async def read_item(item_id: str): ะ’ ะฝะฐัˆะตะผ ัะปัƒั‡ะฐะต ะฟั€ะตั„ะธะบัะพะผ ัะฒะปัะตั‚ัั `/items`. -ะœั‹ ั‚ะฐะบะถะต ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ะฒ ะฝะฐัˆ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ (router) ัะฟะธัะพะบ `ั‚ะตะณะพะฒ` (`tags`) ะธ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ั… `ะพั‚ะฒะตั‚ะพะฒ` (`responses`), ะบะพั‚ะพั€ั‹ะต ัะฒะปััŽั‚ัั ะพะฑั‰ะธะผะธ ะดะปั ะบะฐะถะดะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*. +ะœั‹ ั‚ะฐะบะถะต ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ัะฟะธัะพะบ `tags` ะธ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต `responses`, ะบะพั‚ะพั€ั‹ะต ะฑัƒะดัƒั‚ ะฟั€ะธะผะตะฝัั‚ัŒัั ะบะพ ะฒัะตะผ *ะพะฟะตั€ะฐั†ะธัะผ ะฟัƒั‚ะธ*, ะฒะบะปัŽั‡ั‘ะฝะฝั‹ะผ ะฒ ัั‚ะพั‚ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€. -ะ˜ ะตั‰ั‘ ะผั‹ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ะฒ ะฝะฐัˆ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ ัะฟะธัะพะบ `ะทะฐะฒะธัะธะผะพัั‚ะตะน`, ะบะพั‚ะพั€ั‹ะต ะดะพะปะถะฝั‹ ะฒั‹ะทั‹ะฒะฐั‚ัŒัั ะฟั€ะธ ะบะฐะถะดะพะผ ะพะฑั€ะฐั‰ะตะฝะธะธ ะบ *ัะฝะดะฟะพะธะฝั‚ะฐะผ*. +ะ˜ ะตั‰ั‘ ะผั‹ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ัะฟะธัะพะบ `dependencies`, ะบะพั‚ะพั€ั‹ะต ะฑัƒะดัƒั‚ ะดะพะฑะฐะฒะปะตะฝั‹ ะบะพ ะฒัะตะผ *ะพะฟะตั€ะฐั†ะธัะผ ะฟัƒั‚ะธ* ะฒ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะต ะธ ะฑัƒะดัƒั‚ ะฒั‹ะฟะพะปะฝัั‚ัŒัั/ั€ะฐะทั€ะตัˆะฐั‚ัŒัั ะดะปั ะบะฐะถะดะพะณะพ HTTP-ะทะฐะฟั€ะพัะฐ ะบ ะฝะธะผ. /// tip | ะŸะพะดัะบะฐะทะบะฐ -ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ ั‚ะฐะบะถะต, ะบะฐะบ ะธ ะฒ ัะปัƒั‡ะฐะต ั ะทะฐะฒะธัะธะผะพัั‚ัะผะธ ะฒ ะดะตะบะพั€ะฐั‚ะพั€ะฐั… *ัะฝะดะฟะพะธะฝั‚ะพะฒ* ([ะทะฐะฒะธัะธะผะพัั‚ะธ ะฒ ะดะตะบะพั€ะฐั‚ะพั€ะฐั… ะพะฟะตั€ะฐั†ะธะน ะฟัƒั‚ะธ](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), ะฝะธะบะฐะบะพะณะพ ะทะฝะฐั‡ะตะฝะธั ะฒ *ั„ัƒะฝะบั†ะธัŽ ัะฝะดะฟะพะธะฝั‚ะฐ* ะฟะตั€ะตะดะฐะฝะพ ะฝะต ะฑัƒะดะตั‚. +ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ ั‚ะฐะบ ะถะต, ะบะฐะบ ะธ ะฒ ัะปัƒั‡ะฐะต ั [ะทะฐะฒะธัะธะผะพัั‚ัะผะธ ะฒ ะดะตะบะพั€ะฐั‚ะพั€ะฐั… *ะพะฟะตั€ะฐั†ะธะน ะฟัƒั‚ะธ*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, ะฝะธะบะฐะบะพะต ะทะฝะฐั‡ะตะฝะธะต ะฝะต ะฑัƒะดะตั‚ ะฟะตั€ะตะดะฐะฝะพ ะฒ ะฒะฐัˆัƒ *ั„ัƒะฝะบั†ะธัŽ-ะพะฑั€ะฐะฑะพั‚ั‡ะธะบ ะฟัƒั‚ะธ*. /// -ะ’ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต ะผั‹ ะฟะพะปัƒั‡ะธะผ ัะปะตะดัƒัŽั‰ะธะต ัะฝะดะฟะพะธะฝั‚ั‹: +ะ’ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต ะฟัƒั‚ะธ ะดะปั items ั‚ะตะฟะตั€ัŒ ั‚ะฐะบะธะต: * `/items/` * `/items/{item_id}` ...ะบะฐะบ ะผั‹ ะธ ะฟะปะฐะฝะธั€ะพะฒะฐะปะธ. -* ะžะฝะธ ะฑัƒะดัƒั‚ ะฟะพะผะตั‡ะตะฝั‹ ั‚ะตะณะฐะผะธ ะธะท ะทะฐะดะฐะฝะฝะพะณะพ ัะฟะธัะบะฐ, ะฒ ะฝะฐัˆะตะผ ัะปัƒั‡ะฐะต ัั‚ะพ `"items"`. - * ะญั‚ะธ ั‚ะตะณะธ ะพัะพะฑะตะฝะฝะพ ะฟะพะปะตะทะฝั‹ ะดะปั ัะธัั‚ะตะผั‹ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะธะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ (ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ OpenAPI). -* ะšะฐะถะดั‹ะน ะธะท ะฝะธั… ะฑัƒะดะตั‚ ะฒะบะปัŽั‡ะฐั‚ัŒ ะฟั€ะตะดะพะฟั€ะตะดะตะปะตะฝะฝั‹ะต ะพั‚ะฒะตั‚ั‹ `responses`. -* ะšะฐะถะดั‹ะน *ัะฝะดะฟะพะธะฝั‚* ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ัะฟะธัะพะบ ะทะฐะฒะธัะธะผะพัั‚ะตะน (`dependencies`), ะธัะฟะพะปะฝัะตะผั‹ั… ะฟะตั€ะตะด ะฒั‹ะทะพะฒะพะผ *ัะฝะดะฟะพะธะฝั‚ะฐ*. - * ะ•ัะปะธ ะฒั‹ ะพะฟั€ะตะดะตะปะธะปะธ ะทะฐะฒะธัะธะผะพัั‚ะธ ะฒ ัะฐะผะพะน ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ, **ั‚ะพ ะพะฝะฐ ั‚ะฐะบะถะต ะฑัƒะดะตั‚ ะฒั‹ะฟะพะปะฝะตะฝะฐ**. - * ะกะฝะฐั‡ะฐะปะฐ ะฒั‹ะฟะพะปะฝััŽั‚ัั ะทะฐะฒะธัะธะผะพัั‚ะธ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ, ะทะฐั‚ะตะผ ะฒั‹ะทั‹ะฒะฐัŽั‚ัั [ะทะฐะฒะธัะธะผะพัั‚ะธ ะฒ ะดะตะบะพั€ะฐั‚ะพั€ะต](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, ะธ, ะฝะฐะบะพะฝะตั†, ะพะฑั‹ั‡ะฝั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ะธั‡ะตัะบะธะต ะทะฐะฒะธัะธะผะพัั‚ะธ. - * ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ [ะทะฐะฒะธัะธะผะพัั‚ะธ `Security` ั `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. +* ะžะฝะธ ะฑัƒะดัƒั‚ ะฟะพะผะตั‡ะตะฝั‹ ัะฟะธัะบะพะผ ั‚ะตะณะพะฒ, ัะพะดะตั€ะถะฐั‰ะธะผ ะพะดะฝัƒ ัั‚ั€ะพะบัƒ `"items"`. + * ะญั‚ะธ ยซั‚ะตะณะธยป ะพัะพะฑะตะฝะฝะพ ะฟะพะปะตะทะฝั‹ ะดะปั ัะธัั‚ะตะผ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะธะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ (ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ OpenAPI). +* ะ’ัะต ะพะฝะธ ะฑัƒะดัƒั‚ ะฒะบะปัŽั‡ะฐั‚ัŒ ะฟั€ะตะดะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ะต `responses`. +* ะ’ัะต ัั‚ะธ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะฑัƒะดัƒั‚ ะธะผะตั‚ัŒ ัะฟะธัะพะบ `dependencies`, ะฒั‹ั‡ะธัะปัะตะผั‹ั…/ะฒั‹ะฟะพะปะฝัะตะผั‹ั… ะฟะตั€ะตะด ะฝะธะผะธ. + * ะ•ัะปะธ ะฒั‹ ั‚ะฐะบะถะต ะพะฑัŠัะฒะธั‚ะต ะทะฐะฒะธัะธะผะพัั‚ะธ ะฒ ะบะพะฝะบั€ะตั‚ะฝะพะน *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*, **ะพะฝะธ ั‚ะพะถะต ะฑัƒะดัƒั‚ ะฒั‹ะฟะพะปะฝะตะฝั‹**. + * ะกะฝะฐั‡ะฐะปะฐ ะฒั‹ะฟะพะปะฝััŽั‚ัั ะทะฐะฒะธัะธะผะพัั‚ะธ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ, ะทะฐั‚ะตะผ [`dependencies` ะฒ ะดะตะบะพั€ะฐั‚ะพั€ะต](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, ะธ ะทะฐั‚ะตะผ ะพะฑั‹ั‡ะฝั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ะธั‡ะตัะบะธะต ะทะฐะฒะธัะธะผะพัั‚ะธ. + * ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ [`Security`-ะทะฐะฒะธัะธะผะพัั‚ะธ ั `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. /// tip | ะŸะพะดัะบะฐะทะบะฐ -ะะฐะฟั€ะธะผะตั€, ั ะฟะพะผะพั‰ัŒัŽ ะทะฐะฒะธัะธะผะพัั‚ะตะน ะฒ `APIRouter` ะผั‹ ะผะพะถะตะผ ะฟะพั‚ั€ะตะฑะพะฒะฐั‚ัŒ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ ะดะปั ะดะพัั‚ัƒะฟะฐ ะบะพ ะฒัะตะน ะณั€ัƒะฟะฟะต *ัะฝะดะฟะพะธะฝั‚ะพะฒ*, ะฝะต ัƒะบะฐะทั‹ะฒะฐั ะทะฐะฒะธัะธะผะพัั‚ะธ ะดะปั ะบะฐะถะดะพะน ะพั‚ะดะตะปัŒะฝะพะน ั„ัƒะฝะบั†ะธะธ *ัะฝะดะฟะพะธะฝั‚ะฐ*. +ะะฐะฟั€ะธะผะตั€, ั ะฟะพะผะพั‰ัŒัŽ ะทะฐะฒะธัะธะผะพัั‚ะตะน ะฒ `APIRouter` ะผั‹ ะผะพะถะตะผ ะฟะพั‚ั€ะตะฑะพะฒะฐั‚ัŒ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ ะดะปั ะดะพัั‚ัƒะฟะฐ ะบะพ ะฒัะตะน ะณั€ัƒะฟะฟะต *ะพะฟะตั€ะฐั†ะธะน ะฟัƒั‚ะธ*. ะ”ะฐะถะต ะตัะปะธ ะทะฐะฒะธัะธะผะพัั‚ะธ ะฝะต ะดะพะฑะฐะฒะปััŽั‚ัั ะฟะพ ะพั‚ะดะตะปัŒะฝะพัั‚ะธ ะบ ะบะฐะถะดะพะน ะธะท ะฝะธั…. /// /// check | ะ—ะฐะผะตั‚ะบะฐ -ะŸะฐั€ะฐะผะตั‚ั€ั‹ `prefix`, `tags`, `responses` ะธ `dependencies` ะพั‚ะฝะพััั‚ัั ะบ ั„ัƒะฝะบั†ะธะพะฝะฐะปัƒ **FastAPI**, ะฟะพะผะพะณะฐัŽั‰ะตะผัƒ ะธะทะฑะตะถะฐั‚ัŒ ะดัƒะฑะปะธั€ะพะฒะฐะฝะธั ะบะพะดะฐ. +ะŸะฐั€ะฐะผะตั‚ั€ั‹ `prefix`, `tags`, `responses` ะธ `dependencies` โ€” ัั‚ะพ (ะบะฐะบ ะธ ะฒะพ ะผะฝะพะณะธั… ะดั€ัƒะณะธั… ัะปัƒั‡ะฐัั…) ะฟั€ะพัั‚ะพ ะฒะพะทะผะพะถะฝะพัั‚ัŒ **FastAPI**, ะฟะพะผะพะณะฐัŽั‰ะฐั ะธะทะฑะตะถะฐั‚ัŒ ะดัƒะฑะปะธั€ะพะฒะฐะฝะธั ะบะพะดะฐ. /// ### ะ˜ะผะฟะพั€ั‚ ะทะฐะฒะธัะธะผะพัั‚ะตะน { #import-the-dependencies } -ะะฐัˆ ะบะพะด ะฝะฐั…ะพะดะธั‚ัั ะฒ ะผะพะดัƒะปะต `app.routers.items` (ั„ะฐะนะป `app/routers/items.py`). +ะญั‚ะพั‚ ะบะพะด ะฝะฐั…ะพะดะธั‚ัั ะฒ ะผะพะดัƒะปะต `app.routers.items`, ะฒ ั„ะฐะนะปะต `app/routers/items.py`. -ะ˜ ะฝะฐะผ ะฝัƒะถะฝะพ ะฒั‹ะทะฒะฐั‚ัŒ ั„ัƒะฝะบั†ะธัŽ ะทะฐะฒะธัะธะผะพัั‚ะธ ะธะท ะผะพะดัƒะปั `app.dependencies` (ั„ะฐะนะป `app/dependencies.py`). +ะ˜ ะฝะฐะผ ะฝัƒะถะฝะพ ะฟะพะปัƒั‡ะธั‚ัŒ ั„ัƒะฝะบั†ะธัŽ ะทะฐะฒะธัะธะผะพัั‚ะธ ะธะท ะผะพะดัƒะปั `app.dependencies`, ั„ะฐะนะปะฐ `app/dependencies.py`. -ะœั‹ ะธัะฟะพะปัŒะทัƒะตะผ ะพะฟะตั€ะฐั†ะธัŽ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพะณะพ ะธะผะฟะพั€ั‚ะฐ `..` ะดะปั ะธะผะฟะพั€ั‚ะฐ ะทะฐะฒะธัะธะผะพัั‚ะธ: +ะŸะพัั‚ะพะผัƒ ะผั‹ ะธัะฟะพะปัŒะทัƒะตะผ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝั‹ะน ะธะผะฟะพั€ั‚ ั `..` ะดะปั ะทะฐะฒะธัะธะผะพัั‚ะตะน: {* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} -#### ะšะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝั‹ะน ะธะผะฟะพั€ั‚? { #how-relative-imports-work } +#### ะšะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝั‹ะน ะธะผะฟะพั€ั‚ { #how-relative-imports-work } /// tip | ะŸะพะดัะบะฐะทะบะฐ -ะ•ัะปะธ ะฒั‹ ะฟั€ะตะบั€ะฐัะฝะพ ะทะฝะฐะตั‚ะต, ะบะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะธะผะฟะพั€ั‚ ะฒ Python, ั‚ะพ ะฟะตั€ะตั…ะพะดะธั‚ะต ะบ ัะปะตะดัƒัŽั‰ะตะผัƒ ั€ะฐะทะดะตะปัƒ. +ะ•ัะปะธ ะฒั‹ ะฟั€ะตะบั€ะฐัะฝะพ ะทะฝะฐะตั‚ะต, ะบะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะธะผะฟะพั€ั‚, ะฟะตั€ะตั…ะพะดะธั‚ะต ะบ ัะปะตะดัƒัŽั‰ะตะผัƒ ั€ะฐะทะดะตะปัƒ ะฝะธะถะต. /// -ะžะดะฝะฐ ั‚ะพั‡ะบะฐ `.`, ะบะฐะบ ะฒ ะดะฐะฝะฝะพะผ ะฟั€ะธะผะตั€ะต: +ะžะดะฝะฐ ั‚ะพั‡ะบะฐ `.`, ะบะฐะบ ะทะดะตััŒ: ```Python from .dependencies import get_token_header ``` + ะพะทะฝะฐั‡ะฐะตั‚: -* ะะฐั‡ะฝะธั‚ะต ั ะฟะฐะบะตั‚ะฐ, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ะดะฐะฝะฝั‹ะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/routers/items.py` ั€ะฐัะฟะพะปะพะถะตะฝ ะฒ ะบะฐั‚ะฐะปะพะณะต `app/routers/`)... -* ... ะฝะฐะนะดะธั‚ะต ะผะพะดัƒะปัŒ `dependencies` (ั„ะฐะนะป `app/routers/dependencies.py`)... -* ... ะธ ะธะผะฟะพั€ั‚ะธั€ัƒะนั‚ะต ะธะท ะฝะตะณะพ ั„ัƒะฝะบั†ะธัŽ `get_token_header`. +* ะะฐั‡ะฐั‚ัŒ ะฒ ั‚ะพะผ ะถะต ะฟะฐะบะตั‚ะต, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ัั‚ะพั‚ ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/routers/items.py`) (ะบะฐั‚ะฐะปะพะณ `app/routers/`)... +* ะฝะฐะนั‚ะธ ะผะพะดัƒะปัŒ `dependencies` (ะฒะพะพะฑั€ะฐะถะฐะตะผั‹ะน ั„ะฐะนะป `app/routers/dependencies.py`)... +* ะธ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธะท ะฝะตะณะพ ั„ัƒะฝะบั†ะธัŽ `get_token_header`. -ะš ัะพะถะฐะปะตะฝะธัŽ, ั‚ะฐะบะพะณะพ ั„ะฐะนะปะฐ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚, ะธ ะฝะฐัˆะธ ะทะฐะฒะธัะธะผะพัั‚ะธ ะฝะฐั…ะพะดัั‚ัั ะฒ ั„ะฐะนะปะต `app/dependencies.py`. +ะะพ ั‚ะฐะบะพะณะพ ั„ะฐะนะปะฐ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚, ะฝะฐัˆะธ ะทะฐะฒะธัะธะผะพัั‚ะธ ะฝะฐั…ะพะดัั‚ัั ะฒ ั„ะฐะนะปะต `app/dependencies.py`. ะ’ัะฟะพะผะฝะธั‚ะต, ะบะฐะบ ะฒั‹ะณะปัะดะธั‚ ั„ะฐะนะปะพะฒะฐั ัั‚ั€ัƒะบั‚ัƒั€ะฐ ะฝะฐัˆะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั: @@ -236,7 +238,7 @@ from .dependencies import get_token_header --- -ะ”ะฒะต ั‚ะพั‡ะบะธ `..`, ะบะฐะบ ะฒ ะดะฐะฝะฝะพะผ ะฟั€ะธะผะตั€ะต: +ะ”ะฒะต ั‚ะพั‡ะบะธ `..`, ะบะฐะบ ะทะดะตััŒ: ```Python from ..dependencies import get_token_header @@ -244,12 +246,12 @@ from ..dependencies import get_token_header ะพะทะฝะฐั‡ะฐัŽั‚: -* ะะฐั‡ะฝะธั‚ะต ั ะฟะฐะบะตั‚ะฐ, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ะดะฐะฝะฝั‹ะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/routers/items.py` ะฝะฐั…ะพะดะธั‚ัั ะฒ ะบะฐั‚ะฐะปะพะณะต `app/routers/`)... -* ... ะฟะตั€ะตะนะดะธั‚ะต ะฒ ั€ะพะดะธั‚ะตะปัŒัะบะธะน ะฟะฐะบะตั‚ (ะบะฐั‚ะฐะปะพะณ `app/`)... -* ... ะฝะฐะนะดะธั‚ะต ะฒ ะฝั‘ะผ ะผะพะดัƒะปัŒ `dependencies` (ั„ะฐะนะป `app/dependencies.py`)... -* ... ะธ ะธะผะฟะพั€ั‚ะธั€ัƒะนั‚ะต ะธะท ะฝะตะณะพ ั„ัƒะฝะบั†ะธัŽ `get_token_header`. +* ะะฐั‡ะฐั‚ัŒ ะฒ ั‚ะพะผ ะถะต ะฟะฐะบะตั‚ะต, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ัั‚ะพั‚ ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/routers/items.py`) (ะบะฐั‚ะฐะปะพะณ `app/routers/`)... +* ะฟะตั€ะตะนั‚ะธ ะฒ ั€ะพะดะธั‚ะตะปัŒัะบะธะน ะฟะฐะบะตั‚ (ะบะฐั‚ะฐะปะพะณ `app/`)... +* ะธ ั‚ะฐะผ ะฝะฐะนั‚ะธ ะผะพะดัƒะปัŒ `dependencies` (ั„ะฐะนะป `app/dependencies.py`)... +* ะธ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธะท ะฝะตะณะพ ั„ัƒะฝะบั†ะธัŽ `get_token_header`. -ะญั‚ะพ ั€ะฐะฑะพั‚ะฐะตั‚ ะฒะตั€ะฝะพ! ๐ŸŽ‰ +ะญั‚ะพ ั€ะฐะฑะพั‚ะฐะตั‚ ะบะพั€ั€ะตะบั‚ะฝะพ! ๐ŸŽ‰ --- @@ -261,29 +263,29 @@ from ...dependencies import get_token_header ั‚ะพ ัั‚ะพ ะฑั‹ ะพะทะฝะฐั‡ะฐะปะพ: -* ะะฐั‡ะฝะธั‚ะต ั ะฟะฐะบะตั‚ะฐ, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ะดะฐะฝะฝั‹ะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/routers/items.py` ะฝะฐั…ะพะดะธั‚ัั ะฒ ะบะฐั‚ะฐะปะพะณะต `app/routers/`)... -* ... ะฟะตั€ะตะนะดะธั‚ะต ะฒ ั€ะพะดะธั‚ะตะปัŒัะบะธะน ะฟะฐะบะตั‚ (ะบะฐั‚ะฐะปะพะณ `app/`)... -* ... ะทะฐั‚ะตะผ ะฟะตั€ะตะนะดะธั‚ะต ะฒ ั€ะพะดะธั‚ะตะปัŒัะบะธะน ะฟะฐะบะตั‚ ั‚ะตะบัƒั‰ะตะณะพ ะฟะฐะบะตั‚ะฐ (ั‚ะฐะบะพะณะพ ะฟะฐะบะตั‚ะฐ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚, `app` ะฝะฐั…ะพะดะธั‚ัั ะฝะฐ ัะฐะผะพะผ ะฒะตั€ั…ะฝะตะผ ัƒั€ะพะฒะฝะต ๐Ÿ˜ฑ)... -* ... ะฝะฐะนะดะธั‚ะต ะฒ ะฝั‘ะผ ะผะพะดัƒะปัŒ `dependencies` (ั„ะฐะนะป `app/dependencies.py`)... -* ... ะธ ะธะผะฟะพั€ั‚ะธั€ัƒะนั‚ะต ะธะท ะฝะตะณะพ ั„ัƒะฝะบั†ะธัŽ `get_token_header`. +* ะะฐั‡ะฐั‚ัŒ ะฒ ั‚ะพะผ ะถะต ะฟะฐะบะตั‚ะต, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ัั‚ะพั‚ ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/routers/items.py`) ั€ะฐัะฟะพะปะพะถะตะฝ ะฒ (ะบะฐั‚ะฐะปะพะณะต `app/routers/`)... +* ะฟะตั€ะตะนั‚ะธ ะฒ ั€ะพะดะธั‚ะตะปัŒัะบะธะน ะฟะฐะบะตั‚ (ะบะฐั‚ะฐะปะพะณ `app/`)... +* ะทะฐั‚ะตะผ ะฟะตั€ะตะนั‚ะธ ะฒ ั€ะพะดะธั‚ะตะปัŒัะบะธะน ะฟะฐะบะตั‚ ัั‚ะพะณะพ ะฟะฐะบะตั‚ะฐ (ั€ะพะดะธั‚ะตะปัŒัะบะพะณะพ ะฟะฐะบะตั‚ะฐ ะฝะตั‚, `app` โ€” ะฒะตั€ั…ะฝะธะน ัƒั€ะพะฒะตะฝัŒ ๐Ÿ˜ฑ)... +* ะธ ั‚ะฐะผ ะฝะฐะนั‚ะธ ะผะพะดัƒะปัŒ `dependencies` (ั„ะฐะนะป `app/dependencies.py`)... +* ะธ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธะท ะฝะตะณะพ ั„ัƒะฝะบั†ะธัŽ `get_token_header`. -ะญั‚ะพ ะฑัƒะดะตั‚ ะพั‚ะฝะพัะธั‚ัŒัั ะบ ะฝะตะบะพั‚ะพั€ะพะผัƒ ะฟะฐะบะตั‚ัƒ, ะฝะฐั…ะพะดัั‰ะตะผัƒัั ะฝะฐ ะพะดะธะฝ ัƒั€ะพะฒะตะฝัŒ ะฒั‹ัˆะต ั‡ะตะผ `app/` ะธ ัะพะดะตั€ะถะฐั‰ะตะผัƒ ัะฒะพะน ัะพะฑัั‚ะฒะตะฝะฝั‹ะน ั„ะฐะนะป `__init__.py`. ะะพ ะฝะธั‡ะตะณะพ ั‚ะฐะบะพะณะพ ัƒ ะฝะฐั ะฝะตั‚. ะŸะพัั‚ะพะผัƒ ัั‚ะพ ะฟั€ะธะฒะตะดะตั‚ ะบ ะพัˆะธะฑะบะต ะฒ ะฝะฐัˆะตะผ ะฟั€ะธะผะตั€ะต. ๐Ÿšจ +ะญั‚ะพ ััั‹ะปะฐะปะพััŒ ะฑั‹ ะฝะฐ ะบะฐะบะพะน-ั‚ะพ ะฟะฐะบะตั‚ ะฒั‹ัˆะต `app/`, ัะพ ัะฒะพะธะผ ั„ะฐะนะปะพะผ `__init__.py` ะธ ั‚.ะฟ. ะะพ ัƒ ะฝะฐั ั‚ะฐะบะพะณะพ ะฝะตั‚. ะŸะพัั‚ะพะผัƒ ัั‚ะพ ะฒั‹ะทะฒะฐะปะพ ะฑั‹ ะพัˆะธะฑะบัƒ ะฒ ะฝะฐัˆะตะผ ะฟั€ะธะผะตั€ะต. ๐Ÿšจ -ะขะตะฟะตั€ัŒ ะฒั‹ ะทะฝะฐะตั‚ะต, ะบะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะธะผะฟะพั€ั‚ ะฒ Python, ะธ ัะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพะต ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะฝะธะต ะฒ ัะฒะพะธั… ัะพะฑัั‚ะฒะตะฝะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธัั… ะปัŽะฑะพะณะพ ัƒั€ะพะฒะฝั ัะปะพะถะฝะพัั‚ะธ. ๐Ÿค“ +ะะพ ั‚ะตะฟะตั€ัŒ ะฒั‹ ะทะฝะฐะตั‚ะต, ะบะฐะบ ัั‚ะพ ั€ะฐะฑะพั‚ะฐะตั‚, ั‚ะฐะบ ั‡ั‚ะพ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝั‹ะต ะธะผะฟะพั€ั‚ั‹ ะฒ ัะฒะพะธั… ะฟั€ะธะปะพะถะตะฝะธัั…, ะฝะตะทะฐะฒะธัะธะผะพ ะพั‚ ั‚ะพะณะพ, ะฝะฐัะบะพะปัŒะบะพ ะพะฝะธ ัะปะพะถะฝั‹ะต. ๐Ÿค“ -### ะ”ะพะฑะฐะฒะปะตะฝะธะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธั… ั‚ะตะณะพะฒ (`tags`), ะพั‚ะฒะตั‚ะพะฒ (`responses`) ะธ ะทะฐะฒะธัะธะผะพัั‚ะตะน (`dependencies`) { #add-some-custom-tags-responses-and-dependencies } +### ะ”ะพะฑะฐะฒะปะตะฝะธะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธั… `tags`, `responses` ะธ `dependencies` { #add-some-custom-tags-responses-and-dependencies } -ะœั‹ ะฝะต ะฑัƒะดะตะผ ะดะพะฑะฐะฒะปัั‚ัŒ ะฟั€ะตั„ะธะบั `/items` ะธ ัะฟะธัะพะบ ั‚ะตะณะพะฒ `tags=["items"]` ะดะปั ะบะฐะถะดะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*, ั‚.ะบ. ะผั‹ ัƒะถะต ะธั… ะดะพะฑะฐะฒะธะปะธ ั ะฟะพะผะพั‰ัŒัŽ `APIRouter`. +ะœั‹ ะฝะต ะดะพะฑะฐะฒะปัะตะผ ะฟั€ะตั„ะธะบั `/items` ะธ `tags=["items"]` ะบ ะบะฐะถะดะพะน *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*, ะฟะพั‚ะพะผัƒ ั‡ั‚ะพ ะผั‹ ะดะพะฑะฐะฒะธะปะธ ะธั… ะฒ `APIRouter`. -ะะพ ะฟะพะผะธะผะพ ัั‚ะพะณะพ ะผั‹ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ะฝะพะฒั‹ะต ั‚ะตะณะธ ะดะปั ะบะฐะถะดะพะณะพ ะพั‚ะดะตะปัŒะฝะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*, ะฐ ั‚ะฐะบะถะต ะฝะตะบะพั‚ะพั€ั‹ะต ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ะพั‚ะฒะตั‚ั‹ (`responses`), ั…ะฐั€ะฐะบั‚ะตั€ะฝั‹ะต ะดะปั ะดะฐะฝะฝะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*: +ะะพ ะผั‹ ะฒัั‘ ั€ะฐะฒะฝะพ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ _ะตั‰ั‘_ `tags`, ะบะพั‚ะพั€ั‹ะต ะฑัƒะดัƒั‚ ะฟั€ะธะผะตะฝัั‚ัŒัั ะบ ะบะพะฝะบั€ะตั‚ะฝะพะน *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*, ะฐ ั‚ะฐะบะถะต ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต `responses`, ัะฟะตั†ะธั„ะธั‡ะฝั‹ะต ะดะปั ัั‚ะพะน *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*: {* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | ะŸะพะดัะบะฐะทะบะฐ -ะŸะพัะปะตะดะฝะธะน *ัะฝะดะฟะพะธะฝั‚* ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฑะธะฝะฐั†ะธัŽ ั‚ะตะณะพะฒ: `["items", "custom"]`. +ะญั‚ะฐ ะฟะพัะปะตะดะฝัั ะพะฟะตั€ะฐั†ะธั ะฟัƒั‚ะธ ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ะบะพะผะฑะธะฝะฐั†ะธัŽ ั‚ะตะณะพะฒ: `["items", "custom"]`. -ะ ั‚ะฐะบะถะต ะฒ ะตะณะพ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ ะฑัƒะดัƒั‚ ัะพะดะตั€ะถะฐั‚ัŒัั ะพะฑะฐ ะพั‚ะฒะตั‚ะฐ: ะพะดะธะฝ ะดะปั `404` ะธ ะดั€ัƒะณะพะน ะดะปั `403`. +ะ˜ ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ ัƒ ะฝะตั‘ ะฑัƒะดัƒั‚ ะพะฑะฐ ะพั‚ะฒะตั‚ะฐ: ะพะดะธะฝ ะดะปั `404` ะธ ะพะดะธะฝ ะดะปั `403`. /// @@ -293,29 +295,29 @@ from ...dependencies import get_token_header ะ˜ะผะตะฝะฝะพ ััŽะดะฐ ะฒั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตั‚ะต ะธ ะธะผะตะฝะฝะพ ะทะดะตััŒ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต ะบะปะฐัั `FastAPI`. -ะญั‚ะพ ะพัะฝะพะฒะฝะพะน ั„ะฐะนะป ะฒะฐัˆะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั, ะบะพั‚ะพั€ั‹ะน ะพะฑัŠะตะดะธะฝัะตั‚ ะฒัั‘ ะฒ ะพะดะฝะพ ั†ะตะปะพะต. +ะญั‚ะพ ะพัะฝะพะฒะฝะพะน ั„ะฐะนะป ะฒะฐัˆะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั, ะบะพั‚ะพั€ั‹ะน ัะฒัะทั‹ะฒะฐะตั‚ ะฒัั‘ ะฒะพะตะดะธะฝะพ. -ะ˜ ั‚ะตะฟะตั€ัŒ, ะบะพะณะดะฐ ะฑะพะปัŒัˆะฐั ั‡ะฐัั‚ัŒ ะปะพะณะธะบะธ ะฟั€ะธะปะพะถะตะฝะธั ั€ะฐะทะดะตะปะตะฝะฐ ะฝะฐ ะพั‚ะดะตะปัŒะฝั‹ะต ะผะพะดัƒะปะธ, ะพัะฝะพะฒะฝะพะน ั„ะฐะนะป `app/main.py` ะฑัƒะดะตั‚ ะดะพัั‚ะฐั‚ะพั‡ะฝะพ ะฟั€ะพัั‚ั‹ะผ. +ะ˜ ั‚ะฐะบ ะบะฐะบ ะฑะพะปัŒัˆะฐั ั‡ะฐัั‚ัŒ ะฒะฐัˆะตะน ะปะพะณะธะบะธ ั‚ะตะฟะตั€ัŒ ะฑัƒะดะตั‚ ะฝะฐั…ะพะดะธั‚ัŒัั ะฒ ะพั‚ะดะตะปัŒะฝั‹ั… ัะฟะตั†ะธั„ะธั‡ะฝั‹ั… ะผะพะดัƒะปัั…, ะพัะฝะพะฒะฝะพะน ั„ะฐะนะป ะฑัƒะดะตั‚ ะดะพะฒะพะปัŒะฝะพ ะฟั€ะพัั‚ั‹ะผ. ### ะ˜ะผะฟะพั€ั‚ `FastAPI` { #import-fastapi } -ะ’ั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตั‚ะต ะธ ัะพะทะดะฐะตั‚ะต ะบะปะฐัั `FastAPI` ะบะฐะบ ะพะฑั‹ั‡ะฝะพ. +ะ’ั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตั‚ะต ะธ ัะพะทะดะฐั‘ั‚ะต ะบะปะฐัั `FastAPI` ะบะฐะบ ะพะฑั‹ั‡ะฝะพ. -ะœั‹ ะดะฐะถะต ะผะพะถะตะผ ะพะฑัŠัะฒะธั‚ัŒ [ะณะปะพะฑะฐะปัŒะฝั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ](dependencies/global-dependencies.md){.internal-link target=_blank}, ะบะพั‚ะพั€ั‹ะต ะฑัƒะดัƒั‚ ะพะฑัŠะตะดะธะฝะตะฝั‹ ั ะทะฐะฒะธัะธะผะพัั‚ัะผะธ ะดะปั ะบะฐะถะดะพะณะพ ะพั‚ะดะตะปัŒะฝะพะณะพ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ: +ะ˜ ะผั‹ ะดะฐะถะต ะผะพะถะตะผ ะพะฑัŠัะฒะธั‚ัŒ [ะณะปะพะฑะฐะปัŒะฝั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ](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"] *} ### ะ˜ะผะฟะพั€ั‚ `APIRouter` { #import-the-apirouter } -ะขะตะฟะตั€ัŒ ะผั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตะผ ะดั€ัƒะณะธะต ััƒะฑ-ะผะพะดัƒะปะธ, ัะพะดะตั€ะถะฐั‰ะธะต `APIRouter`: +ะขะตะฟะตั€ัŒ ะผั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตะผ ะดั€ัƒะณะธะต ะฟะพะดะผะพะดัƒะปะธ, ัะพะดะตั€ะถะฐั‰ะธะต `APIRouter`: {* ../../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`, ั‚ะพ ะผั‹ ัะผะพะถะตะผ ะธั… ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ, ะฒะพัะฟะพะปัŒะทะพะฒะฐะฒัˆะธััŒ ะพะฟะตั€ะฐั†ะธะตะน ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพะณะพ ะธะผะฟะพั€ั‚ะฐ `.`. +ะขะฐะบ ะบะฐะบ ั„ะฐะนะปั‹ `app/routers/users.py` ะธ `app/routers/items.py` ัะฒะปััŽั‚ัั ะฟะพะดะผะพะดัƒะปัะผะธ, ะฒั…ะพะดัั‰ะธะผะธ ะฒ ะพะดะธะฝ ะธ ั‚ะพั‚ ะถะต Python-ะฟะฐะบะตั‚ `app`, ะผั‹ ะผะพะถะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะดะฝัƒ ั‚ะพั‡ะบัƒ `.` ะดะปั ะธะผะฟะพั€ั‚ะฐ ั‡ะตั€ะตะท ยซะพั‚ะฝะพัะธั‚ะตะปัŒะฝั‹ะต ะธะผะฟะพั€ั‚ั‹ยป. -### ะšะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะธะผะฟะพั€ั‚? { #how-the-importing-works } +### ะšะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะธะผะฟะพั€ั‚ { #how-the-importing-works } -ะ”ะฐะฝะฝะฐั ัั‚ั€ะพะบะฐ ะบะพะดะฐ: +ะญั‚ะพั‚ ั„ั€ะฐะณะผะตะฝั‚: ```Python from .routers import items, users @@ -323,15 +325,15 @@ from .routers import items, users ะพะทะฝะฐั‡ะฐะตั‚: -* ะะฐั‡ะฝะธั‚ะต ั ะฟะฐะบะตั‚ะฐ, ะฒ ะบะพั‚ะพั€ะพะผ ัะพะดะตั€ะถะธั‚ัั ะดะฐะฝะฝั‹ะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/main.py` ัะพะดะตั€ะถะธั‚ัั ะฒ ะบะฐั‚ะฐะปะพะณะต `app/`)... -* ... ะฝะฐะนะดะธั‚ะต ััƒะฑ-ะฟะฐะบะตั‚ `routers` (ะบะฐั‚ะฐะปะพะณ `app/routers/`)... -* ... ะธ ะธะท ะฝะตะณะพ ะธะผะฟะพั€ั‚ะธั€ัƒะนั‚ะต ััƒะฑ-ะผะพะดัƒะปะธ `items` (ั„ะฐะนะป `app/routers/items.py`) ะธ `users` (ั„ะฐะนะป `app/routers/users.py`)... +* ะะฐั‡ะฐั‚ัŒ ะฒ ั‚ะพะผ ะถะต ะฟะฐะบะตั‚ะต, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ัั‚ะพั‚ ะผะพะดัƒะปัŒ (ั„ะฐะนะป `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`. +ะ’ ะผะพะดัƒะปะต `items` ะฑัƒะดะตั‚ ะฟะตั€ะตะผะตะฝะฝะฐั `router` (`items.router`). ะญั‚ะพ ั‚ะฐ ะถะต ัะฐะผะฐั, ะบะพั‚ะพั€ัƒัŽ ะผั‹ ัะพะทะดะฐะปะธ ะฒ ั„ะฐะนะปะต `app/routers/items.py`, ัั‚ะพ ะพะฑัŠะตะบั‚ `APIRouter`. -ะ˜ ะทะฐั‚ะตะผ ะผั‹ ัะดะตะปะฐะตะผ ั‚ะพ ะถะต ัะฐะผะพะต ะดะปั ะผะพะดัƒะปั `users`. +ะ˜ ะทะฐั‚ะตะผ ะผั‹ ะดะตะปะฐะตะผ ั‚ะพ ะถะต ัะฐะผะพะต ะดะปั ะผะพะดัƒะปั `users`. -ะœั‹ ั‚ะฐะบะถะต ะผะพะณะปะธ ะฑั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธ ะดั€ัƒะณะธะผ ะผะตั‚ะพะดะพะผ: +ะœั‹ ั‚ะฐะบะถะต ะผะพะณะปะธ ะฑั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธั… ั‚ะฐะบ: ```Python from app.routers import items, users @@ -339,44 +341,44 @@ from app.routers import items, users /// info | ะŸั€ะธะผะตั‡ะฐะฝะธะต -ะŸะตั€ะฒะฐั ะฒะตั€ัะธั ัะฒะปัะตั‚ัั ะฟั€ะธะผะตั€ะพะผ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพะณะพ ะธะผะฟะพั€ั‚ะฐ: +ะŸะตั€ะฒะฐั ะฒะตั€ัะธั โ€” ัั‚ะพ ยซะพั‚ะฝะพัะธั‚ะตะปัŒะฝั‹ะน ะธะผะฟะพั€ั‚ยป: ```Python from .routers import items, users ``` -ะ’ั‚ะพั€ะฐั ะฒะตั€ัะธั ัะฒะปัะตั‚ัั ะฟั€ะธะผะตั€ะพะผ ะฐะฑัะพะปัŽั‚ะฝะพะณะพ ะธะผะฟะพั€ั‚ะฐ: +ะ’ั‚ะพั€ะฐั ะฒะตั€ัะธั โ€” ัั‚ะพ ยซะฐะฑัะพะปัŽั‚ะฝั‹ะน ะธะผะฟะพั€ั‚ยป: ```Python from app.routers import items, users ``` -ะฃะทะฝะฐั‚ัŒ ะฑะพะปัŒัˆะต ะพ ะฟะฐะบะตั‚ะฐั… ะธ ะผะพะดัƒะปัั… ะฒ Python ะฒั‹ ะผะพะถะตั‚ะต ะธะท <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">ะพั„ะธั†ะธะฐะปัŒะฝะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ Python ะพ ะผะพะดัƒะปัั…</a> +ะงั‚ะพะฑั‹ ัƒะทะฝะฐั‚ัŒ ะฑะพะปัŒัˆะต ะพ Python-ะฟะฐะบะตั‚ะฐั… ะธ ะผะพะดัƒะปัั…, ะฟั€ะพั‡ะธั‚ะฐะนั‚ะต <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">ะพั„ะธั†ะธะฐะปัŒะฝัƒัŽ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ Python ะพ ะผะพะดัƒะปัั…</a>. /// -### ะ˜ะทะฑะตะณะฐะนั‚ะต ะบะพะฝั„ะปะธะบั‚ะพะฒ ะธะผะตะฝ { #avoid-name-collisions } +### ะ˜ะทะฑะตะณะฐะนั‚ะต ะบะพะฝั„ะปะธะบั‚ะพะฒ ะธะผั‘ะฝ { #avoid-name-collisions } -ะ’ะผะตัั‚ะพ ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฟะตั€ะตะผะตะฝะฝัƒัŽ `router`, ะผั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตะผ ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ััƒะฑ-ะผะพะดัƒะปัŒ `items`. +ะœั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตะผ ะฟะพะดะผะพะดัƒะปัŒ `items` ะฝะฐะฟั€ัะผัƒัŽ, ะฒะผะตัั‚ะพ ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะตะณะพ ะฟะตั€ะตะผะตะฝะฝัƒัŽ `router`. -ะœั‹ ะดะตะปะฐะตะผ ัั‚ะพ ะฟะพั‚ะพะผัƒ, ั‡ั‚ะพ ัƒ ะฝะฐั ะตัั‚ัŒ ะตั‰ั‘ ะพะดะฝะฐ ะฟะตั€ะตะผะตะฝะฝะฐั `router` ะฒ ััƒะฑ-ะผะพะดัƒะปะต `users`. +ะญั‚ะพ ะฟะพั‚ะพะผัƒ, ั‡ั‚ะพ ัƒ ะฝะฐั ั‚ะฐะบะถะต ะตัั‚ัŒ ะดั€ัƒะณะฐั ะฟะตั€ะตะผะตะฝะฝะฐั ั ะธะผะตะฝะตะผ `router` ะฒ ะฟะพะดะผะพะดัƒะปะต `users`. -ะ•ัะปะธ ะฑั‹ ะผั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะปะธ ะธั… ะพะดะฝัƒ ะทะฐ ะดั€ัƒะณะพะน, ะบะฐะบ ะฟะพะบะฐะทะฐะฝะพ ะฒ ะฟั€ะธะผะตั€ะต: +ะ•ัะปะธ ะฑั‹ ะผั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะปะธ ะธั… ะพะดะฝัƒ ะทะฐ ะดั€ัƒะณะพะน, ะบะฐะบ ะทะดะตััŒ: ```Python from .routers.items import router from .routers.users import router ``` -ั‚ะพ ะฟะตั€ะตะผะตะฝะฝะฐั `router` ะธะท `users` ะฟะตั€ะตะฟะธัะฐะป ะฑั‹ ะฟะตั€ะตะผะตะฝะฝัƒัŽ `router` ะธะท `items`, ะธ ัƒ ะฝะฐั ะฝะต ะฑั‹ะปะพ ะฑั‹ ะฒะพะทะผะพะถะฝะพัั‚ะธ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธั… ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพ. +ั‚ะพ `router` ะธะท `users` ะฟะตั€ะตะทะฐะฟะธัะฐะป ะฑั‹ `router` ะธะท `items`, ะธ ะผั‹ ะฝะต ัะผะพะณะปะธ ะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธั… ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพ. -ะŸะพัั‚ะพะผัƒ, ะดะปั ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะฑะต ัั‚ะธ ะฟะตั€ะตะผะตะฝะฝั‹ะต ะฒ ะพะดะฝะพะผ ั„ะฐะนะปะต, ะผั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะปะธ ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‰ะธะต ััƒะฑ-ะผะพะดัƒะปะธ: +ะŸะพัั‚ะพะผัƒ, ั‡ั‚ะพะฑั‹ ะธะผะตั‚ัŒ ะฒะพะทะผะพะถะฝะพัั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะฑะต ะฒ ะพะดะฝะพะผ ั„ะฐะนะปะต, ะผั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตะผ ะฟะพะดะผะพะดัƒะปะธ ะฝะฐะฟั€ัะผัƒัŽ: {* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} -### ะŸะพะดะบะปัŽั‡ะตะฝะธะต ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะพะฒ (`APIRouter`) ะดะปั `users` ะธ ะดะปั `items` { #include-the-apirouters-for-users-and-items } +### ะŸะพะดะบะปัŽั‡ะตะฝะธะต `APIRouter` ะดะปั `users` ะธ `items` { #include-the-apirouters-for-users-and-items } -ะ”ะฐะฒะฐะนั‚ะต ะฟะพะดะบะปัŽั‡ะธะผ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ั‹ (`router`) ะธะท ััƒะฑ-ะผะพะดัƒะปะตะน `users` ะธ `items`: +ะขะตะฟะตั€ัŒ ะดะฐะฒะฐะนั‚ะต ะฟะพะดะบะปัŽั‡ะธะผ `router` ะธะท ะฟะพะดะผะพะดัƒะปะตะน `users` ะธ `items`: {* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} @@ -388,79 +390,78 @@ from .routers.users import router /// -ะก ะฟะพะผะพั‰ัŒัŽ `app.include_router()` ะผั‹ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ะบะฐะถะดั‹ะน ะธะท ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะพะฒ (`APIRouter`) ะฒ ะพัะฝะพะฒะฝะพะต ะฟั€ะธะปะพะถะตะฝะธะต `FastAPI`. +ะก ะฟะพะผะพั‰ัŒัŽ `app.include_router()` ะผั‹ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ะบะฐะถะดั‹ะน `APIRouter` ะฒ ะพัะฝะพะฒะฝะพะต ะฟั€ะธะปะพะถะตะฝะธะต `FastAPI`. -ะžะฝ ะฟะพะดะบะปัŽั‡ะธั‚ ะฒัะต ะผะฐั€ัˆั€ัƒั‚ั‹ ะทะฐะดะฐะฝะฝะพะณะพ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ ะบ ะฝะฐัˆะตะผัƒ ะฟั€ะธะปะพะถะตะฝะธัŽ. +ะžะฝ ะฒะบะปัŽั‡ะธั‚ ะฒัะต ะผะฐั€ัˆั€ัƒั‚ั‹ ัั‚ะพะณะพ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ ะบะฐะบ ั‡ะฐัั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธั. /// note | ะขะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ -ะคะฐะบั‚ะธั‡ะตัะบะธ, ะฒะฝัƒั‚ั€ะธ ะพะฝ ัะพะทะดะฐัั‚ ะฒัะต *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะดะปั ะบะฐะถะดะพะน ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ ะพะฑัŠัะฒะปะตะฝะฝะพะน ะฒ `APIRouter`. +ะคะฐะบั‚ะธั‡ะตัะบะธ, ะฒะฝัƒั‚ั€ะธ ะพะฝ ัะพะทะดะฐัั‚ *ะพะฟะตั€ะฐั†ะธัŽ ะฟัƒั‚ะธ* ะดะปั ะบะฐะถะดะพะน *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*, ะพะฑัŠัะฒะปะตะฝะฝะพะน ะฒ `APIRouter`. -ะ˜ ะฟะพะด ะบะฐะฟะพั‚ะพะผ ะฒัั‘ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ั‚ะฐะบ, ะบะฐะบ ะฑัƒะดั‚ะพ ะฑั‹ ะผั‹ ะธะผะตะตะผ ะดะตะปะพ ั ะพะดะฝะธะผ ั„ะฐะนะปะพะผ ะฟั€ะธะปะพะถะตะฝะธั. +ะขะฐะบ ั‡ั‚ะพ ะฟะพะด ะบะฐะฟะพั‚ะพะผ ะฒัั‘ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ั‚ะฐะบ, ะบะฐะบ ะฑัƒะดั‚ะพ ะฒัั‘ ะฑั‹ะปะพ ะพะดะฝะธะผ ะฟั€ะธะปะพะถะตะฝะธะตะผ. /// /// check | ะ—ะฐะผะตั‚ะบะฐ -ะŸั€ะธ ะฟะพะดะบะปัŽั‡ะตะฝะธะธ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะพะฒ ะฝะต ัั‚ะพะธั‚ ะฑะตัะฟะพะบะพะธั‚ัŒัั ะพ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ. +ะŸั€ะธ ะฟะพะดะบะปัŽั‡ะตะฝะธะธ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะพะฒ ะฝะต ะฝัƒะถะฝะพ ะฑะตัะฟะพะบะพะธั‚ัŒัั ะพ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ. -ะžะฟะตั€ะฐั†ะธั ะฟะพะดะบะปัŽั‡ะตะฝะธั ะทะฐะนะผั‘ั‚ ะผะธะบั€ะพัะตะบัƒะฝะดั‹ ะธ ะฟะพะฝะฐะดะพะฑะธั‚ัั ั‚ะพะปัŒะบะพ ะฟั€ะธ ะทะฐะฟัƒัะบะต ะฟั€ะธะปะพะถะตะฝะธั. +ะญั‚ะพ ะทะฐะนะผั‘ั‚ ะผะธะบั€ะพัะตะบัƒะฝะดั‹ ะธ ะฟั€ะพะธะทะพะนะดั‘ั‚ ั‚ะพะปัŒะบะพ ะฟั€ะธ ัั‚ะฐั€ั‚ะต. -ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ัั‚ะพ ะฝะต ะฟะพะฒะปะธัะตั‚ ะฝะฐ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ. โšก +ะขะฐะบ ั‡ั‚ะพ ัั‚ะพ ะฝะต ะฟะพะฒะปะธัะตั‚ ะฝะฐ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ. โšก /// -### ะŸะพะดะบะปัŽั‡ะตะฝะธะต `APIRouter` ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะผะธ ะฟั€ะตั„ะธะบัะพะผ (`prefix`), ั‚ะตะณะฐะผะธ (`tags`), ะพั‚ะฒะตั‚ะฐะผะธ (`responses`), ะธ ะทะฐะฒะธัะธะผะพัั‚ัะผะธ (`dependencies`) { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies } +### ะŸะพะดะบะปัŽั‡ะตะฝะธะต `APIRouter` ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะผะธ `prefix`, `tags`, `responses` ะธ `dependencies` { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies } ะขะตะฟะตั€ัŒ ะดะฐะฒะฐะนั‚ะต ะฟั€ะตะดัั‚ะฐะฒะธะผ, ั‡ั‚ะพ ะฒะฐัˆะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั ะฟะตั€ะตะดะฐะปะฐ ะฒะฐะผ ั„ะฐะนะป `app/internal/admin.py`. -ะžะฝ ัะพะดะตั€ะถะธั‚ `APIRouter` ั ะฝะตะบะพั‚ะพั€ั‹ะผะธ *ัะฝะดะฟะพะธั‚ะฐะผะธ* ะฐะดะผะธะฝะธัั‚ั€ะธั€ะพะฒะฐะฝะธั, ะบะพั‚ะพั€ั‹ะต ะฒะฐัˆะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั ะธัะฟะพะปัŒะทัƒะตั‚ ะดะปั ะฝะตัะบะพะปัŒะบะธั… ะฟั€ะพะตะบั‚ะพะฒ. +ะžะฝ ัะพะดะตั€ะถะธั‚ `APIRouter` ั ะฝะตะบะพั‚ะพั€ั‹ะผะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะธะฒะฝั‹ะผะธ *ะพะฟะตั€ะฐั†ะธัะผะธ ะฟัƒั‚ะธ*, ะบะพั‚ะพั€ั‹ะต ะฒะฐัˆะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั ะธัะฟะพะปัŒะทัƒะตั‚ ะฒ ะฝะตัะบะพะปัŒะบะธั… ะฟั€ะพะตะบั‚ะฐั…. -ะ’ ะดะฐะฝะฝะพะผ ะฟั€ะธะผะตั€ะต ัั‚ะพ ัะดะตะปะฐั‚ัŒ ะพั‡ะตะฝัŒ ะฟั€ะพัั‚ะพ. ะะพ ะดะฐะฒะฐะนั‚ะต ะฟั€ะตะดะฟะพะปะพะถะธะผ, ั‡ั‚ะพ ะฟะพัะบะพะปัŒะบัƒ ั„ะฐะนะป ะธัะฟะพะปัŒะทัƒะตั‚ัั ะดะปั ะฝะตัะบะพะปัŒะบะธั… ะฟั€ะพะตะบั‚ะพะฒ, -ั‚ะพ ะผั‹ ะฝะต ะผะพะถะตะผ ะผะพะดะธั„ะธั†ะธั€ะพะฒะฐั‚ัŒ ะตะณะพ, ะดะพะฑะฐะฒะปัั ะฟั€ะตั„ะธะบัั‹ (`prefix`), ะทะฐะฒะธัะธะผะพัั‚ะธ (`dependencies`), ั‚ะตะณะธ (`tags`), ะธ ั‚.ะด. ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ะฒ `APIRouter`: +ะ”ะปั ัั‚ะพะณะพ ะฟั€ะธะผะตั€ะฐ ะฒัั‘ ะฑัƒะดะตั‚ ะพั‡ะตะฝัŒ ะฟั€ะพัั‚ะพ. ะะพ ะดะพะฟัƒัั‚ะธะผ, ั‡ั‚ะพ ะฟะพัะบะพะปัŒะบัƒ ะพะฝ ะธัะฟะพะปัŒะทัƒะตั‚ัั ัะพะฒะผะตัั‚ะฝะพ ั ะดั€ัƒะณะธะผะธ ะฟั€ะพะตะบั‚ะฐะผะธ ะฒ ะพั€ะณะฐะฝะธะทะฐั†ะธะธ, ะผั‹ ะฝะต ะผะพะถะตะผ ะผะพะดะธั„ะธั†ะธั€ะพะฒะฐั‚ัŒ ะตะณะพ ะธ ะดะพะฑะฐะฒะธั‚ัŒ `prefix`, `dependencies`, `tags` ะธ ั‚.ะด. ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ะฒ `APIRouter`: {* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} -ะะพ, ะฝะตัะผะพั‚ั€ั ะฝะฐ ัั‚ะพ, ะผั‹ ั…ะพั‚ะธะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะบะฐัั‚ะพะผะฝั‹ะน ะฟั€ะตั„ะธะบั (`prefix`) ะดะปั ะฟะพะดะบะปัŽั‡ะตะฝะฝะพะณะพ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ (`APIRouter`), ะฒ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต ั‡ะตะณะพ, ะบะฐะถะดะฐั *ะพะฟะตั€ะฐั†ะธั ะฟัƒั‚ะธ* ะฑัƒะดะตั‚ ะฝะฐั‡ะธะฝะฐั‚ัŒัั ั `/admin`. ะขะฐะบะถะต ะผั‹ ั…ะพั‚ะธะผ ะทะฐั‰ะธั‚ะธั‚ัŒ ะฝะฐัˆ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ ั ะฟะพะผะพั‰ัŒัŽ ะทะฐะฒะธัะธะผะพัั‚ะตะน, ัะพะทะดะฐะฝะฝั‹ั… ะดะปั ะฝะฐัˆะตะณะพ ะฟั€ะพะตะบั‚ะฐ. ะ˜ ะตั‰ั‘ ะผั‹ ั…ะพั‚ะธะผ ะฒะบะปัŽั‡ะธั‚ัŒ ั‚ะตะณะธ (`tags`) ะธ ะพั‚ะฒะตั‚ั‹ (`responses`). +ะะพ ะผั‹ ะฒัั‘ ั€ะฐะฒะฝะพ ั…ะพั‚ะธะผ ะทะฐะดะฐั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน `prefix` ะฟั€ะธ ะฟะพะดะบะปัŽั‡ะตะฝะธะธ `APIRouter`, ั‡ั‚ะพะฑั‹ ะฒัะต ะตะณะพ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะฝะฐั‡ะธะฝะฐะปะธััŒ ั `/admin`, ั…ะพั‚ะธะผ ะทะฐั‰ะธั‚ะธั‚ัŒ ะตะณะพ ั ะฟะพะผะพั‰ัŒัŽ `dependencies`, ะบะพั‚ะพั€ั‹ะต ัƒ ะฝะฐั ัƒะถะต ะตัั‚ัŒ ะดะปั ัั‚ะพะณะพ ะฟั€ะพะตะบั‚ะฐ, ะธ ั…ะพั‚ะธะผ ะฒะบะปัŽั‡ะธั‚ัŒ `tags` ะธ `responses`. -ะœั‹ ะผะพะถะตะผ ะฟั€ะธะผะตะฝะธั‚ัŒ ะฒัะต ะฒั‹ัˆะตะฟะตั€ะตั‡ะธัะปะตะฝะฝั‹ะต ะฝะฐัั‚ั€ะพะนะบะธ, ะฝะต ะธะทะผะตะฝัั ะฝะฐั‡ะฐะปัŒะฝั‹ะน `APIRouter`. ะะฐะผ ะฒัะตะณะพ ะปะธัˆัŒ ะฝัƒะถะฝะพ ะฟะตั€ะตะดะฐั‚ัŒ ะฝัƒะถะฝั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹ ะฒ `app.include_router()`. +ะœั‹ ะผะพะถะตะผ ะพะฑัŠัะฒะธั‚ัŒ ะฒัั‘ ัั‚ะพ, ะฝะต ะธะทะผะตะฝัั ะธัั…ะพะดะฝั‹ะน `APIRouter`, ะฟะตั€ะตะดะฐะฒ ัั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ั‹ ะฒ `app.include_router()`: {* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} -ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะพั€ะธะณะธะฝะฐะปัŒะฝั‹ะน `APIRouter` ะฝะต ะฑัƒะดะตั‚ ะผะพะดะธั„ะธั†ะธั€ะพะฒะฐะฝ, ะธ ะผั‹ ัะผะพะถะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั„ะฐะนะป `app/internal/admin.py` ัั€ะฐะทัƒ ะฒ ะฝะตัะบะพะปัŒะบะธั… ะฟั€ะพะตะบั‚ะฐั… ะพั€ะณะฐะฝะธะทะฐั†ะธะธ. +ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ ะธัั…ะพะดะฝั‹ะน `APIRouter` ะฝะต ะฑัƒะดะตั‚ ะผะพะดะธั„ะธั†ะธั€ะพะฒะฐะฝ, ะธ ะผั‹ ัะผะพะถะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั„ะฐะนะป `app/internal/admin.py` ัั€ะฐะทัƒ ะฒ ะฝะตัะบะพะปัŒะบะธั… ะฟั€ะพะตะบั‚ะฐั… ะพั€ะณะฐะฝะธะทะฐั†ะธะธ. -ะ’ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต, ะฒ ะฝะฐัˆะตะผ ะฟั€ะธะปะพะถะตะฝะธะธ ะบะฐะถะดั‹ะน *ัะฝะดะฟะพะธะฝั‚* ะผะพะดัƒะปั `admin` ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ: +ะ’ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต ะฒ ะฝะฐัˆะตะผ ะฟั€ะธะปะพะถะตะฝะธะธ ะบะฐะถะดะฐั ะธะท *ะพะฟะตั€ะฐั†ะธะน ะฟัƒั‚ะธ* ะธะท ะผะพะดัƒะปั `admin` ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ: * ะŸั€ะตั„ะธะบั `/admin`. * ะขะตะณ `admin`. * ะ—ะฐะฒะธัะธะผะพัั‚ัŒ `get_token_header`. * ะžั‚ะฒะตั‚ `418`. ๐Ÿต -ะญั‚ะพ ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ะผะตัั‚ะพ ะธัะบะปัŽั‡ะธั‚ะตะปัŒะฝะพ ะดะปั `APIRouter` ะฒ ะฝะฐัˆะตะผ ะฟั€ะธะปะพะถะตะฝะธะธ, ะธ ะฝะต ะทะฐั‚ั€ะพะฝะตั‚ ะปัŽะฑะพะน ะดั€ัƒะณะพะน ะบะพะด, ะธัะฟะพะปัŒะทัƒัŽั‰ะธะน ะตะณะพ. +ะะพ ัั‚ะพ ะฟะพะฒะปะธัะตั‚ ั‚ะพะปัŒะบะพ ะฝะฐ ัั‚ะพั‚ `APIRouter` ะฒ ะฝะฐัˆะตะผ ะฟั€ะธะปะพะถะตะฝะธะธ, ะฐ ะฝะต ะฝะฐ ะปัŽะฑะพะน ะดั€ัƒะณะพะน ะบะพะด, ะบะพั‚ะพั€ั‹ะน ะตะณะพ ะธัะฟะพะปัŒะทัƒะตั‚. -ะะฐะฟั€ะธะผะตั€, ะดั€ัƒะณะธะต ะฟั€ะพะตะบั‚ั‹, ะผะพะณัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั‚ะพั‚ ะถะต ัะฐะผั‹ะน `APIRouter` ั ะดั€ัƒะณะธะผะธ ะผะตั‚ะพะดะฐะผะธ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ. +ะขะฐะบ ั‡ั‚ะพ, ะฝะฐะฟั€ะธะผะตั€, ะดั€ัƒะณะธะต ะฟั€ะพะตะบั‚ั‹ ะผะพะณัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั‚ะพั‚ ะถะต `APIRouter` ั ะดั€ัƒะณะธะผ ะผะตั‚ะพะดะพะผ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ. -### ะŸะพะดะบะปัŽั‡ะตะฝะธะต ะพั‚ะดะตะปัŒะฝะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ* { #include-a-path-operation } +### ะŸะพะดะบะปัŽั‡ะตะฝะธะต *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* { #include-a-path-operation } -ะœั‹ ั‚ะฐะบะถะต ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ *ัะฝะดะฟะพะธะฝั‚* ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ะฒ ะพัะฝะพะฒะฝะพะต ะฟั€ะธะปะพะถะตะฝะธะต `FastAPI`. +ะœั‹ ั‚ะฐะบะถะต ะผะพะถะตะผ ะดะพะฑะฐะฒะปัั‚ัŒ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะฝะฐะฟั€ัะผัƒัŽ ะฒ ะฟั€ะธะปะพะถะตะฝะธะต `FastAPI`. -ะ—ะดะตััŒ ะผั‹ ัั‚ะพ ะดะตะปะฐะตะผ ... ะฟั€ะพัั‚ะพ, ั‡ั‚ะพะฑั‹ ะฟะพะบะฐะทะฐั‚ัŒ, ั‡ั‚ะพ ัั‚ะพ ะฒะพะทะผะพะถะฝะพ ๐Ÿคท: +ะ—ะดะตััŒ ะผั‹ ะดะตะปะฐะตะผ ัั‚ะพ... ะฟั€ะพัั‚ะพ ั‡ั‚ะพะฑั‹ ะฟะพะบะฐะทะฐั‚ัŒ, ั‡ั‚ะพ ะผะพะถะตะผ ๐Ÿคท: {* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} -ะธ ัั‚ะพ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ะบะพั€ั€ะตะบั‚ะฝะพ ะฒะผะตัั‚ะต ั ะดั€ัƒะณะธะผะธ *ัะฝะดะฟะพะธะฝั‚ะฐะผะธ*, ะดะพะฑะฐะฒะปะตะฝะฝั‹ะผะธ ั ะฟะพะผะพั‰ัŒัŽ `app.include_router()`. +ะธ ัั‚ะพ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ะบะพั€ั€ะตะบั‚ะฝะพ ะฒะผะตัั‚ะต ัะพ ะฒัะตะผะธ ะดั€ัƒะณะธะผะธ *ะพะฟะตั€ะฐั†ะธัะผะธ ะฟัƒั‚ะธ*, ะดะพะฑะฐะฒะปะตะฝะฝั‹ะผะธ ั‡ะตั€ะตะท `app.include_router()`. -/// info | ะกะปะพะถะฝั‹ะต ั‚ะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ +/// info | ะžั‡ะตะฝัŒ ั‚ะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ -**ะŸั€ะธะผะตั‡ะฐะฝะธะต**: ัั‚ะพ ัะปะพะถะฝะฐั ั‚ะตั…ะฝะธั‡ะตัะบะฐั ะดะตั‚ะฐะปัŒ, ะบะพั‚ะพั€ัƒัŽ, ัะบะพั€ะตะต ะฒัะตะณะพ, **ะฒั‹ ะผะพะถะตั‚ะต ะฟั€ะพะฟัƒัั‚ะธั‚ัŒ**. +**ะŸั€ะธะผะตั‡ะฐะฝะธะต**: ัั‚ะพ ะพั‡ะตะฝัŒ ั‚ะตั…ะฝะธั‡ะตัะบะฐั ะดะตั‚ะฐะปัŒ, ะบะพั‚ะพั€ัƒัŽ, ะฒะตั€ะพัั‚ะฝะพ, ะผะพะถะฝะพ **ะฟั€ะพัั‚ะพ ะฟั€ะพะฟัƒัั‚ะธั‚ัŒ**. --- -ะœะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ั‹ (`APIRouter`) ะฝะต "ะผะพะฝั‚ะธั€ัƒัŽั‚ัั" ะฟะพ-ะพั‚ะดะตะปัŒะฝะพัั‚ะธ ะธ ะฝะต ะธะทะพะปะธั€ัƒัŽั‚ัั ะพั‚ ะพัั‚ะฐะปัŒะฝะพะณะพ ะฟั€ะธะปะพะถะตะฝะธั. +`APIRouter` ะฝะต ยซะผะพะฝั‚ะธั€ัƒัŽั‚ััยป, ะพะฝะธ ะฝะต ะธะทะพะปะธั€ะพะฒะฐะฝั‹ ะพั‚ ะพัั‚ะฐะปัŒะฝะพะณะพ ะฟั€ะธะปะพะถะตะฝะธั. -ะญั‚ะพ ะฟั€ะพะธัั…ะพะดะธั‚ ะฟะพั‚ะพะผัƒ, ั‡ั‚ะพ ะฝัƒะถะฝะพ ะฒะบะปัŽั‡ะธั‚ัŒ ะธั… *ัะฝะดะฟะพะธะฝั‚ั‹* ะฒ OpenAPI ัั…ะตะผัƒ ะธ ะฒ ะธะฝั‚ะตั€ั„ะตะนั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั. +ะญั‚ะพ ะฟะพั‚ะพะผัƒ, ั‡ั‚ะพ ะผั‹ ั…ะพั‚ะธะผ ะฒะบะปัŽั‡ะธั‚ัŒ ะธั… *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะฒ OpenAPI-ัั…ะตะผัƒ ะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ะธะฝั‚ะตั€ั„ะตะนัั‹. -ะ’ ัะธะปัƒ ั‚ะพะณะพ, ั‡ั‚ะพ ะผั‹ ะฝะต ะผะพะถะตะผ ะธั… ะธะทะพะปะธั€ะพะฒะฐั‚ัŒ ะธ "ะฟั€ะธะผะพะฝั‚ะธั€ะพะฒะฐั‚ัŒ" ะฝะตะทะฐะฒะธัะธะผะพ ะพั‚ ะพัั‚ะฐะปัŒะฝั‹ั…, *ัะฝะดะฟะพะธะฝั‚ั‹* ะบะปะพะฝะธั€ัƒัŽั‚ัั (ะฟะตั€ะตัะพะทะดะฐัŽั‚ัั) ะธ ะฝะต ะฟะพะดะบะปัŽั‡ะฐัŽั‚ัั ะฝะฐะฟั€ัะผัƒัŽ. +ะขะฐะบ ะบะฐะบ ะผั‹ ะฝะต ะผะพะถะตะผ ะฟั€ะพัั‚ะพ ะธะทะพะปะธั€ะพะฒะฐั‚ัŒ ะธั… ะธ ยซัะผะพะฝั‚ะธั€ะพะฒะฐั‚ัŒยป ะฝะตะทะฐะฒะธัะธะผะพ ะพั‚ ะพัั‚ะฐะปัŒะฝะพะณะพ, *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ยซะบะปะพะฝะธั€ัƒัŽั‚ััยป (ะฟะตั€ะตัะพะทะดะฐัŽั‚ัั), ะฐ ะฝะต ะฒะบะปัŽั‡ะฐัŽั‚ัั ะฝะฐะฟั€ัะผัƒัŽ. /// @@ -480,24 +481,24 @@ $ fastapi dev app/main.py ะžั‚ะบั€ะพะนั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ ะฟะพ ะฐะดั€ะตััƒ <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. -ะ’ั‹ ัƒะฒะธะดะธั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบัƒัŽ API ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ. ะžะฝะฐ ะฒะบะปัŽั‡ะฐะตั‚ ะฒ ัะตะฑั ะผะฐั€ัˆั€ัƒั‚ั‹ ะธะท ััƒะฑ-ะผะพะดัƒะปะตะน, ะธัะฟะพะปัŒะทัƒั ะฒะตั€ะฝั‹ะต ะผะฐั€ัˆั€ัƒั‚ั‹, ะฟั€ะตั„ะธะบัั‹ ะธ ั‚ะตะณะธ: +ะ’ั‹ ัƒะฒะธะดะธั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบัƒัŽ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ API, ะฒะบะปัŽั‡ะฐั ะฟัƒั‚ะธ ะธะท ะฒัะตั… ะฟะพะดะผะพะดัƒะปะตะน, ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ ะบะพั€ั€ะตะบั‚ะฝั‹ั… ะฟัƒั‚ะตะน (ะธ ะฟั€ะตั„ะธะบัะพะฒ) ะธ ะบะพั€ั€ะตะบั‚ะฝั‹ั… ั‚ะตะณะพะฒ: <img src="/img/tutorial/bigger-applications/image01.png"> -## ะŸะพะดะบะปัŽั‡ะตะฝะธะต ััƒั‰ะตัั‚ะฒัƒัŽั‰ะตะณะพ ะผะฐั€ัˆั€ัƒั‚ะฐ ั‡ะตั€ะตะท ะฝะพะฒั‹ะน ะฟั€ะตั„ะธะบั (`prefix`) { #include-the-same-router-multiple-times-with-different-prefix } +## ะŸะพะดะบะปัŽั‡ะตะฝะธะต ะพะดะฝะพะณะพ ะธ ั‚ะพะณะพ ะถะต ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ ะฝะตัะบะพะปัŒะบะพ ั€ะฐะท ั ั€ะฐะทะฝั‹ะผะธ `prefix` { #include-the-same-router-multiple-times-with-different-prefix } -ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `.include_router()` ะฝะตัะบะพะปัŒะบะพ ั€ะฐะท ั ะพะดะฝะธะผ ะธ ั‚ะตะผ ะถะต ะผะฐั€ัˆั€ัƒั‚ะพะผ, ะฟั€ะธะผะตะฝะธะฒ ั€ะฐะทะปะธั‡ะฝั‹ะต ะฟั€ะตั„ะธะบัั‹. +ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `.include_router()` ะฝะตัะบะพะปัŒะบะพ ั€ะฐะท ั *ะพะดะฝะธะผ ะธ ั‚ะตะผ ะถะต* ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะพะผ, ะธัะฟะพะปัŒะทัƒั ั€ะฐะทะฝั‹ะต ะฟั€ะตั„ะธะบัั‹. -ะญั‚ะพ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะฟะพะปะตะทะฝั‹ะผ, ะตัะปะธ ะฝัƒะถะฝะพ ะฟั€ะตะดะพัั‚ะฐะฒะธั‚ัŒ ะดะพัั‚ัƒะฟ ะบ ะพะดะฝะพะผัƒ ะธ ั‚ะพะผัƒ ะถะต API ั‡ะตั€ะตะท ั€ะฐะทะปะธั‡ะฝั‹ะต ะฟั€ะตั„ะธะบัั‹, ะฝะฐะฟั€ะธะผะตั€, `/api/v1` ะธ `/api/latest`. +ะญั‚ะพ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะฟะพะปะตะทะฝะพ, ะฝะฐะฟั€ะธะผะตั€, ั‡ั‚ะพะฑั‹ ะฟั€ะตะดะพัั‚ะฐะฒะธั‚ัŒ ะดะพัั‚ัƒะฟ ะบ ะพะดะฝะพะผัƒ ะธ ั‚ะพะผัƒ ะถะต API ั ั€ะฐะทะฝั‹ะผะธ ะฟั€ะตั„ะธะบัะฐะผะธ, ะฝะฐะฟั€ะธะผะตั€ `/api/v1` ะธ `/api/latest`. -ะญั‚ะพ ะฟั€ะพะดะฒะธะฝัƒั‚ั‹ะน ัะฟะพัะพะฑ, ะบะพั‚ะพั€ั‹ะน ะฒะฐะผ ะผะพะถะตั‚ ะธ ะฝะต ะฟั€ะธะณะพะดะธั‚ัั. ะœั‹ ะฟั€ะธะฒะพะดะธะผ ะตะณะพ ะฝะฐ ัะปัƒั‡ะฐะน, ะตัะปะธ ะฒะดั€ัƒะณ ะฒะฐะผ ัั‚ะพ ะฟะพะฝะฐะดะพะฑะธั‚ัั. +ะญั‚ะพ ะฟั€ะพะดะฒะธะฝัƒั‚ะพะต ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต, ะบะพั‚ะพั€ะพะต ะฒะฐะผ ะผะพะถะตั‚ ะธ ะฝะต ะฟะพะฝะฐะดะพะฑะธั‚ัŒัั, ะฝะพ ะพะฝะพ ะตัั‚ัŒ ะฝะฐ ัะปัƒั‡ะฐะน, ะตัะปะธ ะฟะพะฝะฐะดะพะฑะธั‚ัั. -## ะ’ะบะปัŽั‡ะตะฝะธะต ะพะดะฝะพะณะพ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ (`APIRouter`) ะฒ ะดั€ัƒะณะพะน { #include-an-apirouter-in-another } +## ะŸะพะดะบะปัŽั‡ะตะฝะธะต `APIRouter` ะฒ ะดั€ัƒะณะพะน `APIRouter` { #include-an-apirouter-in-another } -ะขะพั‡ะฝะพ ั‚ะฐะบ ะถะต, ะบะฐะบ ะฒั‹ ะฒะบะปัŽั‡ะฐะตั‚ะต `APIRouter` ะฒ ะฟั€ะธะปะพะถะตะฝะธะต `FastAPI`, ะฒั‹ ะผะพะถะตั‚ะต ะฒะบะปัŽั‡ะธั‚ัŒ `APIRouter` ะฒ ะดั€ัƒะณะพะน `APIRouter`: +ะขะพั‡ะฝะพ ั‚ะฐะบ ะถะต, ะบะฐะบ ะฒั‹ ะผะพะถะตั‚ะต ะฟะพะดะบะปัŽั‡ะธั‚ัŒ `APIRouter` ะบ ะฟั€ะธะปะพะถะตะฝะธัŽ `FastAPI`, ะฒั‹ ะผะพะถะตั‚ะต ะฟะพะดะบะปัŽั‡ะธั‚ัŒ `APIRouter` ะบ ะดั€ัƒะณะพะผัƒ `APIRouter`, ะธัะฟะพะปัŒะทัƒั: ```Python router.include_router(other_router) ``` -ะฃะดะพัั‚ะพะฒะตั€ัŒั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ัะดะตะปะฐะปะธ ัั‚ะพ ะดะพ ั‚ะพะณะพ, ะบะฐะบ ะฟะพะดะบะปัŽั‡ะธั‚ัŒ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ (`router`) ะบ ะฒะฐัˆะตะผัƒ `FastAPI` ะฟั€ะธะปะพะถะตะฝะธัŽ, ะธ *ัะฝะดะฟะพะธะฝั‚ั‹* ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ `other_router` ะฑั‹ะปะธ ั‚ะฐะบะถะต ะฟะพะดะบะปัŽั‡ะตะฝั‹. +ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ัะดะตะปะฐะปะธ ัั‚ะพ ะดะพ ะฟะพะดะบะปัŽั‡ะตะฝะธั `router` ะบ ะฟั€ะธะปะพะถะตะฝะธัŽ `FastAPI`, ั‡ั‚ะพะฑั‹ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะธะท `other_router` ั‚ะฐะบะถะต ะฑั‹ะปะธ ะฟะพะดะบะปัŽั‡ะตะฝั‹. diff --git a/docs/ru/docs/tutorial/body-updates.md b/docs/ru/docs/tutorial/body-updates.md index 73f4e66c76..4a7adb2559 100644 --- a/docs/ru/docs/tutorial/body-updates.md +++ b/docs/ru/docs/tutorial/body-updates.md @@ -2,13 +2,13 @@ ## ะžะฑะฝะพะฒะปะตะฝะธะต ั ะทะฐะผะตะฝะพะน ะฟั€ะธ ะฟะพะผะพั‰ะธ `PUT` { #update-replacing-with-put } -ะ”ะปั ะฟะพะปะฝะพะณะพ ะพะฑะฝะพะฒะปะตะฝะธั ัะปะตะผะตะฝั‚ะฐ ะผะพะถะฝะพ ะฒะพัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะพะฟะตั€ะฐั†ะธะตะน <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a>. +ะงั‚ะพะฑั‹ ะพะฑะฝะพะฒะธั‚ัŒ ัะปะตะผะตะฝั‚, ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะฟะตั€ะฐั†ะธัŽ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a>. ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `jsonable_encoder`, ั‡ั‚ะพะฑั‹ ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐั‚ัŒ ะฒั…ะพะดะฝั‹ะต ะดะฐะฝะฝั‹ะต ะฒ ะดะฐะฝะฝั‹ะต, ะบะพั‚ะพั€ั‹ะต ะผะพะถะฝะพ ัะพั…ั€ะฐะฝะธั‚ัŒ ะบะฐะบ JSON (ะฝะฐะฟั€ะธะผะตั€, ะฒ NoSQL-ะฑะฐะทะต ะดะฐะฝะฝั‹ั…). ะะฐะฟั€ะธะผะตั€, ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐะฝะธะต `datetime` ะฒ `str`. {* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *} -`PUT` ะธัะฟะพะปัŒะทัƒะตั‚ัั ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะดะฐะฝะฝั‹ั…, ะบะพั‚ะพั€ั‹ะต ะดะพะปะถะฝั‹ ะฟะพะปะฝะพัั‚ัŒัŽ ะทะฐะผะตะฝะธั‚ัŒ ััƒั‰ะตัั‚ะฒัƒัŽั‰ะธะต ะดะฐะฝะฝั‹ะต. +`PUT` ะธัะฟะพะปัŒะทัƒะตั‚ัั ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะดะฐะฝะฝั‹ั…, ะบะพั‚ะพั€ั‹ะต ะดะพะปะถะฝั‹ ะทะฐะผะตะฝะธั‚ัŒ ััƒั‰ะตัั‚ะฒัƒัŽั‰ะธะต ะดะฐะฝะฝั‹ะต. ### ะŸั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต ะพ ะทะฐะผะตะฝะต { #warning-about-replacing } @@ -24,11 +24,11 @@ ะฟะพัะบะพะปัŒะบัƒ ะพะฝะพ ะฝะต ะฒะบะปัŽั‡ะฐะตั‚ ัƒะถะต ัะพั…ั€ะฐะฝะตะฝะฝั‹ะน ะฐั‚ั€ะธะฑัƒั‚ `"tax": 20.2`, ะฒั…ะพะดะฝะฐั ะผะพะดะตะปัŒ ะฟั€ะธะผะตั‚ ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ `"tax": 10.5`. -ะ˜ ะดะฐะฝะฝั‹ะต ะฑัƒะดัƒั‚ ัะพั…ั€ะฐะฝะตะฝั‹ ั ัั‚ะธะผ "ะฝะพะฒั‹ะผ" `tax`, ั€ะฐะฒะฝั‹ะผ `10,5`. +ะ˜ ะดะฐะฝะฝั‹ะต ะฑัƒะดัƒั‚ ัะพั…ั€ะฐะฝะตะฝั‹ ั ัั‚ะธะผ ยซะฝะพะฒั‹ะผยป `tax`, ั€ะฐะฒะฝั‹ะผ `10.5`. ## ะงะฐัั‚ะธั‡ะฝะพะต ะพะฑะฝะพะฒะปะตะฝะธะต ั ะฟะพะผะพั‰ัŒัŽ `PATCH` { #partial-updates-with-patch } -ะขะฐะบะถะต ะผะพะถะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> ะพะฟะตั€ะฐั†ะธัŽ ะดะปั *ั‡ะฐัั‚ะธั‡ะฝะพะณะพ* ะพะฑะฝะพะฒะปะตะฝะธั ะดะฐะฝะฝั‹ั…. +ะขะฐะบะถะต ะผะพะถะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะฟะตั€ะฐั†ะธัŽ <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> ะดะปั *ั‡ะฐัั‚ะธั‡ะฝะพะณะพ* ะพะฑะฝะพะฒะปะตะฝะธั ะดะฐะฝะฝั‹ั…. ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะผะพะถะฝะพ ะฟะตั€ะตะดะฐะฒะฐั‚ัŒ ั‚ะพะปัŒะบะพ ั‚ะต ะดะฐะฝะฝั‹ะต, ะบะพั‚ะพั€ั‹ะต ะฝะตะพะฑั…ะพะดะธะผะพ ะพะฑะฝะพะฒะธั‚ัŒ, ะพัั‚ะฐะฒะปัั ะพัั‚ะฐะปัŒะฝั‹ะต ะฝะตั‚ั€ะพะฝัƒั‚ั‹ะผะธ. @@ -46,19 +46,13 @@ ### ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฟะฐั€ะฐะผะตั‚ั€ะฐ `exclude_unset` ะฒ Pydantic { #using-pydantics-exclude-unset-parameter } -ะ•ัะปะธ ะฝะตะพะฑั…ะพะดะธะผะพ ะฒั‹ะฟะพะปะฝะธั‚ัŒ ั‡ะฐัั‚ะธั‡ะฝะพะต ะพะฑะฝะพะฒะปะตะฝะธะต, ั‚ะพ ะพั‡ะตะฝัŒ ะฟะพะปะตะทะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `exclude_unset` ะฒ ะผะตั‚ะพะดะต `.model_dump()` ะผะพะดะตะปะธ Pydantic. +ะ•ัะปะธ ะฒั‹ ั…ะพั‚ะธั‚ะต ะฟะพะปัƒั‡ะฐั‚ัŒ ั‡ะฐัั‚ะธั‡ะฝั‹ะต ะพะฑะฝะพะฒะปะตะฝะธั, ะพั‡ะตะฝัŒ ะฟะพะปะตะทะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `exclude_unset` ะฒ `.model_dump()` ะผะพะดะตะปะธ Pydantic. ะะฐะฟั€ะธะผะตั€, `item.model_dump(exclude_unset=True)`. -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั +ะ’ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต ะฑัƒะดะตั‚ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝ `dict`, ัะพะดะตั€ะถะฐั‰ะธะน ั‚ะพะปัŒะบะพ ั‚ะต ะดะฐะฝะฝั‹ะต, ะบะพั‚ะพั€ั‹ะต ะฑั‹ะปะธ ะทะฐะดะฐะฝั‹ ะฟั€ะธ ัะพะทะดะฐะฝะธะธ ะผะพะดะตะปะธ `item`, ะฑะตะท ัƒั‡ะตั‚ะฐ ะทะฝะฐั‡ะตะฝะธะน ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ. -ะ’ Pydantic v1 ะผะตั‚ะพะด ะฝะฐะทั‹ะฒะฐะปัั `.dict()`, ะฒ Pydantic v2 ะพะฝ ะฟะพะผะตั‡ะตะฝ ะบะฐะบ ัƒัั‚ะฐั€ะตะฒัˆะธะน (ะฝะพ ะฒัะต ะตั‰ะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั) ะธ ะฟะตั€ะตะธะผะตะฝะพะฒะฐะฝ ะฒ `.model_dump()`. - -ะŸั€ะธะผะตั€ั‹ ะทะดะตััŒ ะธัะฟะพะปัŒะทัƒัŽั‚ `.dict()` ะดะปั ัะพะฒะผะตัั‚ะธะผะพัั‚ะธ ั Pydantic v1, ะฝะพ ะตัะปะธ ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ Pydantic v2, ะปัƒั‡ัˆะต ะธัะฟะพะปัŒะทัƒะนั‚ะต `.model_dump()`. - -/// - -ะ’ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต ะฑัƒะดะตั‚ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝ ัะปะพะฒะฐั€ัŒ, ัะพะดะตั€ะถะฐั‰ะธะน ั‚ะพะปัŒะบะพ ั‚ะต ะดะฐะฝะฝั‹ะต, ะบะพั‚ะพั€ั‹ะต ะฑั‹ะปะธ ะทะฐะดะฐะฝั‹ ะฟั€ะธ ัะพะทะดะฐะฝะธะธ ะผะพะดะตะปะธ `item`, ะฑะตะท ัƒั‡ะตั‚ะฐ ะทะฝะฐั‡ะตะฝะธะน ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ. ะ—ะฐั‚ะตะผ ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะพ ะดะปั ัะพะทะดะฐะฝะธั ัะปะพะฒะฐั€ั ั‚ะพะปัŒะบะพ ั ั‚ะตะผะธ ะดะฐะฝะฝั‹ะผะธ, ะบะพั‚ะพั€ั‹ะต ะฑั‹ะปะธ ัƒัั‚ะฐะฝะพะฒะปะตะฝั‹ (ะพั‚ะฟั€ะฐะฒะปะตะฝั‹ ะฒ ะทะฐะฟั€ะพัะต), ะพะฟัƒัะบะฐั ะทะฝะฐั‡ะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ: +ะ—ะฐั‚ะตะผ ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะพ ะดะปั ัะพะทะดะฐะฝะธั `dict` ั‚ะพะปัŒะบะพ ั ั‚ะตะผะธ ะดะฐะฝะฝั‹ะผะธ, ะบะพั‚ะพั€ั‹ะต ะฑั‹ะปะธ ัƒัั‚ะฐะฝะพะฒะปะตะฝั‹ (ะพั‚ะฟั€ะฐะฒะปะตะฝั‹ ะฒ ะทะฐะฟั€ะพัะต), ะพะฟัƒัะบะฐั ะทะฝะฐั‡ะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ: {* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *} @@ -66,14 +60,6 @@ ะขะตะฟะตั€ัŒ ะผะพะถะฝะพ ัะพะทะดะฐั‚ัŒ ะบะพะฟะธัŽ ััƒั‰ะตัั‚ะฒัƒัŽั‰ะตะน ะผะพะดะตะปะธ, ะธัะฟะพะปัŒะทัƒั `.model_copy()`, ะธ ะฟะตั€ะตะดะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `update` ั `dict`, ัะพะดะตั€ะถะฐั‰ะธะผ ะดะฐะฝะฝั‹ะต ะดะปั ะพะฑะฝะพะฒะปะตะฝะธั. -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั - -ะ’ Pydantic v1 ะผะตั‚ะพะด ะฝะฐะทั‹ะฒะฐะปัั `.copy()`, ะฒ Pydantic v2 ะพะฝ ะฟะพะผะตั‡ะตะฝ ะบะฐะบ ัƒัั‚ะฐั€ะตะฒัˆะธะน (ะฝะพ ะฒัะต ะตั‰ะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั) ะธ ะฟะตั€ะตะธะผะตะฝะพะฒะฐะฝ ะฒ `.model_copy()`. - -ะŸั€ะธะผะตั€ั‹ ะทะดะตััŒ ะธัะฟะพะปัŒะทัƒัŽั‚ `.copy()` ะดะปั ัะพะฒะผะตัั‚ะธะผะพัั‚ะธ ั Pydantic v1, ะฝะพ ะตัะปะธ ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ Pydantic v2, ะปัƒั‡ัˆะต ะธัะฟะพะปัŒะทัƒะนั‚ะต `.model_copy()`. - -/// - ะะฐะฟั€ะธะผะตั€, `stored_item_model.model_copy(update=update_data)`: {* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} @@ -84,9 +70,9 @@ * (ะžะฟั†ะธะพะฝะฐะปัŒะฝะพ) ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `PATCH` ะฒะผะตัั‚ะพ `PUT`. * ะ˜ะทะฒะปะตั‡ัŒ ัะพั…ั€ะฐะฝั‘ะฝะฝั‹ะต ะดะฐะฝะฝั‹ะต. -* ะŸะพะผะตัั‚ะธั‚ัŒ ัั‚ะธ ะดะฐะฝะฝั‹ะต ะฒ Pydantic ะผะพะดะตะปัŒ. +* ะŸะพะผะตัั‚ะธั‚ัŒ ัั‚ะธ ะดะฐะฝะฝั‹ะต ะฒ Pydantic-ะผะพะดะตะปัŒ. * ะกะณะตะฝะตั€ะธั€ะพะฒะฐั‚ัŒ `dict` ะฑะตะท ะทะฝะฐั‡ะตะฝะธะน ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะธะท ะฒั…ะพะดะฝะพะน ะผะพะดะตะปะธ (ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ `exclude_unset`). - * ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะผะพะถะฝะพ ะพะฑะฝะพะฒะปัั‚ัŒ ั‚ะพะปัŒะบะพ ั‚ะต ะทะฝะฐั‡ะตะฝะธั, ะบะพั‚ะพั€ั‹ะต ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝะพ ัƒัั‚ะฐะฝะพะฒะปะตะฝั‹ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ, ะฒะผะตัั‚ะพ ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะฟะตั€ะตะพะฟั€ะตะดะตะปัั‚ัŒ ะทะฝะฐั‡ะตะฝะธั, ัƒะถะต ัะพั…ั€ะฐะฝะตะฝะฝั‹ะต ะฒ ะผะพะดะตะปะธ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ. + * ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะผะพะถะฝะพ ะพะฑะฝะพะฒะปัั‚ัŒ ั‚ะพะปัŒะบะพ ั‚ะต ะทะฝะฐั‡ะตะฝะธั, ะบะพั‚ะพั€ั‹ะต ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝะพ ัƒัั‚ะฐะฝะพะฒะปะตะฝั‹ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ, ะฒะผะตัั‚ะพ ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะฟะตั€ะตะพะฟั€ะตะดะตะปัั‚ัŒ ัƒะถะต ัะพั…ั€ะฐะฝะตะฝะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั ะทะฝะฐั‡ะตะฝะธัะผะธ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะธะท ะฒะฐัˆะตะน ะผะพะดะตะปะธ. * ะกะพะทะดะฐั‚ัŒ ะบะพะฟะธัŽ ั…ั€ะฐะฝะธะผะพะน ะผะพะดะตะปะธ, ะพะฑะฝะพะฒะธะฒ ะตะต ะฐั‚ั€ะธะฑัƒั‚ั‹ ะฟะพะปัƒั‡ะตะฝะฝั‹ะผะธ ั‡ะฐัั‚ะธั‡ะฝั‹ะผะธ ะพะฑะฝะพะฒะปะตะฝะธัะผะธ (ั ะฟะพะผะพั‰ัŒัŽ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `update`). * ะŸั€ะตะพะฑั€ะฐะทะพะฒะฐั‚ัŒ ัะบะพะฟะธั€ะพะฒะฐะฝะฝัƒัŽ ะผะพะดะตะปัŒ ะฒ ั‚ะพ, ั‡ั‚ะพ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ัะพั…ั€ะฐะฝะตะฝะพ ะฒ ะฒะฐัˆะตะน ะ‘ะ” (ะฝะฐะฟั€ะธะผะตั€, ั ะฟะพะผะพั‰ัŒัŽ `jsonable_encoder`). * ะญั‚ะพ ัั€ะฐะฒะฝะธะผะพ ั ะฟะพะฒั‚ะพั€ะฝั‹ะผ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ ะผะตั‚ะพะดะฐ ะผะพะดะตะปะธ `.model_dump()`, ะฝะพ ะฟั€ะธ ัั‚ะพะผ ะฟั€ะพะธัั…ะพะดะธั‚ ะฟั€ะพะฒะตั€ะบะฐ (ะธ ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐะฝะธะต) ะทะฝะฐั‡ะตะฝะธะน ะฒ ั‚ะธะฟั‹ ะดะฐะฝะฝั‹ั…, ะบะพั‚ะพั€ั‹ะต ะผะพะณัƒั‚ ะฑั‹ั‚ัŒ ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐะฝั‹ ะฒ JSON, ะฝะฐะฟั€ะธะผะตั€, `datetime` ะฒ `str`. @@ -97,7 +83,7 @@ /// tip | ะŸะพะดัะบะฐะทะบะฐ -ะญั‚ัƒ ะถะต ั‚ะตั…ะฝะธะบัƒ ะผะพะถะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธ ะดะปั ะพะฟะตั€ะฐั†ะธะธ HTTP `PUT`. +ะะฐ ัะฐะผะพะผ ะดะตะปะต ัั‚ัƒ ะถะต ั‚ะตั…ะฝะธะบัƒ ะผะพะถะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธ ะดะปั ะพะฟะตั€ะฐั†ะธะธ HTTP `PUT`. ะะพ ะฒ ะฟั€ะธะฒะตะดะตะฝะฝะพะผ ะฟั€ะธะผะตั€ะต ะธัะฟะพะปัŒะทัƒะตั‚ัั `PATCH`, ะฟะพัะบะพะปัŒะบัƒ ะพะฝ ะฑั‹ะป ัะพะทะดะฐะฝ ะธะผะตะฝะฝะพ ะดะปั ั‚ะฐะบะธั… ัะปัƒั‡ะฐะตะฒ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั. diff --git a/docs/ru/docs/tutorial/body.md b/docs/ru/docs/tutorial/body.md index b61f3e7a09..537d7ebc96 100644 --- a/docs/ru/docs/tutorial/body.md +++ b/docs/ru/docs/tutorial/body.md @@ -32,9 +32,10 @@ {* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} + ะขะฐะบ ะถะต, ะบะฐะบ ะฟั€ะธ ะพะฑัŠัะฒะปะตะฝะธะธ ะฟะฐั€ะฐะผะตั‚ั€ะพะฒ ะทะฐะฟั€ะพัะฐ: ะบะพะณะดะฐ ะฐั‚ั€ะธะฑัƒั‚ ะผะพะดะตะปะธ ะธะผะตะตั‚ ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ, ะพะฝ ะฝะต ะพะฑัะทะฐั‚ะตะปะตะฝ. ะ˜ะฝะฐั‡ะต ะพะฝ ะพะฑัะทะฐั‚ะตะปะตะฝ. ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต `None`, ั‡ั‚ะพะฑั‹ ัะดะตะปะฐั‚ัŒ ะตะณะพ ะฟั€ะพัั‚ะพ ะฝะตะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ. -ะะฐะฟั€ะธะผะตั€, ะผะพะดะตะปัŒ ะฒั‹ัˆะต ะพะฟะธัั‹ะฒะฐะตั‚ ั‚ะฐะบะพะน JSON "ะพะฑัŠะตะบั‚" (ะธะปะธ Python `dict`): +ะะฐะฟั€ะธะผะตั€, ะผะพะดะตะปัŒ ะฒั‹ัˆะต ะพะฟะธัั‹ะฒะฐะตั‚ ั‚ะฐะบะพะน JSON "`object`" (ะธะปะธ Python `dict`): ```JSON { @@ -45,7 +46,7 @@ } ``` -...ั‚ะฐะบ ะบะฐะบ `description` ะธ `tax` ัะฒะปััŽั‚ัั ะฝะตะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผะธ (ัะพ ะทะฝะฐั‡ะตะฝะธะตะผ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ `None`), ั‚ะฐะบะพะน JSON "ะพะฑัŠะตะบั‚" ั‚ะพะถะต ะฑัƒะดะตั‚ ะบะพั€ั€ะตะบั‚ะฝั‹ะผ: +...ั‚ะฐะบ ะบะฐะบ `description` ะธ `tax` ัะฒะปััŽั‚ัั ะฝะตะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผะธ (ัะพ ะทะฝะฐั‡ะตะฝะธะตะผ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ `None`), ั‚ะฐะบะพะน JSON "`object`" ั‚ะพะถะต ะฑัƒะดะตั‚ ะบะพั€ั€ะตะบั‚ะฝั‹ะผ: ```JSON { @@ -73,7 +74,7 @@ * ะŸะตั€ะตะดะฐัั‚ ะฟะพะปัƒั‡ะตะฝะฝั‹ะต ะดะฐะฝะฝั‹ะต ะฒ ะฟะฐั€ะฐะผะตั‚ั€ `item`. * ะŸะพัะบะพะปัŒะบัƒ ะฒะฝัƒั‚ั€ะธ ั„ัƒะฝะบั†ะธะธ ะฒั‹ ะพะฑัŠัะฒะธะปะธ ะตะณะพ ั ั‚ะธะฟะพะผ `Item`, ัƒ ะฒะฐั ะฑัƒะดะตั‚ ะฟะพะดะดะตั€ะถะบะฐ ัะพ ัั‚ะพั€ะพะฝั‹ ั€ะตะดะฐะบั‚ะพั€ะฐ ะบะพะดะฐ (ะฐะฒั‚ะพะทะฐะฒะตั€ัˆะตะฝะธะต ะธ ั‚. ะฟ.) ะดะปั ะฒัะตั… ะฐั‚ั€ะธะฑัƒั‚ะพะฒ ะธ ะธั… ั‚ะธะฟะพะฒ. * ะกะณะตะฝะตั€ะธั€ัƒะตั‚ ะพะฟั€ะตะดะตะปะตะฝะธั <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> ะดะปั ะฒะฐัˆะตะน ะผะพะดะตะปะธ; ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธั… ะธ ะฒ ะดั€ัƒะณะธั… ะผะตัั‚ะฐั…, ะตัะปะธ ัั‚ะพ ะธะผะตะตั‚ ัะผั‹ัะป ะดะปั ะฒะฐัˆะตะณะพ ะฟั€ะพะตะบั‚ะฐ. -* ะญั‚ะธ ัั…ะตะผั‹ ะฑัƒะดัƒั‚ ั‡ะฐัั‚ัŒัŽ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝะพะน ัั…ะตะผั‹ OpenAPI ะธ ะฑัƒะดัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะตะน <abbr title="User Interfaces โ€“ ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ะธะฝั‚ะตั€ั„ะตะนัั‹">UIs</abbr>. +* ะญั‚ะธ ัั…ะตะผั‹ ะฑัƒะดัƒั‚ ั‡ะฐัั‚ัŒัŽ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝะพะน ัั…ะตะผั‹ OpenAPI ะธ ะฑัƒะดัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะตะน <abbr title="User Interfaces - ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ะธะฝั‚ะตั€ั„ะตะนัั‹">UIs</abbr>. ## ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะฐั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั { #automatic-docs } @@ -127,14 +128,6 @@ JSON Schema ะฒะฐัˆะธั… ะผะพะดะตะปะตะน ะฑัƒะดะตั‚ ั‡ะฐัั‚ัŒัŽ ัะณะตะฝะตั€ะธั€ะพ {* ../../docs_src/body/tutorial002_py310.py *} -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั - -ะ’ Pydantic v1 ะผะตั‚ะพะด ะฝะฐะทั‹ะฒะฐะปัั `.dict()`, ะฒ Pydantic v2 ะพะฝ ะฑั‹ะป ะฟะพะผะตั‡ะตะฝ ะบะฐะบ ัƒัั‚ะฐั€ะตะฒัˆะธะน (ะฝะพ ะฒัั‘ ะตั‰ั‘ ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั) ะธ ะฟะตั€ะตะธะผะตะฝะพะฒะฐะฝ ะฒ `.model_dump()`. - -ะŸั€ะธะผะตั€ั‹ ะทะดะตััŒ ะธัะฟะพะปัŒะทัƒัŽั‚ `.dict()` ะดะปั ัะพะฒะผะตัั‚ะธะผะพัั‚ะธ ั Pydantic v1, ะฝะพ ะตัะปะธ ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ Pydantic v2, ะธัะฟะพะปัŒะทัƒะนั‚ะต `.model_dump()`. - -/// - ## ะขะตะปะพ ะทะฐะฟั€ะพัะฐ + ะฟะฐั€ะฐะผะตั‚ั€ั‹ ะฟัƒั‚ะธ { #request-body-path-parameters } ะ’ั‹ ะผะพะถะตั‚ะต ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพ ะพะฑัŠัะฒะธั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ั‹ ะฟัƒั‚ะธ ะธ ั‚ะตะปะพ ะทะฐะฟั€ะพัะฐ. @@ -143,6 +136,7 @@ JSON Schema ะฒะฐัˆะธั… ะผะพะดะตะปะตะน ะฑัƒะดะตั‚ ั‡ะฐัั‚ัŒัŽ ัะณะตะฝะตั€ะธั€ะพ {* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} + ## ะขะตะปะพ ะทะฐะฟั€ะพัะฐ + ะฟะฐั€ะฐะผะตั‚ั€ั‹ ะฟัƒั‚ะธ + ะฟะฐั€ะฐะผะตั‚ั€ั‹ ะทะฐะฟั€ะพัะฐ { #request-body-path-query-parameters } ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพ ะพะฑัŠัะฒะธั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ั‹ **ั‚ะตะปะฐ**, **ะฟัƒั‚ะธ** ะธ **ะทะฐะฟั€ะพัะฐ**. @@ -153,7 +147,7 @@ JSON Schema ะฒะฐัˆะธั… ะผะพะดะตะปะตะน ะฑัƒะดะตั‚ ั‡ะฐัั‚ัŒัŽ ัะณะตะฝะตั€ะธั€ะพ ะŸะฐั€ะฐะผะตั‚ั€ั‹ ั„ัƒะฝะบั†ะธะธ ะฑัƒะดัƒั‚ ั€ะฐัะฟะพะทะฝะฐะฝั‹ ัะปะตะดัƒัŽั‰ะธะผ ะพะฑั€ะฐะทะพะผ: -* ะ•ัะปะธ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะฐะบะถะต ะพะฑัŠัะฒะปะตะฝ ะฒ **ะฟัƒั‚ะธ**, ะพะฝ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะบะฐะบ ะฟะฐั€ะฐะผะตั‚ั€ ะฟัƒั‚ะธ. +* ะ•ัะปะธ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะฐะบะถะต ะพะฑัŠัะฒะปะตะฝ ะฒ **ะฟัƒั‚ะธ**, ะพะฝ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะบะฐะบ path-ะฟะฐั€ะฐะผะตั‚ั€. * ะ•ัะปะธ ะฟะฐั€ะฐะผะตั‚ั€ ะธะผะตะตั‚ **ัะบะฐะปัั€ะฝั‹ะน ั‚ะธะฟ** (ะฝะฐะฟั€ะธะผะตั€, `int`, `float`, `str`, `bool` ะธ ั‚. ะฟ.), ะพะฝ ะฑัƒะดะตั‚ ะธะฝั‚ะตั€ะฟั€ะตั‚ะธั€ะพะฒะฐะฝ ะบะฐะบ ะฟะฐั€ะฐะผะตั‚ั€ **ะทะฐะฟั€ะพัะฐ**. * ะ•ัะปะธ ะฟะฐั€ะฐะผะตั‚ั€ ะพะฑัŠัะฒะปะตะฝ ะบะฐะบ ั‚ะธะฟ **ะผะพะดะตะปะธ Pydantic**, ะพะฝ ะฑัƒะดะตั‚ ะธะฝั‚ะตั€ะฟั€ะตั‚ะธั€ะพะฒะฐะฝ ะบะฐะบ **ั‚ะตะปะพ** ะทะฐะฟั€ะพัะฐ. @@ -161,7 +155,7 @@ JSON Schema ะฒะฐัˆะธั… ะผะพะดะตะปะตะน ะฑัƒะดะตั‚ ั‡ะฐัั‚ัŒัŽ ัะณะตะฝะตั€ะธั€ะพ FastAPI ะฟะพะฝะธะผะฐะตั‚, ั‡ั‚ะพ ะทะฝะฐั‡ะตะฝะธะต `q` ะฝะต ัะฒะปัะตั‚ัั ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ ะธะท-ะทะฐ ะทะฝะฐั‡ะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ `= None`. -ะะฝะฝะพั‚ะฐั†ะธะธ ั‚ะธะฟะพะฒ `str | None` (Python 3.10+) ะธะปะธ `Union[str, None]` (Python 3.9+) ะฝะต ะธัะฟะพะปัŒะทัƒัŽั‚ัั FastAPI ะดะปั ะพะฟั€ะตะดะตะปะตะฝะธั ะพะฑัะทะฐั‚ะตะปัŒะฝะพัั‚ะธ; ะพะฝ ัƒะทะฝะฐะตั‚, ั‡ั‚ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะฝะต ะพะฑัะทะฐั‚ะตะปะตะฝ, ะฟะพั‚ะพะผัƒ ั‡ั‚ะพ ัƒ ะฝะตะณะพ ะตัั‚ัŒ ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ `= None`. +ะะฝะฝะพั‚ะฐั†ะธะธ ั‚ะธะฟะพะฒ `str | None` (Python 3.10+) ะธะปะธ `Union` ะฒ `Union[str, None]` (Python 3.9+) ะฝะต ะธัะฟะพะปัŒะทัƒัŽั‚ัั FastAPI ะดะปั ะพะฟั€ะตะดะตะปะตะฝะธั ะพะฑัะทะฐั‚ะตะปัŒะฝะพัั‚ะธ; ะพะฝ ัƒะทะฝะฐะตั‚, ั‡ั‚ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะฝะต ะพะฑัะทะฐั‚ะตะปะตะฝ, ะฟะพั‚ะพะผัƒ ั‡ั‚ะพ ัƒ ะฝะตะณะพ ะตัั‚ัŒ ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ `= None`. ะะพ ะดะพะฑะฐะฒะปะตะฝะธะต ะฐะฝะฝะพั‚ะฐั†ะธะน ั‚ะธะฟะพะฒ ะฟะพะทะฒะพะปะธั‚ ะฒะฐัˆะตะผัƒ ั€ะตะดะฐะบั‚ะพั€ัƒ ะบะพะดะฐ ะปัƒั‡ัˆะต ะฒะฐั ะฟะพะดะดะตั€ะถะธะฒะฐั‚ัŒ ะธ ะพะฑะฝะฐั€ัƒะถะธะฒะฐั‚ัŒ ะพัˆะธะฑะบะธ. @@ -169,4 +163,4 @@ FastAPI ะฟะพะฝะธะผะฐะตั‚, ั‡ั‚ะพ ะทะฝะฐั‡ะตะฝะธะต `q` ะฝะต ัะฒะปัะตั‚ัั ะพะฑ ## ะ‘ะตะท Pydantic { #without-pydantic } -ะ•ัะปะธ ะฒั‹ ะฝะต ั…ะพั‚ะธั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะผะพะดะตะปะธ Pydantic, ะฒั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ั‹ **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/ru/docs/tutorial/extra-models.md b/docs/ru/docs/tutorial/extra-models.md index 2f0ce4e33e..03156f2b4e 100644 --- a/docs/ru/docs/tutorial/extra-models.md +++ b/docs/ru/docs/tutorial/extra-models.md @@ -22,21 +22,13 @@ {* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั +### ะŸั€ะพ `**user_in.model_dump()` { #about-user-in-model-dump } -ะ’ Pydantic v1 ะผะตั‚ะพะด ะฝะฐะทั‹ะฒะฐะปัั `.dict()`, ะฒ Pydantic v2 ะพะฝ ะฟะพะผะตั‡ะตะฝ ะบะฐะบ ัƒัั‚ะฐั€ะตะฒัˆะธะน (ะฝะพ ะฒัั‘ ะตั‰ั‘ ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั) ะธ ะฟะตั€ะตะธะผะตะฝะพะฒะฐะฝ ะฒ `.model_dump()`. +#### `.model_dump()` ะธะท Pydantic { #pydantics-model-dump } -ะ’ ะฟั€ะธะผะตั€ะฐั… ะทะดะตััŒ ะธัะฟะพะปัŒะทัƒะตั‚ัั `.dict()` ะดะปั ัะพะฒะผะตัั‚ะธะผะพัั‚ะธ ั Pydantic v1, ะฝะพ ะตัะปะธ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต Pydantic v2, ัะปะตะดัƒะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `.model_dump()`. +`user_in` โ€” ัั‚ะพ Pydantic-ะผะพะดะตะปัŒ ะบะปะฐััะฐ `UserIn`. -/// - -### ะŸั€ะพ `**user_in.dict()` { #about-user-in-dict } - -#### `.dict()` ะธะท Pydantic { #pydantics-dict } - -`user_in` - ัั‚ะพ Pydantic-ะผะพะดะตะปัŒ ะบะปะฐััะฐ `UserIn`. - -ะฃ Pydantic-ะผะพะดะตะปะตะน ะตัั‚ัŒ ะผะตั‚ะพะด `.dict()`, ะบะพั‚ะพั€ั‹ะน ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ `dict` ั ะดะฐะฝะฝั‹ะผะธ ะผะพะดะตะปะธ. +ะฃ Pydantic-ะผะพะดะตะปะตะน ะตัั‚ัŒ ะผะตั‚ะพะด `.model_dump()`, ะบะพั‚ะพั€ั‹ะน ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ `dict` ั ะดะฐะฝะฝั‹ะผะธ ะผะพะดะตะปะธ. ะŸะพัั‚ะพะผัƒ, ะตัะปะธ ะผั‹ ัะพะทะดะฐะดะธะผ Pydantic-ะพะฑัŠะตะบั‚ `user_in` ั‚ะฐะบะธะผ ัะฟะพัะพะฑะพะผ: @@ -47,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() ``` -ั‚ะพ ั‚ะตะฟะตั€ัŒ ัƒ ะฝะฐั ะตัั‚ัŒ `dict` ั ะดะฐะฝะฝั‹ะผะธ ะผะพะดะตะปะธ ะฒ ะฟะตั€ะตะผะตะฝะฝะพะน `user_dict` (ัั‚ะพ `dict` ะฒะผะตัั‚ะพ ะพะฑัŠะตะบั‚ะฐ Pydantic-ะผะพะดะตะปะธ). +ั‚ะพ ั‚ะตะฟะตั€ัŒ ัƒ ะฝะฐั ะตัั‚ัŒ `dict` ั ะดะฐะฝะฝั‹ะผะธ ะฒ ะฟะตั€ะตะผะตะฝะฝะพะน `user_dict` (ัั‚ะพ `dict` ะฒะผะตัั‚ะพ ะพะฑัŠะตะบั‚ะฐ Pydantic-ะผะพะดะตะปะธ). ะ˜ ะตัะปะธ ะผั‹ ะฒั‹ะทะพะฒะตะผ: @@ -58,7 +50,7 @@ user_dict = user_in.dict() print(user_dict) ``` -ะผั‹ ะผะพะถะตะผ ะฟะพะปัƒั‡ะธั‚ัŒ `dict` ั ั‚ะฐะบะธะผะธ ะดะฐะฝะฝั‹ะผะธ: +ะผั‹ ะฟะพะปัƒั‡ะธะผ Python `dict` ั: ```Python { @@ -71,7 +63,7 @@ print(user_dict) #### ะ ะฐัะฟะฐะบะพะฒะบะฐ `dict` { #unpacking-a-dict } -ะ•ัะปะธ ะผั‹ ะฒะพะทัŒะผั‘ะผ `dict` ะฝะฐะฟะพะดะพะฑะธะต `user_dict` ะธ ะฟะตั€ะตะดะฐะดะธะผ ะตะณะพ ะฒ ั„ัƒะฝะบั†ะธัŽ (ะธะปะธ ะบะปะฐัั), ะธัะฟะพะปัŒะทัƒั `**user_dict`, Python ั€ะฐัะฟะฐะบัƒะตั‚ ะตะณะพ. ะžะฝ ะฟะตั€ะตะดะฐัั‚ ะบะปัŽั‡ะธ ะธ ะทะฝะฐั‡ะตะฝะธั `user_dict` ะฝะฐะฟั€ัะผัƒัŽ ะบะฐะบ ะฐั€ะณัƒะผะตะฝั‚ั‹ ั‚ะธะฟะฐ ะบะปัŽั‡-ะทะฝะฐั‡ะตะฝะธะต. +ะ•ัะปะธ ะผั‹ ะฒะพะทัŒะผั‘ะผ `dict` ะฝะฐะฟะพะดะพะฑะธะต `user_dict` ะธ ะฟะตั€ะตะดะฐะดะธะผ ะตะณะพ ะฒ ั„ัƒะฝะบั†ะธัŽ (ะธะปะธ ะบะปะฐัั), ะธัะฟะพะปัŒะทัƒั `**user_dict`, Python ะตะณะพ "ั€ะฐัะฟะฐะบัƒะตั‚". ะžะฝ ะฟะตั€ะตะดะฐัั‚ ะบะปัŽั‡ะธ ะธ ะทะฝะฐั‡ะตะฝะธั `user_dict` ะฝะฐะฟั€ัะผัƒัŽ ะบะฐะบ ะฐั€ะณัƒะผะตะฝั‚ั‹ ั‚ะธะฟะฐ ะบะปัŽั‡-ะทะฝะฐั‡ะตะฝะธะต. ะŸะพัั‚ะพะผัƒ, ะฟั€ะพะดะพะปะถะฐั ะพะฟะธัะฐะฝะฝั‹ะน ะฒั‹ัˆะต ะฟั€ะธะผะตั€ ั `user_dict`, ะฝะฐะฟะธัะฐะฝะธะต ั‚ะฐะบะพะณะพ ะบะพะดะฐ: @@ -79,7 +71,7 @@ print(user_dict) UserInDB(**user_dict) ``` -ะ‘ัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ั‚ะฐะบ ะถะต, ะบะฐะบ ะฟั€ะธะผะตั€ะฝะพ ั‚ะฐะบะพะน ะบะพะด: +ะฑัƒะดะตั‚ ัะบะฒะธะฒะฐะปะตะฝั‚ะฝะพ: ```Python UserInDB( @@ -90,7 +82,7 @@ UserInDB( ) ``` -ะ˜ะปะธ, ะตัะปะธ ะดะปั ะฑะพะปัŒัˆะตะน ั‚ะพั‡ะฝะพัั‚ะธ ะผั‹ ะฝะฐะฟั€ัะผัƒัŽ ะธัะฟะพะปัŒะทัƒะตะผ `user_dict` ั ะปัŽะฑั‹ะผ ะฟะพั‚ะตะฝั†ะธะฐะปัŒะฝั‹ะผ ัะพะดะตั€ะถะธะผั‹ะผ, ั‚ะพ ัั‚ะพั‚ ะฟั€ะธะผะตั€ ะฑัƒะดะตั‚ ะฒั‹ะณะปัะดะตั‚ัŒ ั‚ะฐะบ: +ะ˜ะปะธ, ะฑะพะปะตะต ั‚ะพั‡ะฝะพ, ะตัะปะธ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `user_dict` ะฝะฐะฟั€ัะผัƒัŽ, ั ะปัŽะฑั‹ะผ ัะพะดะตั€ะถะธะผั‹ะผ, ะบะพั‚ะพั€ะพะต ะพะฝ ะผะพะถะตั‚ ะธะผะตั‚ัŒ ะฒ ะฑัƒะดัƒั‰ะตะผ: ```Python UserInDB( @@ -101,22 +93,22 @@ UserInDB( ) ``` -#### Pydantic-ะผะพะดะตะปัŒ ะธะท ัะพะดะตั€ะถะธะผะพะณะพ ะดั€ัƒะณะพะน ะผะพะดะตะปะธ { #a-pydantic-model-from-the-contents-of-another } +#### Pydantic-ะผะพะดะตะปัŒ ะธะท ัะพะดะตั€ะถะธะผะพะณะพ ะดั€ัƒะณะพะน { #a-pydantic-model-from-the-contents-of-another } -ะšะฐะบ ะฒ ะฟั€ะธะผะตั€ะต ะฒั‹ัˆะต ะผั‹ ะฟะพะปัƒั‡ะธะปะธ `user_dict` ะธะท `user_in.dict()`, ัั‚ะพั‚ ะบะพะด: +ะšะฐะบ ะฒ ะฟั€ะธะผะตั€ะต ะฒั‹ัˆะต ะผั‹ ะฟะพะปัƒั‡ะธะปะธ `user_dict` ะธะท `user_in.model_dump()`, ัั‚ะพั‚ ะบะพะด: ```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()` - ัั‚ะพ `dict`, ะธ ะทะฐั‚ะตะผ ะผั‹ ัƒะบะฐะทั‹ะฒะฐะตะผ, ั‡ั‚ะพะฑั‹ Python ะตะณะพ "ั€ะฐัะฟะฐะบะพะฒะฐะป", ะบะพะณะดะฐ ะฟะตั€ะตะดะฐั‘ะผ ะตะณะพ ะฒ `UserInDB` ะธ ัั‚ะฐะฒะธะผ ะฟะตั€ะตะด ะฝะธะผ `**`. +...ะฟะพั‚ะพะผัƒ ั‡ั‚ะพ `user_in.model_dump()` โ€” ัั‚ะพ `dict`, ะธ ะทะฐั‚ะตะผ ะผั‹ ัƒะบะฐะทั‹ะฒะฐะตะผ, ั‡ั‚ะพะฑั‹ Python ะตะณะพ "ั€ะฐัะฟะฐะบะพะฒะฐะป", ะบะพะณะดะฐ ะฟะตั€ะตะดะฐั‘ะผ ะตะณะพ ะฒ `UserInDB` ั ะฟั€ะตั„ะธะบัะพะผ `**`. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ ะผั‹ ะฟะพะปัƒั‡ะฐะตะผ Pydantic-ะผะพะดะตะปัŒ ะฝะฐ ะพัะฝะพะฒะต ะดะฐะฝะฝั‹ั… ะธะท ะดั€ัƒะณะพะน Pydantic-ะผะพะดะตะปะธ. @@ -125,10 +117,10 @@ UserInDB(**user_in.dict()) ะ˜ ะทะฐั‚ะตะผ, ะตัะปะธ ะผั‹ ะดะพะฑะฐะฒะธะผ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะน ะธะผะตะฝะพะฒะฐะฝะฝั‹ะน ะฐั€ะณัƒะผะตะฝั‚ `hashed_password=hashed_password` ะบะฐะบ ะทะดะตััŒ: ```Python -UserInDB(**user_in.dict(), hashed_password=hashed_password) +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) ``` -... ั‚ะพ ะผั‹ ะฟะพะปัƒั‡ะธะผ ั‡ั‚ะพ-ั‚ะพ ะฟะพะดะพะฑะฝะพะต: +...ั‚ะพ ะฒ ะธั‚ะพะณะต ะฟะพะปัƒั‡ะธั‚ัั ั‡ั‚ะพ-ั‚ะพ ะฟะพะดะพะฑะฝะพะต: ```Python UserInDB( @@ -142,13 +134,13 @@ UserInDB( /// warning | ะŸั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต -ะ’ัะฟะพะผะพะณะฐั‚ะตะปัŒะฝั‹ะต ั„ัƒะฝะบั†ะธะธ `fake_password_hasher` ะธ `fake_save_user` ะธัะฟะพะปัŒะทัƒัŽั‚ัั ั‚ะพะปัŒะบะพ ะดะปั ะดะตะผะพะฝัั‚ั€ะฐั†ะธะธ ะฒะพะทะผะพะถะฝะพะณะพ ะฟะพั‚ะพะบะฐ ะดะฐะฝะฝั‹ั… ะธ, ะบะพะฝะตั‡ะฝะพ, ะฝะต ะพะฑะตัะฟะตั‡ะธะฒะฐัŽั‚ ะฝะฐัั‚ะพัั‰ัƒัŽ ะฑะตะทะพะฟะฐัะฝะพัั‚ัŒ. +ะ’ัะฟะพะผะพะณะฐั‚ะตะปัŒะฝั‹ะต ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ั„ัƒะฝะบั†ะธะธ `fake_password_hasher` ะธ `fake_save_user` ะธัะฟะพะปัŒะทัƒัŽั‚ัั ั‚ะพะปัŒะบะพ ะดะปั ะดะตะผะพะฝัั‚ั€ะฐั†ะธะธ ะฒะพะทะผะพะถะฝะพะณะพ ะฟะพั‚ะพะบะฐ ะดะฐะฝะฝั‹ั… ะธ, ะบะพะฝะตั‡ะฝะพ, ะฝะต ะพะฑะตัะฟะตั‡ะธะฒะฐัŽั‚ ะฝะฐัั‚ะพัั‰ัƒัŽ ะฑะตะทะพะฟะฐัะฝะพัั‚ัŒ. /// ## ะกะพะบั€ะฐั‚ะธั‚ะต ะดัƒะฑะปะธั€ะพะฒะฐะฝะธะต { #reduce-duplication } -ะกะพะบั€ะฐั‰ะตะฝะธะต ะดัƒะฑะปะธั€ะพะฒะฐะฝะธั ะบะพะดะฐ - ัั‚ะพ ะพะดะฝะฐ ะธะท ะณะปะฐะฒะฝั‹ั… ะธะดะตะน **FastAPI**. +ะกะพะบั€ะฐั‰ะตะฝะธะต ะดัƒะฑะปะธั€ะพะฒะฐะฝะธั ะบะพะดะฐ โ€” ัั‚ะพ ะพะดะฝะฐ ะธะท ะณะปะฐะฒะฝั‹ั… ะธะดะตะน **FastAPI**. ะŸะพัะบะพะปัŒะบัƒ ะดัƒะฑะปะธั€ะพะฒะฐะฝะธะต ะบะพะดะฐ ะฟะพะฒั‹ัˆะฐะตั‚ ั€ะธัะบ ะฟะพัะฒะปะตะฝะธั ะฑะฐะณะพะฒ, ะฟั€ะพะฑะปะตะผ ั ะฑะตะทะพะฟะฐัะฝะพัั‚ัŒัŽ, ะฟั€ะพะฑะปะตะผ ะดะตัะธะฝั…ั€ะพะฝะธะทะฐั†ะธะธ ะบะพะดะฐ (ะบะพะณะดะฐ ะฒั‹ ะพะฑะฝะพะฒะปัะตั‚ะต ะบะพะด ะฒ ะพะดะฝะพะผ ะผะตัั‚ะต, ะฝะพ ะฝะต ะพะฑะฝะพะฒะปัะตั‚ะต ะฒ ะดั€ัƒะณะพะผ), ะธ ั‚.ะด. @@ -166,7 +158,7 @@ UserInDB( ## `Union` ะธะปะธ `anyOf` { #union-or-anyof } -ะ’ั‹ ะผะพะถะตั‚ะต ะพะฟั€ะตะดะตะปะธั‚ัŒ ะพั‚ะฒะตั‚ ะบะฐะบ `Union` ะธะท ะดะฒัƒั… ะธะปะธ ะฑะพะปะตะต ั‚ะธะฟะพะฒ. ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะพั‚ะฒะตั‚ ะดะพะปะถะตะฝ ัะพะพั‚ะฒะตั‚ัั‚ะฒะพะฒะฐั‚ัŒ ะพะดะฝะพะผัƒ ะธะท ะฝะธั…. +ะ’ั‹ ะผะพะถะตั‚ะต ะพะฑัŠัะฒะธั‚ัŒ HTTP-ะพั‚ะฒะตั‚ ะบะฐะบ `Union` ะธะท ะดะฒัƒั… ะธะปะธ ะฑะพะปะตะต ั‚ะธะฟะพะฒ. ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ HTTP-ะพั‚ะฒะตั‚ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะปัŽะฑั‹ะผ ะธะท ะฝะธั…. ะžะฝ ะฑัƒะดะตั‚ ะพะฟั€ะตะดะตะปั‘ะฝ ะฒ OpenAPI ะบะฐะบ `anyOf`. @@ -174,7 +166,7 @@ UserInDB( /// note | ะŸั€ะธะผะตั‡ะฐะฝะธะต -ะŸั€ะธ ะพะฑัŠัะฒะปะตะฝะธะธ <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a>, ัะฝะฐั‡ะฐะปะฐ ัƒะบะฐะทั‹ะฒะฐะนั‚ะต ะฝะฐะธะฑะพะปะตะต ะดะตั‚ะฐะปัŒะฝั‹ะต ั‚ะธะฟั‹, ะทะฐั‚ะตะผ ะผะตะฝะตะต ะดะตั‚ะฐะปัŒะฝั‹ะต. ะ’ ะฟั€ะธะผะตั€ะต ะฝะธะถะต ะฑะพะปะตะต ะดะตั‚ะฐะปัŒะฝั‹ะน `PlaneItem` ัั‚ะพะธั‚ ะฟะตั€ะตะด `CarItem` ะฒ `Union[PlaneItem, CarItem]`. +ะŸั€ะธ ะพะฑัŠัะฒะปะตะฝะธะธ <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a> ัะฝะฐั‡ะฐะปะฐ ัƒะบะฐะทั‹ะฒะฐะนั‚ะต ะฝะฐะธะฑะพะปะตะต ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน ั‚ะธะฟ, ะทะฐั‚ะตะผ ะผะตะฝะตะต ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน. ะ’ ะฟั€ะธะผะตั€ะต ะฝะธะถะต ะฑะพะปะตะต ัะฟะตั†ะธั„ะธั‡ะฝั‹ะน `PlaneItem` ัั‚ะพะธั‚ ะฟะตั€ะตะด `CarItem` ะฒ `Union[PlaneItem, CarItem]`. /// @@ -192,19 +184,19 @@ UserInDB( some_variable: PlaneItem | CarItem ``` -ะะพ ะตัะปะธ ะผั‹ ะฟะพะผะตั‰ะฐะตะผ ะตะณะพ ะฒ `response_model=PlaneItem | CarItem` ะผั‹ ะฟะพะปัƒั‡ะธะผ ะพัˆะธะฑะบัƒ, ะฟะพั‚ะพะผัƒ ั‡ั‚ะพ Python ะฟะพะฟั‹ั‚ะฐะตั‚ัั ะฟั€ะพะธะทะฒะตัั‚ะธ **ะฝะตะบะพั€ั€ะตะบั‚ะฝัƒัŽ ะพะฟะตั€ะฐั†ะธัŽ** ะผะตะถะดัƒ `PlaneItem` ะธ `CarItem` ะฒะผะตัั‚ะพ ั‚ะพะณะพ, ั‡ั‚ะพะฑั‹ ะธะฝั‚ะตั€ะฟั€ะตั‚ะธั€ะพะฒะฐั‚ัŒ ัั‚ะพ ะบะฐะบ ะฐะฝะฝะพั‚ะฐั†ะธัŽ ั‚ะธะฟะฐ. +ะะพ ะตัะปะธ ะผั‹ ะฟะพะผะตัั‚ะธะผ ัั‚ะพ ะฒ ะฟั€ะธัะฒะฐะธะฒะฐะฝะธะต `response_model=PlaneItem | CarItem`, ะผั‹ ะฟะพะปัƒั‡ะธะผ ะพัˆะธะฑะบัƒ, ะฟะพั‚ะพะผัƒ ั‡ั‚ะพ Python ะฟะพะฟั‹ั‚ะฐะตั‚ัั ะฟั€ะพะธะทะฒะตัั‚ะธ **ะฝะตะบะพั€ั€ะตะบั‚ะฝัƒัŽ ะพะฟะตั€ะฐั†ะธัŽ** ะผะตะถะดัƒ `PlaneItem` ะธ `CarItem` ะฒะผะตัั‚ะพ ั‚ะพะณะพ, ั‡ั‚ะพะฑั‹ ะธะฝั‚ะตั€ะฟั€ะตั‚ะธั€ะพะฒะฐั‚ัŒ ัั‚ะพ ะบะฐะบ ะฐะฝะฝะพั‚ะฐั†ะธัŽ ั‚ะธะฟะฐ. ## ะกะฟะธัะพะบ ะผะพะดะตะปะตะน { #list-of-models } -ะขะฐะบะธะผ ะถะต ะพะฑั€ะฐะทะพะผ ะฒั‹ ะผะพะถะตั‚ะต ะพะฟั€ะตะดะตะปัั‚ัŒ ะพั‚ะฒะตั‚ั‹ ะบะฐะบ ัะฟะธัะบะธ ะพะฑัŠะตะบั‚ะพะฒ. +ะขะฐะบะธะผ ะถะต ะพะฑั€ะฐะทะพะผ ะฒั‹ ะผะพะถะตั‚ะต ะพะฑัŠัะฒะปัั‚ัŒ HTTP-ะพั‚ะฒะตั‚ั‹, ะฒะพะทะฒั€ะฐั‰ะฐัŽั‰ะธะต ัะฟะธัะบะธ ะพะฑัŠะตะบั‚ะพะฒ. -ะ”ะปั ัั‚ะพะณะพ ะธัะฟะพะปัŒะทัƒะนั‚ะต `typing.List` ะธะท ัั‚ะฐะฝะดะฐั€ั‚ะฝะพะน ะฑะธะฑะปะธะพั‚ะตะบะธ Python (ะธะปะธ ะฟั€ะพัั‚ะพ `list` ะฒ Python 3.9 ะธ ะฒั‹ัˆะต): +ะ”ะปั ัั‚ะพะณะพ ะธัะฟะพะปัŒะทัƒะนั‚ะต ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะน `typing.List` ะฒ Python (ะธะปะธ ะฟั€ะพัั‚ะพ `list` ะฒ Python 3.9 ะธ ะฒั‹ัˆะต): {* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} ## ะžั‚ะฒะตั‚ ั ะฟั€ะพะธะทะฒะพะปัŒะฝั‹ะผ `dict` { #response-with-arbitrary-dict } -ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะพะฟั€ะตะดะตะปะธั‚ัŒ ะพั‚ะฒะตั‚, ะธัะฟะพะปัŒะทัƒั ะฟั€ะพะธะทะฒะพะปัŒะฝั‹ะน ะพะดะฝะพัƒั€ะพะฒะฝะตะฒั‹ะน `dict` ะธ ะพะฟั€ะตะดะตะปัั ั‚ะพะปัŒะบะพ ั‚ะธะฟั‹ ะบะปัŽั‡ะตะน ะธ ะทะฝะฐั‡ะตะฝะธะน ะฑะตะท ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั Pydantic-ะผะพะดะตะปะตะน. +ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะพะฑัŠัะฒะธั‚ัŒ HTTP-ะพั‚ะฒะตั‚, ะธัะฟะพะปัŒะทัƒั ะพะฑั‹ั‡ะฝั‹ะน ะฟั€ะพะธะทะฒะพะปัŒะฝั‹ะน `dict`, ะพะฑัŠัะฒะธะฒ ั‚ะพะปัŒะบะพ ั‚ะธะฟ ะบะปัŽั‡ะตะน ะธ ะทะฝะฐั‡ะตะฝะธะน, ะฑะตะท ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั Pydantic-ะผะพะดะตะปะธ. ะญั‚ะพ ะฟะพะปะตะทะฝะพ, ะตัะปะธ ะฒั‹ ะทะฐั€ะฐะฝะตะต ะฝะต ะทะฝะฐะตั‚ะต ะบะพั€ั€ะตะบั‚ะฝั‹ั… ะฝะฐะทะฒะฐะฝะธะน ะฟะพะปะตะน/ะฐั‚ั€ะธะฑัƒั‚ะพะฒ (ะบะพั‚ะพั€ั‹ะต ะฑัƒะดัƒั‚ ะฝัƒะถะฝั‹ ะฟั€ะธ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ Pydantic-ะผะพะดะตะปะธ). @@ -214,6 +206,6 @@ some_variable: PlaneItem | CarItem ## ะ ะตะทัŽะผะต { #recap } -ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะฝะตัะบะพะปัŒะบะพ Pydantic-ะผะพะดะตะปะตะน ะธ ัะฒะพะฑะพะดะฝะพ ะฟั€ะธะผะตะฝัะนั‚ะต ะฝะฐัะปะตะดะพะฒะฐะฝะธะต ะดะปั ะบะฐะถะดะพะน ะธะท ะฝะธั…. +ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะฝะตัะบะพะปัŒะบะพ Pydantic-ะผะพะดะตะปะตะน ะธ ัะฒะพะฑะพะดะฝะพ ะฟั€ะธะผะตะฝัะนั‚ะต ะฝะฐัะปะตะดะพะฒะฐะฝะธะต ะดะปั ะบะฐะถะดะพะณะพ ัะปัƒั‡ะฐั. -ะ’ะฐะผ ะฝะต ะพะฑัะทะฐั‚ะตะปัŒะฝะพ ะธะผะตั‚ัŒ ะตะดะธะฝัั‚ะฒะตะฝะฝัƒัŽ ะผะพะดะตะปัŒ ะดะฐะฝะฝั‹ั… ะดะปั ะบะฐะถะดะพะน ััƒั‰ะฝะพัั‚ะธ, ะตัะปะธ ัั‚ะฐ ััƒั‰ะฝะพัั‚ัŒ ะดะพะปะถะฝะฐ ะธะผะตั‚ัŒ ะฒะพะทะผะพะถะฝะพัั‚ัŒ ะฑั‹ั‚ัŒ ะฒ ั€ะฐะทะฝั‹ั… "ัะพัั‚ะพัะฝะธัั…". ะšะฐะบ ะฒ ัะปัƒั‡ะฐะต ั "ััƒั‰ะฝะพัั‚ัŒัŽ" ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั, ัƒ ะบะพั‚ะพั€ะพะณะพ ะตัั‚ัŒ ัะพัั‚ะพัะฝะธั ั ะฟะพะปัะผะธ `password`, `password_hash` ะธ ะฑะตะท ะฟะฐั€ะพะปั. +ะ’ะฐะผ ะฝะต ะพะฑัะทะฐั‚ะตะปัŒะฝะพ ะธะผะตั‚ัŒ ะตะดะธะฝัั‚ะฒะตะฝะฝัƒัŽ ะผะพะดะตะปัŒ ะดะฐะฝะฝั‹ั… ะดะปั ะบะฐะถะดะพะน ััƒั‰ะฝะพัั‚ะธ, ะตัะปะธ ัั‚ะฐ ััƒั‰ะฝะพัั‚ัŒ ะดะพะปะถะฝะฐ ะธะผะตั‚ัŒ ะฒะพะทะผะพะถะฝะพัั‚ัŒ ะฑั‹ั‚ัŒ ะฒ ั€ะฐะทะฝั‹ั… "ัะพัั‚ะพัะฝะธัั…". ะšะฐะบ ะฒ ัะปัƒั‡ะฐะต ั "ััƒั‰ะฝะพัั‚ัŒัŽ" ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั, ัƒ ะบะพั‚ะพั€ะพะณะพ ะตัั‚ัŒ ัะพัั‚ะพัะฝะธะต, ะฒะบะปัŽั‡ะฐัŽั‰ะตะต `password`, `password_hash` ะธ ะพั‚ััƒั‚ัั‚ะฒะธะต ะฟะฐั€ะพะปั. diff --git a/docs/ru/docs/tutorial/query-params-str-validations.md b/docs/ru/docs/tutorial/query-params-str-validations.md index 3a4ecc37dc..2bc2fb22c5 100644 --- a/docs/ru/docs/tutorial/query-params-str-validations.md +++ b/docs/ru/docs/tutorial/query-params-str-validations.md @@ -8,7 +8,7 @@ Query-ะฟะฐั€ะฐะผะตั‚ั€ `q` ะธะผะตะตั‚ ั‚ะธะฟ `str | None`, ัั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะพะฝ ะธะผะตะตั‚ ั‚ะธะฟ `str`, ะฝะพ ั‚ะฐะบะถะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ `None`. ะ—ะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝะพ `None`, ะฟะพัั‚ะพะผัƒ FastAPI ะฑัƒะดะตั‚ ะทะฝะฐั‚ัŒ, ั‡ั‚ะพ ะพะฝ ะฝะต ะพะฑัะทะฐั‚ะตะปะตะฝ. -/// note | ะขะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ +/// note | ะŸั€ะธะผะตั‡ะฐะฝะธะต FastAPI ะฟะพะนะผั‘ั‚, ั‡ั‚ะพ ะทะฝะฐั‡ะตะฝะธะต `q` ะฝะต ะพะฑัะทะฐั‚ะตะปัŒะฝะพ, ะธะทโ€‘ะทะฐ ะทะฝะฐั‡ะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ `= None`. @@ -177,7 +177,7 @@ q: str = Query(default="rick") **ะ—ะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ** ัƒ **ะฟะฐั€ะฐะผะตั‚ั€ะฐ ั„ัƒะฝะบั†ะธะธ** โ€” ัั‚ะพ **ะฝะฐัั‚ะพัั‰ะตะต ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ**, ั‡ั‚ะพ ะฑะพะปะตะต ะธะฝั‚ัƒะธั‚ะธะฒะฝะพ ะดะปั Python. ๐Ÿ˜Œ -ะ’ั‹ ะผะพะถะตั‚ะต **ะฒั‹ะทะฒะฐั‚ัŒ** ัั‚ัƒ ะถะต ั„ัƒะฝะบั†ะธัŽ ะฒ **ะดั€ัƒะณะธั… ะผะตัั‚ะฐั…** ะฑะตะท FastAPI, ะธ ะพะฝะฐ ะฑัƒะดะตั‚ **ั€ะฐะฑะพั‚ะฐั‚ัŒ ะบะฐะบ ะพะถะธะดะฐะตั‚ัั**. ะ•ัะปะธ ะตัั‚ัŒ **ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะน** ะฟะฐั€ะฐะผะตั‚ั€ (ะฑะตะท ะทะฝะฐั‡ะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ), ะฒะฐัˆ **ั€ะตะดะฐะบั‚ะพั€ ะบะพะดะฐ** ัะพะพะฑั‰ะธั‚ ะพะฑ ะพัˆะธะฑะบะต, **Python** ั‚ะพะถะต ะฟะพะถะฐะปัƒะตั‚ัั, ะตัะปะธ ะฒั‹ ะทะฐะฟัƒัั‚ะธั‚ะต ะตั‘ ะฑะตะท ะฟะตั€ะตะดะฐั‡ะธ ะพะฑัะทะฐั‚ะตะปัŒะฝะพะณะพ ะฟะฐั€ะฐะผะตั‚ั€ะฐ. +ะ’ั‹ ะผะพะถะตั‚ะต **ะฒั‹ะทะฒะฐั‚ัŒ** ัั‚ัƒ ะถะต ั„ัƒะฝะบั†ะธัŽ ะฒ **ะดั€ัƒะณะธั… ะผะตัั‚ะฐั…** ะฑะตะท FastAPI, ะธ ะพะฝะฐ ะฑัƒะดะตั‚ **ั€ะฐะฑะพั‚ะฐั‚ัŒ ะบะฐะบ ะพะถะธะดะฐะตั‚ัั**. ะ•ัะปะธ ะตัั‚ัŒ **ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะน** ะฟะฐั€ะฐะผะตั‚ั€ (ะฑะตะท ะทะฝะฐั‡ะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ), ะฒะฐัˆ **ั€ะตะดะฐะบั‚ะพั€** ัะพะพะฑั‰ะธั‚ ะพะฑ ะพัˆะธะฑะบะต, **Python** ั‚ะพะถะต ะฟะพะถะฐะปัƒะตั‚ัั, ะตัะปะธ ะฒั‹ ะทะฐะฟัƒัั‚ะธั‚ะต ะตั‘ ะฑะตะท ะฟะตั€ะตะดะฐั‡ะธ ะพะฑัะทะฐั‚ะตะปัŒะฝะพะณะพ ะฟะฐั€ะฐะผะตั‚ั€ะฐ. ะ•ัะปะธ ะฒั‹ ะฝะต ะธัะฟะพะปัŒะทัƒะตั‚ะต `Annotated`, ะฐ ะฟั€ะธะผะตะฝัะตั‚ะต **(ัƒัั‚ะฐั€ะตะฒัˆะธะน) ัั‚ะธะปัŒ ัะพ ะทะฝะฐั‡ะตะฝะธะตะผ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ**, ั‚ะพ ะฟั€ะธ ะฒั‹ะทะพะฒะต ัั‚ะพะน ั„ัƒะฝะบั†ะธะธ ะฑะตะท FastAPI ะฒ **ะดั€ัƒะณะธั… ะผะตัั‚ะฐั…** ะฒะฐะผ ะฝัƒะถะฝะพ **ะฟะพะผะฝะธั‚ัŒ** ะพ ั‚ะพะผ, ั‡ั‚ะพ ะฝะฐะดะพ ะฟะตั€ะตะดะฐั‚ัŒ ะฐั€ะณัƒะผะตะฝั‚ั‹, ั‡ั‚ะพะฑั‹ ะฒัั‘ ั€ะฐะฑะพั‚ะฐะปะพ ะบะพั€ั€ะตะบั‚ะฝะพ, ะธะฝะฐั‡ะต ะทะฝะฐั‡ะตะฝะธั ะฑัƒะดัƒั‚ ะฝะต ั‚ะฐะบะธะผะธ, ะบะฐะบ ะฒั‹ ะพะถะธะดะฐะตั‚ะต (ะฝะฐะฟั€ะธะผะตั€, ะฒะผะตัั‚ะพ `str` ะฑัƒะดะตั‚ `QueryInfo` ะธะปะธ ั‡ั‚ะพ-ั‚ะพ ะฟะพะดะพะฑะฝะพะต). ะ˜ ะฝะธ ั€ะตะดะฐะบั‚ะพั€, ะฝะธ Python ะฝะต ะฑัƒะดัƒั‚ ั€ัƒะณะฐั‚ัŒัั ะฟั€ะธ ัะฐะผะพะผ ะฒั‹ะทะพะฒะต ั„ัƒะฝะบั†ะธะธ โ€” ะพัˆะธะฑะบะฐ ะฟั€ะพัะฒะธั‚ัั ะปะธัˆัŒ ะฟั€ะธ ะพะฟะตั€ะฐั†ะธัั… ะฒะฝัƒั‚ั€ะธ. @@ -191,7 +191,7 @@ q: str = Query(default="rick") ## ะ ะตะณัƒะปัั€ะฝั‹ะต ะฒั‹ั€ะฐะถะตะฝะธั { #add-regular-expressions } -ะ’ั‹ ะผะพะถะตั‚ะต ะพะฟั€ะตะดะตะปะธั‚ัŒ <abbr title="ะ ะตะณัƒะปัั€ะฝะพะต ะฒั‹ั€ะฐะถะตะฝะธะต (regex, regexp) โ€” ัั‚ะพ ะฟะพัะปะตะดะพะฒะฐั‚ะตะปัŒะฝะพัั‚ัŒ ัะธะผะฒะพะปะพะฒ, ะทะฐะดะฐัŽั‰ะฐั ัˆะฐะฑะปะพะฝ ะฟะพะธัะบะฐ ะดะปั ัั‚ั€ะพะบ.">ั€ะตะณัƒะปัั€ะฝะพะต ะฒั‹ั€ะฐะถะตะฝะธะต</abbr> `pattern`, ะบะพั‚ะพั€ะพะผัƒ ะดะพะปะถะตะฝ ัะพะพั‚ะฒะตั‚ัั‚ะฒะพะฒะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€: +ะ’ั‹ ะผะพะถะตั‚ะต ะพะฟั€ะตะดะตะปะธั‚ัŒ <abbr title="ะ ะตะณัƒะปัั€ะฝะพะต ะฒั‹ั€ะฐะถะตะฝะธะต (regex, regexp) - ัั‚ะพ ะฟะพัะปะตะดะพะฒะฐั‚ะตะปัŒะฝะพัั‚ัŒ ัะธะผะฒะพะปะพะฒ, ะทะฐะดะฐัŽั‰ะฐั ัˆะฐะฑะปะพะฝ ะฟะพะธัะบะฐ ะดะปั ัั‚ั€ะพะบ.">ั€ะตะณัƒะปัั€ะฝะพะต ะฒั‹ั€ะฐะถะตะฝะธะต</abbr> `pattern`, ะบะพั‚ะพั€ะพะผัƒ ะดะพะปะถะตะฝ ัะพะพั‚ะฒะตั‚ัั‚ะฒะพะฒะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -205,20 +205,6 @@ q: str = Query(default="rick") ะขะตะฟะตั€ัŒ ะฒั‹ ะทะฝะฐะตั‚ะต, ั‡ั‚ะพ ะบะพะณะดะฐ ะพะฝะธ ะฟะพะฝะฐะดะพะฑัั‚ัั, ะฒั‹ ัะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธั… ะฒ **FastAPI**. -### `regex` ะธะท Pydantic v1 ะฒะผะตัั‚ะพ `pattern` { #pydantic-v1-regex-instead-of-pattern } - -ะ”ะพ Pydantic ะฒะตั€ัะธะธ 2 ะธ ะดะพ FastAPI 0.100.0 ัั‚ะพั‚ ะฟะฐั€ะฐะผะตั‚ั€ ะฝะฐะทั‹ะฒะฐะปัั `regex`, ะฐ ะฝะต `pattern`, ะฝะพ ัะตะนั‡ะฐั ะพะฝ ัƒัั‚ะฐั€ะตะป. - -ะ’ั‹ ะฒัั‘ ะตั‰ั‘ ะผะพะถะตั‚ะต ะฒัั‚ั€ะตั‚ะธั‚ัŒ ั‚ะฐะบะพะน ะบะพะด: - -//// tab | Pydantic v1 - -{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *} - -//// - -ะ˜ะผะตะนั‚ะต ะฒ ะฒะธะดัƒ, ั‡ั‚ะพ ัั‚ะพ ัƒัั‚ะฐั€ะตะปะพ, ะธ ะบะพะด ัะปะตะดัƒะตั‚ ะพะฑะฝะพะฒะธั‚ัŒ ะฝะฐ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฝะพะฒะพะณะพ ะฟะฐั€ะฐะผะตั‚ั€ะฐ `pattern`. ๐Ÿค“ - ## ะ—ะฝะฐั‡ะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ { #default-values } ะšะพะฝะตั‡ะฝะพ, ะผะพะถะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธ ะดั€ัƒะณะธะต ะทะฝะฐั‡ะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ, ะฝะต ั‚ะพะปัŒะบะพ `None`. @@ -279,7 +265,7 @@ q: Annotated[str | None, Query(min_length=3)] = None http://localhost:8000/items/?q=foo&q=bar ``` -ะฒั‹ ะฟะพะปัƒั‡ะธั‚ะต ะผะฝะพะถะตัั‚ะฒะตะฝะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั query-ะฟะฐั€ะฐะผะตั‚ั€ะฐ `q` (`foo` ะธ `bar`) ะฒ ะฒะธะดะต Python-`list` ะฒะฝัƒั‚ั€ะธ ะฒะฐัˆะตะน *ั„ัƒะฝะบั†ะธะธ ะพะฑั€ะฐะฑะพั‚ะบะธ ะฟัƒั‚ะธ*, ะฒ *ะฟะฐั€ะฐะผะตั‚ั€ะต ั„ัƒะฝะบั†ะธะธ* `q`. +ะฒั‹ ะฟะพะปัƒั‡ะธั‚ะต ะผะฝะพะถะตัั‚ะฒะตะฝะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั *query-ะฟะฐั€ะฐะผะตั‚ั€ะพะฒ* `q` (`foo` ะธ `bar`) ะฒ ะฒะธะดะต Python-`list` ะฒะฝัƒั‚ั€ะธ ะฒะฐัˆะตะน *ั„ัƒะฝะบั†ะธะธ-ะพะฑั€ะฐะฑะพั‚ั‡ะธะบะฐ ะฟัƒั‚ะธ*, ะฒ *ะฟะฐั€ะฐะผะตั‚ั€ะต ั„ัƒะฝะบั†ะธะธ* `q`. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะพั‚ะฒะตั‚ ะฝะฐ ัั‚ะพั‚ URL ะฑัƒะดะตั‚: @@ -331,7 +317,7 @@ http://localhost:8000/items/ {* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} -/// note | ะขะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ +/// note | ะŸั€ะธะผะตั‡ะฐะฝะธะต ะ˜ะผะตะนั‚ะต ะฒ ะฒะธะดัƒ, ั‡ั‚ะพ ะฒ ัั‚ะพะผ ัะปัƒั‡ะฐะต FastAPI ะฝะต ะฑัƒะดะตั‚ ะฟั€ะพะฒะตั€ัั‚ัŒ ัะพะดะตั€ะถะธะผะพะต ัะฟะธัะบะฐ. @@ -345,7 +331,7 @@ http://localhost:8000/items/ ะญั‚ะฐ ะธะฝั„ะพั€ะผะฐั†ะธั ะฑัƒะดะตั‚ ะฒะบะปัŽั‡ะตะฝะฐ ะฒ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝัƒัŽ OpenAPI-ัั…ะตะผัƒ ะธ ะธัะฟะพะปัŒะทะพะฒะฐะฝะฐ ะธะฝั‚ะตั€ั„ะตะนัะฐะผะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ ะธ ะฒะฝะตัˆะฝะธะผะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ. -/// note | ะขะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ +/// note | ะŸั€ะธะผะตั‡ะฐะฝะธะต ะŸะพะผะฝะธั‚ะต, ั‡ั‚ะพ ั€ะฐะทะฝั‹ะต ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะผะพะณัƒั‚ ะธะผะตั‚ัŒ ั€ะฐะทะฝั‹ะน ัƒั€ะพะฒะตะฝัŒ ะฟะพะดะดะตั€ะถะบะธ OpenAPI. @@ -415,7 +401,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems /// -ะะฐะฟั€ะธะผะตั€, ัั‚ะฐ ะบะฐัั‚ะพะผะฝะฐั ะฟั€ะพะฒะตั€ะบะฐ ัƒะฑะตะถะดะฐะตั‚ัั, ั‡ั‚ะพ ID ัะปะตะผะตะฝั‚ะฐ ะฝะฐั‡ะธะฝะฐะตั‚ัั ั `isbn-` ะดะปั ะฝะพะผะตั€ะฐ ะบะฝะธะณะธ <abbr title="ISBN ะพะทะฝะฐั‡ะฐะตั‚ International Standard Book Number โ€“ ะœะตะถะดัƒะฝะฐั€ะพะดะฝั‹ะน ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะน ะบะฝะธะถะฝั‹ะน ะฝะพะผะตั€">ISBN</abbr> ะธะปะธ ั `imdb-` ะดะปั ID URL ั„ะธะปัŒะผะฐ ะฝะฐ <abbr title="IMDB (Internet Movie Database) โ€” ะฒะตะฑโ€‘ัะฐะนั‚ ั ะธะฝั„ะพั€ะผะฐั†ะธะตะน ะพ ั„ะธะปัŒะผะฐั…">IMDB</abbr>: +ะะฐะฟั€ะธะผะตั€, ัั‚ะฐ ะบะฐัั‚ะพะผะฝะฐั ะฟั€ะพะฒะตั€ะบะฐ ัƒะฑะตะถะดะฐะตั‚ัั, ั‡ั‚ะพ ID ัะปะตะผะตะฝั‚ะฐ ะฝะฐั‡ะธะฝะฐะตั‚ัั ั `isbn-` ะดะปั ะฝะพะผะตั€ะฐ ะบะฝะธะณะธ <abbr title="ISBN ะพะทะฝะฐั‡ะฐะตั‚ International Standard Book Number - ะœะตะถะดัƒะฝะฐั€ะพะดะฝั‹ะน ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะน ะบะฝะธะถะฝั‹ะน ะฝะพะผะตั€">ISBN</abbr> ะธะปะธ ั `imdb-` ะดะปั ID URL ั„ะธะปัŒะผะฐ ะฝะฐ <abbr title="IMDB (Internet Movie Database) - ะฒะตะฑโ€‘ัะฐะนั‚ ั ะธะฝั„ะพั€ะผะฐั†ะธะตะน ะพ ั„ะธะปัŒะผะฐั…">IMDB</abbr>: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -455,7 +441,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems ะ—ะฐั‚ะตะผ ั `random.choice()` ะผะพะถะฝะพ ะฟะพะปัƒั‡ะธั‚ัŒ **ัะปัƒั‡ะฐะนะฝะพะต ะทะฝะฐั‡ะตะฝะธะต** ะธะท ัะฟะธัะบะฐ โ€” ั‚ะพ ะตัั‚ัŒ ะบะพั€ั‚ะตะถ ะฒะธะดะฐ `(id, name)`. ะญั‚ะพ ะฑัƒะดะตั‚ ั‡ั‚ะพโ€‘ั‚ะพ ะฒั€ะพะดะต `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. -ะŸะพัะปะต ัั‚ะพะณะพ ะผั‹ **ั€ะฐัะฟะฐะบะพะฒั‹ะฒะฐะตะผ** ัั‚ะธ ะดะฒะฐ ะทะฝะฐั‡ะตะฝะธั ะบะพั€ั‚ะตะถะฐ ะฒ ะฟะตั€ะตะผะตะฝะฝั‹ะต `id` ะธ `name`. +ะŸะพัะปะต ัั‚ะพะณะพ ะผั‹ **ะฟั€ะธัะฒะฐะธะฒะฐะตะผ ัั‚ะธ ะดะฒะฐ ะทะฝะฐั‡ะตะฝะธั** ะบะพั€ั‚ะตะถะฐ ะฟะตั€ะตะผะตะฝะฝั‹ะผ `id` ะธ `name`. ะขะฐะบ ั‡ั‚ะพ, ะตัะปะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะฟะตั€ะตะดะฐะป ID ัะปะตะผะตะฝั‚ะฐ, ะพะฝ ะฒัั‘ ั€ะฐะฒะฝะพ ะฟะพะปัƒั‡ะธั‚ ัะปัƒั‡ะฐะนะฝัƒัŽ ั€ะตะบะพะผะตะฝะดะฐั†ะธัŽ. diff --git a/docs/ru/docs/tutorial/response-model.md b/docs/ru/docs/tutorial/response-model.md index 07308c1db2..22a811cd57 100644 --- a/docs/ru/docs/tutorial/response-model.md +++ b/docs/ru/docs/tutorial/response-model.md @@ -6,11 +6,11 @@ {* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} -FastAPI ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะพั‚ ั‚ะธะฟ ะพั‚ะฒะตั‚ะฐ ะดะปั: +FastAPI ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะพั‚ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ, ั‡ั‚ะพะฑั‹: -* **ะ’ะฐะปะธะดะฐั†ะธะธ** ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ั… ะดะฐะฝะฝั‹ั…. - * ะ•ัะปะธ ะดะฐะฝะฝั‹ะต ะฝะตะฒะฐะปะธะดะฝั‹ (ะฝะฐะฟั€ะธะผะตั€, ะพั‚ััƒั‚ัั‚ะฒัƒะตั‚ ะฟะพะปะต), ัั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะบะพะด *ะฒะฐัˆะตะณะพ* ะฟั€ะธะปะพะถะตะฝะธั ั€ะฐะฑะพั‚ะฐะตั‚ ะฝะตะบะพั€ั€ะตะบั‚ะฝะพ ะธ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ ะฝะต ั‚ะพ, ั‡ั‚ะพ ะดะพะปะถะตะฝ. ะ’ ั‚ะฐะบะพะผ ัะปัƒั‡ะฐะต ะฑัƒะดะตั‚ ะฒะพะทะฒั€ะฐั‰ะตะฝะฐ ะพัˆะธะฑะบะฐ ัะตั€ะฒะตั€ะฐ ะฒะผะตัั‚ะพ ะฝะตะฟั€ะฐะฒะธะปัŒะฝั‹ั… ะดะฐะฝะฝั‹ั…. ะขะฐะบ ะฒั‹ ะธ ะฒะฐัˆะธ ะบะปะธะตะฝั‚ั‹ ะผะพะถะตั‚ะต ะฑั‹ั‚ัŒ ัƒะฒะตั€ะตะฝั‹, ั‡ั‚ะพ ะฟะพะปัƒั‡ะธั‚ะต ะพะถะธะดะฐะตะผั‹ะต ะดะฐะฝะฝั‹ะต ะธ ะพะถะธะดะฐะตะผัƒัŽ ัั‚ั€ัƒะบั‚ัƒั€ัƒ. -* ะ”ะพะฑะฐะฒะปะตะฝะธั **JSON Schema** ะดะปั ะพั‚ะฒะตั‚ะฐ ะฒ OpenAPI *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. +* **ะ’ะฐะปะธะดะธั€ะพะฒะฐั‚ัŒ** ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะต ะดะฐะฝะฝั‹ะต. + * ะ•ัะปะธ ะดะฐะฝะฝั‹ะต ะฝะตะฒะฐะปะธะดะฝั‹ (ะฝะฐะฟั€ะธะผะตั€, ะพั‚ััƒั‚ัั‚ะฒัƒะตั‚ ะฟะพะปะต), ัั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะบะพะด *ะฒะฐัˆะตะณะพ* ะฟั€ะธะปะพะถะตะฝะธั ั€ะฐะฑะพั‚ะฐะตั‚ ะฝะตะบะพั€ั€ะตะบั‚ะฝะพ ะธ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ ะฝะต ั‚ะพ, ั‡ั‚ะพ ะดะพะปะถะตะฝ. ะ’ ั‚ะฐะบะพะผ ัะปัƒั‡ะฐะต ะฑัƒะดะตั‚ ะฒะพะทะฒั€ะฐั‰ะตะฝะฐ ะพัˆะธะฑะบะฐ ัะตั€ะฒะตั€ะฐ ะฒะผะตัั‚ะพ ะฝะตะฟั€ะฐะฒะธะปัŒะฝั‹ั… ะดะฐะฝะฝั‹ั…. ะขะฐะบ ะฒั‹ ะธ ะฒะฐัˆะธ ะบะปะธะตะฝั‚ั‹ ะผะพะถะตั‚ะต ะฑั‹ั‚ัŒ ัƒะฒะตั€ะตะฝั‹, ั‡ั‚ะพ ะฟะพะปัƒั‡ะธั‚ะต ะพะถะธะดะฐะตะผั‹ะต ะดะฐะฝะฝั‹ะต ะธ ะพะถะธะดะฐะตะผัƒัŽ ัั‚ั€ัƒะบั‚ัƒั€ัƒ ะดะฐะฝะฝั‹ั…. +* ะ”ะพะฑะฐะฒะธั‚ัŒ **JSON Schema** ะดะปั ะพั‚ะฒะตั‚ะฐ ะฒ OpenAPI *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. * ะญั‚ะพ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐะฝะพ **ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะตะน**. * ะญั‚ะพ ั‚ะฐะบะถะต ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐะฝะพ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะฐะผะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะณะตะฝะตั€ะฐั†ะธะธ ะบะปะธะตะฝั‚ัะบะพะณะพ ะบะพะดะฐ. @@ -23,7 +23,7 @@ FastAPI ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะพั‚ ั‚ะธะฟ ะพั‚ะฒะตั‚ะฐ ะดะปั: ะ‘ั‹ะฒะฐัŽั‚ ัะปัƒั‡ะฐะธ, ะบะพะณะดะฐ ะฒะฐะผ ะฝัƒะถะฝะพ ะธะปะธ ั…ะพั‡ะตั‚ัั ะฒะพะทะฒั€ะฐั‰ะฐั‚ัŒ ะดะฐะฝะฝั‹ะต, ะบะพั‚ะพั€ั‹ะต ะฝะต ะฒ ั‚ะพั‡ะฝะพัั‚ะธ ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‚ ะพะฑัŠัะฒะปะตะฝะฝะพะผัƒ ั‚ะธะฟัƒ. -ะะฐะฟั€ะธะผะตั€, ะฒั‹ ะผะพะถะตั‚ะต ั…ะพั‚ะตั‚ัŒ **ะฒะพะทะฒั€ะฐั‰ะฐั‚ัŒ ัะปะพะฒะฐั€ัŒ (dict)** ะธะปะธ ะพะฑัŠะตะบั‚ ะธะท ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…, ะฝะพ **ะพะฑัŠัะฒะธั‚ัŒ ะตะณะพ ะบะฐะบ Pydantic-ะผะพะดะตะปัŒ**. ะขะพะณะดะฐ Pydantic-ะผะพะดะตะปัŒ ะฒั‹ะฟะพะปะฝะธั‚ ะดะพะบัƒะผะตะฝั‚ะธั€ะพะฒะฐะฝะธะต ะดะฐะฝะฝั‹ั…, ะฒะฐะปะธะดะฐั†ะธัŽ ะธ ั‚.ะฟ. ะดะปั ะพะฑัŠะตะบั‚ะฐ, ะบะพั‚ะพั€ั‹ะน ะฒั‹ ะฒะตั€ะฝัƒะปะธ (ะฝะฐะฟั€ะธะผะตั€, ัะปะพะฒะฐั€ั ะธะปะธ ะพะฑัŠะตะบั‚ะฐ ะธะท ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…). +ะะฐะฟั€ะธะผะตั€, ะฒั‹ ะผะพะถะตั‚ะต ั…ะพั‚ะตั‚ัŒ **ะฒะพะทะฒั€ะฐั‰ะฐั‚ัŒ ัะปะพะฒะฐั€ัŒ** ะธะปะธ ะพะฑัŠะตะบั‚ ะธะท ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…, ะฝะพ **ะพะฑัŠัะฒะธั‚ัŒ ะตะณะพ ะบะฐะบ Pydantic-ะผะพะดะตะปัŒ**. ะขะพะณะดะฐ Pydantic-ะผะพะดะตะปัŒ ะฒั‹ะฟะพะปะฝะธั‚ ะดะพะบัƒะผะตะฝั‚ะธั€ะพะฒะฐะฝะธะต ะดะฐะฝะฝั‹ั…, ะฒะฐะปะธะดะฐั†ะธัŽ ะธ ั‚.ะฟ. ะดะปั ะพะฑัŠะตะบั‚ะฐ, ะบะพั‚ะพั€ั‹ะน ะฒั‹ ะฒะตั€ะฝัƒะปะธ (ะฝะฐะฟั€ะธะผะตั€, ัะปะพะฒะฐั€ั ะธะปะธ ะพะฑัŠะตะบั‚ะฐ ะธะท ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…). ะ•ัะปะธ ะฒั‹ ะดะพะฑะฐะฒะธั‚ะต ะฐะฝะฝะพั‚ะฐั†ะธัŽ ะฒะพะทะฒั€ะฐั‰ะฐะตะผะพะณะพ ั‚ะธะฟะฐ, ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะธ ั€ะตะดะฐะบั‚ะพั€ั‹ ะบะพะดะฐ ะฝะฐั‡ะฝัƒั‚ ะถะฐะปะพะฒะฐั‚ัŒัั (ะธ ะฑัƒะดัƒั‚ ะฟั€ะฐะฒั‹), ั‡ั‚ะพ ั„ัƒะฝะบั†ะธั ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ ั‚ะธะฟ (ะฝะฐะฟั€ะธะผะตั€, dict), ะพั‚ะปะธั‡ะฝั‹ะน ะพั‚ ะพะฑัŠัะฒะปะตะฝะฝะพะณะพ (ะฝะฐะฟั€ะธะผะตั€, Pydantic-ะผะพะดะตะปัŒ). @@ -47,13 +47,13 @@ FastAPI ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะพั‚ ั‚ะธะฟ ะพั‚ะฒะตั‚ะฐ ะดะปั: `response_model` ะฟั€ะธะฝะธะผะฐะตั‚ ั‚ะพั‚ ะถะต ั‚ะธะฟ, ั‡ั‚ะพ ะฒั‹ ะฑั‹ ะพะฑัŠัะฒะธะปะธ ะดะปั ะฟะพะปั Pydantic-ะผะพะดะตะปะธ, ั‚ะพ ะตัั‚ัŒ ัั‚ะพ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะพะดะฝะฐ Pydantic-ะผะพะดะตะปัŒ, ะฐ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ, ะฝะฐะฟั€ะธะผะตั€, `list` Pydantic-ะผะพะดะตะปะตะน, ะบะฐะบ `List[Item]`. -FastAPI ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `response_model` ะดะปั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ, ะฒะฐะปะธะดะฐั†ะธะธ ะธ ั‚. ะฟ., ะฐ ั‚ะฐะบะถะต ะดะปั **ะบะพะฝะฒะตั€ั‚ะฐั†ะธะธ ะธ ั„ะธะปัŒั‚ั€ะฐั†ะธะธ ะฒั‹ั…ะพะดะฝั‹ั… ะดะฐะฝะฝั‹ั…** ะบ ะพะฑัŠัะฒะปะตะฝะฝะพะผัƒ ั‚ะธะฟัƒ. +FastAPI ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะพั‚ `response_model` ะดะปั ะดะพะบัƒะผะตะฝั‚ะธั€ะพะฒะฐะฝะธั, ะฒะฐะปะธะดะฐั†ะธะธ ะดะฐะฝะฝั‹ั… ะธ ั‚.ะฟ., ะฐ ั‚ะฐะบะถะต ะดะปั **ะบะพะฝะฒะตั€ั‚ะฐั†ะธะธ ะธ ั„ะธะปัŒั‚ั€ะฐั†ะธะธ ะฒั‹ั…ะพะดะฝั‹ั… ะดะฐะฝะฝั‹ั…** ะบ ะพะฑัŠัะฒะปะตะฝะฝะพะผัƒ ั‚ะธะฟัƒ. /// tip | ะกะพะฒะตั‚ -ะ•ัะปะธ ัƒ ะฒะฐั ะฒ ั€ะตะดะฐะบั‚ะพั€ะต ะบะพะดะฐ, mypy ะธ ั‚. ะฟ. ะฒะบะปัŽั‡ะตะฝั‹ ัั‚ั€ะพะณะธะต ะฟั€ะพะฒะตั€ะบะธ ั‚ะธะฟะพะฒ, ะฒั‹ ะผะพะถะตั‚ะต ะพะฑัŠัะฒะธั‚ัŒ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ ั„ัƒะฝะบั†ะธะธ ะบะฐะบ `Any`. +ะ•ัะปะธ ัƒ ะฒะฐั ะฒ ั€ะตะดะฐะบั‚ะพั€ะต ะบะพะดะฐ, mypy ะธ ั‚.ะฟ. ะฒะบะปัŽั‡ะตะฝั‹ ัั‚ั€ะพะณะธะต ะฟั€ะพะฒะตั€ะบะธ ั‚ะธะฟะพะฒ, ะฒั‹ ะผะพะถะตั‚ะต ะพะฑัŠัะฒะธั‚ัŒ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ ั„ัƒะฝะบั†ะธะธ ะบะฐะบ `Any`. -ะขะฐะบ ะฒั‹ ัะพะพะฑั‰ะธั‚ะต ั€ะตะดะฐะบั‚ะพั€ัƒ, ั‡ั‚ะพ ะฝะฐะผะตั€ะตะฝะฝะพ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต ั‡ั‚ะพ ัƒะณะพะดะฝะพ. ะะพ FastAPI ะฒัั‘ ั€ะฐะฒะฝะพ ะฒั‹ะฟะพะปะฝะธั‚ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ ะดะฐะฝะฝั‹ั…, ะฒะฐะปะธะดะฐั†ะธัŽ, ั„ะธะปัŒั‚ั€ะฐั†ะธัŽ ะธ ั‚.ะด. ั ะฟะพะผะพั‰ัŒัŽ `response_model`. +ะขะฐะบ ะฒั‹ ัะพะพะฑั‰ะธั‚ะต ั€ะตะดะฐะบั‚ะพั€ัƒ, ั‡ั‚ะพ ะฝะฐะผะตั€ะตะฝะฝะพ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต ั‡ั‚ะพ ัƒะณะพะดะฝะพ. ะะพ FastAPI ะฒัั‘ ั€ะฐะฒะฝะพ ะฒั‹ะฟะพะปะฝะธั‚ ะดะพะบัƒะผะตะฝั‚ะธั€ะพะฒะฐะฝะธะต, ะฒะฐะปะธะดะฐั†ะธัŽ, ั„ะธะปัŒั‚ั€ะฐั†ะธัŽ ะดะฐะฝะฝั‹ั… ะธ ั‚.ะด. ั ะฟะพะผะพั‰ัŒัŽ `response_model`. /// @@ -61,7 +61,7 @@ FastAPI ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `response_model` ะดะปั ะดะพะบัƒะผะต ะ•ัะปะธ ะฒั‹ ะพะฑัŠัะฒะธั‚ะต ะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ, ะธ `response_model`, ะฟั€ะธะพั€ะธั‚ะตั‚ ะฑัƒะดะตั‚ ัƒ `response_model`, ะธะผะตะฝะฝะพ ะตะณะพ ะธัะฟะพะปัŒะทัƒะตั‚ FastAPI. -ะขะฐะบ ะฒั‹ ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ะบะพั€ั€ะตะบั‚ะฝั‹ะต ะฐะฝะฝะพั‚ะฐั†ะธะธ ั‚ะธะฟะพะฒ ะบ ัะฒะพะธะผ ั„ัƒะฝะบั†ะธัะผ, ะดะฐะถะต ะตัะปะธ ั„ะฐะบั‚ะธั‡ะตัะบะธ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต ั‚ะธะฟ, ะพั‚ะปะธั‡ะฝั‹ะน ะพั‚ ะผะพะดะตะปะธ ะพั‚ะฒะตั‚ะฐ, ั‡ั‚ะพะฑั‹ ะธะผะธ ะฟะพะปัŒะทะพะฒะฐะปะธััŒ ั€ะตะดะฐะบั‚ะพั€ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะฒั€ะพะดะต mypy. ะ˜ ะฟั€ะธ ัั‚ะพะผ FastAPI ะฟั€ะพะดะพะปะถะธั‚ ะฒั‹ะฟะพะปะฝัั‚ัŒ ะฒะฐะปะธะดะฐั†ะธัŽ ะดะฐะฝะฝั‹ั…, ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ ะธ ั‚.ะด. ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ `response_model`. +ะขะฐะบ ะฒั‹ ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ะบะพั€ั€ะตะบั‚ะฝั‹ะต ะฐะฝะฝะพั‚ะฐั†ะธะธ ั‚ะธะฟะพะฒ ะบ ัะฒะพะธะผ ั„ัƒะฝะบั†ะธัะผ, ะดะฐะถะต ะตัะปะธ ั„ะฐะบั‚ะธั‡ะตัะบะธ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต ั‚ะธะฟ, ะพั‚ะปะธั‡ะฝั‹ะน ะพั‚ ะผะพะดะตะปะธ ะพั‚ะฒะตั‚ะฐ, ั‡ั‚ะพะฑั‹ ะธะผะธ ะฟะพะปัŒะทะพะฒะฐะปะธััŒ ั€ะตะดะฐะบั‚ะพั€ ะบะพะดะฐ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะฒั€ะพะดะต mypy. ะ˜ ะฟั€ะธ ัั‚ะพะผ FastAPI ะฟั€ะพะดะพะปะถะธั‚ ะฒั‹ะฟะพะปะฝัั‚ัŒ ะฒะฐะปะธะดะฐั†ะธัŽ ะดะฐะฝะฝั‹ั…, ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ ะธ ั‚.ะด. ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ `response_model`. ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ัƒะบะฐะทะฐั‚ัŒ `response_model=None`, ั‡ั‚ะพะฑั‹ ะพั‚ะบะปัŽั‡ะธั‚ัŒ ัะพะทะดะฐะฝะธะต ะผะพะดะตะปะธ ะพั‚ะฒะตั‚ะฐ ะดะปั ะดะฐะฝะฝะพะน *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. ะญั‚ะพ ะผะพะถะตั‚ ะฟะพะฝะฐะดะพะฑะธั‚ัŒัั, ะตัะปะธ ะฒั‹ ะดะพะฑะฐะฒะปัะตั‚ะต ะฐะฝะฝะพั‚ะฐั†ะธะธ ั‚ะธะฟะพะฒ ะดะปั ะฒะตั‰ะตะน, ะฝะต ัะฒะปััŽั‰ะธั…ัั ะฒะฐะปะธะดะฝั‹ะผะธ ะฟะพะปัะผะธ Pydantic. ะŸั€ะธะผะตั€ ะฒั‹ ัƒะฒะธะดะธั‚ะต ะฝะธะถะต. @@ -75,7 +75,7 @@ FastAPI ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `response_model` ะดะปั ะดะพะบัƒะผะต ะงั‚ะพะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `EmailStr`, ัะฝะฐั‡ะฐะปะฐ ัƒัั‚ะฐะฝะพะฒะธั‚ะต <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>. -ะกะพะทะดะฐะนั‚ะต [ะฒะธั€ั‚ัƒะฐะปัŒะฝะพะต ะพะบั€ัƒะถะตะฝะธะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒะธั€ัƒะนั‚ะต ะตะณะพ ะธ ะทะฐั‚ะตะผ ัƒัั‚ะฐะฝะพะฒะธั‚ะต ะฟะฐะบะตั‚, ะฝะฐะฟั€ะธะผะตั€: +ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ัะพะทะดะฐะปะธ [ะฒะธั€ั‚ัƒะฐะปัŒะฝะพะต ะพะบั€ัƒะถะตะฝะธะต](../virtual-environments.md){.internal-link target=_blank}, ะฐะบั‚ะธะฒะธั€ะพะฒะฐะปะธ ะตะณะพ, ะฐ ะทะฐั‚ะตะผ ัƒัั‚ะฐะฝะพะฒะธั‚ะต ะฟะฐะบะตั‚, ะฝะฐะฟั€ะธะผะตั€: ```console $ pip install email-validator @@ -105,7 +105,7 @@ $ pip install "pydantic[email]" /// -## ะ”ะพะฑะฐะฒะธั‚ัŒ ะผะพะดะตะปัŒ ะดะปั ะพั‚ะฒะตั‚ะฐ { #add-an-output-model } +## ะ”ะพะฑะฐะฒะธั‚ัŒ ะฒั‹ั…ะพะดะฝัƒัŽ ะผะพะดะตะปัŒ { #add-an-output-model } ะ’ะผะตัั‚ะพ ัั‚ะพะณะพ ะผั‹ ะผะพะถะตะผ ัะพะทะดะฐั‚ัŒ ะฒั…ะพะดะฝัƒัŽ ะผะพะดะตะปัŒ ั ะฟะฐั€ะพะปะตะผ ะฒ ะพั‚ะบั€ั‹ั‚ะพะผ ะฒะธะดะต ะธ ะฒั‹ั…ะพะดะฝัƒัŽ ะผะพะดะตะปัŒ ะฑะตะท ะฝะตะณะพ: @@ -123,7 +123,7 @@ $ pip install "pydantic[email]" ### `response_model` ะธะปะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ { #response-model-or-return-type } -ะ’ ัั‚ะพะผ ัะปัƒั‡ะฐะต, ะฟะพัะบะพะปัŒะบัƒ ะดะฒะต ะผะพะดะตะปะธ ั€ะฐะทะปะธั‡ะฐัŽั‚ัั, ะตัะปะธ ะฑั‹ ะผั‹ ะฐะฝะฝะพั‚ะธั€ะพะฒะฐะปะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ ั„ัƒะฝะบั†ะธะธ ะบะฐะบ `UserOut`, ั€ะตะดะฐะบั‚ะพั€ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะฟะพะถะฐะปะพะฒะฐะปะธััŒ ะฑั‹, ั‡ั‚ะพ ะผั‹ ะฒะพะทะฒั€ะฐั‰ะฐะตะผ ะฝะตะฒะตั€ะฝั‹ะน ั‚ะธะฟ, ั‚ะฐะบ ะบะฐะบ ัั‚ะพ ั€ะฐะทะฝั‹ะต ะบะปะฐััั‹. +ะ’ ัั‚ะพะผ ัะปัƒั‡ะฐะต, ะฟะพัะบะพะปัŒะบัƒ ะดะฒะต ะผะพะดะตะปะธ ั€ะฐะทะปะธั‡ะฐัŽั‚ัั, ะตัะปะธ ะฑั‹ ะผั‹ ะฐะฝะฝะพั‚ะธั€ะพะฒะฐะปะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ ั„ัƒะฝะบั†ะธะธ ะบะฐะบ `UserOut`, ั€ะตะดะฐะบั‚ะพั€ ะบะพะดะฐ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะฟะพะถะฐะปะพะฒะฐะปะธััŒ ะฑั‹, ั‡ั‚ะพ ะผั‹ ะฒะพะทะฒั€ะฐั‰ะฐะตะผ ะฝะตะฒะตั€ะฝั‹ะน ั‚ะธะฟ, ั‚ะฐะบ ะบะฐะบ ัั‚ะพ ั€ะฐะทะฝั‹ะต ะบะปะฐััั‹. ะŸะพัั‚ะพะผัƒ ะฒ ัั‚ะพะผ ะฟั€ะธะผะตั€ะต ะผั‹ ะดะพะปะถะฝั‹ ะพะฑัŠัะฒะธั‚ัŒ ั‚ะธะฟ ะพั‚ะฒะตั‚ะฐ ะฒ ะฟะฐั€ะฐะผะตั‚ั€ะต `response_model`. @@ -135,33 +135,33 @@ $ pip install "pydantic[email]" ะœั‹ ั…ะพั‚ะธะผ, ั‡ั‚ะพะฑั‹ FastAPI ะฟั€ะพะดะพะปะถะฐะป **ั„ะธะปัŒั‚ั€ะพะฒะฐั‚ัŒ** ะดะฐะฝะฝั‹ะต ั ะฟะพะผะพั‰ัŒัŽ ะผะพะดะตะปะธ ะพั‚ะฒะตั‚ะฐ. ะขะฐะบ ั‡ั‚ะพ, ะดะฐะถะต ะตัะปะธ ั„ัƒะฝะบั†ะธั ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ ะฑะพะปัŒัˆะต ะดะฐะฝะฝั‹ั…, ะฒ ะพั‚ะฒะตั‚ ะฑัƒะดัƒั‚ ะฒะบะปัŽั‡ะตะฝั‹ ั‚ะพะปัŒะบะพ ะฟะพะปั, ะพะฑัŠัะฒะปะตะฝะฝั‹ะต ะฒ ะผะพะดะตะปะธ ะพั‚ะฒะตั‚ะฐ. -ะ’ ะฟั€ะตะดั‹ะดัƒั‰ะตะผ ะฟั€ะธะผะตั€ะต, ะฟะพัะบะพะปัŒะบัƒ ะบะปะฐััั‹ ะฑั‹ะปะธ ั€ะฐะทะฝั‹ะผะธ, ะฝะฐะผ ะฟั€ะธัˆะปะพััŒ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `response_model`. ะะพ ัั‚ะพ ั‚ะฐะบะถะต ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะผั‹ ั‚ะตั€ัะตะผ ะฟะพะดะดะตั€ะถะบัƒ ะพั‚ ั€ะตะดะฐะบั‚ะพั€ะฐ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ, ะฟั€ะพะฒะตั€ััŽั‰ะธั… ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ ั„ัƒะฝะบั†ะธะธ. +ะ’ ะฟั€ะตะดั‹ะดัƒั‰ะตะผ ะฟั€ะธะผะตั€ะต, ะฟะพัะบะพะปัŒะบัƒ ะบะปะฐััั‹ ะฑั‹ะปะธ ั€ะฐะทะฝั‹ะผะธ, ะฝะฐะผ ะฟั€ะธัˆะปะพััŒ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `response_model`. ะะพ ัั‚ะพ ั‚ะฐะบะถะต ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ะผั‹ ั‚ะตั€ัะตะผ ะฟะพะดะดะตั€ะถะบัƒ ะพั‚ ั€ะตะดะฐะบั‚ะพั€ะฐ ะบะพะดะฐ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ, ะฟั€ะพะฒะตั€ััŽั‰ะธั… ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ ั„ัƒะฝะบั†ะธะธ. ะžะดะฝะฐะบะพ ะฒ ะฑะพะปัŒัˆะธะฝัั‚ะฒะต ั‚ะฐะบะธั… ัะปัƒั‡ะฐะตะฒ ะฝะฐะผ ะฝัƒะถะฝะพ ะปะธัˆัŒ **ะพั‚ั„ะธะปัŒั‚ั€ะพะฒะฐั‚ัŒ/ัƒะฑั€ะฐั‚ัŒ** ะฝะตะบะพั‚ะพั€ั‹ะต ะดะฐะฝะฝั‹ะต, ะบะฐะบ ะฒ ัั‚ะพะผ ะฟั€ะธะผะตั€ะต. -ะ˜ ะฒ ัั‚ะธั… ัะปัƒั‡ะฐัั… ะผั‹ ะผะพะถะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะบะปะฐััั‹ ะธ ะฝะฐัะปะตะดะพะฒะฐะฝะธะต, ั‡ั‚ะพะฑั‹ ะฒะพัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั **ะฐะฝะฝะพั‚ะฐั†ะธัะผะธ ั‚ะธะฟะพะฒ** ั„ัƒะฝะบั†ะธะน ะดะปั ะปัƒั‡ัˆะตะน ะฟะพะดะดะตั€ะถะบะธ ะฒ ั€ะตะดะฐะบั‚ะพั€ะต ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะฐั… ะธ ะฟั€ะธ ัั‚ะพะผ ะฟะพะปัƒั‡ะธั‚ัŒ **ั„ะธะปัŒั‚ั€ะฐั†ะธัŽ ะดะฐะฝะฝั‹ั…** ะพั‚ FastAPI. +ะ˜ ะฒ ัั‚ะธั… ัะปัƒั‡ะฐัั… ะผั‹ ะผะพะถะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะบะปะฐััั‹ ะธ ะฝะฐัะปะตะดะพะฒะฐะฝะธะต, ั‡ั‚ะพะฑั‹ ะฒะพัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั **ะฐะฝะฝะพั‚ะฐั†ะธัะผะธ ั‚ะธะฟะพะฒ** ั„ัƒะฝะบั†ะธะน ะดะปั ะปัƒั‡ัˆะตะน ะฟะพะดะดะตั€ะถะบะธ ะฒ ั€ะตะดะฐะบั‚ะพั€ะต ะบะพะดะฐ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะฐั… ะธ ะฟั€ะธ ัั‚ะพะผ ะฟะพะปัƒั‡ะธั‚ัŒ **ั„ะธะปัŒั‚ั€ะฐั†ะธัŽ ะดะฐะฝะฝั‹ั…** ะพั‚ FastAPI. {* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *} -ะขะฐะบ ะผั‹ ะฟะพะปัƒั‡ะฐะตะผ ะฟะพะดะดะตั€ะถะบัƒ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ (ั€ะตะดะฐะบั‚ะพั€ั‹, mypy) โ€” ะบะพะด ะบะพั€ั€ะตะบั‚ะตะฝ ั ั‚ะพั‡ะบะธ ะทั€ะตะฝะธั ั‚ะธะฟะพะฒ โ€” ะธ ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพ ะฟะพะปัƒั‡ะฐะตะผ ั„ะธะปัŒั‚ั€ะฐั†ะธัŽ ะดะฐะฝะฝั‹ั… ะพั‚ FastAPI. +ะขะฐะบ ะผั‹ ะฟะพะปัƒั‡ะฐะตะผ ะฟะพะดะดะตั€ะถะบัƒ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ โ€” ั€ะตะดะฐะบั‚ะพั€ะพะฒ ะบะพะดะฐ ะธ mypy, ั‚ะฐะบ ะบะฐะบ ัั‚ะพั‚ ะบะพะด ะบะพั€ั€ะตะบั‚ะตะฝ ั ั‚ะพั‡ะบะธ ะทั€ะตะฝะธั ั‚ะธะฟะพะฒ โ€” ะธ ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพ ะฟะพะปัƒั‡ะฐะตะผ ั„ะธะปัŒั‚ั€ะฐั†ะธัŽ ะดะฐะฝะฝั‹ั… ะพั‚ FastAPI. ะšะฐะบ ัั‚ะพ ั€ะฐะฑะพั‚ะฐะตั‚? ะ”ะฐะฒะฐะนั‚ะต ั€ะฐะทะฑะตั€ั‘ะผัั. ๐Ÿค“ ### ะะฝะฝะพั‚ะฐั†ะธะธ ั‚ะธะฟะพะฒ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ { #type-annotations-and-tooling } -ะกะฝะฐั‡ะฐะปะฐ ะฟะพัะผะพั‚ั€ะธะผ, ะบะฐะบ ัั‚ะพ ัƒะฒะธะดัั‚ ั€ะตะดะฐะบั‚ะพั€ั‹, mypy ะธ ะดั€ัƒะณะธะต ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹. +ะกะฝะฐั‡ะฐะปะฐ ะฟะพัะผะพั‚ั€ะธะผ, ะบะฐะบ ัั‚ะพ ัƒะฒะธะดัั‚ ั€ะตะดะฐะบั‚ะพั€ ะบะพะดะฐ, mypy ะธ ะดั€ัƒะณะธะต ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹. -`BaseUser` ัะพะดะตั€ะถะธั‚ ะฑะฐะทะพะฒั‹ะต ะฟะพะปั. ะ—ะฐั‚ะตะผ `UserIn` ะฝะฐัะปะตะดัƒะตั‚ัั ะพั‚ `BaseUser` ะธ ะดะพะฑะฐะฒะปัะตั‚ ะฟะพะปะต `password`, ั‚ะพ ะตัั‚ัŒ ะพะฝ ะฒะบะปัŽั‡ะฐะตั‚ ะฒัะต ะฟะพะปั ะพะฑะตะธั… ะผะพะดะตะปะตะน. +`BaseUser` ัะพะดะตั€ะถะธั‚ ะฑะฐะทะพะฒั‹ะต ะฟะพะปั. ะ—ะฐั‚ะตะผ `UserIn` ะฝะฐัะปะตะดัƒะตั‚ัั ะพั‚ `BaseUser` ะธ ะดะพะฑะฐะฒะปัะตั‚ ะฟะพะปะต `password`, ั‚ะพ ะตัั‚ัŒ ะพะฝ ะฑัƒะดะตั‚ ะฒะบะปัŽั‡ะฐั‚ัŒ ะฒัะต ะฟะพะปั ะพะฑะตะธั… ะผะพะดะตะปะตะน. ะœั‹ ะฐะฝะฝะพั‚ะธั€ัƒะตะผ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ ั„ัƒะฝะบั†ะธะธ ะบะฐะบ `BaseUser`, ะฝะพ ั„ะฐะบั‚ะธั‡ะตัะบะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผ ัะบะทะตะผะฟะปัั€ `UserIn`. -ะ ะตะดะฐะบั‚ะพั€, mypy ะธ ะดั€ัƒะณะธะต ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะฝะต ะฑัƒะดัƒั‚ ะฒะพะทั€ะฐะถะฐั‚ัŒ, ะฟะพั‚ะพะผัƒ ั‡ั‚ะพ ั ั‚ะพั‡ะบะธ ะทั€ะตะฝะธั ั‚ะธะฟะพะฒ `UserIn` โ€” ะฟะพะดะบะปะฐัั `BaseUser`, ั‡ั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ัั‚ะพ *ะฒะฐะปะธะดะฝั‹ะน* ั‚ะธะฟ ะฒะตะทะดะต, ะณะดะต ะพะถะธะดะฐะตั‚ัั ั‡ั‚ะพ-ั‚ะพ, ัะฒะปััŽั‰ะตะตัั `BaseUser`. +ะ ะตะดะฐะบั‚ะพั€ ะบะพะดะฐ, mypy ะธ ะดั€ัƒะณะธะต ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะฝะต ะฑัƒะดัƒั‚ ะฒะพะทั€ะฐะถะฐั‚ัŒ, ะฟะพั‚ะพะผัƒ ั‡ั‚ะพ ั ั‚ะพั‡ะบะธ ะทั€ะตะฝะธั ั‚ะธะฟะพะฒ `UserIn` โ€” ะฟะพะดะบะปะฐัั `BaseUser`, ั‡ั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ ัั‚ะพ *ะฒะฐะปะธะดะฝั‹ะน* ั‚ะธะฟ ะฒะตะทะดะต, ะณะดะต ะพะถะธะดะฐะตั‚ัั ั‡ั‚ะพ-ั‚ะพ, ัะฒะปััŽั‰ะตะตัั `BaseUser`. ### ะคะธะปัŒั‚ั€ะฐั†ะธั ะดะฐะฝะฝั‹ั… FastAPI { #fastapi-data-filtering } -ะขะตะฟะตั€ัŒ, ะดะปั FastAPI: ะพะฝ ัƒะฒะธะดะธั‚ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ ะธ ัƒะฑะตะดะธั‚ัั, ั‡ั‚ะพ ั‚ะพ, ั‡ั‚ะพ ะฒั‹ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต, ะฒะบะปัŽั‡ะฐะตั‚ **ั‚ะพะปัŒะบะพ** ะฟะพะปั, ะพะฑัŠัะฒะปะตะฝะฝั‹ะต ะฒ ัั‚ะพะผ ั‚ะธะฟะต. +ะขะตะฟะตั€ัŒ ะดะปั FastAPI: ะพะฝ ัƒะฒะธะดะธั‚ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ะน ั‚ะธะฟ ะธ ัƒะฑะตะดะธั‚ัั, ั‡ั‚ะพ ั‚ะพ, ั‡ั‚ะพ ะฒั‹ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต, ะฒะบะปัŽั‡ะฐะตั‚ **ั‚ะพะปัŒะบะพ** ะฟะพะปั, ะพะฑัŠัะฒะปะตะฝะฝั‹ะต ะฒ ัั‚ะพะผ ั‚ะธะฟะต. -FastAPI ะดะตะปะฐะตั‚ ะฝะตัะบะพะปัŒะบะพ ะฒะตั‰ะตะน ะฒะฝัƒั‚ั€ะธ ะฒะผะตัั‚ะต ั Pydantic, ั‡ั‚ะพะฑั‹ ะณะฐั€ะฐะฝั‚ะธั€ะพะฒะฐั‚ัŒ, ั‡ั‚ะพ ั‚ะต ะถะต ะฟั€ะฐะฒะธะปะฐ ะฝะฐัะปะตะดะพะฒะฐะฝะธั ะบะปะฐััะพะฒ ะฝะต ะธัะฟะพะปัŒะทัƒัŽั‚ัั ะดะปั ั„ะธะปัŒั‚ั€ะฐั†ะธะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ั… ะดะฐะฝะฝั‹ั…, ะธะฝะฐั‡ะต ะฒั‹ ะผะพะณะปะธ ะฑั‹ ะฒะตั€ะฝัƒั‚ัŒ ะณะพั€ะฐะทะดะพ ะฑะพะปัŒัˆะต ะดะฐะฝะฝั‹ั…, ั‡ะตะผ ะพะถะธะดะฐะปะธ. +FastAPI ะดะตะปะฐะตั‚ ะฝะตัะบะพะปัŒะบะพ ะฒะตั‰ะตะน ะฒะฝัƒั‚ั€ะธ ะฒะผะตัั‚ะต ั Pydantic, ั‡ั‚ะพะฑั‹ ะณะฐั€ะฐะฝั‚ะธั€ะพะฒะฐั‚ัŒ, ั‡ั‚ะพ ั‚ะต ะถะต ะฟั€ะฐะฒะธะปะฐ ะฝะฐัะปะตะดะพะฒะฐะฝะธั ะบะปะฐััะพะฒ ะฝะต ะธัะฟะพะปัŒะทัƒัŽั‚ัั ะดะปั ั„ะธะปัŒั‚ั€ะฐั†ะธะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ั… ะดะฐะฝะฝั‹ั…, ะธะฝะฐั‡ะต ะฒั‹ ะผะพะณะปะธ ะฑั‹ ะฒ ะธั‚ะพะณะต ะฒะตั€ะฝัƒั‚ัŒ ะฝะฐะผะฝะพะณะพ ะฑะพะปัŒัˆะต ะดะฐะฝะฝั‹ั…, ั‡ะตะผ ะพะถะธะดะฐะปะธ. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ ะฒั‹ ะฟะพะปัƒั‡ะฐะตั‚ะต ะปัƒั‡ัˆะตะต ะธะท ะพะฑะพะธั… ะผะธั€ะพะฒ: ะฐะฝะฝะพั‚ะฐั†ะธะธ ั‚ะธะฟะพะฒ ั **ะฟะพะดะดะตั€ะถะบะพะน ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ** ะธ **ั„ะธะปัŒั‚ั€ะฐั†ะธัŽ ะดะฐะฝะฝั‹ั…**. @@ -171,17 +171,17 @@ FastAPI ะดะตะปะฐะตั‚ ะฝะตัะบะพะปัŒะบะพ ะฒะตั‰ะตะน ะฒะฝัƒั‚ั€ะธ ะฒะผะตัั‚ะต ั <img src="/img/tutorial/response-model/image01.png"> -ะ˜ ะพะฑะต ะผะพะดะตะปะธ ะธัะฟะพะปัŒะทัƒัŽั‚ัั ะฒ ะธะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ API: +ะ˜ ะพะฑะต ะผะพะดะตะปะธ ะฑัƒะดัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะฒ ะธะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ API: <img src="/img/tutorial/response-model/image02.png"> ## ะ”ั€ัƒะณะธะต ะฐะฝะฝะพั‚ะฐั†ะธะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ั… ั‚ะธะฟะพะฒ { #other-return-type-annotations } -ะ‘ั‹ะฒะฐัŽั‚ ัะปัƒั‡ะฐะธ, ะบะพะณะดะฐ ะฒั‹ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต ั‡ั‚ะพ-ั‚ะพ, ั‡ั‚ะพ ะฝะต ัะฒะปัะตั‚ัั ะฒะฐะปะธะดะฝั‹ะผ ะฟะพะปะตะผ Pydantic, ะธ ะฐะฝะฝะพั‚ะธั€ัƒะตั‚ะต ัั‚ะพ ะฒ ั„ัƒะฝะบั†ะธะธ ั‚ะพะปัŒะบะพ ั€ะฐะดะธ ะฟะพะดะดะตั€ะถะบะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ (ั€ะตะดะฐะบั‚ะพั€, mypy ะธ ั‚. ะด.). +ะ‘ั‹ะฒะฐัŽั‚ ัะปัƒั‡ะฐะธ, ะบะพะณะดะฐ ะฒั‹ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต ั‡ั‚ะพ-ั‚ะพ, ั‡ั‚ะพ ะฝะต ัะฒะปัะตั‚ัั ะฒะฐะปะธะดะฝั‹ะผ ะฟะพะปะตะผ Pydantic, ะธ ะฐะฝะฝะพั‚ะธั€ัƒะตั‚ะต ัั‚ะพ ะฒ ั„ัƒะฝะบั†ะธะธ ั‚ะพะปัŒะบะพ ั€ะฐะดะธ ะฟะพะดะดะตั€ะถะบะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ (ั€ะตะดะฐะบั‚ะพั€ ะบะพะดะฐ, mypy ะธ ั‚.ะด.). ### ะ’ะพะทะฒั€ะฐั‚ Response ะฝะฐะฟั€ัะผัƒัŽ { #return-a-response-directly } -ะกะฐะผั‹ะน ั€ะฐัะฟั€ะพัั‚ั€ะฐะฝั‘ะฝะฝั‹ะน ัะปัƒั‡ะฐะน โ€” [ะฒะพะทะฒั€ะฐั‰ะฐั‚ัŒ Response ะฝะฐะฟั€ัะผัƒัŽ, ะบะฐะบ ะพะฟะธัะฐะฝะพ ะดะฐะปะตะต ะฒ ั€ะฐะทะดะตะปะฐั… ะดะปั ะฟั€ะพะดะฒะธะฝัƒั‚ั‹ั…](../advanced/response-directly.md){.internal-link target=_blank}. +ะกะฐะผั‹ะน ั€ะฐัะฟั€ะพัั‚ั€ะฐะฝั‘ะฝะฝั‹ะน ัะปัƒั‡ะฐะน โ€” [ะฒะพะทะฒั€ะฐั‰ะฐั‚ัŒ Response ะฝะฐะฟั€ัะผัƒัŽ, ะบะฐะบ ะพะฟะธัะฐะฝะพ ะดะฐะปะตะต ะฒ ั€ะฐะทะดะตะปะฐั… ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ ะดะปั ะฟั€ะพะดะฒะธะฝัƒั‚ั‹ั…](../advanced/response-directly.md){.internal-link target=_blank}. {* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} @@ -195,7 +195,7 @@ FastAPI ะดะตะปะฐะตั‚ ะฝะตัะบะพะปัŒะบะพ ะฒะตั‰ะตะน ะฒะฝัƒั‚ั€ะธ ะฒะผะตัั‚ะต ั {* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} -ะญั‚ะพ ั‚ะพะถะต ัั€ะฐะฑะพั‚ะฐะตั‚, ั‚ะฐะบ ะบะฐะบ `RedirectResponse` โ€” ะฟะพะดะบะปะฐัั `Response`, ะธ FastAPI ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะพะฑั€ะฐะฑะพั‚ะฐะตั‚ ัั‚ะพั‚ ัะปัƒั‡ะฐะน. +ะญั‚ะพ ั‚ะพะถะต ัั€ะฐะฑะพั‚ะฐะตั‚, ั‚ะฐะบ ะบะฐะบ `RedirectResponse` โ€” ะฟะพะดะบะปะฐัั `Response`, ะธ FastAPI ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะพะฑั€ะฐะฑะพั‚ะฐะตั‚ ัั‚ะพั‚ ะฟั€ะพัั‚ะพะน ัะปัƒั‡ะฐะน. ### ะะตะบะพั€ั€ะตะบั‚ะฝั‹ะต ะฐะฝะฝะพั‚ะฐั†ะธะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ั… ั‚ะธะฟะพะฒ { #invalid-return-type-annotations } @@ -209,15 +209,15 @@ FastAPI ะดะตะปะฐะตั‚ ะฝะตัะบะพะปัŒะบะพ ะฒะตั‰ะตะน ะฒะฝัƒั‚ั€ะธ ะฒะผะตัั‚ะต ั ### ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะผะพะดะตะปัŒ ะพั‚ะฒะตั‚ะฐ { #disable-response-model } -ะŸั€ะพะดะพะปะถะฐั ะฟั€ะธะผะตั€ ะฒั‹ัˆะต, ะฒั‹ ะผะพะถะตั‚ะต ะฝะต ั…ะพั‚ะตั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะฐะฝะดะฐั€ั‚ะฝัƒัŽ ะฒะฐะปะธะดะฐั†ะธัŽ ะดะฐะฝะฝั‹ั…, ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ, ั„ะธะปัŒั‚ั€ะฐั†ะธัŽ ะธ ั‚.ะด., ะฒั‹ะฟะพะปะฝัะตะผั‹ะต FastAPI. +ะŸั€ะพะดะพะปะถะฐั ะฟั€ะธะผะตั€ ะฒั‹ัˆะต, ะฒั‹ ะผะพะถะตั‚ะต ะฝะต ั…ะพั‚ะตั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะต ะฒะฐะปะธะดะฐั†ะธัŽ ะดะฐะฝะฝั‹ั…, ะดะพะบัƒะผะตะฝั‚ะธั€ะพะฒะฐะฝะธะต, ั„ะธะปัŒั‚ั€ะฐั†ะธัŽ ะธ ั‚.ะฟ., ะฒั‹ะฟะพะปะฝัะตะผั‹ะต FastAPI. -ะะพ ะฟั€ะธ ัั‚ะพะผ ะฒั‹ ะผะพะถะตั‚ะต ั…ะพั‚ะตั‚ัŒ ัะพั…ั€ะฐะฝะธั‚ัŒ ะฐะฝะฝะพั‚ะฐั†ะธัŽ ะฒะพะทะฒั€ะฐั‰ะฐะตะผะพะณะพ ั‚ะธะฟะฐ ะฒ ั„ัƒะฝะบั†ะธะธ, ั‡ั‚ะพะฑั‹ ะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะฟะพะดะดะตั€ะถะบะพะน ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ (ั€ะตะดะฐะบั‚ะพั€ั‹, ะฟั€ะพะฒะตั€ะบะธ ั‚ะธะฟะพะฒ ะฒั€ะพะดะต mypy). +ะะพ ะฟั€ะธ ัั‚ะพะผ ะฒั‹ ะผะพะถะตั‚ะต ั…ะพั‚ะตั‚ัŒ ัะพั…ั€ะฐะฝะธั‚ัŒ ะฐะฝะฝะพั‚ะฐั†ะธัŽ ะฒะพะทะฒั€ะฐั‰ะฐะตะผะพะณะพ ั‚ะธะฟะฐ ะฒ ั„ัƒะฝะบั†ะธะธ, ั‡ั‚ะพะฑั‹ ะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะฟะพะดะดะตั€ะถะบะพะน ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ ะฒั€ะพะดะต ั€ะตะดะฐะบั‚ะพั€ะพะฒ ะบะพะดะฐ ะธ ะธะฝัั‚ั€ัƒะผะตะฝั‚ะพะฒ ะฟั€ะพะฒะตั€ะบะธ ั‚ะธะฟะพะฒ (ะฝะฐะฟั€ะธะผะตั€, mypy). ะ’ ัั‚ะพะผ ัะปัƒั‡ะฐะต ะฒั‹ ะผะพะถะตั‚ะต ะพั‚ะบะปัŽั‡ะธั‚ัŒ ะณะตะฝะตั€ะฐั†ะธัŽ ะผะพะดะตะปะธ ะพั‚ะฒะตั‚ะฐ, ัƒัั‚ะฐะฝะพะฒะธะฒ `response_model=None`: {* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *} -ะขะฐะบ FastAPI ะฟั€ะพะฟัƒัั‚ะธั‚ ะณะตะฝะตั€ะฐั†ะธัŽ ะผะพะดะตะปะธ ะพั‚ะฒะตั‚ะฐ, ะธ ะฒั‹ ัะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะปัŽะฑั‹ะต ะฐะฝะฝะพั‚ะฐั†ะธะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ั… ั‚ะธะฟะพะฒ, ะฝะต ะฒะปะธัั ะฝะฐ ะฒะฐัˆะต ะฟั€ะธะปะพะถะตะฝะธะต FastAPI. ๐Ÿค“ +ะขะฐะบ FastAPI ะฟั€ะพะฟัƒัั‚ะธั‚ ะณะตะฝะตั€ะฐั†ะธัŽ ะผะพะดะตะปะธ ะพั‚ะฒะตั‚ะฐ, ะธ ะฒั‹ ัะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะปัŽะฑั‹ะต ะฐะฝะฝะพั‚ะฐั†ะธะธ ะฒะพะทะฒั€ะฐั‰ะฐะตะผั‹ั… ั‚ะธะฟะพะฒ, ะบะพั‚ะพั€ั‹ะต ะฒะฐะผ ะฝัƒะถะฝั‹, ะฑะตะท ะฒะปะธัะฝะธั ะฝะฐ ะฒะฐัˆะต ะฟั€ะธะปะพะถะตะฝะธะต FastAPI. ๐Ÿค“ ## ะŸะฐั€ะฐะผะตั‚ั€ั‹ ะบะพะดะธั€ะพะฒะฐะฝะธั ะผะพะดะตะปะธ ะพั‚ะฒะตั‚ะฐ { #response-model-encoding-parameters } @@ -252,20 +252,6 @@ FastAPI ะดะตะปะฐะตั‚ ะฝะตัะบะพะปัŒะบะพ ะฒะตั‰ะตะน ะฒะฝัƒั‚ั€ะธ ะฒะผะตัั‚ะต ั /// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั -ะ’ Pydantic v1 ะผะตั‚ะพะด ะฝะฐะทั‹ะฒะฐะปัั `.dict()`, ะฒ Pydantic v2 ะพะฝ ะฑั‹ะป ะฟะพะผะตั‡ะตะฝ ะบะฐะบ ัƒัั‚ะฐั€ะตะฒัˆะธะน (ะฝะพ ะฒัั‘ ะตั‰ั‘ ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั) ะธ ะฟะตั€ะตะธะผะตะฝะพะฒะฐะฝ ะฒ `.model_dump()`. - -ะŸั€ะธะผะตั€ั‹ ะทะดะตััŒ ะธัะฟะพะปัŒะทัƒัŽั‚ `.dict()` ะดะปั ัะพะฒะผะตัั‚ะธะผะพัั‚ะธ ั Pydantic v1, ะฝะพ ะตัะปะธ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต Pydantic v2, ะฟั€ะธะผะตะฝัะนั‚ะต `.model_dump()`. - -/// - -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั - -FastAPI ะธัะฟะพะปัŒะทัƒะตั‚ ะผะตั‚ะพะด `.dict()` ัƒ Pydantic-ะผะพะดะตะปะตะน ั <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">ะฟะฐั€ะฐะผะตั‚ั€ะพะผ `exclude_unset`</a>, ั‡ั‚ะพะฑั‹ ะดะพะฑะธั‚ัŒัั ั‚ะฐะบะพะณะพ ะฟะพะฒะตะดะตะฝะธั. - -/// - -/// info | ะ˜ะฝั„ะพั€ะผะฐั†ะธั - ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ: * `response_model_exclude_defaults=True` @@ -312,7 +298,7 @@ FastAPI ะดะพัั‚ะฐั‚ะพั‡ะฝะพ ัƒะผะตะฝ (ะฝะฐ ัะฐะผะพะผ ะดะตะปะต, ัั‚ะพ Pydantic ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ ะทะฝะฐั‡ะตะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะผะพะณัƒั‚ ะฑั‹ั‚ัŒ ะปัŽะฑั‹ะผะธ, ะฝะต ั‚ะพะปัŒะบะพ `None`. -ะญั‚ะพ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ัะฟะธัะพะบ (`[]`), ั‡ะธัะปะพ ั ะฟะปะฐะฒะฐัŽั‰ะตะน ั‚ะพั‡ะบะพะน `10.5` ะธ ั‚. ะด. +ะญั‚ะพ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ัะฟะธัะพะบ (`[]`), ั‡ะธัะปะพ ั ะฟะปะฐะฒะฐัŽั‰ะตะน ั‚ะพั‡ะบะพะน `10.5` ะธ ั‚.ะด. /// @@ -346,7 +332,7 @@ FastAPI ะดะพัั‚ะฐั‚ะพั‡ะฝะพ ัƒะผะตะฝ (ะฝะฐ ัะฐะผะพะผ ะดะตะปะต, ัั‚ะพ Pydantic #### ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต `list` ะฒะผะตัั‚ะพ `set` { #using-lists-instead-of-sets } -ะ•ัะปะธ ะฒั‹ ะทะฐะฑั‹ะปะธ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `set` ะธ ะฟั€ะธะผะตะฝะธะปะธ `list` ะธะปะธ `tuple`, FastAPI ะฒัั‘ ั€ะฐะฒะฝะพ ะฟั€ะตะพะฑั€ะฐะทัƒะตั‚ ัั‚ะพ ะฒ `set`, ะธ ะฒัั‘ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ะบะพั€ั€ะตะบั‚ะฝะพ: +ะ•ัะปะธ ะฒั‹ ะทะฐะฑั‹ะปะธ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `set` ะธ ะฟั€ะธะผะตะฝะธะปะธ `list` ะธะปะธ `tuple` ะฒะผะตัั‚ะพ ะฝะตะณะพ, FastAPI ะฒัั‘ ั€ะฐะฒะฝะพ ะฟั€ะตะพะฑั€ะฐะทัƒะตั‚ ัั‚ะพ ะฒ `set`, ะธ ะฒัั‘ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ะบะพั€ั€ะตะบั‚ะฝะพ: {* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *} diff --git a/docs/ru/docs/tutorial/schema-extra-example.md b/docs/ru/docs/tutorial/schema-extra-example.md index 5891f0d12d..e4a97c8801 100644 --- a/docs/ru/docs/tutorial/schema-extra-example.md +++ b/docs/ru/docs/tutorial/schema-extra-example.md @@ -8,36 +8,14 @@ ะ’ั‹ ะผะพะถะตั‚ะต ะพะฑัŠัะฒะธั‚ัŒ `examples` ะดะปั ะผะพะดะตะปะธ Pydantic, ะบะพั‚ะพั€ั‹ะต ะฑัƒะดัƒั‚ ะดะพะฑะฐะฒะปะตะฝั‹ ะฒ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝัƒัŽ JSON Schema. -//// tab | Pydantic v2 - {* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *} - -//// - ะญั‚ะฐ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะฐั ะธะฝั„ะพั€ะผะฐั†ะธั ะฑัƒะดะตั‚ ะดะพะฑะฐะฒะปะตะฝะฐ ะบะฐะบ ะตัั‚ัŒ ะฒ ะฒั‹ั…ะพะดะฝัƒัŽ **JSON Schema** ัั‚ะพะน ะผะพะดะตะปะธ ะธ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ API. -//// tab | Pydantic v2 - -ะ’ Pydantic ะฒะตั€ัะธะธ 2 ะฒั‹ ะฑัƒะดะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฐั‚ั€ะธะฑัƒั‚ `model_config`, ะบะพั‚ะพั€ั‹ะน ะฟั€ะธะฝะธะผะฐะตั‚ `dict`, ะบะฐะบ ะพะฟะธัะฐะฝะพ ะฒ <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">ะ”ะพะบัƒะผะตะฝั‚ะฐั†ะธะธ Pydantic: ะšะพะฝั„ะธะณัƒั€ะฐั†ะธั</a>. +ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฐั‚ั€ะธะฑัƒั‚ `model_config`, ะบะพั‚ะพั€ั‹ะน ะฟั€ะธะฝะธะผะฐะตั‚ `dict`, ะบะฐะบ ะพะฟะธัะฐะฝะพ ะฒ <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">ะ”ะพะบัƒะผะตะฝั‚ะฐั†ะธะธ Pydantic: ะšะพะฝั„ะธะณัƒั€ะฐั†ะธั</a>. ะ’ั‹ ะผะพะถะตั‚ะต ะทะฐะดะฐั‚ัŒ `"json_schema_extra"` ั `dict`, ัะพะดะตั€ะถะฐั‰ะธะผ ะปัŽะฑั‹ะต ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ะดะฐะฝะฝั‹ะต, ะบะพั‚ะพั€ั‹ะต ะฒั‹ ั…ะพั‚ะธั‚ะต ะฒะธะดะตั‚ัŒ ะฒ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝะพะน JSON Schema, ะฒะบะปัŽั‡ะฐั `examples`. -//// - -//// tab | Pydantic v1 - -ะ’ Pydantic ะฒะตั€ัะธะธ 1 ะฒั‹ ะฑัƒะดะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฒะฝัƒั‚ั€ะตะฝะฝะธะน ะบะปะฐัั `Config` ะธ `schema_extra`, ะบะฐะบ ะพะฟะธัะฐะฝะพ ะฒ <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">ะ”ะพะบัƒะผะตะฝั‚ะฐั†ะธะธ Pydantic: ะะฐัั‚ั€ะพะนะบะฐ ัั…ะตะผั‹</a>. - -ะ’ั‹ ะผะพะถะตั‚ะต ะทะฐะดะฐั‚ัŒ `schema_extra` ัะพ `dict`, ัะพะดะตั€ะถะฐั‰ะธะผ ะปัŽะฑั‹ะต ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ะดะฐะฝะฝั‹ะต, ะบะพั‚ะพั€ั‹ะต ะฒั‹ ั…ะพั‚ะธั‚ะต ะฒะธะดะตั‚ัŒ ะฒ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝะฝะพะน JSON Schema, ะฒะบะปัŽั‡ะฐั `examples`. - -//// - /// tip | ะŸะพะดัะบะฐะทะบะฐ ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั‚ะพั‚ ะถะต ะฟั€ะธั‘ะผ, ั‡ั‚ะพะฑั‹ ั€ะฐััˆะธั€ะธั‚ัŒ JSON Schema ะธ ะดะพะฑะฐะฒะธั‚ัŒ ัะฒะพัŽ ัะพะฑัั‚ะฒะตะฝะฝัƒัŽ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝัƒัŽ ะธะฝั„ะพั€ะผะฐั†ะธัŽ. @@ -124,7 +102,7 @@ OpenAPI 3.1.0 (ะธัะฟะพะปัŒะทัƒะตั‚ัั ะฝะฐั‡ะธะฝะฐั ั FastAPI 0.99.0) ะดะพะฑ ะšะปัŽั‡ะธ `dict` ะธะดะตะฝั‚ะธั„ะธั†ะธั€ัƒัŽั‚ ะบะฐะถะดั‹ะน ะฟั€ะธะผะตั€, ะฐ ะบะฐะถะดะพะต ะทะฝะฐั‡ะตะฝะธะต โ€” ัั‚ะพ ะตั‰ั‘ ะพะดะธะฝ `dict`. -ะšะฐะถะดั‹ะน ะบะพะฝะบั€ะตั‚ะฝั‹ะน ะฟั€ะธะผะตั€โ€‘`dict` ะฒ `examples` ะผะพะถะตั‚ ัะพะดะตั€ะถะฐั‚ัŒ: +ะšะฐะถะดั‹ะน ะบะพะฝะบั€ะตั‚ะฝั‹ะน ะฟั€ะธะผะตั€ `dict` ะฒ `examples` ะผะพะถะตั‚ ัะพะดะตั€ะถะฐั‚ัŒ: * `summary`: ะšั€ะฐั‚ะบะพะต ะพะฟะธัะฐะฝะธะต ะฟั€ะธะผะตั€ะฐ. * `description`: ะŸะพะดั€ะพะฑะฝะพะต ะพะฟะธัะฐะฝะธะต, ะบะพั‚ะพั€ะพะต ะผะพะถะตั‚ ัะพะดะตั€ะถะฐั‚ัŒ ั‚ะตะบัั‚ ะฒ Markdown. @@ -135,7 +113,7 @@ OpenAPI 3.1.0 (ะธัะฟะพะปัŒะทัƒะตั‚ัั ะฝะฐั‡ะธะฝะฐั ั FastAPI 0.99.0) ะดะพะฑ {* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} -### OpenAPI-ะฟั€ะธะผะตั€ั‹ ะฒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ { #openapi-examples-in-the-docs-ui } +### OpenAPI-ะฟั€ะธะผะตั€ั‹ ะฒ UI ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ { #openapi-examples-in-the-docs-ui } ะก `openapi_examples`, ะดะพะฑะฐะฒะปะตะฝะฝั‹ะผ ะฒ `Body()`, ัั‚ั€ะฐะฝะธั†ะฐ `/docs` ะฑัƒะดะตั‚ ะฒั‹ะณะปัะดะตั‚ัŒ ั‚ะฐะบ: @@ -213,7 +191,7 @@ OpenAPI ั‚ะฐะบะถะต ะดะพะฑะฐะฒะธะปะฐ ะฟะพะปั `example` ะธ `examples` ะฒ ะดั€ัƒะณ ### Swagger UI ะธ ัะฟะตั†ะธั„ะธั‡ะฝั‹ะต ะดะปั OpenAPI `examples` { #swagger-ui-and-openapi-specific-examples } -ะ ะฐะฝัŒัˆะต, ะฟะพัะบะพะปัŒะบัƒ Swagger UI ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะป ะฝะตัะบะพะปัŒะบะพ ะฟั€ะธะผะตั€ะพะฒ JSON Schema (ะฟะพ ัะพัั‚ะพัะฝะธัŽ ะฝะฐ 2023-08-26), ัƒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน ะฝะต ะฑั‹ะปะพ ัะฟะพัะพะฑะฐ ะฟะพะบะฐะทะฐั‚ัŒ ะฝะตัะบะพะปัŒะบะพ ะฟั€ะธะผะตั€ะพะฒ ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ. +ะขะตะฟะตั€ัŒ, ะฟะพัะบะพะปัŒะบัƒ Swagger UI ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะป ะฝะตัะบะพะปัŒะบะพ ะฟั€ะธะผะตั€ะพะฒ JSON Schema (ะฟะพ ัะพัั‚ะพัะฝะธัŽ ะฝะฐ 2023-08-26), ัƒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน ะฝะต ะฑั‹ะปะพ ัะฟะพัะพะฑะฐ ะฟะพะบะฐะทะฐั‚ัŒ ะฝะตัะบะพะปัŒะบะพ ะฟั€ะธะผะตั€ะพะฒ ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ. ะงั‚ะพะฑั‹ ั€ะตัˆะธั‚ัŒ ัั‚ะพ, FastAPI `0.103.0` **ะดะพะฑะฐะฒะธะป ะฟะพะดะดะตั€ะถะบัƒ** ะพะฑัŠัะฒะปะตะฝะธั ั‚ะพะณะพ ะถะต ัั‚ะฐั€ะพะณะพ, **ัะฟะตั†ะธั„ะธั‡ะฝะพะณะพ ะดะปั OpenAPI**, ะฟะพะปั `examples` ั ะฝะพะฒั‹ะผ ะฟะฐั€ะฐะผะตั‚ั€ะพะผ `openapi_examples`. ๐Ÿค“ From e0abd210f64110de77c09909245e9652a4fec78e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:03:31 +0000 Subject: [PATCH 100/110] =?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 33926e3afc..f4572ffe8b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update translations for ru (update-outdated). PR [#14693](https://github.com/fastapi/fastapi/pull/14693) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for pt (update-outdated). PR [#14724](https://github.com/fastapi/fastapi/pull/14724) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update Korean LLM prompt. PR [#14740](https://github.com/fastapi/fastapi/pull/14740) by [@hard-coders](https://github.com/hard-coders). * ๐ŸŒ Improve LLM prompt for Turkish translations. PR [#14728](https://github.com/fastapi/fastapi/pull/14728) by [@Kadermiyanyedi](https://github.com/Kadermiyanyedi). From b9b75ba5f1a51c2edcae3df4dffa2b0f1a2353d0 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:07:05 +0300 Subject: [PATCH 101/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20LLM=20prompt=20?= =?UTF-8?q?for=20Russian=20translations=20(#14733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add some specific translations --- docs/ru/llm-prompt.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/ru/llm-prompt.md b/docs/ru/llm-prompt.md index 6a437bdd14..9131a5d3b4 100644 --- a/docs/ru/llm-prompt.md +++ b/docs/ru/llm-prompt.md @@ -90,5 +90,12 @@ For the following technical terms, use these specific translations to ensure con * serve (meaning providing access to something): ยซะพั‚ะดะฐะฒะฐั‚ัŒยป (or `ะฟั€ะตะดะพัั‚ะฐะฒะปัั‚ัŒ ะดะพัั‚ัƒะฟ ะบ`) * recap (noun): ั€ะตะทัŽะผะต * utility function: ะฒัะฟะพะผะพะณะฐั‚ะตะปัŒะฝะฐั ั„ัƒะฝะบั†ะธั +* fast to code: ะฟะพะทะฒะพะปัะตั‚ ะฑั‹ัั‚ั€ะพ ะฟะธัะฐั‚ัŒ ะบะพะด +* Tutorial - User Guide: ะฃั‡ะตะฑะฝะธะบ - ะ ัƒะบะพะฒะพะดัั‚ะฒะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั +* submodule: ะฟะพะดะผะพะดัƒะปัŒ +* subpackage: ะฟะพะดะฟะฐะบะตั‚ +* router: ั€ะพัƒั‚ะตั€ +* building, deploying, accessing (when describing features of FastAPI Cloud): ัะพะทะดะฐะฝะธe ะพะฑั€ะฐะทะฐ, ั€ะฐะทะฒะตั€ั‚ั‹ะฒะฐะฝะธะต ะธ ะดะพัั‚ัƒะฟ +* type checker tool: ะธะฝัั‚ั€ัƒะผะตะฝั‚ ะฟั€ะพะฒะตั€ะบะธ ั‚ะธะฟะพะฒ Do not add whitespace in `ั‚.ะด.`, `ั‚.ะฟ.`. From 6e47171e9cb79a4616014cb9fce5b88912a820ee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 07:07:28 +0000 Subject: [PATCH 102/110] =?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 f4572ffe8b..62b611f8a9 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update LLM prompt for Russian translations. PR [#14733](https://github.com/fastapi/fastapi/pull/14733) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐ŸŒ Update translations for ru (update-outdated). PR [#14693](https://github.com/fastapi/fastapi/pull/14693) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for pt (update-outdated). PR [#14724](https://github.com/fastapi/fastapi/pull/14724) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update Korean LLM prompt. PR [#14740](https://github.com/fastapi/fastapi/pull/14740) by [@hard-coders](https://github.com/hard-coders). From 509afeb475e602bd11f2ffa165ccd0ed3d10a19f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= <tiangolo@gmail.com> Date: Thu, 22 Jan 2026 01:27:31 -0800 Subject: [PATCH 103/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20de=20(update-outdated)=20(#14690)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ๐ŸŒ Update translations for de (update-outdated) * Apply suggestions from code review --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- docs/de/docs/_llm-test.md | 2 +- docs/de/docs/tutorial/bigger-applications.md | 26 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/de/docs/_llm-test.md b/docs/de/docs/_llm-test.md index bc7ce363c7..0b95fe3a8d 100644 --- a/docs/de/docs/_llm-test.md +++ b/docs/de/docs/_llm-test.md @@ -189,7 +189,7 @@ Siehe Abschnitt `### Links` im allgemeinen Prompt in `scripts/translate.py`. //// -## HTML โ€žabbrโ€œ-Elemente { #html-abbr-elements } +## HTML-โ€žabbrโ€œ-Elemente { #html-abbr-elements } //// tab | Test diff --git a/docs/de/docs/tutorial/bigger-applications.md b/docs/de/docs/tutorial/bigger-applications.md index 963baf44d5..d478d77c27 100644 --- a/docs/de/docs/tutorial/bigger-applications.md +++ b/docs/de/docs/tutorial/bigger-applications.md @@ -56,19 +56,19 @@ from app.routers import items Die gleiche Dateistruktur mit Kommentaren: -``` +```bash . -โ”œโ”€โ”€ app # โ€žappโ€œ ist ein Python-Package -โ”‚ย ย  โ”œโ”€โ”€ __init__.py # diese Datei macht โ€žappโ€œ zu einem โ€žPython-Packageโ€œ -โ”‚ย ย  โ”œโ”€โ”€ main.py # โ€žmainโ€œ-Modul, z. B. import app.main -โ”‚ย ย  โ”œโ”€โ”€ dependencies.py # โ€ždependenciesโ€œ-Modul, z. B. import app.dependencies -โ”‚ย ย  โ””โ”€โ”€ routers # โ€žroutersโ€œ ist ein โ€žPython-Subpackageโ€œ -โ”‚ย ย  โ”‚ โ”œโ”€โ”€ __init__.py # macht โ€žroutersโ€œ zu einem โ€žPython-Subpackageโ€œ -โ”‚ย ย  โ”‚ โ”œโ”€โ”€ items.py # โ€žitemsโ€œ-Submodul, z. B. import app.routers.items -โ”‚ย ย  โ”‚ โ””โ”€โ”€ users.py # โ€žusersโ€œ-Submodul, z. B. import app.routers.users -โ”‚ย ย  โ””โ”€โ”€ internal # โ€žinternalโ€œ ist ein โ€žPython-Subpackageโ€œ -โ”‚ย ย  โ”œโ”€โ”€ __init__.py # macht โ€žinternalโ€œ zu einem โ€žPython-Subpackageโ€œ -โ”‚ย ย  โ””โ”€โ”€ admin.py # โ€žadminโ€œ-Submodul, z. B. import app.internal.admin +โ”œโ”€โ”€ app # "app" ist ein Python-Package +โ”‚ย ย  โ”œโ”€โ”€ __init__.py # diese Datei macht "app" zu einem "Python-Package" +โ”‚ย ย  โ”œโ”€โ”€ main.py # "main"-Modul, z. B. import app.main +โ”‚ย ย  โ”œโ”€โ”€ dependencies.py # "dependencies"-Modul, z. B. import app.dependencies +โ”‚ย ย  โ””โ”€โ”€ routers # "routers" ist ein "Python-Subpackage" +โ”‚ย ย  โ”‚ โ”œโ”€โ”€ __init__.py # macht "routers" zu einem "Python-Subpackage" +โ”‚ย ย  โ”‚ โ”œโ”€โ”€ items.py # "items"-Submodul, z. B. import app.routers.items +โ”‚ย ย  โ”‚ โ””โ”€โ”€ users.py # "users"-Submodul, z. B. import app.routers.users +โ”‚ย ย  โ””โ”€โ”€ internal # "internal" ist ein "Python-Subpackage" +โ”‚ย ย  โ”œโ”€โ”€ __init__.py # macht "internal" zu einem "Python-Subpackage" +โ”‚ย ย  โ””โ”€โ”€ admin.py # "admin"-Submodul, z. B. import app.internal.admin ``` ## `APIRouter` { #apirouter } @@ -479,7 +479,7 @@ $ fastapi dev app/main.py </div> -und รถffnen Sie die Dokumentation unter <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. +Und รถffnen Sie die Dokumentation unter <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. Sie sehen die automatische API-Dokumentation, einschlieรŸlich der Pfade aller Submodule, mit den richtigen Pfaden (und Prรคfixen) und den richtigen Tags: From f1a39cab12c615ba50b21f32159e523baa560424 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:27:58 +0000 Subject: [PATCH 104/110] =?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 62b611f8a9..1cd2b887f6 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update translations for de (update-outdated). PR [#14690](https://github.com/fastapi/fastapi/pull/14690) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update LLM prompt for Russian translations. PR [#14733](https://github.com/fastapi/fastapi/pull/14733) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐ŸŒ Update translations for ru (update-outdated). PR [#14693](https://github.com/fastapi/fastapi/pull/14693) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update translations for pt (update-outdated). PR [#14724](https://github.com/fastapi/fastapi/pull/14724) by [@tiangolo](https://github.com/tiangolo). From 74cc27fd5aa1e9376d30105fe014ab8b2b1319b1 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem <svlandeg@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:32:34 +0100 Subject: [PATCH 105/110] =?UTF-8?q?=F0=9F=94=A7=20Ensure=20that=20an=20edi?= =?UTF-8?q?t=20to=20`uv.lock`=20gets=20the=20`internal`=20label=20(#14759)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add uv.lock to files for labeling the PR with 'internal' --- .github/labeler.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index cdaefbf2d8..57c5e1120f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -31,6 +31,7 @@ internal: - .pre-commit-config.yaml - pdm_build.py - requirements*.txt + - uv.lock - docs/en/data/sponsors.yml - docs/en/overrides/main.html - all-globs-to-all-files: From 597b435ae7f0c4694f654bce6c7c27206aa690ef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:33:00 +0000 Subject: [PATCH 106/110] =?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 1cd2b887f6..f1321b7809 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -36,6 +36,7 @@ hide: ### Internal +* ๐Ÿ”ง Ensure that an edit to `uv.lock` gets the `internal` label. PR [#14759](https://github.com/fastapi/fastapi/pull/14759) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ”ง Update sponsors: remove Requestly. PR [#14735](https://github.com/fastapi/fastapi/pull/14735) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update sponsors, LambdaTest changes to TestMu AI. PR [#14734](https://github.com/fastapi/fastapi/pull/14734) by [@tiangolo](https://github.com/tiangolo). * โฌ† Bump actions/cache from 4 to 5. PR [#14511](https://github.com/fastapi/fastapi/pull/14511) by [@dependabot[bot]](https://github.com/apps/dependabot). From eaf07c5d849ce4564b8708b4965a232fd026f1fe Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Sun, 25 Jan 2026 00:16:10 +0300 Subject: [PATCH 107/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20ko=20(update=20outdated,=20found=20by=20fixer=20tool)=20(?= =?UTF-8?q?#14738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update outdated pages found by fixer tool * Re-translate with updated prompt (fixed translation for `you`) * Re-translate with `gtpt-5` model * Re-translate with new preferred translation for `burger` * Re-translate with new preferred translations for `app` and `command` --- docs/ko/docs/async.md | 372 ++++++++++++++------------ docs/ko/docs/fastapi-cli.md | 96 +++---- docs/ko/docs/features.md | 176 ++++++------ docs/ko/docs/help-fastapi.md | 199 +++++++------- docs/ko/docs/history-design-future.md | 82 +++--- 5 files changed, 468 insertions(+), 457 deletions(-) diff --git a/docs/ko/docs/async.md b/docs/ko/docs/async.md index ec503d5406..36f1ca6bf1 100644 --- a/docs/ko/docs/async.md +++ b/docs/ko/docs/async.md @@ -1,18 +1,18 @@ -# ๋™์‹œ์„ฑ๊ณผ async / await +# ๋™์‹œ์„ฑ๊ณผ async / await { #concurrency-and-async-await } -*๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์—์„œ์˜ `async def` ๋ฌธ๋ฒ•์— ๋Œ€ํ•œ ์„ธ๋ถ€์‚ฌํ•ญ๊ณผ ๋น„๋™๊ธฐ ์ฝ”๋“œ, ๋™์‹œ์„ฑ ๋ฐ ๋ณ‘๋ ฌ์„ฑ์— ๋Œ€ํ•œ ๋ฐฐ๊ฒฝ +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ์˜ `async def` ๋ฌธ๋ฒ•์— ๋Œ€ํ•œ ์„ธ๋ถ€์‚ฌํ•ญ๊ณผ ๋น„๋™๊ธฐ ์ฝ”๋“œ, ๋™์‹œ์„ฑ ๋ฐ ๋ณ‘๋ ฌ์„ฑ์— ๋Œ€ํ•œ ๋ฐฐ๊ฒฝ -## ๋ฐ”์˜์‹  ๊ฒฝ์šฐ +## ๋ฐ”์˜์‹ ๊ฐ€์š”? { #in-a-hurry } -<strong>์š”์•ฝ</strong> +<abbr title="too long; didn't read - ๋„ˆ๋ฌด ๊ธธ์–ด์„œ ์ฝ์ง€ ์•Š์Œ"><strong>TL;DR:</strong></abbr> -๋‹ค์Œ๊ณผ ๊ฐ™์ด `await`๋ฅผ ์‚ฌ์šฉํ•ด ํ˜ธ์ถœํ•˜๋Š” ์ œ3์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ: +๋‹ค์Œ๊ณผ ๊ฐ™์ด `await`๋ฅผ ์‚ฌ์šฉํ•ด ํ˜ธ์ถœํ•˜๋ผ๊ณ  ์•ˆ๋‚ดํ•˜๋Š” ์ œ3์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ: ```Python results = await some_library() ``` -๋‹ค์Œ์ฒ˜๋Ÿผ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๋ฅผ `async def`๋ฅผ ์‚ฌ์šฉํ•ด ์„ ์–ธํ•˜์‹ญ์‹œ์˜ค: +๋‹ค์Œ์ฒ˜๋Ÿผ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๋ฅผ `async def`๋ฅผ ์‚ฌ์šฉํ•ด ์„ ์–ธํ•˜์‹ญ์‹œ์˜ค: ```Python hl_lines="2" @app.get('/') @@ -29,7 +29,7 @@ async def read_results(): --- -๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, API, ํŒŒ์ผ์‹œ์Šคํ…œ ๋“ฑ๊ณผ ์˜์‚ฌ์†Œํ†ตํ•˜๋Š” ์ œ3์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๊ทธ๊ฒƒ์ด `await`๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ(ํ˜„์žฌ ๊ฑฐ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๊ทธ๋Ÿฌํ•ฉ๋‹ˆ๋‹ค), *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๋ฅผ ์ผ๋ฐ˜์ ์ธ `def`๋ฅผ ์‚ฌ์šฉํ•ด ์„ ์–ธํ•˜์‹ญ์‹œ์˜ค: +๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, API, ํŒŒ์ผ์‹œ์Šคํ…œ ๋“ฑ๊ณผ ์˜์‚ฌ์†Œํ†ตํ•˜๋Š” ์ œ3์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๊ทธ๊ฒƒ์ด `await` ์‚ฌ์šฉ์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ(ํ˜„์žฌ ๋Œ€๋ถ€๋ถ„์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๊ทธ๋Ÿฌํ•ฉ๋‹ˆ๋‹ค), *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๋ฅผ ์ผ๋ฐ˜์ ์ธ `def`๋ฅผ ์‚ฌ์šฉํ•ด ์„ ์–ธํ•˜์‹ญ์‹œ์˜ค: ```Python hl_lines="2" @app.get('/') @@ -40,23 +40,23 @@ def results(): --- -๋งŒ์•ฝ ๋‹น์‹ ์˜ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์ด (์–ด์งธ์„œ์ธ์ง€) ๋‹ค๋ฅธ ๋ฌด์—‡๊ณผ ์˜์‚ฌ์†Œํ†ตํ•˜๊ณ  ๊ทธ๊ฒƒ์ด ์‘๋‹ตํ•˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด `async def`๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. +๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด (์–ด์งธ์„œ์ธ์ง€) ๋‹ค๋ฅธ ์–ด๋–ค ๊ฒƒ๊ณผ๋„ ํ†ต์‹ ํ•˜๊ณ  ๊ทธ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด, ๋‚ด๋ถ€์—์„œ `await`๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋”๋ผ๋„ `async def`๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. --- -๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด, ๊ทธ๋ƒฅ `def`๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. +์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด, ์ผ๋ฐ˜์ ์ธ `def`๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. --- -**์ฐธ๊ณ **: *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์—์„œ ํ•„์š”ํ•œ๋งŒํผ `def`์™€ `async def`๋ฅผ ํ˜ผ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ๊ฐ€์žฅ ์•Œ๋งž์€ ๊ฒƒ์„ ์„ ํƒํ•ด์„œ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. FastAPI๊ฐ€ ์ž์ฒด์ ์œผ๋กœ ์•Œ๋งž์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**์ฐธ๊ณ **: *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ํ•„์š”ํ•œ ๋งŒํผ `def`์™€ `async def`๋ฅผ ํ˜ผ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ๊ฐ์— ๋Œ€ํ•ด ๊ฐ€์žฅ ์•Œ๋งž์€ ์˜ต์…˜์„ ์„ ํƒํ•ด ์ •์˜ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. FastAPI๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. -์–ด์ฐŒ๋˜์—ˆ๋“ , ์ƒ๊ธฐ ์–ด๋– ํ•œ ๊ฒฝ์šฐ๋ผ๋„, FastAPI๋Š” ์—ฌ์ „ํžˆ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ž‘๋™ํ•˜๊ณ  ๋งค์šฐ ๋น ๋ฆ…๋‹ˆ๋‹ค. +์–ด์จŒ๋“  ์œ„์˜ ์–ด๋–ค ๊ฒฝ์šฐ์—์„œ๋„ FastAPI๋Š” ์—ฌ์ „ํžˆ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ ๋งค์šฐ ๋น ๋ฆ…๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‚˜ ์ƒ๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•จ์œผ๋กœ์จ ์–ด๋А ์ •๋„์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์œ„์˜ ๋‹จ๊ณ„๋ฅผ ๋”ฐ๋ฅด๋ฉด, ๋ช‡ ๊ฐ€์ง€ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ +## ๊ธฐ์ˆ ์  ์„ธ๋ถ€์‚ฌํ•ญ { #technical-details } -์ตœ์‹  ํŒŒ์ด์ฌ ๋ฒ„์ „์€ `async`์™€ `await` ๋ฌธ๋ฒ•๊ณผ ํ•จ๊ป˜ **โ€œ์ฝ”๋ฃจํ‹ดโ€**์ด๋ผ๊ณ  ํ•˜๋Š” ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋Š” **โ€œ๋น„๋™๊ธฐ ์ฝ”๋“œโ€**๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +์ตœ์‹  ํŒŒ์ด์ฌ ๋ฒ„์ „์€ **โ€œ์ฝ”๋ฃจํ‹ดโ€**์ด๋ผ๊ณ  ํ•˜๋Š” ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋Š” **โ€œ๋น„๋™๊ธฐ ์ฝ”๋“œโ€**๋ฅผ **`async` ๋ฐ `await`** ๋ฌธ๋ฒ•๊ณผ ํ•จ๊ป˜ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์„น์…˜๋“ค์—์„œ ํ•ด๋‹น ๋ฌธ์žฅ์„ ๋ถ€๋ถ„๋ณ„๋กœ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: @@ -64,251 +64,283 @@ def results(): * **`async`์™€ `await`** * **์ฝ”๋ฃจํ‹ด** -## ๋น„๋™๊ธฐ ์ฝ”๋“œ +## ๋น„๋™๊ธฐ ์ฝ”๋“œ { #asynchronous-code } -๋น„๋™๊ธฐ ์ฝ”๋“œ๋ž€ ์–ธ์–ด ๐Ÿ’ฌ ๊ฐ€ ์ฝ”๋“œ์˜ ์–ด๋А ํ•œ ๋ถ€๋ถ„์—์„œ, ์ปดํ“จํ„ฐ / ํ”„๋กœ๊ทธ๋žจ๐Ÿค–์—๊ฒŒ *๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€*๊ฐ€ ์–ด๋”˜๊ฐ€์—์„œ ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผํ•œ๋‹ค๊ณ  ๋งํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. *๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€*๊ฐ€ โ€œ๋А๋ฆฐ-ํŒŒ์ผ" ๐Ÿ“ ์ด๋ผ๊ณ  ๋ถˆ๋ฆฐ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. +๋น„๋™๊ธฐ ์ฝ”๋“œ๋Š” ์–ธ์–ด ๐Ÿ’ฌ ๊ฐ€ ์ฝ”๋“œ์˜ ์–ด๋А ํ•œ ๋ถ€๋ถ„์—์„œ ์ปดํ“จํ„ฐ/ํ”„๋กœ๊ทธ๋žจ ๐Ÿค– ์—๊ฒŒ, ์–ด๋А ์‹œ์ ์—๋Š” ์–ด๋”˜๊ฐ€์—์„œ *๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€*๊ฐ€ ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•œ๋‹ค๊ณ  ๋งํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. ๊ทธ *๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€*๋ฅผ "slow-file" ๐Ÿ“ ์ด๋ผ๊ณ  ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ โ€œ๋А๋ฆฐ-ํŒŒ์ผโ€ ๐Ÿ“์ด ๋๋‚ ๋•Œ๊นŒ์ง€ ์ปดํ“จํ„ฐ๋Š” ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋”ฐ๋ผ์„œ ๊ทธ ์‹œ๊ฐ„ ๋™์•ˆ ์ปดํ“จํ„ฐ๋Š” "slow-file" ๐Ÿ“ ์ด ๋๋‚˜๋Š” ๋™์•ˆ ๋‹ค๋ฅธ ์ž‘์—…์„ ํ•˜๋Ÿฌ ๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ ๋‹ค์Œ ์ปดํ“จํ„ฐ / ํ”„๋กœ๊ทธ๋žจ ๐Ÿค– ์€ ๋‹ค์‹œ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐํšŒ๊ฐ€ ์žˆ์„ ๋•Œ๋งˆ๋‹ค ๋‹ค์‹œ ๋Œ์•„์˜ค๊ฑฐ๋‚˜, ํ˜น์€ ๋‹น์‹œ์— ์ˆ˜ํ–‰ํ•ด์•ผํ•˜๋Š” ์ž‘์—…๋“ค์ด ์™„๋ฃŒ๋  ๋•Œ๋งˆ๋‹ค ๋‹ค์‹œ ๋Œ์•„์˜ต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ ๐Ÿค– ์€ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๋˜ ์ž‘์—… ์ค‘ ์–ด๋А ๊ฒƒ์ด ์ด๋ฏธ ์™„๋ฃŒ๋˜์—ˆ๋Š”์ง€, ๊ทธ๊ฒƒ ๐Ÿค– ์ด ํ•ด์•ผํ•˜๋Š” ๋ชจ๋“  ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. +๊ทธ ๋‹ค์Œ ์ปดํ“จํ„ฐ/ํ”„๋กœ๊ทธ๋žจ ๐Ÿค– ์€ ๋‹ค์‹œ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ค‘์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐํšŒ๊ฐ€ ์žˆ์„ ๋•Œ๋งˆ๋‹ค ๋Œ์•„์˜ค๊ฑฐ๋‚˜, ํ˜น์€ ๊ทธ ์‹œ์ ์— ํ•ด์•ผ ํ•  ์ž‘์—…์„ ๋ชจ๋‘ ๋๋‚ผ ๋•Œ๋งˆ๋‹ค ๋Œ์•„์˜ต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ธฐ๋‹ค๋ฆฌ๋˜ ์ž‘์—… ์ค‘ ์ด๋ฏธ ๋๋‚œ ๊ฒƒ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋ฉด์„œ, ํ•ด์•ผ ํ–ˆ๋˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. -๋‹ค์Œ์œผ๋กœ, ๊ทธ๊ฒƒ ๐Ÿค– ์€ ์™„๋ฃŒํ•  ์ฒซ๋ฒˆ์งธ ์ž‘์—…์— ์ฐฉ์ˆ˜ํ•˜๊ณ (์šฐ๋ฆฌ์˜ "๋А๋ฆฐ-ํŒŒ์ผ" ๐Ÿ“ ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ฉ์‹œ๋‹ค) ๊ทธ์— ๋Œ€ํ•ด ์ˆ˜ํ–‰ํ•ด์•ผํ•˜๋Š” ์ž‘์—…์„ ๊ณ„์†ํ•ฉ๋‹ˆ๋‹ค. +๋‹ค์Œ์œผ๋กœ, ์™„๋ฃŒ๋œ ์ฒซ ๋ฒˆ์งธ ์ž‘์—…(์šฐ๋ฆฌ์˜ "slow-file" ๐Ÿ“ ์ด๋ผ๊ณ  ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค)์„ ๊ฐ€์ ธ์™€์„œ, ๊ทธ์— ๋Œ€ํ•ด ํ•ด์•ผ ํ–ˆ๋˜ ์ž‘์—…์„ ๊ณ„์†ํ•ฉ๋‹ˆ๋‹ค. -"๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ"์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋น„๊ต์  "๋А๋ฆฐ" (ํ”„๋กœ์„ธ์„œ์™€ RAM ๋ฉ”๋ชจ๋ฆฌ ์†๋„์— ๋น„ํ•ด) <abbr title="Input and Output">I/O</abbr> ์ž‘์—…์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ์˜ ๊ฒƒ๋“ค์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค: +์ด "๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ"์€ ์ผ๋ฐ˜์ ์œผ๋กœ ํ”„๋กœ์„ธ์„œ์™€ RAM ๋ฉ”๋ชจ๋ฆฌ ์†๋„์— ๋น„ํ•ด ์ƒ๋Œ€์ ์œผ๋กœ "๋А๋ฆฐ" <abbr title="Input and Output - ์ž…๋ ฅ/์ถœ๋ ฅ">I/O</abbr> ์ž‘์—…์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค: -* ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์ „์†ก๋˜๋Š” ๋ฐ์ดํ„ฐ -* ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ˆ˜์‹ ํ• , ๋‹น์‹ ์˜ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ๋ถ€ํ„ฐ ์ „์†ก๋˜๋Š” ๋ฐ์ดํ„ฐ -* ์‹œ์Šคํ…œ์ด ์ฝ๊ณ  ํ”„๋กœ๊ทธ๋žจ์— ์ „๋‹ฌํ•  ๋””์Šคํฌ ๋‚ด์˜ ํŒŒ์ผ ๋‚ด์šฉ -* ๋‹น์‹ ์˜ ํ”„๋กœ๊ทธ๋žจ์ด ์‹œ์Šคํ…œ์— ์ „๋‹ฌํ•˜๋Š”, ๋””์Šคํฌ์— ์ž‘์„ฑ๋  ๋‚ด์šฉ +* ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋Š” ๊ฒƒ +* ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ๊ทธ๋žจ์ด ๋ณด๋‚ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š” ๊ฒƒ +* ์‹œ์Šคํ…œ์ด ๋””์Šคํฌ์˜ ํŒŒ์ผ ๋‚ด์šฉ์„ ์ฝ์–ด์„œ ํ”„๋กœ๊ทธ๋žจ์— ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ +* ํ”„๋กœ๊ทธ๋žจ์ด ์‹œ์Šคํ…œ์— ์ „๋‹ฌํ•œ ๋‚ด์šฉ์„ ๋””์Šคํฌ์— ์“ฐ๋Š” ๊ฒƒ * ์›๊ฒฉ API ์ž‘์—… -* ์™„๋ฃŒ๋  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—… -* ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ -* ๊ธฐํƒ€ +* ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋Š” ๊ฒƒ +* ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ +* ๊ธฐํƒ€ ๋“ฑ๋“ฑ -์ˆ˜ํ–‰ ์‹œ๊ฐ„์˜ ๋Œ€๋ถ€๋ถ„์ด <abbr title="Input and Output">I/O</abbr> ์ž‘์—…์„ ๊ธฐ๋‹ค๋ฆฌ๋Š”๋ฐ์— ์†Œ์š”๋˜๊ธฐ ๋•Œ๋ฌธ์—, "I/O์— ๋ฌถ์ธ" ์ž‘์—…์ด๋ผ๊ณ  ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. +์‹คํ–‰ ์‹œ๊ฐ„์˜ ๋Œ€๋ถ€๋ถ„์ด <abbr title="Input and Output - ์ž…๋ ฅ/์ถœ๋ ฅ">I/O</abbr> ์ž‘์—…์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋ฐ ์†Œ๋น„๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฅผ "I/O bound" ์ž‘์—…์ด๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. -์ด๊ฒƒ์€ "๋น„๋™๊ธฐ"๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š”๋ฐ ์ปดํ“จํ„ฐ / ํ”„๋กœ๊ทธ๋žจ์ด ์ž‘์—… ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ง€๊ณ  ์ผ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก, ๋А๋ฆฐ ์ž‘์—…์— "๋™๊ธฐํ™”"๋˜์–ด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ์ž‘์—…์ด ์™„๋ฃŒ๋  ์ •ํ™•ํ•œ ์‹œ์ ๋งŒ์„ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +์ด๊ฒƒ์€ ์ปดํ“จํ„ฐ/ํ”„๋กœ๊ทธ๋žจ์ด ๋А๋ฆฐ ์ž‘์—…๊ณผ "๋™๊ธฐํ™”"๋˜์–ด, ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์€ ์ฑ„ ๊ทธ ์ž‘์—…์ด ๋๋‚˜๋Š” ์ •ํ™•ํ•œ ์‹œ์ ๋งŒ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์™€ ์ผ์„ ๊ณ„์†ํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— "๋น„๋™๊ธฐ"๋ผ๊ณ  ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. -์ด ๋Œ€์‹ ์—, "๋น„๋™๊ธฐ" ์‹œ์Šคํ…œ์—์„œ๋Š”, ์ž‘์—…์€ ์ผ๋‹จ ์™„๋ฃŒ๋˜๋ฉด, ์ปดํ“จํ„ฐ / ํ”„๋กœ๊ทธ๋žจ์ด ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ๋Š” ์ผ์„ ์™„๋ฃŒํ•˜๊ณ  ์ดํ›„ ๋‹ค์‹œ ๋Œ์•„์™€์„œ ๊ทธ๊ฒƒ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„ ์ด๋ฅผ ์‚ฌ์šฉํ•ด ์ž‘์—…์„ ์ง€์†ํ•  ๋•Œ๊นŒ์ง€ ์ž ์‹œ (๋ช‡ ๋งˆ์ดํฌ๋กœ์ดˆ) ๋Œ€๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋Œ€์‹  "๋น„๋™๊ธฐ" ์‹œ์Šคํ…œ์—์„œ๋Š”, ์ž‘์—…์ด ๋๋‚˜๋ฉด ์ปดํ“จํ„ฐ/ํ”„๋กœ๊ทธ๋žจ์ด ํ•˜๋Ÿฌ ๊ฐ”๋˜ ์ผ์„ ๋งˆ์น  ๋•Œ๊นŒ์ง€ ์ž ์‹œ(๋ช‡ ๋งˆ์ดํฌ๋กœ์ดˆ) ์ค„์—์„œ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€, ๋‹ค์‹œ ๋Œ์•„์™€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„ ์ด๋ฅผ ์‚ฌ์šฉํ•ด ์ž‘์—…์„ ๊ณ„์†ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -"๋™๊ธฐ"("๋น„๋™๊ธฐ"์˜ ๋ฐ˜๋Œ€)๋Š” ์ปดํ“จํ„ฐ / ํ”„๋กœ๊ทธ๋žจ์ด ์ƒ์ดํ•œ ์ž‘์—…๋“ค๊ฐ„ ์ „ํ™˜์„ ํ•˜๊ธฐ ์ „์— ๊ทธ๊ฒƒ์ด ๋Œ€๊ธฐ๋ฅผ ๋™๋ฐ˜ํ•˜๊ฒŒ ๋ ์ง€๋ผ๋„ ๋ชจ๋“  ์ˆœ์„œ๋ฅผ ๋”ฐ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— "์ˆœ์ฐจ"๋ผ๋Š” ์šฉ์–ด๋กœ๋„ ํ”ํžˆ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. +"๋™๊ธฐ"(โ€œ๋น„๋™๊ธฐโ€์˜ ๋ฐ˜๋Œ€)๋Š” ๋ณดํ†ต "์ˆœ์ฐจ"๋ผ๋Š” ์šฉ์–ด๋กœ๋„ ๋ถˆ๋ฆฌ๋Š”๋ฐ, ์ปดํ“จํ„ฐ/ํ”„๋กœ๊ทธ๋žจ์ด ๋‹ค๋ฅธ ์ž‘์—…์œผ๋กœ ์ „ํ™˜ํ•˜๊ธฐ ์ „์— ๋ชจ๋“  ๋‹จ๊ณ„๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ๋”ฐ๋ฅด๊ธฐ ๋•Œ๋ฌธ์ด๋ฉฐ, ๊ทธ ๋‹จ๊ณ„๋“ค์— ๊ธฐ๋‹ค๋ฆผ์ด ํฌํ•จ๋˜์–ด ์žˆ๋”๋ผ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. -### ๋™์‹œ์„ฑ๊ณผ ๋ฒ„๊ฑฐ +### ๋™์‹œ์„ฑ๊ณผ ํ–„๋ฒ„๊ฑฐ { #concurrency-and-burgers } -์œ„์—์„œ ์„ค๋ช…ํ•œ **๋น„๋™๊ธฐ** ์ฝ”๋“œ์— ๋Œ€ํ•œ ๊ฐœ๋…์€ ์ข…์ข… **"๋™์‹œ์„ฑ"**์ด๋ผ๊ณ ๋„ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ **"๋ณ‘๋ ฌ์„ฑ"**๊ณผ๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค. +์œ„์—์„œ ์„ค๋ช…ํ•œ **๋น„๋™๊ธฐ** ์ฝ”๋“œ์— ๋Œ€ํ•œ ๊ฐœ๋…์€ ๋•Œ๋•Œ๋กœ **"๋™์‹œ์„ฑ"**์ด๋ผ๊ณ ๋„ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. ์ด๋Š” **"๋ณ‘๋ ฌ์„ฑ"**๊ณผ๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค. -**๋™์‹œ์„ฑ**๊ณผ **๋ณ‘๋ ฌ์„ฑ**์€ ๋ชจ๋‘ "๋™์‹œ์— ์ผ์–ด๋‚˜๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์ผ๋“ค"๊ณผ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. +**๋™์‹œ์„ฑ**๊ณผ **๋ณ‘๋ ฌ์„ฑ**์€ ๋ชจ๋‘ "๋Œ€๋žต ๊ฐ™์€ ์‹œ๊ฐ„์— ์ผ์–ด๋‚˜๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์ผ๋“ค"๊ณผ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ *๋™์‹œ์„ฑ*๊ณผ *๋ณ‘๋ ฌ์„ฑ*์˜ ์„ธ๋ถ€์ ์ธ ๊ฐœ๋…์—๋Š” ๊ฝค ์ฐจ์ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. -์ฐจ์ด๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด, ๋‹ค์Œ์˜ ๋ฒ„๊ฑฐ์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ์ƒ์ƒํ•ด๋ณด์‹ญ์‹œ์˜ค: +์ฐจ์ด๋ฅผ ๋ณด๊ธฐ ์œ„ํ•ด, ๋‹ค์Œ์˜ ํ–„๋ฒ„๊ฑฐ ์ด์•ผ๊ธฐ๋ฅผ ์ƒ์ƒํ•ด๋ณด์„ธ์š”: -### ๋™์‹œ ๋ฒ„๊ฑฐ +### ๋™์‹œ ํ–„๋ฒ„๊ฑฐ { #concurrent-burgers } -๋‹น์‹ ์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ์™€ ํŒจ์ŠคํŠธํ‘ธ๋“œ ๐Ÿ” ๋ฅผ ๋จน์œผ๋Ÿฌ ๊ฐ”์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์€ ์ ์› ๐Ÿ’ ์ด ๋‹น์‹  ์•ž์— ์žˆ๋Š” ์‚ฌ๋žŒ๋“ค์˜ ์ฃผ๋ฌธ์„ ๋ฐ›์„ ๋™์•ˆ ์ค„์„ ์„œ์„œ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ๋ถ„์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์™€ ํŒจ์ŠคํŠธํ‘ธ๋“œ๋ฅผ ๋จน์œผ๋Ÿฌ ๊ฐ”๊ณ , ์ ์›์ด ์—ฌ๋Ÿฌ๋ถ„ ์•ž ์‚ฌ๋žŒ๋“ค์˜ ์ฃผ๋ฌธ์„ ๋ฐ›๋Š” ๋™์•ˆ ์ค„์„ ์„œ์„œ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. ๐Ÿ˜ -์ด์ œ ๋‹น์‹ ์˜ ์ˆœ์„œ๊ฐ€ ๋˜์–ด์„œ, ๋‹น์‹ ์€ ๋‹น์‹ ๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ๋ฅผ ์œ„ํ•œ ๋‘ ๊ฐœ์˜ ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ์ฃผ๋ฌธํ•ฉ๋‹ˆ๋‹ค. +<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration"> -๋‹น์‹ ์ด ๋ˆ์„ ๋ƒ…๋‹ˆ๋‹ค ๐Ÿ’ธ. +์ด์ œ ์—ฌ๋Ÿฌ๋ถ„ ์ฐจ๋ก€๊ฐ€ ๋˜์–ด, ์—ฌ๋Ÿฌ๋ถ„๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€๋ฅผ ์œ„ํ•ด ๋งค์šฐ ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ํ–„๋ฒ„๊ฑฐ 2๊ฐœ๋ฅผ ์ฃผ๋ฌธํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ”๐Ÿ” -์ ์› ๐Ÿ’ ์€ ์ฃผ๋ฐฉ ๐Ÿ‘จโ€๐Ÿณ ์— ์š”๋ฆฌ๋ฅผ ํ•˜๋ผ๊ณ  ์ „๋‹ฌํ•˜๊ณ , ๋”ฐ๋ผ์„œ ๊ทธ๋“ค์€ ๋‹น์‹ ์˜ ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ์ค€๋น„ํ•ด์•ผํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ฒŒ๋ฉ๋‹ˆ๋‹ค(๊ทธ๋“ค์ด ์ง€๊ธˆ์€ ๋‹น์‹  ์•ž ๊ณ ๊ฐ๋“ค์˜ ์ฃผ๋ฌธ์„ ์ค€๋น„ํ•˜๊ณ  ์žˆ์„์ง€๋ผ๋„ ๋ง์ž…๋‹ˆ๋‹ค). +<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration"> -์ ์› ๐Ÿ’ ์€ ๋‹น์‹ ์˜ ์ˆœ์„œ๊ฐ€ ์ ํžŒ ๋ฒˆํ˜ธํ‘œ๋ฅผ ์ค๋‹ˆ๋‹ค. +์ ์›์€ ์ฃผ๋ฐฉ์˜ ์š”๋ฆฌ์‚ฌ์—๊ฒŒ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งํ•ด, (์ง€๊ธˆ์€ ์•ž์„  ์†๋‹˜๋“ค์˜ ์ฃผ๋ฌธ์„ ์ค€๋น„ํ•˜๊ณ  ์žˆ๋”๋ผ๋„) ์—ฌ๋Ÿฌ๋ถ„์˜ ํ–„๋ฒ„๊ฑฐ๋ฅผ ์ค€๋น„ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. -๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ, ๋‹น์‹ ์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ์™€ ํ•จ๊ป˜ ํ…Œ์ด๋ธ”์„ ๊ณ ๋ฅด๊ณ , ์ž๋ฆฌ์— ์•‰์•„ ์˜ค๋žซ๋™์•ˆ (๋‹น์‹ ์ด ์ฃผ๋ฌธํ•œ ๋ฒ„๊ฑฐ๋Š” ๊ฝค๋‚˜ ๊ณ ๊ธ‰์Šค๋Ÿฝ๊ธฐ ๋•Œ๋ฌธ์— ์ค€๋น„ํ•˜๋Š”๋ฐ ์‹œ๊ฐ„์ด ์กฐ๊ธˆ ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค โœจ๐Ÿ”โœจ) ๋Œ€ํ™”๋ฅผ ๋‚˜๋ˆ•๋‹ˆ๋‹ค. +<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration"> -์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ์™€ ํ…Œ์ด๋ธ”์— ์•‰์•„์„œ ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ, ๊ทธ ์‚ฌ๋žŒ ๐Ÿ˜ ์ด ์–ผ๋งˆ๋‚˜ ๋ฉ‹์ง€๊ณ , ์‚ฌ๋ž‘์Šค๋Ÿฝ๊ณ , ๋˜‘๋˜‘ํ•œ์ง€ ๊ฐํƒ„ํ•˜๋ฉฐ ์‹œ๊ฐ„์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค โœจ๐Ÿ˜โœจ. +์—ฌ๋Ÿฌ๋ถ„์ด ๋ˆ์„ ๋ƒ…๋‹ˆ๋‹ค. ๐Ÿ’ธ -์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ์™€ ๊ธฐ๋‹ค๋ฆฌ๋ฉด์„œ ์–˜๊ธฐํ•˜๋Š” ๋™์•ˆ, ๋•Œ๋•Œ๋กœ, ๋‹น์‹ ์€ ๋‹น์‹ ์˜ ์ฐจ๋ก€๊ฐ€ ๋˜์—ˆ๋Š”์ง€ ๋ณด๊ธฐ ์œ„ํ•ด ์นด์šดํ„ฐ์˜ ๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. +์ ์›์€ ์—ฌ๋Ÿฌ๋ถ„ ์ฐจ๋ก€ ๋ฒˆํ˜ธ๋ฅผ ์ค๋‹ˆ๋‹ค. -๊ทธ๋Ÿฌ๋‹ค ์–ด๋А ์ˆœ๊ฐ„, ๋‹น์‹ ์˜ ์ฐจ๋ก€๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์นด์šดํ„ฐ์— ๊ฐ€์„œ, ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ๋ฐ›๊ณ , ํ…Œ์ด๋ธ”๋กœ ๋‹ค์‹œ ๋Œ์•„์˜ต๋‹ˆ๋‹ค. +<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration"> -๋‹น์‹ ๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ๋Š” ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ๋จน์œผ๋ฉฐ ์ข‹์€ ์‹œ๊ฐ„์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค โœจ. +๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ, ์—ฌ๋Ÿฌ๋ถ„์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์™€ ํ•จ๊ป˜ ์ž๋ฆฌ๋ฅผ ๊ณ ๋ฅด๊ณ  ์•‰์•„ ์˜ค๋žซ๋™์•ˆ ๋Œ€ํ™”๋ฅผ ๋‚˜๋ˆ•๋‹ˆ๋‹ค(์—ฌ๋Ÿฌ๋ถ„์˜ ํ–„๋ฒ„๊ฑฐ๋Š” ๋งค์šฐ ๊ณ ๊ธ‰์Šค๋Ÿฝ๊ธฐ ๋•Œ๋ฌธ์— ์ค€๋น„ํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค). + +์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์™€ ํ…Œ์ด๋ธ”์— ์•‰์•„ ํ–„๋ฒ„๊ฑฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ, ๊ทธ ์‚ฌ๋žŒ์ด ์–ผ๋งˆ๋‚˜ ๋ฉ‹์ง€๊ณ  ๊ท€์—ฝ๊ณ  ๋˜‘๋˜‘ํ•œ์ง€ ๊ฐํƒ„ํ•˜๋ฉฐ ์‹œ๊ฐ„์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค โœจ๐Ÿ˜โœจ. + +<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration"> + +๊ธฐ๋‹ค๋ฆฌ๋ฉฐ ๋Œ€ํ™”ํ•˜๋Š” ๋™์•ˆ, ๋•Œ๋•Œ๋กœ ์—ฌ๋Ÿฌ๋ถ„์€ ์นด์šดํ„ฐ์— ํ‘œ์‹œ๋˜๋Š” ๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•ด ์—ฌ๋Ÿฌ๋ถ„ ์ฐจ๋ก€์ธ์ง€ ๋ด…๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฌ๋‹ค ์–ด๋А ์ˆœ๊ฐ„ ๋งˆ์นจ๋‚ด ์—ฌ๋Ÿฌ๋ถ„ ์ฐจ๋ก€๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์€ ์นด์šดํ„ฐ์— ๊ฐ€์„œ ํ–„๋ฒ„๊ฑฐ๋ฅผ ๋ฐ›๊ณ , ํ…Œ์ด๋ธ”๋กœ ๋Œ์•„์˜ต๋‹ˆ๋‹ค. + +<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration"> + +์—ฌ๋Ÿฌ๋ถ„๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€๋Š” ํ–„๋ฒ„๊ฑฐ๋ฅผ ๋จน์œผ๋ฉฐ ์ข‹์€ ์‹œ๊ฐ„์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. โœจ + +<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration"> + +/// info | ์ •๋ณด + +์•„๋ฆ„๋‹ค์šด ์ผ๋Ÿฌ์ŠคํŠธ: <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. ๐ŸŽจ + +/// --- -๋‹น์‹ ์ด ์ด ์ด์•ผ๊ธฐ์—์„œ ์ปดํ“จํ„ฐ / ํ”„๋กœ๊ทธ๋žจ ๐Ÿค– ์ด๋ผ๊ณ  ์ƒ์ƒํ•ด๋ณด์‹ญ์‹œ์˜ค. +์ด ์ด์•ผ๊ธฐ์—์„œ ์—ฌ๋Ÿฌ๋ถ„์ด ์ปดํ“จํ„ฐ/ํ”„๋กœ๊ทธ๋žจ ๐Ÿค– ์ด๋ผ๊ณ  ์ƒ์ƒํ•ด๋ณด์„ธ์š”. -์ค„์„ ์„œ์„œ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ, ๋‹น์‹ ์€ ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๊ณ  ๐Ÿ˜ด ๋‹น์‹ ์˜ ์ฐจ๋ก€๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋ฉฐ, ์–ด๋– ํ•œ "์ƒ์‚ฐ์ ์ธ" ์ผ๋„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ ์› ๐Ÿ’ ์ด (์Œ์‹์„ ์ค€๋น„ํ•˜์ง€๋Š” ์•Š๊ณ ) ์ฃผ๋ฌธ์„ ๋ฐ›๊ธฐ๋งŒ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ค„์ด ๋นจ๋ฆฌ ์ค„์–ด๋“ค์–ด์„œ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. +์ค„์„ ์„œ ์žˆ๋Š” ๋™์•ˆ, ์—ฌ๋Ÿฌ๋ถ„์€ ๊ทธ๋ƒฅ ์‰ฌ๊ณ  ๐Ÿ˜ด, ์ฐจ๋ก€๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋ฉฐ, ๊ทธ๋‹ค์ง€ "์ƒ์‚ฐ์ ์ธ" ์ผ์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ ์›์€ ์ฃผ๋ฌธ๋งŒ ๋ฐ›์ง€(์Œ์‹์„ ์ค€๋น„ํ•˜์ง„ ์•Š๊ธฐ) ๋•Œ๋ฌธ์— ์ค„์ด ๋น ๋ฅด๊ฒŒ ์ค„์–ด๋“ค์–ด ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. -๊ทธ๋‹ค์Œ, ๋‹น์‹ ์ด ์ฐจ๋ก€๊ฐ€ ์˜ค๋ฉด, ๋‹น์‹ ์€ ์‹ค์ œ๋กœ "์ƒ์‚ฐ์ ์ธ" ์ผ ๐Ÿค“ ์„ ํ•ฉ๋‹ˆ๋‹ค. ๋‹น์‹ ์€ ๋ฉ”๋‰ด๋ฅผ ๋ณด๊ณ , ๋ฌด์—‡์„ ๋จน์„์ง€ ๊ฒฐ์ •ํ•˜๊ณ , ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ์˜ ์„ ํƒ์„ ๋ฌป๊ณ , ๋ˆ์„ ๋‚ด๊ณ  ๐Ÿ’ธ , ๋งž๋Š” ์นด๋“œ๋ฅผ ๋ƒˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๋น„์šฉ์ด ์ œ๋Œ€๋กœ ์ง€๋ถˆ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์ฃผ๋ฌธ์ด ์ œ๋Œ€๋กœ ๋“ค์–ด๊ฐ”๋Š”์ง€ ํ™•์ธ์„ ํ•˜๋Š” ์ž‘์—… ๋“ฑ๋“ฑ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ ๋‹ค์Œ ์—ฌ๋Ÿฌ๋ถ„ ์ฐจ๋ก€๊ฐ€ ๋˜๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์€ ์‹ค์ œ๋กœ "์ƒ์‚ฐ์ ์ธ" ์ผ์„ ํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”๋‰ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , ๋ฌด์—‡์„ ๋จน์„์ง€ ๊ฒฐ์ •ํ•˜๊ณ , ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์˜ ์„ ํƒ์„ ํ™•์ธํ•˜๊ณ , ๊ฒฐ์ œํ•˜๊ณ , ์˜ฌ๋ฐ”๋ฅธ ํ˜„๊ธˆ์ด๋‚˜ ์นด๋“œ๋ฅผ ๋ƒˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์ •ํ™•ํžˆ ์ฒญ๊ตฌ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์ฃผ๋ฌธ์— ์˜ฌ๋ฐ”๋ฅธ ํ•ญ๋ชฉ๋“ค์ด ๋“ค์–ด๊ฐ”๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋“ฑ๋“ฑ์„ ํ•ฉ๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์ดํ›„์—๋Š”, ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ์•„์ง ๋ฐ›์ง€ ๋ชปํ–ˆ์Œ์—๋„, ๋ฒ„๊ฑฐ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ๐Ÿ•™ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ ์› ๐Ÿ’ ๊ณผ์˜ ์ž‘์—…์€ "์ผ์‹œ์ •์ง€" โธ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๊ทธ ๋‹ค์Œ์—๋Š”, ์•„์ง ํ–„๋ฒ„๊ฑฐ๋ฅผ ๋ฐ›์ง€ ๋ชปํ–ˆ๋”๋ผ๋„, ํ–„๋ฒ„๊ฑฐ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ๐Ÿ•™ ํ•˜๋ฏ€๋กœ ์ ์›๊ณผ์˜ ์ž‘์—…์€ "์ผ์‹œ์ •์ง€" โธ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ๋ฒˆํ˜ธํ‘œ๋ฅผ ๋ฐ›๊ณ  ์นด์šดํ„ฐ์—์„œ ๋‚˜์™€ ํ…Œ์ด๋ธ”์— ์•‰์œผ๋ฉด, ๋‹น์‹ ์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ์™€ ๊ทธ "์ž‘์—…" โฏ ๐Ÿค“ ์— ๋ฒˆ๊ฐˆ์•„๊ฐ€๋ฉฐ ๐Ÿ”€ ์ง‘์ค‘ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋‹น์‹ ์€ ๋‹ค์‹œ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ์—๊ฒŒ ์ž‘์—…์„ ๊ฑฐ๋Š” ๋งค์šฐ "์ƒ์‚ฐ์ ์ธ" ์ผ ๐Ÿค“ ์„ ํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๋ฒˆํ˜ธ๋ฅผ ๋“ค๊ณ  ์นด์šดํ„ฐ์—์„œ ๋ฒ—์–ด๋‚˜ ํ…Œ์ด๋ธ”์— ์•‰์œผ๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์—๊ฒŒ ๊ด€์‹ฌ์„ ์ „ํ™˜ ๐Ÿ”€ ํ•˜๊ณ , ๊ทธ์— ๋Œ€ํ•œ "์ž‘์—…" โฏ ๐Ÿค“ ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์—ฌ๋Ÿฌ๋ถ„์€ ๋‹ค์‹œ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์—๊ฒŒ ์ž‘์—…์„ ๊ฑฐ๋Š” ๋งค์šฐ "์ƒ์‚ฐ์ ์ธ" ์ผ์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค ๐Ÿ˜. -์ ์› ๐Ÿ’ ์ด ์นด์šดํ„ฐ ํ™”๋ฉด์— ๋‹น์‹ ์˜ ๋ฒˆํ˜ธ๋ฅผ ํ‘œ์‹œํ•จ์œผ๋กœ์จ "๋ฒ„๊ฑฐ ๐Ÿ” ๊ฐ€ ์ค€๋น„๋˜์—ˆ์Šต๋‹ˆ๋‹ค"๋ผ๊ณ  ํ•ด๋„, ๋‹น์‹ ์€ ์ฆ‰์‹œ ๋›ฐ์ณ๋‚˜๊ฐ€์ง€๋Š” ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹น์‹ ์€ ๋‹น์‹ ์˜ ๋ฒˆํ˜ธ๋ฅผ ๊ฐ–๊ณ ์žˆ๊ณ , ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์€ ๊ทธ๋“ค์˜ ๋ฒˆํ˜ธ๋ฅผ ๊ฐ–๊ณ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์•„๋ฌด๋„ ๋‹น์‹ ์˜ ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ํ›”์ณ๊ฐ€์ง€ ์•Š๋Š”๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +๊ทธ ๋‹ค์Œ ์ ์› ๐Ÿ’ ์ด ์นด์šดํ„ฐ ํ™”๋ฉด์— ์—ฌ๋Ÿฌ๋ถ„ ๋ฒˆํ˜ธ๋ฅผ ๋„์›Œ "ํ–„๋ฒ„๊ฑฐ๋ฅผ ๋งŒ๋“ค์—ˆ์–ด์š”"๋ผ๊ณ  ๋งํ•˜์ง€๋งŒ, ํ‘œ์‹œ๋œ ๋ฒˆํ˜ธ๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„ ์ฐจ๋ก€๋กœ ๋ฐ”๋€Œ์—ˆ๋‹ค๊ณ  ํ•ด์„œ ์ฆ‰์‹œ ๋ฏธ์นœ ๋“ฏ์ด ๋›ฐ์–ด๊ฐ€์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์€ ์—ฌ๋Ÿฌ๋ถ„ ๋ฒˆํ˜ธ๋ฅผ ๊ฐ–๊ณ  ์žˆ๊ณ , ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์€ ๊ทธ๋“ค์˜ ๋ฒˆํ˜ธ๋ฅผ ๊ฐ–๊ณ  ์žˆ์œผ๋‹ˆ, ์•„๋ฌด๋„ ์—ฌ๋Ÿฌ๋ถ„ ํ–„๋ฒ„๊ฑฐ๋ฅผ ํ›”์ณ๊ฐˆ ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -๊ทธ๋ž˜์„œ ๋‹น์‹ ์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ๊ฐ€ ์ด์•ผ๊ธฐ๋ฅผ ๋๋‚ผ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ ํ›„ (ํ˜„์žฌ ์ž‘์—… ์™„๋ฃŒ โฏ / ์ง„ํ–‰ ์ค‘์ธ ์ž‘์—… ์ฒ˜๋ฆฌ ๐Ÿค“ ), ์ •์ค‘ํ•˜๊ฒŒ ๋ฏธ์†Œ์ง“๊ณ  ๋ฒ„๊ฑฐ๋ฅผ ๊ฐ€์ง€๋Ÿฌ ๊ฐ€๊ฒ ๋‹ค๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค โธ. +๊ทธ๋ž˜์„œ ์—ฌ๋Ÿฌ๋ถ„์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€๊ฐ€ ์ด์•ผ๊ธฐ๋ฅผ ๋๋‚ผ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ ๋‹ค์Œ(ํ˜„์žฌ ์ž‘์—… โฏ / ์ฒ˜๋ฆฌ ์ค‘์ธ ์ž‘์—… ๐Ÿค“ ์„ ๋๋‚ด๊ณ ), ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ฏธ์†Œ ์ง€์œผ๋ฉฐ ํ–„๋ฒ„๊ฑฐ๋ฅผ ๊ฐ€์ง€๋Ÿฌ ๊ฐ€๊ฒ ๋‹ค๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค โธ. -๊ทธ๋‹ค์Œ ๋‹น์‹ ์€ ์นด์šดํ„ฐ์— ๊ฐ€์„œ ๐Ÿ”€ , ์ดˆ๊ธฐ ์ž‘์—…์„ ์ด์ œ ์™„๋ฃŒํ•˜๊ณ  โฏ , ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ๋ฐ›๊ณ , ๊ฐ์‚ฌํ•˜๋‹ค๊ณ  ๋งํ•˜๊ณ  ํ…Œ์ด๋ธ”๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ด๋กœ์จ ์นด์šดํ„ฐ์™€์˜ ์ƒํ˜ธ์ž‘์šฉ ๋‹จ๊ณ„ / ์ž‘์—…์ด ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค โน. +๊ทธ ๋‹ค์Œ ์—ฌ๋Ÿฌ๋ถ„์€ ์นด์šดํ„ฐ๋กœ ๊ฐ€์„œ ๐Ÿ”€, ์ด์ œ ๋๋‚œ ์ดˆ๊ธฐ ์ž‘์—… โฏ ์œผ๋กœ ๋Œ์•„์™€ ํ–„๋ฒ„๊ฑฐ๋ฅผ ๋ฐ›๊ณ , ๊ฐ์‚ฌ ์ธ์‚ฌ๋ฅผ ํ•˜๊ณ , ํ…Œ์ด๋ธ”๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ด๋กœ์จ ์นด์šดํ„ฐ์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๊ทธ ๋‹จ๊ณ„/์ž‘์—…์ด ๋๋‚ฉ๋‹ˆ๋‹ค โน. ๊ทธ๋ฆฌ๊ณ  ์ด๋Š” ์ƒˆ๋กœ์šด ์ž‘์—…์ธ "ํ–„๋ฒ„๊ฑฐ ๋จน๊ธฐ" ๐Ÿ”€ โฏ ๋ฅผ ๋งŒ๋“ค์ง€๋งŒ, ์ด์ „ ์ž‘์—…์ธ "ํ–„๋ฒ„๊ฑฐ ๋ฐ›๊ธฐ"๋Š” ๋๋‚ฌ์Šต๋‹ˆ๋‹ค โน. -์ด์ „ ์ž‘์—…์ธ "๋ฒ„๊ฑฐ ๋ฐ›๊ธฐ"๊ฐ€ ์ข…๋ฃŒ๋˜๋ฉด โน "๋ฒ„๊ฑฐ ๋จน๊ธฐ"๋ผ๋Š” ์ƒˆ๋กœ์šด ์ž‘์—…์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค ๐Ÿ”€ โฏ. +### ๋ณ‘๋ ฌ ํ–„๋ฒ„๊ฑฐ { #parallel-burgers } -### ๋ณ‘๋ ฌ ๋ฒ„๊ฑฐ +์ด์ œ ์ด๊ฒƒ์ด "๋™์‹œ ํ–„๋ฒ„๊ฑฐ"๊ฐ€ ์•„๋‹ˆ๋ผ "๋ณ‘๋ ฌ ํ–„๋ฒ„๊ฑฐ"๋ผ๊ณ  ์ƒ์ƒํ•ด๋ด…์‹œ๋‹ค. -์ด์ œ "๋™์‹œ ๋ฒ„๊ฑฐ"๊ฐ€ ์•„๋‹Œ "๋ณ‘๋ ฌ ๋ฒ„๊ฑฐ"๋ฅผ ์ƒ์ƒํ•ด๋ณด์‹ญ์‹œ์˜ค. +์—ฌ๋Ÿฌ๋ถ„์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์™€ ํ•จ๊ป˜ ๋ณ‘๋ ฌ ํŒจ์ŠคํŠธํ‘ธ๋“œ๋ฅผ ๋จน์œผ๋Ÿฌ ๊ฐ‘๋‹ˆ๋‹ค. -๋‹น์‹ ์€ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ์™€ ํ•จ๊ป˜ ๋ณ‘๋ ฌ ํŒจ์ŠคํŠธํ‘ธ๋“œ ๐Ÿ” ๋ฅผ ๋จน์œผ๋Ÿฌ ๊ฐ”์Šต๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ๋ถ„์€ ์—ฌ๋Ÿฌ ๋ช…(์˜ˆ: 8๋ช…)์˜ ์ ์›์ด ๋™์‹œ์— ์š”๋ฆฌ์‚ฌ์ด๊ธฐ๋„ ํ•˜์—ฌ ์—ฌ๋Ÿฌ๋ถ„ ์•ž ์‚ฌ๋žŒ๋“ค์˜ ์ฃผ๋ฌธ์„ ๋ฐ›๋Š” ๋™์•ˆ ์ค„์„ ์„œ ์žˆ์Šต๋‹ˆ๋‹ค. -๋‹น์‹ ์€ ์—ฌ๋Ÿฌ๋ช…(8๋ช…์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค)์˜ ์ ์›์ด ๋‹น์‹  ์•ž ์‚ฌ๋žŒ๋“ค์˜ ์ฃผ๋ฌธ์„ ๋ฐ›์œผ๋ฉฐ ๋™์‹œ์— ์š”๋ฆฌ ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ ๋„ ํ•˜๋Š” ๋™์•ˆ ์ค„์„ ์„œ์„œ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ๋ถ„ ์•ž์˜ ๋ชจ๋“  ์‚ฌ๋žŒ๋“ค์€, 8๋ช…์˜ ์ ์› ๊ฐ๊ฐ์ด ๋‹ค์Œ ์ฃผ๋ฌธ์„ ๋ฐ›๊ธฐ ์ „์— ๋ฐ”๋กœ ํ–„๋ฒ„๊ฑฐ๋ฅผ ์ค€๋น„ํ•˜๋Ÿฌ ๊ฐ€๊ธฐ ๋•Œ๋ฌธ์—, ์นด์šดํ„ฐ๋ฅผ ๋– ๋‚˜์ง€ ์•Š๊ณ  ํ–„๋ฒ„๊ฑฐ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. -๋‹น์‹  ์•ž ๋ชจ๋“  ์‚ฌ๋žŒ๋“ค์ด ๋ฒ„๊ฑฐ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ์นด์šดํ„ฐ์—์„œ ๋– ๋‚˜์ง€ ์•Š๊ณ  ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค ๐Ÿ•™ . ์™œ๋ƒํ•˜๋ฉด 8๋ช…์˜ ์ง์›๋“ค์ด ๋‹ค์Œ ์ฃผ๋ฌธ์„ ๋ฐ›๊ธฐ ์ „์— ๋ฒ„๊ฑฐ๋ฅผ ์ค€๋น„ํ•˜๋Ÿฌ ๊ฐ€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration"> -๋งˆ์นจ๋‚ด ๋‹น์‹ ์˜ ์ฐจ๋ก€๊ฐ€ ์™”๊ณ , ๋‹น์‹ ์€ ๋‹น์‹ ๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ๋ฅผ ์œ„ํ•œ ๋‘ ๊ฐœ์˜ ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ์ฃผ๋ฌธํ•ฉ๋‹ˆ๋‹ค. +๋งˆ์นจ๋‚ด ์—ฌ๋Ÿฌ๋ถ„ ์ฐจ๋ก€๊ฐ€ ๋˜์–ด, ์—ฌ๋Ÿฌ๋ถ„๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€๋ฅผ ์œ„ํ•ด ๋งค์šฐ ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ํ–„๋ฒ„๊ฑฐ 2๊ฐœ๋ฅผ ์ฃผ๋ฌธํ•ฉ๋‹ˆ๋‹ค. -๋‹น์‹ ์ด ๋น„์šฉ์„ ์ง€๋ถˆํ•ฉ๋‹ˆ๋‹ค ๐Ÿ’ธ . +์—ฌ๋Ÿฌ๋ถ„์ด ๋ˆ์„ ๋ƒ…๋‹ˆ๋‹ค ๐Ÿ’ธ. -์ ์›์ด ์ฃผ๋ฐฉ์— ๊ฐ‘๋‹ˆ๋‹ค ๐Ÿ‘จโ€๐Ÿณ . +<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration"> -๋‹น์‹ ์€ ๋ฒˆํ˜ธํ‘œ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ˆ„๊ตฌ๋„ ๋‹น์‹ ์˜ ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ๋Œ€์‹  ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์—†๋„๋ก ์นด์šดํ„ฐ์— ์„œ์„œ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค ๐Ÿ•™ . +์ ์›์€ ์ฃผ๋ฐฉ์œผ๋กœ ๊ฐ‘๋‹ˆ๋‹ค. -๋‹น์‹ ๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์ƒˆ์น˜๊ธฐํ•ด์„œ ๋ฒ„๊ฑฐ๋ฅผ ๊ฐ€์ ธ๊ฐ€์ง€ ๋ชปํ•˜๊ฒŒ ํ•˜๋А๋ผ ๋ฐ”์˜๊ธฐ ๋•Œ๋ฌธ์— ๐Ÿ•™ , ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์—๊ฒŒ ์ฃผ์˜๋ฅผ ๊ธฐ์šธ์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค ๐Ÿ˜ž . +์—ฌ๋Ÿฌ๋ถ„์€ ๋ฒˆํ˜ธํ‘œ๊ฐ€ ์—†์œผ๋ฏ€๋กœ, ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์—ฌ๋Ÿฌ๋ถ„๋ณด๋‹ค ๋จผ์ € ํ–„๋ฒ„๊ฑฐ๋ฅผ ๊ฐ€์ ธ๊ฐ€์ง€ ๋ชปํ•˜๋„๋ก ์นด์šดํ„ฐ ์•ž์— ์„œ์„œ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค ๐Ÿ•™. -์ด๊ฒƒ์€ "๋™๊ธฐ" ์ž‘์—…์ด๊ณ , ๋‹น์‹ ์€ ์ ์›/์š”๋ฆฌ์‚ฌ ๐Ÿ‘จโ€๐Ÿณ ์™€ "๋™๊ธฐํ™”" ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์€ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ๐Ÿ•™ , ์ ์›/์š”๋ฆฌ์‚ฌ ๐Ÿ‘จโ€๐Ÿณ ๊ฐ€ ๋ฒ„๊ฑฐ ๐Ÿ” ์ค€๋น„๋ฅผ ์™„๋ฃŒํ•œ ํ›„ ๋‹น์‹ ์—๊ฒŒ ์ฃผ๊ฑฐ๋‚˜, ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๊ทธ๊ฒƒ์„ ๊ฐ€์ ธ๊ฐ€๋Š” ๊ทธ ์ˆœ๊ฐ„์— ๊ทธ ๊ณณ์— ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค. +<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration"> -์นด์šดํ„ฐ ์•ž์—์„œ ์˜ค๋žซ๋™์•ˆ ๊ธฐ๋‹ค๋ฆฐ ํ›„์— ๐Ÿ•™ , ์ ์›/์š”๋ฆฌ์‚ฌ ๐Ÿ‘จโ€๐Ÿณ ๊ฐ€ ๋‹น์‹ ์˜ ๋ฒ„๊ฑฐ ๐Ÿ” ๋ฅผ ๊ฐ€์ง€๊ณ  ๋Œ์•„์˜ต๋‹ˆ๋‹ค. +์—ฌ๋Ÿฌ๋ถ„๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€๋Š” ํ–„๋ฒ„๊ฑฐ๊ฐ€ ๋‚˜์˜ค๋ฉด ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๋ผ์–ด๋“ค์–ด ๊ฐ€์ ธ๊ฐ€์ง€ ๋ชปํ•˜๊ฒŒ ํ•˜๋А๋ผ ๋ฐ”์˜๊ธฐ ๋•Œ๋ฌธ์—, ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์—๊ฒŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ž -๋‹น์‹ ์€ ๋ฒ„๊ฑฐ๋ฅผ ๋ฐ›๊ณ  ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์™€ ํ•จ๊ป˜ ํ…Œ์ด๋ธ”๋กœ ๋Œ์•„์˜ต๋‹ˆ๋‹ค. +์ด๊ฒƒ์€ "๋™๊ธฐ" ์ž‘์—…์ด๋ฉฐ, ์—ฌ๋Ÿฌ๋ถ„์€ ์ ์›/์š”๋ฆฌ์‚ฌ ๐Ÿ‘จโ€๐Ÿณ ์™€ "๋™๊ธฐํ™”"๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ ์›/์š”๋ฆฌ์‚ฌ ๐Ÿ‘จโ€๐Ÿณ ๊ฐ€ ํ–„๋ฒ„๊ฑฐ๋ฅผ ์™„์„ฑํ•ด ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ์ฃผ๋Š” ์ •ํ™•ํ•œ ์ˆœ๊ฐ„์— ๊ทธ ์ž๋ฆฌ์— ์žˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ, ์—ฌ๋Ÿฌ๋ถ„์€ ๊ธฐ๋‹ค๋ ค์•ผ ๐Ÿ•™ ํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๊ฐ€์ ธ๊ฐˆ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -๋‹จ์ง€ ๋จน๊ธฐ๋งŒ ํ•˜๋‹ค๊ฐ€, ๋‹ค ๋จน์—ˆ์Šต๋‹ˆ๋‹ค ๐Ÿ” โน. +<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration"> -์นด์šดํ„ฐ ์•ž์—์„œ ๊ธฐ๋‹ค๋ฆฌ๋ฉด์„œ ๐Ÿ•™ ๋„ˆ๋ฌด ๋งŽ์€ ์‹œ๊ฐ„์„ ํ—ˆ๋น„ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€ํ™”๋ฅผ ํ•˜๊ฑฐ๋‚˜ ์ž‘์—…์„ ๊ฑธ ์‹œ๊ฐ„์ด ๊ฑฐ์˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค ๐Ÿ˜ž . +๊ทธ๋Ÿฌ๋‹ค ์ ์›/์š”๋ฆฌ์‚ฌ ๐Ÿ‘จโ€๐Ÿณ ๊ฐ€ ์นด์šดํ„ฐ ์•ž์—์„œ ์˜ค๋žซ๋™์•ˆ ๊ธฐ๋‹ค๋ฆฐ ๐Ÿ•™ ๋์— ๋งˆ์นจ๋‚ด ํ–„๋ฒ„๊ฑฐ๋ฅผ ๊ฐ€์ง€๊ณ  ๋Œ์•„์˜ต๋‹ˆ๋‹ค. + +<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration"> + +์—ฌ๋Ÿฌ๋ถ„์€ ํ–„๋ฒ„๊ฑฐ๋ฅผ ๋ฐ›์•„ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์™€ ํ…Œ์ด๋ธ”๋กœ ๊ฐ‘๋‹ˆ๋‹ค. + +๊ทธ๋ƒฅ ๋จน๊ณ , ๋์ž…๋‹ˆ๋‹ค. โน + +<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration"> + +๋Œ€๋ถ€๋ถ„์˜ ์‹œ๊ฐ„์„ ์นด์šดํ„ฐ ์•ž์—์„œ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋ฐ ๐Ÿ•™ ์ผ๊ธฐ ๋•Œ๋ฌธ์—, ๋Œ€ํ™”ํ•˜๊ฑฐ๋‚˜ ์ž‘์—…์„ ๊ฑธ ์‹œ๊ฐ„์€ ๋งŽ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ž + +/// info | ์ •๋ณด + +์•„๋ฆ„๋‹ค์šด ์ผ๋Ÿฌ์ŠคํŠธ: <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. ๐ŸŽจ + +/// --- -์ด ๋ณ‘๋ ฌ ๋ฒ„๊ฑฐ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ, ๋‹น์‹ ์€ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ๐Ÿ•™ , ์˜ค๋žœ ์‹œ๊ฐ„๋™์•ˆ "์นด์šดํ„ฐ์—์„œ ๊ธฐ๋‹ค๋ฆฌ๋Š”" ๐Ÿ•™ ๋ฐ์— ์ฃผ์˜๋ฅผ ๊ธฐ์šธ์ด๋Š” โฏ ๋‘ ๊ฐœ์˜ ํ”„๋กœ์„ธ์„œ(๋‹น์‹ ๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€๐Ÿ˜)๋ฅผ ๊ฐ€์ง„ ์ปดํ“จํ„ฐ / ํ”„๋กœ๊ทธ๋žจ ๐Ÿค– ์ž…๋‹ˆ๋‹ค. +์ด ๋ณ‘๋ ฌ ํ–„๋ฒ„๊ฑฐ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ, ์—ฌ๋Ÿฌ๋ถ„์€ ๋‘ ๊ฐœ์˜ ํ”„๋กœ์„ธ์„œ(์—ฌ๋Ÿฌ๋ถ„๊ณผ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€)๋ฅผ ๊ฐ€์ง„ ์ปดํ“จํ„ฐ/ํ”„๋กœ๊ทธ๋žจ ๐Ÿค– ์ด๋ฉฐ, ๋‘˜ ๋‹ค ๊ธฐ๋‹ค๋ฆฌ๊ณ  ๐Ÿ•™ ์˜ค๋žซ๋™์•ˆ "์นด์šดํ„ฐ์—์„œ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ" ๐Ÿ•™ ์— ์ฃผ์˜๋ฅผ โฏ ๊ธฐ์šธ์ž…๋‹ˆ๋‹ค. -ํŒจ์ŠคํŠธํ‘ธ๋“œ์ ์—๋Š” 8๊ฐœ์˜ ํ”„๋กœ์„ธ์„œ(์ ์›/์š”๋ฆฌ์‚ฌ) ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋™์‹œ ๋ฒ„๊ฑฐ๋Š” ๋‹จ ๋‘ ๊ฐœ(ํ•œ ๋ช…์˜ ์ง์›๊ณผ ํ•œ ๋ช…์˜ ์š”๋ฆฌ์‚ฌ) ๐Ÿ’ ๐Ÿ‘จโ€๐Ÿณ ๋งŒ์„ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. +ํŒจ์ŠคํŠธํ‘ธ๋“œ์ ์—๋Š” 8๊ฐœ์˜ ํ”„๋กœ์„ธ์„œ(์ ์›/์š”๋ฆฌ์‚ฌ)๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋™์‹œ ํ–„๋ฒ„๊ฑฐ ๊ฐ€๊ฒŒ๋Š” 2๊ฐœ(์ ์› 1๋ช…, ์š”๋ฆฌ์‚ฌ 1๋ช…)๋งŒ ์žˆ์—ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ, ๋ณ‘๋ ฌ ๋ฒ„๊ฑฐ ์˜ˆ์‹œ๊ฐ€ ์ตœ์„ ์€ ์•„๋‹™๋‹ˆ๋‹ค ๐Ÿ˜ž . +ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ์ตœ์ข… ๊ฒฝํ—˜์€ ๊ทธ๋‹ค์ง€ ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ž --- -์ด ์˜ˆ์‹œ๋Š” ๋ฒ„๊ฑฐ๐Ÿ” ์ด์•ผ๊ธฐ์™€ ๊ฒฐ์ด ๊ฐ™์Šต๋‹ˆ๋‹ค. +์ด๊ฒƒ์ด ํ–„๋ฒ„๊ฑฐ์˜ ๋ณ‘๋ ฌ ๋ฒ„์ „์— ํ•ด๋‹นํ•˜๋Š” ์ด์•ผ๊ธฐ์ž…๋‹ˆ๋‹ค. ๐Ÿ” -๋” "ํ˜„์‹ค์ ์ธ" ์˜ˆ์‹œ๋กœ, ์€ํ–‰์„ ์ƒ์ƒํ•ด๋ณด์‹ญ์‹œ์˜ค. +์ข€ ๋” "ํ˜„์‹ค์ ์ธ" ์˜ˆ์‹œ๋กœ, ์€ํ–‰์„ ์ƒ์ƒํ•ด๋ณด์„ธ์š”. -์ตœ๊ทผ๊นŒ์ง€, ๋Œ€๋‹ค์ˆ˜์˜ ์€ํ–‰์—๋Š” ๋‹ค์ˆ˜์˜ ์€ํ–‰์›๋“ค ๐Ÿ‘จโ€๐Ÿ’ผ๐Ÿ‘จโ€๐Ÿ’ผ๐Ÿ‘จโ€๐Ÿ’ผ๐Ÿ‘จโ€๐Ÿ’ผ ๊ณผ ๊ธด ์ค„ ๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. +์ตœ๊ทผ๊นŒ์ง€ ๋Œ€๋ถ€๋ถ„์˜ ์€ํ–‰์—๋Š” ์—ฌ๋Ÿฌ ์€ํ–‰์› ๐Ÿ‘จโ€๐Ÿ’ผ๐Ÿ‘จโ€๐Ÿ’ผ๐Ÿ‘จโ€๐Ÿ’ผ๐Ÿ‘จโ€๐Ÿ’ผ ๊ณผ ๊ธด ์ค„ ๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™๐Ÿ•™ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. -๋ชจ๋“  ์€ํ–‰์›๋“ค์€ ํ•œ ๋ช… ํ•œ ๋ช…์˜ ๊ณ ๊ฐ๋“ค์„ ์ฐจ๋ก€๋กœ ์ƒ๋Œ€ํ•ฉ๋‹ˆ๋‹ค ๐Ÿ‘จโ€๐Ÿ’ผโฏ . +๋ชจ๋“  ์€ํ–‰์›์ด ํ•œ ๊ณ ๊ฐ์”ฉ ์ˆœ์„œ๋Œ€๋กœ ๋ชจ๋“  ์ผ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค ๐Ÿ‘จโ€๐Ÿ’ผโฏ. -๊ทธ๋ฆฌ๊ณ  ๋‹น์‹ ์€ ์˜ค๋žซ๋™์•ˆ ์ค„์—์„œ ๊ธฐ๋‹ค๋ ค์•ผํ•˜๊ณ  ๐Ÿ•™ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋‹น์‹ ์˜ ์ฐจ๋ก€๋ฅผ ์žƒ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ๋ถ„์€ ์˜ค๋žซ๋™์•ˆ ์ค„์—์„œ ๊ธฐ๋‹ค๋ ค์•ผ ๐Ÿ•™ ํ•˜๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์ฐจ๋ก€๋ฅผ ์žƒ์Šต๋‹ˆ๋‹ค. -์•„๋งˆ ๋‹น์‹ ์€ ์€ํ–‰ ๐Ÿฆ ์‹ฌ๋ถ€๋ฆ„์— ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ๋ฅผ ๋ฐ๋ ค๊ฐ€๊ณ  ์‹ถ์ง€๋Š” ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์•„๋งˆ ์€ํ–‰ ๐Ÿฆ ์—…๋ฌด๋ฅผ ๋ณด๋Ÿฌ ๊ฐˆ ๋•Œ ์ง์‚ฌ๋ž‘ ์ƒ๋Œ€ ๐Ÿ˜ ๋ฅผ ๋ฐ๋ ค๊ฐ€๊ณ  ์‹ถ์ง€๋Š” ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -### ๋ฒ„๊ฑฐ ์˜ˆ์‹œ์˜ ๊ฒฐ๋ก  +### ํ–„๋ฒ„๊ฑฐ ์˜ˆ์‹œ์˜ ๊ฒฐ๋ก  { #burger-conclusion } -"์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์™€์˜ ํŒจ์ŠคํŠธํ‘ธ๋“œ์  ๋ฒ„๊ฑฐ" ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ, ์˜ค๋žœ ๊ธฐ๋‹ค๋ฆผ ๐Ÿ•™ ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ ์‹œ์Šคํ…œ โธ๐Ÿ”€โฏ ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค. +"์ง์‚ฌ๋ž‘ ์ƒ๋Œ€์™€์˜ ํŒจ์ŠคํŠธํ‘ธ๋“œ์  ํ–„๋ฒ„๊ฑฐ" ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” ๊ธฐ๋‹ค๋ฆผ ๐Ÿ•™ ์ด ๋งŽ๊ธฐ ๋•Œ๋ฌธ์—, ๋™์‹œ ์‹œ์Šคํ…œ โธ๐Ÿ”€โฏ ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋” ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค. -๋Œ€๋‹ค์ˆ˜์˜ ์›น ์‘์šฉํ”„๋กœ๊ทธ๋žจ์˜ ๊ฒฝ์šฐ๊ฐ€ ๊ทธ๋Ÿฌํ•ฉ๋‹ˆ๋‹ค. +๋Œ€๋ถ€๋ถ„์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. -๋งค์šฐ ๋งŽ์€ ์ˆ˜์˜ ์œ ์ €๊ฐ€ ์žˆ์ง€๋งŒ, ์„œ๋ฒ„๋Š” ๊ทธ๋“ค์˜ ์š”์ฒญ์„ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด ๊ทธ๋‹ฅ-์ข‹์ง€-์•Š์€ ์—ฐ๊ฒฐ์„ ๊ธฐ๋‹ค๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค ๐Ÿ•™ . +๋งค์šฐ ๋งŽ์€ ์‚ฌ์šฉ์ž๋“ค์ด ์žˆ๊ณ , ์„œ๋ฒ„๋Š” ๊ทธ๋“ค์˜ ์ข‹์ง€ ์•Š์€ ์—ฐ๊ฒฐ์„ ํ†ตํ•ด ์š”์ฒญ์ด ์ „์†ก๋˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค ๐Ÿ•™. -๊ทธ๋ฆฌ๊ณ  ์‘๋‹ต์ด ๋Œ์•„์˜ฌ ๋•Œ๊นŒ์ง€ ๋‹ค์‹œ ๊ธฐ๋‹ค๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค ๐Ÿ•™ . +๊ทธ๋ฆฌ๊ณ  ์‘๋‹ต์ด ๋Œ์•„์˜ค๊ธฐ๋ฅผ ๋‹ค์‹œ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค ๐Ÿ•™. -์ด "๊ธฐ๋‹ค๋ฆผ" ๐Ÿ•™ ์€ ๋งˆ์ดํฌ๋กœ์ดˆ ๋‹จ์œ„์ด์ง€๋งŒ, ๋ชจ๋‘ ๋”ํ•ด์ง€๋ฉด, ๊ฒฐ๊ตญ์—๋Š” ๋งค์šฐ ๊ธด ๋Œ€๊ธฐ์‹œ๊ฐ„์ด ๋ฉ๋‹ˆ๋‹ค. +์ด "๊ธฐ๋‹ค๋ฆผ" ๐Ÿ•™ ์€ ๋งˆ์ดํฌ๋กœ์ดˆ ๋‹จ์œ„๋กœ ์ธก์ •๋˜์ง€๋งŒ, ๋ชจ๋‘ ํ•ฉ์น˜๋ฉด ๊ฒฐ๊ตญ ๊ฝค ๋งŽ์€ ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด ๋ฉ๋‹ˆ๋‹ค. -๋”ฐ๋ผ์„œ ์›น API๋ฅผ ์œ„ํ•ด ๋น„๋™๊ธฐ โธ๐Ÿ”€โฏ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค. +๊ทธ๋ž˜์„œ ์›น API์—๋Š” ๋น„๋™๊ธฐ โธ๐Ÿ”€โฏ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค. -๋Œ€๋ถ€๋ถ„์˜ ์กด์žฌํ•˜๋Š” ์œ ๋ช…ํ•œ ํŒŒ์ด์ฌ ํ”„๋ ˆ์ž„์›Œํฌ (Flask์™€ Django ๋“ฑ)์€ ์ƒˆ๋กœ์šด ๋น„๋™๊ธฐ ๊ธฐ๋Šฅ๋“ค์ด ํŒŒ์ด์ฌ์— ์กด์žฌํ•˜๊ธฐ ์ „์— ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ, ๊ทธ๋“ค์˜ ๋ฐฐํฌ ๋ฐฉ์‹์€ ๋ณ‘๋ ฌ ์‹คํ–‰๊ณผ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๋งŒํผ ๊ฐ•๋ ฅํ•˜์ง€๋Š” ์•Š์€ ์˜ˆ์ „ ๋ฒ„์ „์˜ ๋น„๋™๊ธฐ ์‹คํ–‰์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +์ด๋Ÿฌํ•œ ์ข…๋ฅ˜์˜ ๋น„๋™๊ธฐ์„ฑ์€ NodeJS๊ฐ€ ์ธ๊ธฐ ์žˆ๋Š” ์ด์œ (๋น„๋ก NodeJS๊ฐ€ ๋ณ‘๋ ฌ์€ ์•„๋‹ˆ์ง€๋งŒ)์ด์ž, ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋กœ์„œ Go์˜ ๊ฐ•์ ์ž…๋‹ˆ๋‹ค. -๋น„๋™๊ธฐ ์›น ํŒŒ์ด์ฌ(ASGI)์— ๋Œ€ํ•œ ์ฃผ์š” ๋ช…์„ธ๊ฐ€ ์›น์†Œ์ผ“์„ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด Django์—์„œ ๊ฐœ๋ฐœ ๋˜์—ˆ์Œ์—๋„ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ์ด **FastAPI**๋กœ ์–ป๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ˆ˜์ค€์˜ ์„ฑ๋Šฅ์ž…๋‹ˆ๋‹ค. -์ด๋Ÿฌํ•œ ์ข…๋ฅ˜์˜ ๋น„๋™๊ธฐ์„ฑ์€ (NodeJS๋Š” ๋ณ‘๋ ฌ์ ์ด์ง€ ์•Š์Œ์—๋„) NodeJS๊ฐ€ ์‚ฌ๋ž‘๋ฐ›๋Š” ์ด์œ ์ด๊ณ , ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋กœ์„œ์˜ Go์˜ ๊ฐ•์ ์ž…๋‹ˆ๋‹ค. +๋˜ํ•œ ๋ณ‘๋ ฌ์„ฑ๊ณผ ๋น„๋™๊ธฐ์„ฑ์„ ๋™์‹œ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋Œ€๋ถ€๋ถ„์˜ ํ…Œ์ŠคํŠธ๋œ NodeJS ํ”„๋ ˆ์ž„์›Œํฌ๋ณด๋‹ค ๋” ๋†’์€ ์„ฑ๋Šฅ์„ ์–ป๊ณ , C์— ๋” ๊ฐ€๊นŒ์šด ์ปดํŒŒ์ผ ์–ธ์–ด์ธ Go์™€ ๋™๋“ฑํ•œ ์„ฑ๋Šฅ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค <a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(๋ชจ๋‘ Starlette ๋•๋ถ„์ž…๋‹ˆ๋‹ค)</a>. -๊ทธ๋ฆฌ๊ณ  **FastAPI**๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๋™์ผํ•œ ์„ฑ๋Šฅ์„ ๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +### ๋™์‹œ์„ฑ์ด ๋ณ‘๋ ฌ์„ฑ๋ณด๋‹ค ๋” ๋‚˜์€๊ฐ€์š”? { #is-concurrency-better-than-parallelism } -๋˜ํ•œ ๋ณ‘๋ ฌ์„ฑ๊ณผ ๋น„๋™๊ธฐ์„ฑ์„ ๋™์‹œ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋Œ€๋ถ€๋ถ„์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์™„๋ฃŒ๋œ NodeJS ํ”„๋ ˆ์ž„์›Œํฌ๋ณด๋‹ค ๋” ๋†’์€ ์„ฑ๋Šฅ์„ ์–ป๊ณ  C์— ๋” ๊ฐ€๊นŒ์šด ์ปดํŒŒ์ผ ์–ธ์–ด์ธ Go์™€ ๋™๋“ฑํ•œ ์„ฑ๋Šฅ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค<a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(๋ชจ๋‘ Starlette ๋•๋ถ„์ž…๋‹ˆ๋‹ค)</a>. +์•„๋‹ˆ์š”! ๊ทธ๊ฒŒ ์ด ์ด์•ผ๊ธฐ์˜ ๊ตํ›ˆ์€ ์•„๋‹™๋‹ˆ๋‹ค. -### ๋™์‹œ์„ฑ์ด ๋ณ‘๋ ฌ์„ฑ๋ณด๋‹ค ๋” ๋‚˜์€๊ฐ€? +๋™์‹œ์„ฑ์€ ๋ณ‘๋ ฌ์„ฑ๊ณผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งŽ์€ ๊ธฐ๋‹ค๋ฆผ์ด ํฌํ•จ๋˜๋Š” **ํŠน์ •ํ•œ** ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” ๋” ๋‚ซ์Šต๋‹ˆ๋‹ค. ๊ทธ ๋•Œ๋ฌธ์— ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์—์„œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ณ‘๋ ฌ์„ฑ๋ณด๋‹ค ํ›จ์”ฌ ๋” ๋‚ซ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ชจ๋“  ๊ฒƒ์— ํ•ด๋‹นํ•˜์ง„ ์•Š์Šต๋‹ˆ๋‹ค. -๊ทธ๋ ‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค! ๊ทธ๊ฒƒ์ด ์ด์•ผ๊ธฐ์˜ ๊ตํ›ˆ์€ ์•„๋‹™๋‹ˆ๋‹ค. +๊ทธ๋ž˜์„œ ๊ท ํ˜•์„ ๋งž์ถ”๊ธฐ ์œ„ํ•ด, ๋‹ค์Œ์˜ ์งง์€ ์ด์•ผ๊ธฐ๋ฅผ ์ƒ์ƒํ•ด๋ณด์„ธ์š”: -๋™์‹œ์„ฑ์€ ๋ณ‘๋ ฌ์„ฑ๊ณผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์€ ๋งŽ์€ ๋Œ€๊ธฐ๋ฅผ ํ•„์š”๋กœํ•˜๋Š” **ํŠน์ •ํ•œ** ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” ๋” ๋‚ซ์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด, ์›น ์‘์šฉํ”„๋กœ๊ทธ๋žจ ๊ฐœ๋ฐœ์—์„œ ๋™์‹œ์„ฑ์ด ๋ณ‘๋ ฌ์„ฑ๋ณด๋‹ค ์ผ๋ฐ˜์ ์œผ๋กœ ํ›จ์”ฌ ๋‚ซ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ชจ๋“  ๊ฒฝ์šฐ์— ๊ทธ๋Ÿฐ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. - -๋”ฐ๋ผ์„œ, ๊ท ํ˜•์„ ๋งž์ถ”๊ธฐ ์œ„ํ•ด, ๋‹ค์Œ์˜ ์งง์€ ์ด์•ผ๊ธฐ๋ฅผ ์ƒ์ƒํ•ด๋ณด์‹ญ์‹œ์˜ค: - -> ๋‹น์‹ ์€ ํฌ๊ณ , ๋”๋Ÿฌ์šด ์ง‘์„ ์ฒญ์†Œํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. +> ์—ฌ๋Ÿฌ๋ถ„์€ ํฌ๊ณ  ๋”๋Ÿฌ์šด ์ง‘์„ ์ฒญ์†Œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. *๋„ค, ์ด๊ฒŒ ์ „๋ถ€์ž…๋‹ˆ๋‹ค*. --- -์–ด๋””์—๋„ ๋Œ€๊ธฐ ๐Ÿ•™ ๋Š” ์—†๊ณ , ์ง‘์•ˆ ๊ณณ๊ณณ์—์„œ ํ•ด์•ผํ•˜๋Š” ๋งŽ์€ ์ž‘์—…๋“ค๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค. +์–ด๋””์—๋„ ๊ธฐ๋‹ค๋ฆผ ๐Ÿ•™ ์€ ์—†๊ณ , ์ง‘์˜ ์—ฌ๋Ÿฌ ์žฅ์†Œ์—์„œ ํ•ด์•ผ ํ•  ์ผ์ด ๋งŽ์„ ๋ฟ์ž…๋‹ˆ๋‹ค. -๋ฒ„๊ฑฐ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ์ฒ˜์Œ์—๋Š” ๊ฑฐ์‹ค, ๊ทธ ๋‹ค์Œ์€ ๋ถ€์—Œ๊ณผ ๊ฐ™์€ ์‹์œผ๋กœ ์ˆœ์„œ๋ฅผ ์ •ํ•  ์ˆ˜๋„ ์žˆ์œผ๋‚˜, ๋ฌด์—‡๋„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ๐Ÿ•™ ์•Š๊ณ  ๊ณ„์†ํ•ด์„œ ์ฒญ์†Œ ์ž‘์—…๋งŒ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ˆœ์„œ๋Š” ์•„๋ฌด๋Ÿฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +ํ–„๋ฒ„๊ฑฐ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ๊ฑฐ์‹ค๋ถ€ํ„ฐ, ๊ทธ ๋‹ค์Œ์€ ๋ถ€์—Œ์ฒ˜๋Ÿผ ์ˆœ์„œ๋ฅผ ์ •ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์–ด๋–ค ๊ฒƒ๋„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ๐Ÿ•™ ์•Š๊ณ  ๊ณ„์† ์ฒญ์†Œ๋งŒ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ˆœ์„œ๋Š” ์•„๋ฌด๋Ÿฐ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -์ˆœ์„œ๊ฐ€ ์žˆ๋“  ์—†๋“  ๋™์ผํ•œ ์‹œ๊ฐ„์ด ์†Œ์š”๋  ๊ฒƒ์ด๊ณ (๋™์‹œ์„ฑ) ๋™์ผํ•œ ์–‘์˜ ์ž‘์—…์„ ํ•˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ˆœ์„œ๊ฐ€ ์žˆ๋“  ์—†๋“ (๋™์‹œ์„ฑ) ๋๋‚ด๋Š” ๋ฐ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„์€ ๊ฐ™๊ณ , ๊ฐ™์€ ์–‘์˜ ์ผ์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์ด ๊ฒฝ์šฐ์—์„œ, 8๋ช…์˜ ์ „(ๅ‰)-์ ์›/์š”๋ฆฌ์‚ฌ์ด๋ฉด์„œ-ํ˜„(็พ)-์ฒญ์†Œ๋ถ€ ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘จโ€๐Ÿณ ๋ฅผ ๊ณ ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ๊ทธ๋“ค ๊ฐ์ž(๊ทธ๋ฆฌ๊ณ  ๋‹น์‹ )๊ฐ€ ์ง‘์˜ ํ•œ ๋ถ€๋ถ„์”ฉ ๋งก์•„ ์ฒญ์†Œ๋ฅผ ํ•œ๋‹ค๋ฉด, ๋‹น์‹ ์€ **๋ณ‘๋ ฌ์ **์œผ๋กœ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ณ , ์กฐ๊ธˆ์˜ ๋„์›€์ด ์žˆ๋‹ค๋ฉด, ํ›จ์”ฌ ๋” ๋นจ๋ฆฌ ๋๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์ด ๊ฒฝ์šฐ, ์ „(ๅ‰) ์ ์›/์š”๋ฆฌ์‚ฌ์ด์ž ํ˜„(็พ) ์ฒญ์†Œ๋ถ€๊ฐ€ ๋œ 8๋ช…์„ ๋ฐ๋ ค์˜ฌ ์ˆ˜ ์žˆ๊ณ , ๊ฐ์ž(๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ๋ถ„)๊ฐ€ ์ง‘์˜ ๊ตฌ์—ญ์„ ํ•˜๋‚˜์”ฉ ๋งก์•„ ์ฒญ์†Œํ•œ๋‹ค๋ฉด, ์ถ”๊ฐ€ ๋„์›€๊ณผ ํ•จ๊ป˜ ๋ชจ๋“  ์ผ์„ **๋ณ‘๋ ฌ**๋กœ ์ˆ˜ํ–‰ํ•˜์—ฌ ํ›จ์”ฌ ๋” ๋นจ๋ฆฌ ๋๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ, (๋‹น์‹ ์„ ํฌํ•จํ•œ) ๊ฐ๊ฐ์˜ ์ฒญ์†Œ๋ถ€๋“ค์€ ํ”„๋กœ์„ธ์„œ๊ฐ€ ๋  ๊ฒƒ์ด๊ณ , ๊ฐ์ž์˜ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. +์ด ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ (์—ฌ๋Ÿฌ๋ถ„์„ ํฌํ•จํ•œ) ๊ฐ ์ฒญ์†Œ๋ถ€๋Š” ํ”„๋กœ์„ธ์„œ๊ฐ€ ๋˜์–ด, ๋งก์€ ์ผ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. -์‹คํ–‰ ์‹œ๊ฐ„์˜ ๋Œ€๋ถ€๋ถ„์ด ๋Œ€๊ธฐ๊ฐ€ ์•„๋‹Œ ์‹ค์ œ ์ž‘์—…์— ์†Œ์š”๋˜๊ณ , ์ปดํ“จํ„ฐ์—์„œ ์ž‘์—…์€ <abbr title="Central Processing Unit">CPU</abbr>์—์„œ ์ด๋ฃจ์–ด์ง€๋ฏ€๋กœ, ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ "CPU์— ๋ฌถ์˜€"๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ์‹คํ–‰ ์‹œ๊ฐ„์˜ ๋Œ€๋ถ€๋ถ„์ด ๊ธฐ๋‹ค๋ฆผ์ด ์•„๋‹ˆ๋ผ ์‹ค์ œ ์ž‘์—…์— ์“ฐ์ด๊ณ , ์ปดํ“จํ„ฐ์—์„œ ์ž‘์—…์€ <abbr title="Central Processing Unit - ์ค‘์•™ ์ฒ˜๋ฆฌ ์žฅ์น˜">CPU</abbr>๊ฐ€ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ, ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ "CPU bound"๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. --- -CPU์— ๋ฌถ์ธ ์—ฐ์‚ฐ์— ๊ด€ํ•œ ํ”ํ•œ ์˜ˆ์‹œ๋Š” ๋ณต์žกํ•œ ์ˆ˜ํ•™ ์ฒ˜๋ฆฌ๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. +CPU bound ์ž‘์—…์˜ ํ”ํ•œ ์˜ˆ์‹œ๋Š” ๋ณต์žกํ•œ ์ˆ˜ํ•™ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ๋“ค์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: -* **์˜ค๋””์˜ค** ๋˜๋Š” **์ด๋ฏธ์ง€** ์ฒ˜๋ฆฌ. -* **์ปดํ“จํ„ฐ ๋น„์ „**: ํ•˜๋‚˜์˜ ์ด๋ฏธ์ง€๋Š” ์ˆ˜๋ฐฑ๊ฐœ์˜ ํ”ฝ์…€๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ๊ณ , ๊ฐ ํ”ฝ์…€์€ 3๊ฐœ์˜ ๊ฐ’ / ์ƒ‰์„ ๊ฐ–๊ณ  ์žˆ์œผ๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ํ•ด๋‹น ํ”ฝ์…€๋“ค์— ๋Œ€ํ•ด ๋™์‹œ์— ๋ฌด์–ธ๊ฐ€๋ฅผ ๊ณ„์‚ฐํ•ด์•ผํ•˜๋Š” ์ฒ˜๋ฆฌ. -* **๋จธ์‹ ๋Ÿฌ๋‹**: ์ผ๋ฐ˜์ ์œผ๋กœ ๋งŽ์€ "ํ–‰๋ ฌ"๊ณผ "๋ฒกํ„ฐ" ๊ณฑ์…ˆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ฑฐ๋Œ€ํ•œ ์Šคํ”„๋ ˆ๋“œ ์‹œํŠธ์— ์ˆ˜๋“ค์ด ์žˆ๊ณ  ๊ทธ ์ˆ˜๋“ค์„ ๋™์‹œ์— ๊ณฑํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์‹ญ์‹œ์˜ค. -* **๋”ฅ๋Ÿฌ๋‹**: ๋จธ์‹ ๋Ÿฌ๋‹์˜ ํ•˜์œ„์˜์—ญ์œผ๋กœ, ๋™์ผํ•œ ์˜ˆ์‹œ๊ฐ€ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋‹จ์ง€ ์ด ๊ฒฝ์šฐ์—๋Š” ํ•˜๋‚˜์˜ ์Šคํ”„๋ ˆ๋“œ ์‹œํŠธ์— ๊ณฑํ•ด์•ผํ•  ์ˆ˜๋“ค์ด ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๊ฑฐ๋Œ€ํ•œ ์„ธํŠธ์˜ ์Šคํ”„๋ ˆ๋“œ ์‹œํŠธ๋“ค์ด ์žˆ๊ณ , ๋งŽ์€ ๊ฒฝ์šฐ์—, ์ด ๋ชจ๋ธ๋“ค์„ ๋งŒ๋“ค๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํŠน์ˆ˜ํ•œ ํ”„๋กœ์„ธ์„œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +* **์˜ค๋””์˜ค** ๋˜๋Š” **์ด๋ฏธ์ง€** ์ฒ˜๋ฆฌ +* **์ปดํ“จํ„ฐ ๋น„์ „**: ์ด๋ฏธ์ง€๋Š” ์ˆ˜๋ฐฑ๋งŒ ๊ฐœ์˜ ํ”ฝ์…€๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ, ๊ฐ ํ”ฝ์…€์€ 3๊ฐœ์˜ ๊ฐ’/์ƒ‰์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. ๋ณดํ†ต ๊ทธ ํ”ฝ์…€๋“ค์— ๋Œ€ํ•ด ๋™์‹œ์— ๋ฌด์–ธ๊ฐ€๋ฅผ ๊ณ„์‚ฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +* **๋จธ์‹ ๋Ÿฌ๋‹**: ๋ณดํ†ต ๋งŽ์€ "matrix"์™€ "vector" ๊ณฑ์…ˆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ˆซ์ž๊ฐ€ ์žˆ๋Š” ๊ฑฐ๋Œ€ํ•œ ์Šคํ”„๋ ˆ๋“œ์‹œํŠธ๋ฅผ ์ƒ๊ฐํ•˜๊ณ , ๊ทธ ๋ชจ๋“  ์ˆ˜๋ฅผ ๋™์‹œ์— ๊ณฑํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์„ธ์š”. +* **๋”ฅ๋Ÿฌ๋‹**: ๋จธ์‹ ๋Ÿฌ๋‹์˜ ํ•˜์œ„ ๋ถ„์•ผ์ด๋ฏ€๋กœ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ ๊ณฑํ•ด์•ผ ํ•  ์ˆซ์ž๊ฐ€ ์žˆ๋Š” ์Šคํ”„๋ ˆ๋“œ์‹œํŠธ๊ฐ€ ํ•˜๋‚˜๊ฐ€ ์•„๋‹ˆ๋ผ, ์•„์ฃผ ํฐ ์ง‘ํ•ฉ์ด๋ฉฐ, ๋งŽ์€ ๊ฒฝ์šฐ ๊ทธ ๋ชจ๋ธ์„ ๋งŒ๋“ค๊ณ /๋˜๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํŠน๋ณ„ํ•œ ํ”„๋กœ์„ธ์„œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -### ๋™์‹œ์„ฑ + ๋ณ‘๋ ฌ์„ฑ: ์›น + ๋จธ์‹ ๋Ÿฌ๋‹ +### ๋™์‹œ์„ฑ + ๋ณ‘๋ ฌ์„ฑ: ์›น + ๋จธ์‹ ๋Ÿฌ๋‹ { #concurrency-parallelism-web-machine-learning } -**FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์›น ๊ฐœ๋ฐœ์—์„œ๋Š” ๋งค์šฐ ํ”ํ•œ ๋™์‹œ์„ฑ์˜ ์ด์ ์„ (NodeJS์˜ ์ฃผ๋œ ๋งค๋ ฅ๋งŒํผ) ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์›น ๊ฐœ๋ฐœ์—์„œ ๋งค์šฐ ํ”ํ•œ ๋™์‹œ์„ฑ์˜ ์ด์ ์„( NodeJS์˜ ์ฃผ์š” ๋งค๋ ฅ๊ณผ ๊ฐ™์€) ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋จธ์‹ ๋Ÿฌ๋‹ ์‹œ์Šคํ…œ๊ณผ ๊ฐ™์ด **CPU์— ๋ฌถ์ธ** ์ž‘์—…์„ ์œ„ํ•ด ๋ณ‘๋ ฌ์„ฑ๊ณผ ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ(๋‹ค์ˆ˜์˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ณ‘๋ ฌ์ ์œผ๋กœ ๋™์ž‘์‹œํ‚ค๋Š” ๊ฒƒ)์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +๋˜ํ•œ ๋จธ์‹ ๋Ÿฌ๋‹ ์‹œ์Šคํ…œ์ฒ˜๋Ÿผ **CPU bound** ์›Œํฌ๋กœ๋“œ์— ๋Œ€ํ•ด ๋ณ‘๋ ฌ์„ฑ๊ณผ ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ(์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰)์„ ํ™œ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -ํŒŒ์ด์ฌ์ด **๋ฐ์ดํ„ฐ ์‚ฌ์ด์–ธ์Šค**, ๋จธ์‹ ๋Ÿฌ๋‹๊ณผ ํŠนํžˆ ๋”ฅ๋Ÿฌ๋‹์— ์˜ ์ฃผ๋œ ์–ธ์–ด๋ผ๋Š” ๊ฐ„๋‹จํ•œ ์‚ฌ์‹ค์— ๋”ํ•ด์„œ, ์ด๊ฒƒ์€ FastAPI๋ฅผ ๋ฐ์ดํ„ฐ ์‚ฌ์ด์–ธ์Šค / ๋จธ์‹ ๋Ÿฌ๋‹ ์›น API์™€ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์— (๋‹ค๋ฅธ ๊ฒƒ๋“ค๋ณด๋‹ค) ์ข‹์€ ์„ ํƒ์ง€๊ฐ€ ๋˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. +์ด๊ฒƒ์€ ํŒŒ์ด์ฌ์ด **๋ฐ์ดํ„ฐ ์‚ฌ์ด์–ธ์Šค**, ๋จธ์‹ ๋Ÿฌ๋‹, ํŠนํžˆ ๋”ฅ๋Ÿฌ๋‹์˜ ์ฃผ์š” ์–ธ์–ด๋ผ๋Š” ๋‹จ์ˆœํ•œ ์‚ฌ์‹ค๊ณผ ๋”ํ•ด์ ธ, FastAPI๋ฅผ ๋ฐ์ดํ„ฐ ์‚ฌ์ด์–ธ์Šค/๋จธ์‹ ๋Ÿฌ๋‹ ์›น API ๋ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(๊ทธ ์™ธ์—๋„ ๋งŽ์€ ๊ฒƒ๋“ค)์— ๋งค์šฐ ์ž˜ ๋งž๋Š” ์„ ํƒ์œผ๋กœ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. -๋ฐฐํฌ์‹œ ๋ณ‘๋ ฌ์„ ์–ด๋–ป๊ฒŒ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š”์ง€ ์•Œ๊ณ ์‹ถ๋‹ค๋ฉด, [๋ฐฐํฌ](deployment/index.md){.internal-link target=_blank}๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์‹ญ์‹œ์˜ค. +ํ”„๋กœ๋•์…˜์—์„œ ์ด ๋ณ‘๋ ฌ์„ฑ์„ ์–ด๋–ป๊ฒŒ ๋‹ฌ์„ฑํ•˜๋Š”์ง€ ๋ณด๋ ค๋ฉด [๋ฐฐํฌ](deployment/index.md){.internal-link target=_blank} ์„น์…˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. -## `async`์™€ `await` +## `async`์™€ `await` { #async-and-await } -์ตœ์‹  ํŒŒ์ด์ฌ ๋ฒ„์ „์—๋Š” ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ์ •์˜ํ•˜๋Š” ๋งค์šฐ ์ง๊ด€์ ์ธ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ด๊ฒƒ์„ ํ‰๋ฒ”ํ•œ "์ˆœ์ฐจ์ " ์ฝ”๋“œ๋กœ ๋ณด์ด๊ฒŒ ํ•˜๊ณ , ์ ์ ˆํ•œ ์ˆœ๊ฐ„์— ๋‹น์‹ ์„ ์œ„ํ•ด "๋Œ€๊ธฐ"ํ•ฉ๋‹ˆ๋‹ค. +์ตœ์‹  ํŒŒ์ด์ฌ ๋ฒ„์ „์—๋Š” ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ์ •์˜ํ•˜๋Š” ๋งค์šฐ ์ง๊ด€์ ์ธ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์ด๋ฅผ ํ‰๋ฒ”ํ•œ "์ˆœ์ฐจ" ์ฝ”๋“œ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•˜๊ณ , ์ ์ ˆํ•œ ์ˆœ๊ฐ„์— ์—ฌ๋Ÿฌ๋ถ„์„ ์œ„ํ•ด "๊ธฐ๋‹ค๋ฆผ"์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. -์—ฐ์‚ฐ์ด ๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์ „์— ๋Œ€๊ธฐ๋ฅผ ํ•ด์•ผํ•˜๊ณ  ์ƒˆ๋กœ์šด ํŒŒ์ด์ฌ ๊ธฐ๋Šฅ๋“ค์„ ์ง€์›ํ•œ๋‹ค๋ฉด, ์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ฒฐ๊ณผ๋ฅผ ์ฃผ๊ธฐ ์ „์— ๊ธฐ๋‹ค๋ฆผ์ด ํ•„์š”ํ•œ ์ž‘์—…์ด ์žˆ๊ณ , ์ด๋Ÿฌํ•œ ์ƒˆ๋กœ์šด ํŒŒ์ด์ฌ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•œ๋‹ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```Python burgers = await get_burgers(2) ``` -์—ฌ๊ธฐ์„œ ํ•ต์‹ฌ์€ `await`์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํŒŒ์ด์ฌ์—๊ฒŒ `burgers` ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์ด์ „์— `get_burgers(2)`์˜ ์ž‘์—…์ด ์™„๋ฃŒ๋˜๊ธฐ๋ฅผ ๐Ÿ•™ ๊ธฐ๋‹ค๋ฆฌ๋ผ๊ณ  โธ ๋งํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด, ํŒŒ์ด์ฌ์€ ๊ทธ๋™์•ˆ (๋‹ค๋ฅธ ์š”์ฒญ์„ ๋ฐ›๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€) ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด๋„ ๋œ๋‹ค๋Š” ๊ฒƒ์„ ๐Ÿ”€ โฏ ์•Œ๊ฒŒ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์„œ ํ•ต์‹ฌ์€ `await`์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํŒŒ์ด์ฌ์—๊ฒŒ `get_burgers(2)`๊ฐ€ ๊ทธ ์ผ์„ ๋๋‚ผ ๋•Œ๊นŒ์ง€ ๐Ÿ•™ ๊ธฐ๋‹ค๋ฆฌ๋„๋ก โธ ๋งํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ `burgers`์— ์ €์žฅํ•˜๊ธฐ ์ „์— ์™„๋ฃŒ๋˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํŒŒ์ด์ฌ์€ ๊ทธ๋™์•ˆ(์˜ˆ: ๋‹ค๋ฅธ ์š”์ฒญ์„ ๋ฐ›๋Š” ๊ฒƒ์ฒ˜๋Ÿผ) ๋‹ค๋ฅธ ์ผ์„ ํ•˜๋Ÿฌ ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ ๐Ÿ”€ โฏ ์„ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -`await`๊ฐ€ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด, ์ด๊ฒƒ์€ ๋น„๋™๊ธฐ๋ฅผ ์ง€์›ํ•˜๋Š” ํ•จ์ˆ˜ ๋‚ด๋ถ€์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ ํ•จ์ˆ˜๋ฅผ `async def`๋ฅผ ์‚ฌ์šฉํ•ด ์ •์˜ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: +`await`๊ฐ€ ๋™์ž‘ํ•˜๋ ค๋ฉด, ์ด ๋น„๋™๊ธฐ์„ฑ์„ ์ง€์›ํ•˜๋Š” ํ•จ์ˆ˜ ๋‚ด๋ถ€์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ ค๋ฉด `async def`๋กœ ์„ ์–ธํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: ```Python hl_lines="1" async def get_burgers(number: int): - # Do some asynchronous stuff to create the burgers + # ํ–„๋ฒ„๊ฑฐ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ return burgers ``` -...`def`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ : +...`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 ``` -`async def`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ํŒŒ์ด์ฌ์€ ํ•ด๋‹น ํ•จ์ˆ˜ ๋‚ด์—์„œ `await` ํ‘œํ˜„์— ์ฃผ์˜ํ•ด์•ผํ•œ๋‹ค๋Š” ์‚ฌ์‹ค๊ณผ, ํ•ด๋‹น ํ•จ์ˆ˜์˜ ์‹คํ–‰์„ "์ผ์‹œ์ •์ง€"โธํ•˜๊ณ  ๋‹ค์‹œ ๋Œ์•„์˜ค๊ธฐ ์ „๊นŒ์ง€ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰๐Ÿ”€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ๋ฉ๋‹ˆ๋‹ค. +`async def`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ํŒŒ์ด์ฌ์€ ๊ทธ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ `await` ํ‘œํ˜„์‹์— ์ฃผ์˜ํ•ด์•ผ ํ•˜๋ฉฐ, ๊ทธ ํ•จ์ˆ˜์˜ ์‹คํ–‰์„ "์ผ์‹œ์ •์ง€" โธ ํ•˜๊ณ  ๋‹ค์‹œ ๋Œ์•„์˜ค๊ธฐ ์ „์— ๋‹ค๋ฅธ ์ผ์„ ํ•˜๋Ÿฌ ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ ๐Ÿ”€ ์„ ์•Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. -`async def`f ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ ์ž ํ•  ๋•Œ, "๋Œ€๊ธฐ"ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์•„๋ž˜๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +`async def` ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ ์ž ํ•  ๋•Œ๋Š”, ๊ทธ ํ•จ์ˆ˜๋ฅผ "await" ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์•„๋ž˜๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: ```Python -# This won't work, because get_burgers was defined with: async def +# ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. get_burgers๋Š” async def๋กœ ์ •์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค burgers = get_burgers(2) ``` --- -๋”ฐ๋ผ์„œ, `await`f๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด `async def`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: +๋”ฐ๋ผ์„œ, `await`๋กœ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋งํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ทธ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๋ฅผ `async def`๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: ```Python hl_lines="2-3" @app.get('/burgers') @@ -317,94 +349,96 @@ async def read_burgers(): return burgers ``` -### ๋” ์„ธ๋ถ€์ ์ธ ๊ธฐ์ˆ ์  ์‚ฌํ•ญ +### ๋” ์„ธ๋ถ€์ ์ธ ๊ธฐ์ˆ ์  ์‚ฌํ•ญ { #more-technical-details } -`await`๊ฐ€ `async def`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ๋งŒ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋ˆˆ์น˜์ฑ„์…จ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +`await`๋Š” `async def`๋กœ ์ •์˜๋œ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ˆˆ์น˜์ฑ„์…จ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ๋™์‹œ์—, `async def`๋กœ ์ •์˜๋œ ํ•จ์ˆ˜๋“ค์€ "๋Œ€๊ธฐ"๋˜์–ด์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, `async def`๋ฅผ ์‚ฌ์šฉํ•œ ํ•จ์ˆ˜๋“ค์€ ์—ญ์‹œ `async def`๋ฅผ ์‚ฌ์šฉํ•œ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ๋งŒ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๋™์‹œ์—, `async def`๋กœ ์ •์˜๋œ ํ•จ์ˆ˜๋Š” "await" ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ `async def`๋ฅผ ๊ฐ€์ง„ ํ•จ์ˆ˜๋Š” `async def`๋กœ ์ •์˜๋œ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ๋งŒ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋ ‡๋‹ค๋ฉด ๋‹ญ์ด ๋จผ์ €๋ƒ, ๋‹ฌ๊ฑ€์ด ๋จผ์ €๋ƒ, ์ฒซ `async` ํ•จ์ˆ˜๋ฅผ ์–ด๋–ป๊ฒŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ฒ ์Šต๋‹ˆ๊นŒ? +๊ทธ๋ ‡๋‹ค๋ฉด, ๋‹ญ์ด ๋จผ์ €๋ƒ ๋‹ฌ๊ฑ€์ด ๋จผ์ €๋ƒ์ฒ˜๋Ÿผ, ์ฒซ ๋ฒˆ์งธ `async` ํ•จ์ˆ˜๋Š” ์–ด๋–ป๊ฒŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? -**FastAPI**๋ฅผ ์‚ฌ์šฉํ•ด ์ž‘์—…ํ•œ๋‹ค๋ฉด ์ด๊ฒƒ์„ ๊ฑฑ์ •ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๊ทธ "์ฒซ" ํ•จ์ˆ˜๋Š” ๋‹น์‹ ์˜ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ๋  ๊ฒƒ์ด๊ณ , FastAPI๋Š” ์–ด๋–ป๊ฒŒ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€ ์•Œ๊ณ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +**FastAPI**๋กœ ์ž‘์—…ํ•œ๋‹ค๋ฉด ๊ฑฑ์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ "์ฒซ" ํ•จ์ˆ˜๋Š” ์—ฌ๋Ÿฌ๋ถ„์˜ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๊ฐ€ ๋  ๊ฒƒ์ด๊ณ , FastAPI๋Š” ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ FastAPI๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  `async` / `await`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์ด ์—ญ์‹œ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ FastAPI ์—†์ด `async` / `await`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ๊ทธ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. -### ๋‹น์‹ ๋งŒ์˜ ๋น„๋™๊ธฐ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ +### ์—ฌ๋Ÿฌ๋ถ„๋งŒ์˜ async ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ { #write-your-own-async-code } -Starlette(๊ทธ๋ฆฌ๊ณ  FastAPI)๋Š” <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ ์žˆ๊ณ , ๋”ฐ๋ผ์„œ ํŒŒ์ด์ฌ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> ๋ฐ <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>์™€ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. +Starlette(๊ทธ๋ฆฌ๊ณ  **FastAPI**)๋Š” <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ํŒŒ์ด์ฌ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a>์™€ <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a> ๋ชจ๋‘์™€ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. -ํŠนํžˆ, ์ฝ”๋“œ์—์„œ ๊ณ ๊ธ‰ ํŒจํ„ด์ด ํ•„์š”ํ•œ ๊ณ ๊ธ‰ ๋™์‹œ์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ง์ ‘์ ์œผ๋กœ <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํŠนํžˆ, ์ฝ”๋“œ์—์„œ ๋” ๊ณ ๊ธ‰ ํŒจํ„ด์ด ํ•„์š”ํ•œ ๊ณ ๊ธ‰ ๋™์‹œ์„ฑ ์‚ฌ์šฉ ์‚ฌ๋ก€์—์„œ๋Š” ์ง์ ‘ <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -FastAPI๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋”๋ผ๋„, ๋†’์€ ํ˜ธํ™˜์„ฑ ๋ฐ <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>์˜ ์ด์ (์˜ˆ: *๊ตฌ์กฐํ™”๋œ ๋™์‹œ์„ฑ*)์„ ์ทจํ•˜๊ธฐ ์œ„ํ•ด <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>๋ฅผ ์‚ฌ์šฉํ•ด ๋น„๋™๊ธฐ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  FastAPI๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋”๋ผ๋„, ๋†’์€ ํ˜ธํ™˜์„ฑ์„ ํ™•๋ณดํ•˜๊ณ  ๊ทธ ์ด์ (์˜ˆ: *structured concurrency*)์„ ์–ป๊ธฐ ์œ„ํ•ด <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>๋กœ ์—ฌ๋Ÿฌ๋ถ„๋งŒ์˜ async ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -### ๋น„๋™๊ธฐ ์ฝ”๋“œ์˜ ๋‹ค๋ฅธ ํ˜•ํƒœ +์ €๋Š” AnyIO ์œ„์— ์–‡์€ ๋ ˆ์ด์–ด๋กœ ๋˜ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ, ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์„ ์กฐ๊ธˆ ๊ฐœ์„ ํ•˜๊ณ  ๋” ๋‚˜์€ **์ž๋™์™„์„ฑ**, **์ธ๋ผ์ธ ์˜ค๋ฅ˜** ๋“ฑ์„ ์–ป๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ **์ดํ•ด**ํ•˜๊ณ  **์—ฌ๋Ÿฌ๋ถ„๋งŒ์˜ async ์ฝ”๋“œ**๋ฅผ ์ž‘์„ฑํ•˜๋„๋ก ๋•๋Š” ์นœ์ ˆํ•œ ์†Œ๊ฐœ์™€ ํŠœํ† ๋ฆฌ์–ผ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. ํŠนํžˆ **async ์ฝ”๋“œ์™€ ์ผ๋ฐ˜**(blocking/๋™๊ธฐ) ์ฝ”๋“œ๋ฅผ **๊ฒฐํ•ฉ**ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์•„์ฃผ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. -ํŒŒ์ด์ฌ์—์„œ `async`์™€ `await`๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ ๊ฒƒ์€ ๋น„๊ต์  ์ตœ๊ทผ์˜ ์ผ์ž…๋‹ˆ๋‹ค. +### ๋น„๋™๊ธฐ ์ฝ”๋“œ์˜ ๋‹ค๋ฅธ ํ˜•ํƒœ { #other-forms-of-asynchronous-code } -ํ•˜์ง€๋งŒ ์ด๋กœ ์ธํ•ด ๋น„๋™๊ธฐ ์ฝ”๋“œ ์ž‘์—…์ด ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด์กŒ์Šต๋‹ˆ๋‹ค. +`async`์™€ `await`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด ์Šคํƒ€์ผ์€ ์–ธ์–ด์—์„œ ๋น„๊ต์  ์ตœ๊ทผ์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -๊ฐ™์€ (๋˜๋Š” ๊ฑฐ์˜ ์œ ์‚ฌํ•œ) ๋ฌธ๋ฒ•์€ ์ตœ์‹  ๋ฒ„์ „์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ(๋ธŒ๋ผ์šฐ์ €์™€ NodeJS)์—๋„ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฃจ๋Š” ์ผ์„ ํ›จ์”ฌ ๋” ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ๊ทธ ์ด์ „์—, ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ๊ฝค ๋ณต์žกํ•˜๊ณ  ์–ด๋ ค์šด ์ผ์ด์—ˆ์Šต๋‹ˆ๋‹ค. +๊ฑฐ์˜ ๋™์ผํ•œ ๋ฌธ๋ฒ•์ด ์ตœ๊ทผ ๋ธŒ๋ผ์šฐ์ €์™€ NodeJS์˜ ์ตœ์‹  JavaScript์—๋„ ํฌํ•จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -ํŒŒ์ด์ฌ์˜ ์˜ˆ์ „ ๋ฒ„์ „์ด๋ผ๋ฉด, ์Šค๋ ˆ๋“œ ๋˜๋Š” <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ณ , ๋””๋ฒ„๊น…ํ•˜๊ณ , ์ด์— ๋Œ€ํ•ด ์ƒ๊ฐํ•˜๋Š”๊ฒŒ ํ›จ์”ฌ ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ๊ทธ ์ด์ „์—๋Š” ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋” ๋ณต์žกํ•˜๊ณ  ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค. -์˜ˆ์ „ ๋ฒ„์ „์˜ NodeJS / ๋ธŒ๋ผ์šฐ์ € ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ผ๋ฉด, "์ฝœ๋ฐฑ ํ•จ์ˆ˜"๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋กœ ์ธํ•ด "์ฝœ๋ฐฑ ์ง€์˜ฅ"์— ๋น ์ง€๊ฒŒ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด์ „ ๋ฒ„์ „์˜ ํŒŒ์ด์ฌ์—์„œ๋Š” ์Šค๋ ˆ๋“œ ๋˜๋Š” <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ณ , ๋””๋ฒ„๊น…ํ•˜๊ณ , ์ด์— ๋Œ€ํ•ด ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋” ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค. -## ์ฝ”๋ฃจํ‹ด +์ด์ „ ๋ฒ„์ „์˜ NodeJS/๋ธŒ๋ผ์šฐ์ € JavaScript์—์„œ๋Š” "callback"์„ ์‚ฌ์šฉํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” "callback hell"๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค. -**์ฝ”๋ฃจํ‹ด**์€ `async def` ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์นญํ•˜๋Š” ๋งค์šฐ ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ์šฉ์–ด์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ํŒŒ์ด์ฌ์€ ๊ทธ๊ฒƒ์ด ์‹œ์ž‘๋˜๊ณ  ์–ด๋А ์‹œ์ ์—์„œ ์™„๋ฃŒ๋˜์ง€๋งŒ ๋‚ด๋ถ€์— `await`๊ฐ€ ์žˆ์„ ๋•Œ๋งˆ๋‹ค ๋‚ด๋ถ€์ ์œผ๋กœ ์ผ์‹œ์ •์ง€โธ๋  ์ˆ˜๋„ ์žˆ๋Š” ํ•จ์ˆ˜์™€ ์œ ์‚ฌํ•œ ๊ฒƒ์ด๋ผ๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ณ ์žˆ์Šต๋‹ˆ๋‹ค. +## ์ฝ”๋ฃจํ‹ด { #coroutines } -๊ทธ๋Ÿฌ๋‚˜ `async` ๋ฐ `await`์™€ ํ•จ๊ป˜ ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด ๋ชจ๋“  ๊ธฐ๋Šฅ๋“ค์€ "์ฝ”๋ฃจํ‹ด"์œผ๋กœ ๊ฐ„๋‹จํžˆ ์š”์•ฝ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ Go์˜ ์ฃผ๋œ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ธ "๊ณ ๋ฃจํ‹ด"์— ๊ฒฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +**์ฝ”๋ฃจํ‹ด**์€ `async def` ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ๋งค์šฐ ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ์šฉ์–ด์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ํŒŒ์ด์ฌ์€ ๊ทธ๊ฒƒ์ด ํ•จ์ˆ˜์™€ ๋น„์Šทํ•œ ๋ฌด์–ธ๊ฐ€๋กœ์„œ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๊ณ , ์–ด๋А ์‹œ์ ์— ๋๋‚˜์ง€๋งŒ, ๋‚ด๋ถ€์— `await`๊ฐ€ ์žˆ์„ ๋•Œ๋งˆ๋‹ค ๋‚ด๋ถ€์ ์œผ๋กœ๋„ ์ผ์‹œ์ •์ง€ โธ ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -## ๊ฒฐ๋ก  +ํ•˜์ง€๋งŒ `async` ๋ฐ `await`์™€ ํ•จ๊ป˜ ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด ๋ชจ๋“  ๊ธฐ๋Šฅ์€ ์ข…์ข… "์ฝ”๋ฃจํ‹ด"์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ์š”์•ฝ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” Go์˜ ์ฃผ์š” ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ธ "Goroutines"์— ๋น„๊ฒฌ๋ฉ๋‹ˆ๋‹ค. -์ƒ๊ธฐ ๋ฌธ์žฅ์„ ๋‹ค์‹œ ํ•œ ๋ฒˆ ๋ด…์‹œ๋‹ค: +## ๊ฒฐ๋ก  { #conclusion } -> ์ตœ์‹  ํŒŒ์ด์ฌ ๋ฒ„์ „์€ **`async` ๋ฐ `await`** ๋ฌธ๋ฒ•๊ณผ ํ•จ๊ป˜ **โ€œ์ฝ”๋ฃจํ‹ดโ€**์ด๋ผ๊ณ  ํ•˜๋Š” ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋Š” **โ€œ๋น„๋™๊ธฐ ์ฝ”๋“œโ€**๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +์œ„์˜ ๊ฐ™์€ ๋ฌธ์žฅ์„ ๋‹ค์‹œ ๋ด…์‹œ๋‹ค: -์ด์ œ ์ด ๋ง์„ ์กฐ๊ธˆ ๋” ์ดํ•ดํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. โœจ +> ์ตœ์‹  ํŒŒ์ด์ฌ ๋ฒ„์ „์€ **โ€œ์ฝ”๋ฃจํ‹ดโ€**์ด๋ผ๊ณ  ํ•˜๋Š” ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋Š” **โ€œ๋น„๋™๊ธฐ ์ฝ”๋“œโ€**๋ฅผ **`async` ๋ฐ `await`** ๋ฌธ๋ฒ•๊ณผ ํ•จ๊ป˜ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. -์ด๊ฒƒ์ด (Starlette์„ ํ†ตํ•ด) FastAPI๋ฅผ ๊ฐ•ํ•˜๊ฒŒ ํ•˜๋ฉด์„œ ๊ทธ๊ฒƒ์ด ์ธ์ƒ์ ์ธ ์„ฑ๋Šฅ์„ ๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. +์ด์ œ ๋” ์ดํ•ด๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. โœจ -## ๋งค์šฐ ์„ธ๋ถ€์ ์ธ ๊ธฐ์ˆ ์  ์‚ฌํ•ญ +์ด ๋ชจ๋“  ๊ฒƒ์ด FastAPI(Starlette์„ ํ†ตํ•ด)๋ฅผ ๊ตฌ๋™ํ•˜๊ณ , ์ธ์ƒ์ ์ธ ์„ฑ๋Šฅ์„ ๋‚ด๊ฒŒ ํ•˜๋Š” ์›๋™๋ ฅ์ž…๋‹ˆ๋‹ค. + +## ๋งค์šฐ ์„ธ๋ถ€์ ์ธ ๊ธฐ์ˆ ์  ์‚ฌํ•ญ { #very-technical-details } /// warning | ๊ฒฝ๊ณ  -์ด ๋ถ€๋ถ„์€ ๋„˜์–ด๊ฐ€๋„ ๋ฉ๋‹ˆ๋‹ค. +์ด ๋ถ€๋ถ„์€ ์•„๋งˆ ๊ฑด๋„ˆ๋›ฐ์–ด๋„ ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ๋“ค์€ **FastAPI**๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ๋งค์šฐ ์„ธ๋ถ€์ ์ธ ๊ธฐ์ˆ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. -๋งŒ์•ฝ ๊ธฐ์ˆ ์  ์ง€์‹(์ฝ”๋ฃจํ‹ด, ์Šค๋ ˆ๋“œ, ๋ธ”๋กํ‚น ๋“ฑ)์ด ์žˆ๊ณ  FastAPI๊ฐ€ ์–ด๋–ป๊ฒŒ `async def` vs `def`๋ฅผ ๋‹ค๋ฃจ๋Š”์ง€ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด, ๊ณ„์†ํ•˜์‹ญ์‹œ์˜ค. +(์ฝ”๋ฃจํ‹ด, ์Šค๋ ˆ๋“œ, ๋ธ”๋กœํ‚น ๋“ฑ) ๊ฐ™์€ ๊ธฐ์ˆ  ์ง€์‹์ด ๊ฝค ์žˆ๊ณ  FastAPI๊ฐ€ `async def`์™€ ์ผ๋ฐ˜ `def`๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด, ๊ณ„์† ์ฝ์–ด๋ณด์„ธ์š”. /// -### ๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜ +### ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ { #path-operation-functions } -๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜๋ฅผ `async def` ๋Œ€์‹  ์ผ๋ฐ˜์ ์ธ `def`๋กœ ์„ ์–ธํ•˜๋Š” ๊ฒฝ์šฐ, (์„œ๋ฒ„๋ฅผ ์ฐจ๋‹จํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ) ๊ทธ๊ฒƒ์„ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹  ๋Œ€๊ธฐ์ค‘์ธ ์™ธ๋ถ€ ์Šค๋ ˆ๋“œํ’€์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +*๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๋ฅผ `async def` ๋Œ€์‹  ์ผ๋ฐ˜์ ์ธ `def`๋กœ ์„ ์–ธํ•˜๋ฉด, (์„œ๋ฒ„๋ฅผ ๋ธ”๋กœํ‚นํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹ ) ์™ธ๋ถ€ ์Šค๋ ˆ๋“œํ’€์—์„œ ์‹คํ–‰๋˜๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ await ํ•ฉ๋‹ˆ๋‹ค. -๋งŒ์•ฝ ์ƒ๊ธฐ์— ๋ฌ˜์‚ฌ๋œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋žจ์„ ์‚ฌ์šฉํ•ด์™”๊ณ  ์•ฝ๊ฐ„์˜ ์„ฑ๋Šฅ ํ–ฅ์ƒ (์•ฝ 100 ๋‚˜๋…ธ์ดˆ)์„ ์œ„ํ•ด `def`๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ณ„์‚ฐ๋งŒ์„ ์œ„ํ•œ ์‚ฌ์†Œํ•œ *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๋ฅผ ์ •์˜ํ•ด์™”๋‹ค๋ฉด, **FastAPI**๋Š” ์ด์™€๋Š” ๋ฐ˜๋Œ€๋ผ๋Š” ๊ฒƒ์— ์ฃผ์˜ํ•˜์‹ญ์‹œ์˜ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์—, *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*๊ฐ€ ๋ธ”๋กœํ‚น <abbr title="Input/Output: ๋””์Šคํฌ ์ฝ๊ธฐ ๋˜๋Š” ์“ฐ๊ธฐ, ๋„คํŠธ์›Œํฌ ํ†ต์‹ .">I/O</abbr>๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํ•œ `async def`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด ๋” ๋‚ซ์Šต๋‹ˆ๋‹ค. +์œ„์—์„œ ์„ค๋ช…ํ•œ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ๋‹ค๋ฅธ async ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•ด๋ณธ ์ ์ด ์žˆ๊ณ , ์•„์ฃผ ์ž‘์€ ์„ฑ๋Šฅ ํ–ฅ์ƒ(์•ฝ 100 ๋‚˜๋…ธ์ดˆ)์„ ์œ„ํ•ด ๊ณ„์‚ฐ๋งŒ ํ•˜๋Š” ์‚ฌ์†Œํ•œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*๋ฅผ ์ผ๋ฐ˜ `def`๋กœ ์ •์˜ํ•˜๊ณค ํ–ˆ๋‹ค๋ฉด, **FastAPI**์—์„œ๋Š” ๊ทธ ํšจ๊ณผ๊ฐ€ ์ •๋ฐ˜๋Œ€๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์—์„œ ๋ธ”๋กœํ‚น <abbr title="Input/Output - ์ž…๋ ฅ/์ถœ๋ ฅ: ๋””์Šคํฌ ์ฝ๊ธฐ ๋˜๋Š” ์“ฐ๊ธฐ, ๋„คํŠธ์›Œํฌ ํ†ต์‹ .">I/O</abbr> ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํ•œ `async def`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด ๋” ๋‚ซ์Šต๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘, FastAPI๊ฐ€ ๋‹น์‹ ์ด ์ „์— ์‚ฌ์šฉํ•˜๋˜ ํ”„๋ ˆ์ž„์›Œํฌ๋ณด๋‹ค [๋” ๋น ๋ฅผ](index.md#_11){.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`๋กœ ์ƒ์„ฑ๋œ ๊ฒƒ๋“ค์€ "๋Œ€๊ธฐ"๋˜๋Š” ๋Œ€์‹ ์— (์Šค๋ ˆ๋“œํ’€๋กœ๋ถ€ํ„ฐ) ์™ธ๋ถ€ ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. +์„œ๋กœ๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š” ์—ฌ๋Ÿฌ ์˜์กด์„ฑ๊ณผ [ํ•˜์œ„ ์˜์กด์„ฑ](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๊ฐ€ ๋‹น์‹ ์„ ์œ„ํ•ด ํ˜ธ์ถœํ•˜๋Š” ํ•จ์ˆ˜์™€๋Š” ๋ฐ˜๋Œ€์ž…๋‹ˆ๋‹ค: *๊ฒฝ๋กœ ์ž‘๋™ ํ•จ์ˆ˜*์™€ ์˜์กด์„ฑ +์ด๋Š” FastAPI๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์„ ์œ„ํ•ด ํ˜ธ์ถœํ•˜๋Š” ํ•จ์ˆ˜(์ฆ‰, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜*์™€ ์˜์กด์„ฑ)์™€ ๋Œ€๋น„๋ฉ๋‹ˆ๋‹ค. -๋งŒ์•ฝ ๋‹น์‹ ์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๊ฐ€ `def`๋ฅผ ์‚ฌ์šฉํ•œ ์ผ๋ฐ˜์ ์ธ ํ•จ์ˆ˜๋ผ๋ฉด, ์Šค๋ ˆ๋“œํ’€์—์„œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ง์ ‘ ํ˜ธ์ถœ(๋‹น์‹ ์ด ์ฝ”๋“œ์— ์ž‘์„ฑํ•œ ๋Œ€๋กœ)๋  ๊ฒƒ์ด๊ณ , `async def`๋กœ ์ƒ์„ฑ๋œ ํ•จ์ˆ˜๋ผ๋ฉด ์ฝ”๋“œ์—์„œ ํ˜ธ์ถœํ•  ๋•Œ ๊ทธ ํ•จ์ˆ˜๋ฅผ `await` ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๊ฐ€ `def`๋กœ ๋งŒ๋“  ์ผ๋ฐ˜ ํ•จ์ˆ˜๋ผ๋ฉด, ์Šค๋ ˆ๋“œํ’€์ด ์•„๋‹ˆ๋ผ ์ง์ ‘(์ฝ”๋“œ์— ์ž‘์„ฑํ•œ ๋Œ€๋กœ) ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  `async def`๋กœ ์ƒ์„ฑ๋œ ํ•จ์ˆ˜๋ผ๋ฉด, ์ฝ”๋“œ์—์„œ ํ˜ธ์ถœํ•  ๋•Œ ๊ทธ ํ•จ์ˆ˜๋ฅผ `await` ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. --- -๋‹ค์‹œ ๋งํ•˜์ง€๋งŒ, ์ด๊ฒƒ์€ ๋‹น์‹ ์ด ์ด๊ฒƒ์— ๋Œ€ํ•ด ์ฐพ๊ณ ์žˆ๋˜ ๊ฒฝ์šฐ์— ํ•œํ•ด ์œ ์šฉํ•  ๋งค์šฐ ์„ธ๋ถ€์ ์ธ ๊ธฐ์ˆ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. +๋‹ค์‹œ ๋งํ•˜์ง€๋งŒ, ์ด๊ฒƒ๋“ค์€ ์•„๋งˆ๋„ ์ด๋ฅผ ์ฐพ๊ณ  ์žˆ์—ˆ๋˜ ๊ฒฝ์šฐ์— ์œ ์šฉํ•œ ๋งค์šฐ ์„ธ๋ถ€์ ์ธ ๊ธฐ์ˆ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. -๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ, ์ƒ๊ธฐ์˜ ๊ฐ€์ด๋“œ๋ผ์ธ๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: [๋ฐ”์˜์‹  ๊ฒฝ์šฐ](#_1). +๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด, ์œ„ ์„น์…˜์˜ ๊ฐ€์ด๋“œ๋ผ์ธ์ด๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค: <a href="#in-a-hurry">๋ฐ”์˜์‹ ๊ฐ€์š”?</a>. diff --git a/docs/ko/docs/fastapi-cli.md b/docs/ko/docs/fastapi-cli.md index a1160c71fc..0d87ce3219 100644 --- a/docs/ko/docs/fastapi-cli.md +++ b/docs/ko/docs/fastapi-cli.md @@ -1,83 +1,75 @@ -# FastAPI CLI +# FastAPI CLI { #fastapi-cli } -**FastAPI CLI**๋Š” FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๊ณ , ํ”„๋กœ์ ํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋“ฑ ๋‹ค์–‘ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ปค๋งจ๋“œ ๋ผ์ธ ํ”„๋กœ๊ทธ๋žจ์ž…๋‹ˆ๋‹ค. +**FastAPI CLI**๋Š” FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„œ๋น™ํ•˜๊ณ , FastAPI ํ”„๋กœ์ ํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋“ฑ ๋‹ค์–‘ํ•œ ์ž‘์—…์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ปค๋งจ๋“œ ๋ผ์ธ ํ”„๋กœ๊ทธ๋žจ์ž…๋‹ˆ๋‹ค. -FastAPI๋ฅผ ์„ค์น˜ํ•  ๋•Œ (์˜ˆ: `pip install "fastapi[standard]"` ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ), `fastapi-cli`๋ผ๋Š” ํŒจํ‚ค์ง€๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์ด ํŒจํ‚ค์ง€๋Š” ํ„ฐ๋ฏธ๋„์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” `fastapi` ๋ช…๋ น์–ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +FastAPI๋ฅผ ์„ค์น˜ํ•  ๋•Œ(์˜ˆ: `pip install "fastapi[standard]"`), `fastapi-cli`๋ผ๋Š” ํŒจํ‚ค์ง€๊ฐ€ ํฌํ•จ๋˜๋ฉฐ, ์ด ํŒจํ‚ค์ง€๋Š” ํ„ฐ๋ฏธ๋„์—์„œ `fastapi` ๋ช…๋ น์–ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -๊ฐœ๋ฐœ์šฉ์œผ๋กœ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด `fastapi dev` ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ฐœ๋ฐœ์šฉ์œผ๋กœ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋ ค๋ฉด `fastapi dev` ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: <div class="termy"> ```console -$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u> -<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font> -<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font> -<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files -<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> +$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u> - โ•ญโ”€ <font color="#8AE234"><b>Python module file</b></font> โ”€โ•ฎ - โ”‚ โ”‚ - โ”‚ ๐Ÿ main.py โ”‚ - โ”‚ โ”‚ - โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + <span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server ๐Ÿš€ -<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font> -<font color="#3465A4">INFO </font> Found importable FastAPI app + Searching for package file structure from directories with + <font color="#3465A4">__init__.py</font> files + Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> - โ•ญโ”€ <font color="#8AE234"><b>Importable FastAPI app</b></font> โ”€โ•ฎ - โ”‚ โ”‚ - โ”‚ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> โ”‚ - โ”‚ โ”‚ - โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + <span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> ๐Ÿ main.py -<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font> + <span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the + following code: - <span style="background-color:#C4A000"><font color="#2E3436">โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ FastAPI CLI - Development mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ Serving at: http://127.0.0.1:8000 โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ API docs: http://127.0.0.1:8000/docs โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ Running in development mode, for production use: โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ”‚ โ”‚</font></span> - <span style="background-color:#C4A000"><font color="#2E3436">โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ</font></span> + <u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u> -<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] -<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit) -<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font> -<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>] -<font color="#4E9A06">INFO</font>: Waiting for application startup. -<font color="#4E9A06">INFO</font>: Application startup complete. + <span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font> + + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> + <span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font> + + <span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use: + <b>fastapi run</b> + + Logs: + + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories: + <b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C to + quit<b>)</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b> + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. + <span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. ``` </div> -`fastapi`๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ๋ช…๋ น์–ด ํ”„๋กœ๊ทธ๋žจ์€ **FastAPI CLI**์ž…๋‹ˆ๋‹ค. +`fastapi`๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ์ปค๋งจ๋“œ ๋ผ์ธ ํ”„๋กœ๊ทธ๋žจ์€ **FastAPI CLI**์ž…๋‹ˆ๋‹ค. -FastAPI CLI๋Š” Python ํ”„๋กœ๊ทธ๋žจ์˜ ๊ฒฝ๋กœ(์˜ˆ: `main.py`)๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›์•„, `FastAPI` ์ธ์Šคํ„ด์Šค(์ผ๋ฐ˜์ ์œผ๋กœ `app`์œผ๋กœ ๋ช…๋ช…)๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅธ ์ž„ํฌํŠธ ๊ณผ์ •์„ ๊ฒฐ์ •ํ•œ ํ›„ ์ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. +FastAPI CLI๋Š” Python ํ”„๋กœ๊ทธ๋žจ์˜ ๊ฒฝ๋กœ(์˜ˆ: `main.py`)๋ฅผ ๋ฐ›์•„ `FastAPI` ์ธ์Šคํ„ด์Šค(์ผ๋ฐ˜์ ์œผ๋กœ `app`์œผ๋กœ ์ด๋ฆ„์„ ๋ถ™์ž„)๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ , ์˜ฌ๋ฐ”๋ฅธ ์ž„ํฌํŠธ ๊ณผ์ •์„ ๊ฒฐ์ •ํ•œ ๋‹ค์Œ ์„œ๋น™ํ•ฉ๋‹ˆ๋‹ค. -ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” `fastapi run` ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿš€ +ํ”„๋กœ๋•์…˜์—์„œ๋Š” ๋Œ€์‹  `fastapi run`์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿš€ -๋‚ด๋ถ€์ ์œผ๋กœ, **FastAPI CLI**๋Š” ๊ณ ์„ฑ๋Šฅ์˜, ํ”„๋กœ๋•์…˜์— ์ ํ•ฉํ•œ, ASGI ์„œ๋ฒ„์ธ <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ˜Ž +๋‚ด๋ถ€์ ์œผ๋กœ **FastAPI CLI**๋Š” ๊ณ ์„ฑ๋Šฅ์˜, ํ”„๋กœ๋•์…˜์— ์ ํ•ฉํ•œ ASGI ์„œ๋ฒ„์ธ <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ˜Ž -## `fastapi dev` +## `fastapi dev` { #fastapi-dev } -`fastapi dev` ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ฉด ๊ฐœ๋ฐœ ๋ชจ๋“œ๊ฐ€ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. +`fastapi dev`๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๊ฐœ๋ฐœ ๋ชจ๋“œ๊ฐ€ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. -๊ธฐ๋ณธ์ ์œผ๋กœ **์ž๋™ ์žฌ์‹œ์ž‘(auto-reload)** ๊ธฐ๋Šฅ์ด ํ™œ์„ฑํ™”๋˜์–ด, ์ฝ”๋“œ์— ๋ณ€๊ฒฝ์ด ์ƒ๊ธฐ๋ฉด ์„œ๋ฒ„๋ฅผ ์ž๋™์œผ๋กœ ๋‹ค์‹œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๊ธฐ๋Šฅ์€ ๋ฆฌ์†Œ์Šค๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋ฉฐ, ๋น„ํ™œ์„ฑํ™”ํ–ˆ์„ ๋•Œ๋ณด๋‹ค ์•ˆ์ •์„ฑ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์„œ๋ฒ„๋Š” ์ปดํ“จํ„ฐ๊ฐ€ ์ž์ฒด์ ์œผ๋กœ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” IP ์ฃผ์†Œ(`localhost`)์ธ `127.0.0.1`์—์„œ ์—ฐ๊ฒฐ์„ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค. +๊ธฐ๋ณธ์ ์œผ๋กœ **auto-reload**๊ฐ€ ํ™œ์„ฑํ™”๋˜์–ด ์ฝ”๋“œ์— ๋ณ€๊ฒฝ์ด ์ƒ๊ธฐ๋ฉด ์„œ๋ฒ„๋ฅผ ์ž๋™์œผ๋กœ ๋‹ค์‹œ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋ฉฐ, ๋น„ํ™œ์„ฑํ™”ํ–ˆ์„ ๋•Œ๋ณด๋‹ค ์•ˆ์ •์„ฑ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ปดํ“จํ„ฐ๊ฐ€ ์ž์‹ ๊ณผ๋งŒ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•œ(`localhost`) IP์ธ `127.0.0.1`์—์„œ ์—ฐ๊ฒฐ์„ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค. -## `fastapi run` +## `fastapi run` { #fastapi-run } -`fastapi run` ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ํ”„๋กœ๋•์…˜ ๋ชจ๋“œ๋กœ FastAPI๊ฐ€ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. +`fastapi run`์„ ์‹คํ–‰ํ•˜๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ํ”„๋กœ๋•์…˜ ๋ชจ๋“œ๋กœ FastAPI๊ฐ€ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. -๊ธฐ๋ณธ์ ์œผ๋กœ **์ž๋™ ์žฌ์‹œ์ž‘(auto-reload)** ๊ธฐ๋Šฅ์ด ๋น„ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  IP ์ฃผ์†Œ์ธ `0.0.0.0`์—์„œ ์—ฐ๊ฒฐ์„ ๋Œ€๊ธฐํ•˜๋ฏ€๋กœ ํ•ด๋‹น ์ปดํ“จํ„ฐ์™€ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์‚ฌ๋žŒ์ด ๊ณต๊ฐœ์ ์œผ๋กœ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ์™€ ๊ฐ™์€ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. +๊ธฐ๋ณธ์ ์œผ๋กœ **auto-reload**๋Š” ๋น„ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  IP ์ฃผ์†Œ๋ฅผ ์˜๋ฏธํ•˜๋Š” `0.0.0.0`์—์„œ ์—ฐ๊ฒฐ์„ ๋Œ€๊ธฐํ•˜๋ฏ€๋กœ, ํ•ด๋‹น ์ปดํ“จํ„ฐ์™€ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ˆ„๊ตฌ์—๊ฒŒ๋‚˜ ๊ณต๊ฐœ์ ์œผ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค. ๋ณดํ†ต ํ”„๋กœ๋•์…˜์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ์‹คํ–‰ํ•˜๋ฉฐ, ์˜ˆ๋ฅผ ๋“ค์–ด ์ปจํ…Œ์ด๋„ˆ์—์„œ ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. -์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•˜๋Š” ๋ฐฉ์‹์— ๋”ฐ๋ผ ๋‹ค๋ฅด์ง€๋งŒ, ๋Œ€๋ถ€๋ถ„ "์ข…๋ฃŒ ํ”„๋ก์‹œ(termination proxy)"๋ฅผ ํ™œ์šฉํ•ด HTTPS๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋ฐฐํฌ ์„œ๋น„์Šค ์ œ๊ณต์ž๊ฐ€ ์ด ์ž‘์—…์„ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•ด์ค„ ์ˆ˜๋„ ์žˆ๊ณ , ์ง์ ‘ ์„ค์ •ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์œ„์— "termination proxy"๋ฅผ ๋‘๊ณ  HTTPS๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ฒŒ(๊ทธ๋ฆฌ๊ณ  ์ฒ˜๋ฆฌํ•ด์•ผ) ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•˜๋Š” ๋ฐฉ์‹์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง€๋ฉฐ, ์ œ๊ณต์ž๊ฐ€ ์ด ์ž‘์—…์„ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•ด์ค„ ์ˆ˜๋„ ์žˆ๊ณ  ์ง์ ‘ ์„ค์ •ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -/// tip +/// tip | ํŒ -์ž์„ธํ•œ ๋‚ด์šฉ์€ [deployment documentation](deployment/index.md){.internal-link target=\_blank}์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ž์„ธํ•œ ๋‚ด์šฉ์€ [๋ฐฐํฌ ๋ฌธ์„œ](deployment/index.md){.internal-link target=_blank}์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /// diff --git a/docs/ko/docs/features.md b/docs/ko/docs/features.md index dfbf479998..17cc9289f7 100644 --- a/docs/ko/docs/features.md +++ b/docs/ko/docs/features.md @@ -1,43 +1,43 @@ -# ๊ธฐ๋Šฅ +# ๊ธฐ๋Šฅ { #features } -## FastAPI์˜ ๊ธฐ๋Šฅ +## FastAPI์˜ ๊ธฐ๋Šฅ { #fastapi-features } **FastAPI**๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: -### ๊ฐœ๋ฐฉํ˜• ํ‘œ์ค€์„ ๊ธฐ๋ฐ˜์œผ๋กœ +### ๊ฐœ๋ฐฉํ˜• ํ‘œ์ค€์„ ๊ธฐ๋ฐ˜์œผ๋กœ { #based-on-open-standards } -* <abbr title="์—”๋“œํฌ์ธํŠธ, ๋ผ์šฐํŠธ๋กœ๋„ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค">๊ฒฝ๋กœ</abbr><abbr title="POST, GET, PUT, DELETE์™€ ๊ฐ™์€ HTTP ๋ฉ”์†Œ๋“œ๋กœ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค">์ž‘๋™</abbr>, ๋งค๊ฐœ๋ณ€์ˆ˜, ๋ณธ๋ฌธ ์š”์ฒญ, ๋ณด์•ˆ ๊ทธ ์™ธ์˜ ์„ ์–ธ์„ ํฌํ•จํ•œ API ์ƒ์„ฑ์„ ์œ„ํ•œ <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> -* <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (OpenAPI ์ž์ฒด๊ฐ€ JSON Schema๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค)๋ฅผ ์‚ฌ์šฉํ•œ ์ž๋™ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ๋ฌธ์„œํ™”. -* ๋‹จ์ˆœํžˆ ๋– ์˜ฌ๋ ค์„œ ๋ง๋ถ™์ธ ๊ธฐ๋Šฅ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์„ธ์‹ฌํ•œ ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์นœ ํ›„, ์ด๋Ÿฌํ•œ ํ‘œ์ค€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -* ์ด๋Š” ๋˜ํ•œ ๋‹ค์–‘ํ•œ ์–ธ์–ด๋กœ ์ž๋™์ ์ธ **ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ์ƒ์„ฑ**์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +* <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a>: <abbr title="๋˜ํ•œ ๋‹ค์Œ์œผ๋กœ๋„ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค: ์—”๋“œํฌ์ธํŠธ, ๋ผ์šฐํŠธ">path</abbr> <abbr title="HTTP ๋ฉ”์†Œ๋“œ(POST, GET, PUT, DELETE ๋“ฑ)๋กœ๋„ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค">operations</abbr>, ๋งค๊ฐœ๋ณ€์ˆ˜, ์š”์ฒญ ๋ณธ๋ฌธ, ๋ณด์•ˆ ๋“ฑ์˜ ์„ ์–ธ์„ ํฌํ•จํ•˜์—ฌ API๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +* <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a>๋ฅผ ์‚ฌ์šฉํ•œ ์ž๋™ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ๋ฌธ์„œํ™”(OpenAPI ์ž์ฒด๊ฐ€ JSON Schema๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค). +* ๋‹จ์ˆœํžˆ ๋– ์˜ฌ๋ ค์„œ ๋ง๋ถ™์ธ ๋ ˆ์ด์–ด๊ฐ€ ์•„๋‹ˆ๋ผ, ์„ธ์‹ฌํ•œ ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์นœ ๋’ค ์ด๋Ÿฌํ•œ ํ‘œ์ค€์„ ์ค‘์‹ฌ์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +* ์ด๋Š” ๋˜ํ•œ ๋‹ค์–‘ํ•œ ์–ธ์–ด๋กœ ์ž๋™ **ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ์ƒ์„ฑ**์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. -### ๋ฌธ์„œ ์ž๋™ํ™” +### ๋ฌธ์„œ ์ž๋™ํ™” { #automatic-docs } -๋Œ€ํ™”ํ˜• API ๋ฌธ์„œ์™€ ์›น ํƒ์ƒ‰ ์œ ์ € ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ OpenAPI๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ธฐ์—, 2๊ฐ€์ง€ ์˜ต์…˜์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ๋“ค์–ด๊ฐ„ ์—ฌ๋Ÿฌ ์˜ต์…˜์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. +๋Œ€ํ™”ํ˜• API ๋ฌธ์„œ์™€ ํƒ์ƒ‰์šฉ ์›น ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ OpenAPI๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ธฐ์— ์—ฌ๋Ÿฌ ์˜ต์…˜์ด ์žˆ์œผ๋ฉฐ, ๊ธฐ๋ณธ์œผ๋กœ 2๊ฐ€์ง€๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. -* ๋Œ€ํ™”ํ˜• ํƒ์ƒ‰ <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>๋ฅผ ์ด์šฉํ•ด, ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐ”๋กœ ์—ฌ๋Ÿฌ๋ถ„์˜ API๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ๋Œ€ํ™”ํ˜• ํƒ์ƒ‰์ด ๊ฐ€๋Šฅํ•œ <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ API๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>์„ ์ด์šฉํ•ด API ๋ฌธ์„œํ™”๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>์„ ์ด์šฉํ•œ ๋Œ€์ฒด API ๋ฌธ์„œํ™”. ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### ๊ทธ์ € ํ˜„๋Œ€ ํŒŒ์ด์ฌ +### ๊ทธ์ € ํ˜„๋Œ€ ํŒŒ์ด์ฌ { #just-modern-python } -(Pydantic ๋•๋ถ„์—) FastAPI๋Š” ํ‘œ์ค€ **ํŒŒ์ด์ฌ 3.6 ํƒ€์ž…** ์„ ์–ธ์— ๊ธฐ๋ฐ˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ ๋ฐฐ์šธ ๋ฌธ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ์ € ํ‘œ์ค€์ ์ธ ํ˜„๋Œ€ ํŒŒ์ด์ฌ์ž…๋‹ˆ๋‹ค. +( Pydantic ๋•๋ถ„์—) ๋ชจ๋“  ๊ฒƒ์ด ํ‘œ์ค€ **Python ํƒ€์ž…** ์„ ์–ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ ๋ฐฐ์šธ ๋ฌธ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ์ € ํ‘œ์ค€์ ์ธ ํ˜„๋Œ€ ํŒŒ์ด์ฌ์ž…๋‹ˆ๋‹ค. -๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด ํŒŒ์ด์ฌ ํƒ€์ž…์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€์— ๋Œ€ํ•œ 2๋ถ„ ์ •๋„์˜ ๋ณต์Šต์ด ํ•„์š”ํ•˜๋‹ค๋ฉด (๋น„๋ก ์—ฌ๋Ÿฌ๋ถ„์ด FastAPI๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค ํ•˜๋”๋ผ๋„), ๋‹ค์Œ์˜ ์งง์€ ์ž์Šต์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”: [ํŒŒ์ด์ฌ ํƒ€์ž…](python-types.md){.internal-link target=\_blank}. +Python ํƒ€์ž…์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ 2๋ถ„ ์ •๋„ ๋ณต์Šต์ด ํ•„์š”ํ•˜๋‹ค๋ฉด(FastAPI๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋”๋ผ๋„), ๋‹ค์Œ์˜ ์งง์€ ์ž์Šต์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”: [Python ํƒ€์ž…](python-types.md){.internal-link target=_blank}. -์—ฌ๋Ÿฌ๋ถ„์€ ํƒ€์ž…์„ ์ด์šฉํ•œ ํ‘œ์ค€ ํŒŒ์ด์ฌ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์—ฌ๋Ÿฌ๋ถ„์€ ํƒ€์ž…์ด ์žˆ๋Š” ํ‘œ์ค€ Python์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค: ```Python from datetime import date from pydantic import BaseModel -# ๋ณ€์ˆ˜๋ฅผ str๋กœ ์„ ์–ธ -# ๊ทธ ํ›„ ํ•จ์ˆ˜ ์•ˆ์—์„œ ํŽธ์ง‘๊ธฐ ์ง€์›์„ ๋ฐ›์œผ์„ธ์š” +# ๋ณ€์ˆ˜๋ฅผ str๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค +# ๊ทธ๋ฆฌ๊ณ  ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ํŽธ์ง‘๊ธฐ ์ง€์›์„ ๋ฐ›์Šต๋‹ˆ๋‹ค def main(user_id: str): return user_id @@ -49,7 +49,7 @@ class User(BaseModel): joined: date ``` -์œ„์˜ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +๊ทธ ๋‹ค์Œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```Python my_user: User = User(id=3, name="John Doe", joined="2018-07-19") @@ -65,23 +65,23 @@ my_second_user: User = User(**second_user_data) /// info | ์ •๋ณด -`**second_user_data`๊ฐ€ ๋œปํ•˜๋Š” ๊ฒƒ: +`**second_user_data`๋Š” ๋‹ค์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค: -`second_user_data` ๋”•์…”๋„ˆ๋ฆฌ์˜ ํ‚ค์™€ ๊ฐ’์„ ํ‚ค-๊ฐ’ ์ธ์ž๋กœ์„œ ๋ฐ”๋กœ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค: `User(id=4, name="Mary", joined="2018-11-30")` +`second_user_data` `dict`์˜ ํ‚ค์™€ ๊ฐ’์„ ํ‚ค-๊ฐ’ ์ธ์ž๋กœ์„œ ๋ฐ”๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ์œผ๋กœ, ๋‹ค์Œ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค: `User(id=4, name="Mary", joined="2018-11-30")` /// -### ํŽธ์ง‘๊ธฐ ์ง€์› +### ํŽธ์ง‘๊ธฐ ์ง€์› { #editor-support } -๋ชจ๋“  ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฝ๊ณ  ์ง๊ด€์ ์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ, ์ข‹์€ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•˜๊ธฐ๋„ ์ „์— ๋ชจ๋“  ๊ฒฐ์ •๋“ค์€ ์—ฌ๋Ÿฌ ํŽธ์ง‘๊ธฐ์—์„œ ํ…Œ์ŠคํŠธ๋ฉ๋‹ˆ๋‹ค. +ํ”„๋ ˆ์ž„์›Œํฌ ์ „์ฒด๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฝ๊ณ  ์ง๊ด€์ ์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ, ์ตœ๊ณ ์˜ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•˜๊ธฐ๋„ ์ „์— ๋ชจ๋“  ๊ฒฐ์ •์€ ์—ฌ๋Ÿฌ ํŽธ์ง‘๊ธฐ์—์„œ ํ…Œ์ŠคํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -์ตœ๊ทผ ํŒŒ์ด์ฌ ๊ฐœ๋ฐœ์ž ์„ค๋ฌธ์กฐ์‚ฌ์—์„œ <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">"์ž๋™ ์™„์„ฑ"์ด ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋Šฅ</a>์ด๋ผ๋Š” ๊ฒƒ์ด ๋ฐํ˜€์กŒ์Šต๋‹ˆ๋‹ค. +Python ๊ฐœ๋ฐœ์ž ์„ค๋ฌธ์กฐ์‚ฌ์—์„œ <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๊ฐ€ "์ž๋™ ์™„์„ฑ"์ด๋ผ๋Š” ์ </a>์ด ๋ถ„๋ช…ํ•ฉ๋‹ˆ๋‹ค. -**FastAPI** ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๋ชจ๋“  ๋ถ€๋ถ„์€ ์ด๋ฅผ ์ถฉ์กฑํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ž๋™์™„์„ฑ์€ ์–ด๋А ๊ณณ์—์„œ๋‚˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. +**FastAPI** ํ”„๋ ˆ์ž„์›Œํฌ ์ „์ฒด๋Š” ์ด๋ฅผ ๋งŒ์กฑํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์ž๋™ ์™„์„ฑ์€ ์–ด๋””์„œ๋‚˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ๋ถ„์€ ๋ฌธ์„œ๋กœ ๋‹ค์‹œ ๋Œ์•„์˜ฌ ์ผ์ด ๊ฑฐ์˜ ์—†์„ ๊ฒ๋‹ˆ๋‹ค. +๋ฌธ์„œ๋กœ ๋‹ค์‹œ ๋Œ์•„์˜ฌ ์ผ์€ ๊ฑฐ์˜ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -๋‹ค์Œ์€ ํŽธ์ง‘๊ธฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์—ฌ๋Ÿฌ๋ถ„์„ ๋„์™€์ฃผ๋Š”์ง€ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค: +ํŽธ์ง‘๊ธฐ๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์„ ์–ด๋–ป๊ฒŒ ๋„์™€์ค„ ์ˆ˜ ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด์„ธ์š”: * <a href="https://code.visualstudio.com/" class="external-link" target="_blank">Visual Studio Code</a>์—์„œ: @@ -91,111 +91,111 @@ my_second_user: User = User(**second_user_data) ![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`), ๊ทธ ์™ธ. + * ์ตœ์†Œ/์ตœ๋Œ€ ๊ธธ์ด๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฌธ์ž์—ด(`str`) ํ•„๋“œ. + * ์ตœ์†Œ/์ตœ๋Œ€ ๊ฐ’์„ ๊ฐ€์ง€๋Š” ์ˆซ์ž(`int`, `float`) ๋“ฑ. -* ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋”์šฑ ์ด์ƒ‰์ ์ธ ํƒ€์ž…์— ๋Œ€ํ•ด ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +* ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ข€ ๋” ์ด์ƒ‰์ ์ธ ํƒ€์ž…์— ๋Œ€ํ•œ ๊ฒ€์ฆ: * URL. - * ์ด๋ฉ”์ผ. + * Email. * UUID. - * ...๋‹ค๋ฅธ ๊ฒƒ๋“ค. + * ...๊ทธ ์™ธ. -๋ชจ๋“  ๊ฒ€์ฆ์€ ๊ฒฌ๊ณ ํ•˜๋ฉด์„œ ์ž˜ ํ™•๋ฆฝ๋œ **Pydantic**์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. +๋ชจ๋“  ๊ฒ€์ฆ์€ ์ž˜ ํ™•๋ฆฝ๋˜์–ด ์žˆ๊ณ  ๊ฒฌ๊ณ ํ•œ **Pydantic**์ด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. -### ๋ณด์•ˆ๊ณผ ์ธ์ฆ +### ๋ณด์•ˆ๊ณผ ์ธ์ฆ { #security-and-authentication } -๋ณด์•ˆ๊ณผ ์ธ์ฆ์ด ํ†ตํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๊ณผ์˜ ํƒ€ํ˜‘์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ณด์•ˆ๊ณผ ์ธ์ฆ์ด ํ†ตํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๊ณผ ํƒ€ํ˜‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -๋‹ค์Œ์„ ํฌํ•จํ•˜๋Š”, ๋ชจ๋“  ๋ณด์•ˆ ์Šคํ‚ค๋งˆ๊ฐ€ OpenAPI์— ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +๋‹ค์Œ์„ ํฌํ•จํ•ด OpenAPI์— ์ •์˜๋œ ๋ชจ๋“  ๋ณด์•ˆ ์Šคํ‚ค๋งˆ: * HTTP Basic. -* **OAuth2** (**JWT tokens** ๋˜ํ•œ ํฌํ•จ). [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=\_blank}์— ์žˆ๋Š” ์ž์Šต์„œ๋ฅผ ํ™•์ธํ•ด ๋ณด์„ธ์š”. +* **OAuth2**(**JWT tokens** ๋˜ํ•œ ํฌํ•จ). [JWT๋ฅผ ์‚ฌ์šฉํ•œ OAuth2](tutorial/security/oauth2-jwt.md){.internal-link target=_blank} ์ž์Šต์„œ๋ฅผ ํ™•์ธํ•ด ๋ณด์„ธ์š”. * ๋‹ค์Œ์— ๋“ค์–ด ์žˆ๋Š” API ํ‚ค: * ํ—ค๋”. - * ๋งค๊ฐœ๋ณ€์ˆ˜. - * ์ฟ ํ‚ค ๋ฐ ๊ทธ ์™ธ. + * ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜. + * ์ฟ ํ‚ค ๋“ฑ. -์ถ”๊ฐ€์ ์œผ๋กœ (**์„ธ์…˜ ์ฟ ํ‚ค**๋ฅผ ํฌํ•จํ•œ) ๋ชจ๋“  ๋ณด์•ˆ ๊ธฐ๋Šฅ์€ Starlette์— ์žˆ์Šต๋‹ˆ๋‹ค. +์ถ”๊ฐ€๋กœ Starlette์˜ ๋ชจ๋“  ๋ณด์•ˆ ๊ธฐ๋Šฅ(**์„ธ์…˜ ์ฟ ํ‚ค** ํฌํ•จ)๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. -๋ชจ๋‘ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ์™€ ์ปดํฌ๋„ŒํŠธ๋กœ ๋งŒ๋“ค์–ด์ ธ ์žˆ์–ด ์—ฌ๋Ÿฌ๋ถ„์˜ ์‹œ์Šคํ…œ, ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ, ๊ด€๊ณ„ํ˜• ๋ฐ NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ๊ณผ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ชจ๋‘ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„๊ตฌ์™€ ์ปดํฌ๋„ŒํŠธ๋กœ ๋งŒ๋“ค์–ด์ ธ ์žˆ์–ด, ์—ฌ๋Ÿฌ๋ถ„์˜ ์‹œ์Šคํ…œ, ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ, ๊ด€๊ณ„ํ˜• ๋ฐ NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ๊ณผ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### ์˜์กด์„ฑ ์ฃผ์ž… +### ์˜์กด์„ฑ ์ฃผ์ž… { #dependency-injection } -FastAPI๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ๋งค์šฐ ๊ฐ„ํŽธํ•˜์ง€๋งŒ, ์—„์ฒญ๋‚œ <abbr title='"์ปดํฌ๋„ŒํŠธ", "์ž์›", "์„œ๋น„์Šค", "์ œ๊ณต์ž"๋กœ๋„ ์•Œ๋ ค์ง„'><strong>์˜์กด์„ฑ ์ฃผ์ž…</strong></abbr>์‹œ์Šคํ…œ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +FastAPI๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ๋งค์šฐ ์‰ฝ์ง€๋งŒ, ๋งค์šฐ ๊ฐ•๋ ฅํ•œ <abbr title='๋˜ํ•œ ๋‹ค์Œ์œผ๋กœ๋„ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค: "์ปดํฌ๋„ŒํŠธ", "์ž์›", "์„œ๋น„์Šค", "์ œ๊ณต์ž"'><strong>Dependency Injection</strong></abbr> ์‹œ์Šคํ…œ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -* ์˜์กด์„ฑ์€ ์˜์กด์„ฑ์„ ๊ฐ€์งˆ์ˆ˜๋„ ์žˆ์–ด, ์ด๋ฅผ ํ†ตํ•ด ์˜์กด์„ฑ์˜ ๊ณ„์ธต์ด๋‚˜ **์˜์กด์„ฑ์˜ "๊ทธ๋ž˜ํ”„"**๋ฅผ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค. -* ๋ชจ๋“  ๊ฒƒ์ด ํ”„๋ ˆ์ž„์›Œํฌ์— ์˜ํ•ด **์ž๋™์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค**. -* ๋ชจ๋“  ์˜์กด์„ฑ์€ ์š”์ฒญ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์š”๊ตฌํ•˜์—ฌ ์ž๋™ ๋ฌธ์„œํ™”์™€ **๊ฒฝ๋กœ ์ž‘๋™ ์ œ์•ฝ์„ ๊ฐ•ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค**. -* ์˜์กด์„ฑ์—์„œ ์ •์˜๋œ _๊ฒฝ๋กœ ์ž‘๋™_ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•ด์„œ๋„ **์ž๋™ ๊ฒ€์ฆ**์ด ์ด๋ฃจ์–ด ์ง‘๋‹ˆ๋‹ค. -* ๋ณต์žกํ•œ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์‹œ์Šคํ…œ, **๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ**, ๋“ฑ๋“ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. -* ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํ”„๋ก ํŠธ์—”๋“œ ๋“ฑ๊ณผ ๊ด€๋ จ๋˜์–ด **ํƒ€ํ˜‘ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค**. ํ•˜์ง€๋งŒ ๊ทธ ๋ชจ๋“  ๊ฒƒ๊ณผ ์‰ฝ๊ฒŒ ํ†ตํ•ฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. +* ์˜์กด์„ฑ๋„ ์˜์กด์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์–ด, ์˜์กด์„ฑ์˜ ๊ณ„์ธต ๋˜๋Š” **์˜์กด์„ฑ์˜ "๊ทธ๋ž˜ํ”„"**๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +* ๋ชจ๋“  ๊ฒƒ์ด ํ”„๋ ˆ์ž„์›Œํฌ์— ์˜ํ•ด **์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค**. +* ๋ชจ๋“  ์˜์กด์„ฑ์€ ์š”์ฒญ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, **๊ฒฝ๋กœ ์ฒ˜๋ฆฌ** ์ œ์•ฝ๊ณผ ์ž๋™ ๋ฌธ์„œํ™”๋ฅผ ๊ฐ•ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ์˜์กด์„ฑ์— ์ •์˜๋œ *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ* ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•ด์„œ๋„ **์ž๋™ ๊ฒ€์ฆ**์„ ํ•ฉ๋‹ˆ๋‹ค. +* ๋ณต์žกํ•œ ์‚ฌ์šฉ์ž ์ธ์ฆ ์‹œ์Šคํ…œ, **๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ** ๋“ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +* ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํ”„๋ก ํŠธ์—”๋“œ ๋“ฑ๊ณผ **ํƒ€ํ˜‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**. ํ•˜์ง€๋งŒ ๋ชจ๋‘์™€ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### ์ œํ•œ ์—†๋Š” "ํ”Œ๋Ÿฌ๊ทธ์ธ" +### ์ œํ•œ ์—†๋Š” "ํ”Œ๋Ÿฌ๊ทธ์ธ" { #unlimited-plug-ins } -๋˜๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ, ๊ทธ๊ฒƒ๋“ค์„ ์‚ฌ์šฉํ•  ํ•„์š” ์—†์ด ํ•„์š”ํ•œ ์ฝ”๋“œ๋งŒ ์ž„ํฌํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ๋Š”, ๊ทธ๊ฒƒ๋“ค์ด ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์ž„ํฌํŠธํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. -์–ด๋А ํ†ตํ•ฉ๋„ (์˜์กด์„ฑ๊ณผ ํ•จ๊ป˜) ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฝ๊ฒŒ ์„ค๊ณ„๋˜์–ด ์žˆ์–ด, *๊ฒฝ๋กœ ์ž‘๋™*์— ์‚ฌ์šฉ๋œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ๊ตฌ์กฐ์™€ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ 2์ค„์˜ ์ฝ”๋“œ๋กœ ์—ฌ๋Ÿฌ๋ถ„์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์‚ฌ์šฉํ•  "ํ”Œ๋Ÿฌ๊ทธ์ธ"์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์–ด๋–ค ํ†ตํ•ฉ์ด๋“ (์˜์กด์„ฑ๊ณผ ํ•จ๊ป˜) ์‚ฌ์šฉํ•˜๊ธฐ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์–ด, *๊ฒฝ๋กœ ์ฒ˜๋ฆฌ*์— ์‚ฌ์šฉ๋œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ๊ตฌ์กฐ์™€ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•ด 2์ค„์˜ ์ฝ”๋“œ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์šฉ "ํ”Œ๋Ÿฌ๊ทธ์ธ"์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ +### ํ…Œ์ŠคํŠธ๋จ { #tested } -* 100% <abbr title="์ž๋™์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋œ ์ฝ”๋“œ์˜ ์–‘">ํ…Œ์ŠคํŠธ ๋ฒ”์œ„</abbr>. -* 100% <abbr title="ํŒŒ์ด์ฌ์˜ ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜, ์ด๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ๋ถ„์˜ ํŽธ์ง‘๊ธฐ์™€ ์™ธ๋ถ€ ๋„๊ตฌ๋Š” ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ๋” ๋‚˜์€ ์ง€์›์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค">ํƒ€์ž…์ด ๋ช…์‹œ๋œ</abbr> ์ฝ”๋“œ ๋ฒ ์ด์Šค. -* ์ƒ์šฉ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ์˜ ์‚ฌ์šฉ. +* 100% <abbr title="์ž๋™์œผ๋กœ ํ…Œ์ŠคํŠธ๋˜๋Š” ์ฝ”๋“œ์˜ ์–‘">test coverage</abbr>. +* 100% <abbr title="Python ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ, ์ด๋ฅผ ํ†ตํ•ด ํŽธ์ง‘๊ธฐ์™€ ์™ธ๋ถ€ ๋„๊ตฌ๊ฐ€ ๋” ๋‚˜์€ ์ง€์›์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค">type annotated</abbr> ์ฝ”๋“œ ๋ฒ ์ด์Šค. +* ํ”„๋กœ๋•์…˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -## Starlette ๊ธฐ๋Šฅ +## Starlette ๊ธฐ๋Šฅ { #starlette-features } -**FastAPI**๋Š” <a href="https://www.starlette.dev/" class="external-link" target="_blank"><strong>Starlette</strong></a>๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์ถ•๋˜์—ˆ์œผ๋ฉฐ, ์ด์™€ ์™„์ „ํžˆ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์—ฌ๋Ÿฌ๋ถ„์ด ๋ณด์œ ํ•˜๊ณ  ์žˆ๋Š” ์–ด๋–ค ์ถ”๊ฐ€์ ์ธ Starlette ์ฝ”๋“œ๋„ ์ž‘๋™ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**FastAPI**๋Š” <a href="https://www.starlette.dev/" class="external-link" target="_blank"><strong>Starlette</strong></a>์™€ ์™„์ „ํžˆ ํ˜ธํ™˜๋˜๋ฉฐ(๋˜ํ•œ ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค). ๋”ฐ๋ผ์„œ ์ถ”๊ฐ€๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” Starlette ์ฝ”๋“œ๋„ ๋ชจ๋‘ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. -`FastAPI`๋Š” ์‹ค์ œ๋กœ `Starlette`์˜ ํ•˜์œ„ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ, ์—ฌ๋Ÿฌ๋ถ„์ด ์ด๋ฏธ Starlette์„ ์•Œ๊ณ  ์žˆ๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉด, ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ์ด ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +`FastAPI`๋Š” ์‹ค์ œ๋กœ `Starlette`์˜ ํ•˜์œ„ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ Starlette์„ ์ด๋ฏธ ์•Œ๊ณ  ์žˆ๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ์ด ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -**FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ๋ถ„์€ **Starlette**์˜ ๊ธฐ๋Šฅ ๋Œ€๋ถ€๋ถ„์„ ์–ป๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค(FastAPI๊ฐ€ ๋‹จ์ˆœํžˆ Starlette๋ฅผ ๊ฐ•ํ™”ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค): +**FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด **Starlette**์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค(FastAPI๋Š” Starlette์— ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ๋”ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค): -* ์•„์ฃผ ์ธ์ƒ์ ์ธ ์„ฑ๋Šฅ. ์ด๋Š” <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">**NodeJS**์™€ **Go**์™€ ๋™๋“ฑํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ€์žฅ ๋น ๋ฅธ ํŒŒ์ด์ฌ ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค</a>. +* ์ •๋ง ์ธ์ƒ์ ์ธ ์„ฑ๋Šฅ. <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">**NodeJS**์™€ **Go**์— ๋ฒ„๊ธˆ๊ฐ€๋Š”, ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ€์žฅ ๋น ๋ฅธ Python ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค</a>. * **WebSocket** ์ง€์›. -* ํ”„๋กœ์„ธ์Šค ๋‚ด์˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…. -* ์‹œ์ž‘๊ณผ ์ข…๋ฃŒ ์ด๋ฒคํŠธ. +* ํ”„๋กœ์„ธ์Šค ๋‚ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…. +* ์‹œ์ž‘ ๋ฐ ์ข…๋ฃŒ ์ด๋ฒคํŠธ. * HTTPX ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ ํด๋ผ์ด์–ธํŠธ. * **CORS**, GZip, ์ •์  ํŒŒ์ผ, ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต. * **์„ธ์…˜๊ณผ ์ฟ ํ‚ค** ์ง€์›. -* 100% ํ…Œ์ŠคํŠธ ๋ฒ”์œ„. -* 100% ํƒ€์ž…์ด ๋ช…์‹œ๋œ ์ฝ”๋“œ ๋ฒ ์ด์Šค. +* 100% test coverage. +* 100% type annotated codebase. -## Pydantic ๊ธฐ๋Šฅ +## Pydantic ๊ธฐ๋Šฅ { #pydantic-features } -**FastAPI**๋Š” <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a>์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ Pydantic๊ณผ ์™„๋ฒฝํ•˜๊ฒŒ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์–ด๋А ์ถ”๊ฐ€์ ์ธ Pydantic ์ฝ”๋“œ๋ฅผ ์—ฌ๋Ÿฌ๋ถ„์ด ๊ฐ€์ง€๊ณ  ์žˆ๋“  ์ž‘๋™ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +**FastAPI**๋Š” <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a>๊ณผ ์™„๋ฒฝํ•˜๊ฒŒ ํ˜ธํ™˜๋˜๋ฉฐ(๋˜ํ•œ ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค). ๋”ฐ๋ผ์„œ ์ถ”๊ฐ€๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” Pydantic ์ฝ”๋“œ๋„ ๋ชจ๋‘ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. -Pydantic์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š”, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์œ„ํ•œ <abbr title="Object-Relational Mapper">ORM</abbr>, <abbr title="Object-Document Mapper">ODM</abbr>์„ ํฌํ•จํ•œ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. +๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์œ„ํ•œ <abbr title="Object-Relational Mapper - ๊ฐ์ฒด-๊ด€๊ณ„ ๋งคํผ">ORM</abbr>, <abbr title="Object-Document Mapper - ๊ฐ์ฒด-๋ฌธ์„œ ๋งคํผ">ODM</abbr>๊ณผ ๊ฐ™์€, Pydantic์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. -์ด๋Š” ๋ชจ๋“  ๊ฒƒ์ด ์ž๋™์œผ๋กœ ๊ฒ€์ฆ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋งŽ์€ ๊ฒฝ์šฐ์—์„œ ์š”์ฒญ์„ ํ†ตํ•ด ์–ป์€ ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ, **์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ** ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋Š” ๋ชจ๋“  ๊ฒƒ์ด ์ž๋™์œผ๋กœ ๊ฒ€์ฆ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋งŽ์€ ๊ฒฝ์šฐ ์š”์ฒญ์—์„œ ์–ป์€ ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ **์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ** ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. -๋ฐ˜๋Œ€๋กœ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ด๋ฉฐ, ๋งŽ์€ ๊ฒฝ์šฐ์—์„œ ์—ฌ๋Ÿฌ๋ถ„์€ **์ง์ ‘ ํด๋ผ์ด์–ธํŠธ๋กœ** ๊ทธ์ € ๊ฐ์ฒด๋ฅผ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋ฐ˜๋Œ€๋กœ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ด๋ฉฐ, ๋งŽ์€ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์–ป์€ ๊ฐ์ฒด๋ฅผ **์ง์ ‘ ํด๋ผ์ด์–ธํŠธ๋กœ** ๊ทธ๋Œ€๋กœ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด (๋ชจ๋“  ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด FastAPI๊ฐ€ Pydantic์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ธฐ ์žˆ๊ธฐ์—) **Pydantic**์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: +**FastAPI**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด(๋ชจ๋“  ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด FastAPI๊ฐ€ Pydantic์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ธฐ์—) **Pydantic**์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค: -* **์–ด๋ ต์ง€ ์•Š์€ ์–ธ์–ด**: - * ์ƒˆ๋กœ์šด ์Šคํ‚ค๋งˆ ์ •์˜ ๋งˆ์ดํฌ๋กœ ์–ธ์–ด๋ฅผ ๋ฐฐ์šฐ์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. - * ์—ฌ๋Ÿฌ๋ถ„์ด ํŒŒ์ด์ฌ ํƒ€์ž…์„ ์•ˆ๋‹ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์€ Pydantic์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์•„๋Š” ๊ฒ๋‹ˆ๋‹ค. -* ์—ฌ๋Ÿฌ๋ถ„์˜ **<abbr title="ํ†ตํ•ฉ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ, ์ฝ”๋“œ ํŽธ์ง‘๊ธฐ์™€ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค">IDE</abbr>/<abbr title="์ฝ”๋“œ ์—๋Ÿฌ๋ฅผ ํ™•์ธํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ">๋ฆฐํ„ฐ</abbr>/๋‡Œ**์™€ ์ž˜ ์–ด์šธ๋ฆฝ๋‹ˆ๋‹ค: - * Pydantic ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋Š” ๋‹จ์ˆœ ์—ฌ๋Ÿฌ๋ถ„์ด ์ •์˜ํ•œ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ž๋™ ์™„์„ฑ, ๋ฆฐํŒ…, mypy ๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ๋ถ„์˜ ์ง๊ด€๊นŒ์ง€ ์—ฌ๋Ÿฌ๋ถ„์˜ ๊ฒ€์ฆ๋œ ๋ฐ์ดํ„ฐ์™€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. +* **No brainfuck**: + * ์ƒˆ๋กœ์šด ์Šคํ‚ค๋งˆ ์ •์˜ ๋งˆ์ดํฌ๋กœ ์–ธ์–ด๋ฅผ ๋ฐฐ์šธ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. + * Python ํƒ€์ž…์„ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด Pydantic ์‚ฌ์šฉ๋ฒ•๋„ ์•Œ๊ณ  ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. +* ์—ฌ๋Ÿฌ๋ถ„์˜ **<abbr title="Integrated Development Environment - ํ†ตํ•ฉ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ: ์ฝ”๋“œ ํŽธ์ง‘๊ธฐ์™€ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค">IDE</abbr>/<abbr title="์ฝ”๋“œ ์˜ค๋ฅ˜๋ฅผ ํ™•์ธํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ">linter</abbr>/๋‡Œ**์™€ ์ž˜ ์–ด์šธ๋ฆฝ๋‹ˆ๋‹ค: + * pydantic ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ์ •์˜ํ•œ ํด๋ž˜์Šค ์ธ์Šคํ„ด์Šค์ผ ๋ฟ์ด๋ฏ€๋กœ, ์ž๋™ ์™„์„ฑ, ๋ฆฐํŒ…, mypy, ๊ทธ๋ฆฌ๊ณ  ์ง๊ด€๊นŒ์ง€๋„ ๊ฒ€์ฆ๋œ ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. * **๋ณต์žกํ•œ ๊ตฌ์กฐ**๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค: - * ๊ณ„์ธต์ ์ธ Pydantic ๋ชจ๋ธ, ํŒŒ์ด์ฌ `typing`์˜ `List`์™€ `Dict`, ๊ทธ ์™ธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. - * ๊ทธ๋ฆฌ๊ณ  ๊ฒ€์ฆ์ž๋Š” ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ๋ช…ํ™•ํ•˜๊ณ  ์‰ฝ๊ฒŒ ์ •์˜ ๋ฐ ํ™•์ธํ•˜๋ฉฐ JSON ์Šคํ‚ค๋งˆ๋กœ ๋ฌธ์„œํ™”ํ•ฉ๋‹ˆ๋‹ค. - * ์—ฌ๋Ÿฌ๋ถ„์€ ๊นŠ๊ฒŒ **์ค‘์ฒฉ๋œ JSON** ๊ฐ์ฒด๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ๊ฐ์ฒด ๋ชจ๋‘ ๊ฒ€์ฆํ•˜๊ณ  ์„ค๋ช…์„ ๋ถ™์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* **ํ™•์žฅ ๊ฐ€๋Šฅ์„ฑ**: - * Pydantic์€ ์‚ฌ์šฉ์ž ์ •์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ฑฐ๋‚˜, ๊ฒ€์ฆ์ž ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ๋ถ™์€ ๋ชจ๋ธ์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ฆ์„ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* 100% ํ…Œ์ŠคํŠธ ๋ฒ”์œ„. + * ๊ณ„์ธต์ ์ธ Pydantic ๋ชจ๋ธ, Python `typing`์˜ `List`์™€ `Dict` ๋“ฑ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * ๊ทธ๋ฆฌ๊ณ  validator๋Š” ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ๋ช…ํ™•ํ•˜๊ณ  ์‰ฝ๊ฒŒ ์ •์˜ํ•˜๊ณ , ๊ฒ€์‚ฌํ•˜๊ณ , JSON Schema๋กœ ๋ฌธ์„œํ™”ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. + * ๊นŠ๊ฒŒ **์ค‘์ฒฉ๋œ JSON** ๊ฐ์ฒด๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ๋ชจ๋‘ ๊ฒ€์ฆํ•˜๊ณ  ์ฃผ์„์„ ๋‹ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* **ํ™•์žฅ ๊ฐ€๋Šฅ**: + * Pydantic์€ ์‚ฌ์šฉ์ž ์ •์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ฑฐ๋‚˜, validator decorator๊ฐ€ ๋ถ™์€ ๋ชจ๋ธ ๋ฉ”์„œ๋“œ๋กœ ๊ฒ€์ฆ์„ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* 100% test coverage. diff --git a/docs/ko/docs/help-fastapi.md b/docs/ko/docs/help-fastapi.md index b65ef959cb..a4abbe7afa 100644 --- a/docs/ko/docs/help-fastapi.md +++ b/docs/ko/docs/help-fastapi.md @@ -1,4 +1,4 @@ -# FastAPI ์ง€์› - ๋„์›€ ๋ฐ›๊ธฐ +# FastAPI ์ง€์› - ๋„์›€ ๋ฐ›๊ธฐ { #help-fastapi-get-help } **FastAPI** ๊ฐ€ ๋งˆ์Œ์— ๋“œ์‹œ๋‚˜์š”? @@ -10,9 +10,9 @@ FastAPI, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž, ๊ฐœ๋ฐœ์ž๋ฅผ ์‘์›ํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? ๋˜ํ•œ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋„ ๋ช‡ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค. -## ๋‰ด์Šค๋ ˆํ„ฐ ๊ตฌ๋… +## ๋‰ด์Šค๋ ˆํ„ฐ ๊ตฌ๋… { #subscribe-to-the-newsletter } -[**FastAPI and friends** ๋‰ด์Šค๋ ˆํ„ฐ](newsletter.md){.internal-link target=\_blank}๋ฅผ ๊ตฌ๋…ํ•˜์—ฌ ์ตœ์‹  ์ •๋ณด๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +(์ž์ฃผ ๋ฐœ์†ก๋˜์ง€ ์•Š๋Š”) [**FastAPI and friends** ๋‰ด์Šค๋ ˆํ„ฐ](newsletter.md){.internal-link target=_blank}๋ฅผ ๊ตฌ๋…ํ•˜์—ฌ ์ตœ์‹  ์ •๋ณด๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: * FastAPI and friends์— ๋Œ€ํ•œ ๋‰ด์Šค ๐Ÿš€ * ๊ฐ€์ด๋“œ ๐Ÿ“ @@ -20,65 +20,65 @@ FastAPI, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž, ๊ฐœ๋ฐœ์ž๋ฅผ ์‘์›ํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? * ํš๊ธฐ์ ์ธ ๋ณ€ํ™” ๐Ÿšจ * ํŒ๊ณผ ์š”๋ น โœ… -## ํŠธ์œ„ํ„ฐ์—์„œ FastAPI ํŒ”๋กœ์šฐํ•˜๊ธฐ +## X(Twitter)์—์„œ FastAPI ํŒ”๋กœ์šฐํ•˜๊ธฐ { #follow-fastapi-on-x-twitter } <a href="https://x.com/fastapi" class="external-link" target="_blank">**X (Twitter)**์˜ @fastapi๋ฅผ ํŒ”๋กœ์šฐ</a>ํ•˜์—ฌ **FastAPI** ์— ๋Œ€ํ•œ ์ตœ์‹  ๋‰ด์Šค๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿฆ -## Star **FastAPI** in GitHub +## GitHub์—์„œ **FastAPI**์— Star ์ฃผ๊ธฐ { #star-fastapi-in-github } GitHub์—์„œ FastAPI์— "star"๋ฅผ ๋ถ™์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ์˜ star ๋ฒ„ํŠผ์„ ํด๋ฆญ): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. โญ๏ธ ์Šคํƒ€๋ฅผ ๋Š˜๋ฆผ์œผ๋กœ์จ, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋“ค์ด ์ข€ ๋” ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ๊ณ , ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์œ ์šฉํ•œ ๊ฒƒ์ž„์„ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## GitHub ์ €์žฅ์†Œ์—์„œ ๋ฆด๋ฆฌ์ฆˆ ํ™•์ธ +## ๋ฆด๋ฆฌ์ฆˆ ํ™•์ธ์„ ์œ„ํ•ด GitHub ์ €์žฅ์†Œ ๋ณด๊ธฐ { #watch-the-github-repository-for-releases } -GitHub์—์„œ FastAPI๋ฅผ "watch"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ watch ๋ฒ„ํŠผ์„ ํด๋ฆญ): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. ๐Ÿ‘€ +GitHub์—์„œ FastAPI๋ฅผ "watch"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ "watch" ๋ฒ„ํŠผ์„ ํด๋ฆญ): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. ๐Ÿ‘€ -์—ฌ๊ธฐ์„œ "Releases only"์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์„œ "Releases only"๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๋ ‡๊ฒŒํ•˜๋ฉด, **FastAPI** ์˜ ๋ฒ„๊ทธ ์ˆ˜์ • ๋ฐ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„ ๋“ฑ์˜ ์ƒˆ๋กœ์šด ์ž๋ฃŒ (์ตœ์‹  ๋ฒ„์ „)์ด ์žˆ์„ ๋•Œ๋งˆ๋‹ค (์ด๋ฉ”์ผ) ํ†ต์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒํ•˜๋ฉด, **FastAPI** ์˜ ๋ฒ„๊ทธ ์ˆ˜์ • ๋ฐ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„ ๋“ฑ์˜ ์ƒˆ๋กœ์šด ๋ฆด๋ฆฌ์ฆˆ(์ƒˆ ๋ฒ„์ „)๊ฐ€ ์žˆ์„ ๋•Œ๋งˆ๋‹ค (์ด๋ฉ”์ผ) ํ†ต์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ๊ฐœ๋ฐœ์ž์™€์˜ ์—ฐ๊ฒฐ +## ๊ฐœ๋ฐœ์ž์™€์˜ ์—ฐ๊ฒฐ { #connect-with-the-author } -<a href="https://tiangolo.com" class="external-link" target="_blank">๊ฐœ๋ฐœ์ž(Sebastiรกn Ramรญrez / `tiangolo`)</a>์™€ ์—ฐ๋ฝ์„ ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ฐœ๋ฐœ์ž(์ž‘์„ฑ์ž)์ธ <a href="https://tiangolo.com" class="external-link" target="_blank">์ €(Sebastiรกn Ramรญrez / `tiangolo`)</a>์™€ ์—ฐ๋ฝ์„ ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์€ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -* <a href="https://github.com/tiangolo" class="external-link" target="_blank">**GitHub**์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ.</a>. - * ๋‹น์‹ ์—๊ฒŒ ๋„์›€์ด ๋  ์ €์˜ ๋‹ค๋ฅธ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. +* <a href="https://github.com/tiangolo" class="external-link" target="_blank">**GitHub**์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ</a>. + * ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ๋„์›€์ด ๋  ์ €์˜ ๋‹ค๋ฅธ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. * ์ƒˆ๋กœ์šด ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ์„ ๋•Œ ํ™•์ธํ•˜๋ ค๋ฉด ํŒ”๋กœ์šฐ ํ•˜์‹ญ์‹œ์˜ค. * <a href="https://x.com/tiangolo" class="external-link" target="_blank">**X (Twitter)**</a> ๋˜๋Š” <a href="https://fosstodon.org/@tiangolo" class="external-link" target="_blank">Mastodon</a>์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ. * FastAPI์˜ ์‚ฌ์šฉ ์šฉ๋„๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š” (๊ทธ๊ฒƒ์„ ๋“ฃ๋Š” ๊ฒƒ์„ ์ข‹์•„ํ•ฉ๋‹ˆ๋‹ค). * ๋ฐœํ‘œ๋‚˜ ์ƒˆ๋กœ์šด ํˆด ์ถœ์‹œ ์†Œ์‹์„ ๋ฐ›์•„๋ณด์‹ญ์‹œ์˜ค. - * <a href="https://x.com/fastapi" class="external-link" target="_blank">**X (Twitter)**์˜ @fastapi๋ฅผ ํŒ”๋กœ์šฐ</a> (๋ณ„๋„ ๊ณ„์ •์—์„œ) ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">**LinkedIn**์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ.</a>. - * ์ƒˆ๋กœ์šด ํˆด์˜ ๋ฐœํ‘œ๋‚˜ ์ถœ์‹œ ์†Œ์‹์„ ๋ฐ›์•„๋ณด์‹ญ์‹œ์˜ค. (๋‹จ, X (Twitter)๋ฅผ ๋” ์ž์ฃผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค ๐Ÿคทโ€โ™‚). + * <a href="https://x.com/fastapi" class="external-link" target="_blank">X(Twitter)์—์„œ @fastapi๋ฅผ ํŒ”๋กœ์šฐ</a> (๋ณ„๋„ ๊ณ„์ •์—์„œ) ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">**LinkedIn**์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ</a>. + * ์ƒˆ๋กœ์šด ํˆด์˜ ๋ฐœํ‘œ๋‚˜ ์ถœ์‹œ ์†Œ์‹์„ ๋ฐ›์•„๋ณด์‹ญ์‹œ์˜ค (๋‹จ, X (Twitter)๋ฅผ ๋” ์ž์ฃผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค ๐Ÿคทโ€โ™‚). * <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> ๋˜๋Š” <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a>์—์„œ ์ œ๊ฐ€ ์ž‘์„ฑํ•œ ๋‚ด์šฉ์„ ์ฝ์–ด ๋ณด์‹ญ์‹œ์˜ค (๋˜๋Š” ํŒ”๋กœ์šฐ). - * ๋‹ค๋ฅธ ๊ธฐ์‚ฌ๋‚˜ ์•„์ด๋””์–ด๋“ค์„ ์ฝ๊ณ , ์ œ๊ฐ€ ๋งŒ๋“ค์–ด์™”๋˜ ํˆด์— ๋Œ€ํ•ด์„œ๋„ ์ฝ์œผ์‹ญ์‹œ์˜ค. - * ์ƒˆ๋กœ์šด ๊ธฐ์‚ฌ๋ฅผ ์ฝ๊ธฐ ์œ„ํ•ด ํŒ”๋กœ์šฐ ํ•˜์‹ญ์‹œ์˜ค. + * ๋‹ค๋ฅธ ์•„์ด๋””์–ด์™€ ๊ธฐ์‚ฌ๋“ค์„ ์ฝ๊ณ , ์ œ๊ฐ€ ๋งŒ๋“ค์–ด์™”๋˜ ํˆด์— ๋Œ€ํ•ด์„œ๋„ ์ฝ์œผ์‹ญ์‹œ์˜ค. + * ์ƒˆ๋กœ์šด ๋‚ด์šฉ์„ ๊ฒŒ์‹œํ•  ๋•Œ ์ฝ๊ธฐ ์œ„ํ•ด ํŒ”๋กœ์šฐ ํ•˜์‹ญ์‹œ์˜ค. -## **FastAPI**์— ๋Œ€ํ•œ ํŠธ์œ— +## **FastAPI**์— ๋Œ€ํ•ด ํŠธ์œ—ํ•˜๊ธฐ { #tweet-about-fastapi } -<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">**FastAPI**์— ๋Œ€ํ•ด ํŠธ์œ—</a> ํ•˜๊ณ  FastAPI๊ฐ€ ๋งˆ์Œ์— ๋“œ๋Š” ์ด์œ ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”. ๐ŸŽ‰ +<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">**FastAPI**์— ๋Œ€ํ•ด ํŠธ์œ—</a> ํ•˜๊ณ  ์ €์™€ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ FastAPI๊ฐ€ ๋งˆ์Œ์— ๋“œ๋Š” ์ด์œ ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”. ๐ŸŽ‰ **FastAPI**๊ฐ€ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š”์ง€, ์–ด๋–ค ์ ์ด ๋งˆ์Œ์— ๋“ค์—ˆ๋Š”์ง€, ์–ด๋–ค ํ”„๋กœ์ ํŠธ/ํšŒ์‚ฌ์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ๋“ฑ์— ๋Œ€ํ•ด ๋“ฃ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. -## FastAPI์— ํˆฌํ‘œํ•˜๊ธฐ +## FastAPI์— ํˆฌํ‘œํ•˜๊ธฐ { #vote-for-fastapi } * <a href="https://www.slant.co/options/34241/~fastapi-review" class="external-link" target="_blank">Slant์—์„œ **FastAPI** ์— ๋Œ€ํ•ด ํˆฌํ‘œํ•˜์‹ญ์‹œ์˜ค</a>. * <a href="https://alternativeto.net/software/fastapi/about/" class="external-link" target="_blank">AlternativeTo์—์„œ **FastAPI** ์— ๋Œ€ํ•ด ํˆฌํ‘œํ•˜์‹ญ์‹œ์˜ค</a>. -* <a href="https://stackshare.io/pypi-fastapi" class="external-link" target="_blank">StackShare์—์„œ **FastAPI** ์— ๋Œ€ํ•ด ํˆฌํ‘œํ•˜์‹ญ์‹œ์˜ค</a>. +* <a href="https://stackshare.io/pypi-fastapi" class="external-link" target="_blank">StackShare์—์„œ **FastAPI**๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ‘œ์‹œํ•˜์„ธ์š”</a>. -## GitHub์˜ ์ด์Šˆ๋กœ ๋‹ค๋ฅธ์‚ฌ๋žŒ ๋•๊ธฐ +## GitHub์—์„œ ์งˆ๋ฌธ์œผ๋กœ ๋‹ค๋ฅธ ์‚ฌ๋žŒ ๋•๊ธฐ { #help-others-with-questions-in-github } ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์งˆ๋ฌธ์— ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -* <a href="https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub ๋””์Šค์ปค์…˜</a> -* <a href="https://github.com/fastapi/fastapi/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub ์ด์Šˆ</a> +* <a href="https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a> +* <a href="https://github.com/fastapi/fastapi/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a> ๋งŽ์€ ๊ฒฝ์šฐ, ์—ฌ๋Ÿฌ๋ถ„์€ ์ด๋ฏธ ๊ทธ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต์„ ์•Œ๊ณ  ์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ -๋งŒ์•ฝ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์˜ ๋ฌธ์ œ๋ฅผ ๋„์™€์ค€๋‹ค๋ฉด, ๊ณต์‹์ ์ธ [FastAPI ์ „๋ฌธ๊ฐ€](fastapi-people.md#fastapi-experts){.internal-link target=\_blank} ๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐ŸŽ‰ +๋งŒ์•ฝ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์˜ ์งˆ๋ฌธ์„ ๋„์™€์ค€๋‹ค๋ฉด, ๊ณต์‹์ ์ธ [FastAPI ์ „๋ฌธ๊ฐ€](fastapi-people.md#fastapi-experts){.internal-link target=_blank}๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐ŸŽ‰ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ ์€: ์นœ์ ˆํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‚ฌ๋žŒ๋“ค์€ ์ขŒ์ ˆ๊ฐ์„ ์•ˆ๊ณ  ์˜ค๋ฉฐ, ๋งŽ์€ ๊ฒฝ์šฐ ์ตœ์„ ์˜ ๋ฐฉ์‹์œผ๋กœ ์งˆ๋ฌธํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ตœ๋Œ€ํ•œ ์นœ์ ˆํ•˜๊ฒŒ ๋Œ€ํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•˜์„ธ์š”. ๐Ÿค— @@ -86,183 +86,170 @@ GitHub์—์„œ FastAPI๋ฅผ "watch"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ watch ๋ฒ„ --- -๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์งˆ๋ฌธ (๋””์Šค์ปค์…˜ ๋˜๋Š” ์ด์Šˆ์—์„œ) ํ•ด๊ฒฐ์„ ๋„์šธ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. +๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์งˆ๋ฌธ(๋””์Šค์ปค์…˜ ๋˜๋Š” ์ด์Šˆ์—์„œ) ํ•ด๊ฒฐ์„ ๋„์šธ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. -### ์งˆ๋ฌธ ์ดํ•ดํ•˜๊ธฐ +### ์งˆ๋ฌธ ์ดํ•ดํ•˜๊ธฐ { #understand-the-question } * ์งˆ๋ฌธํ•˜๋Š” ์‚ฌ๋žŒ์ด ๊ฐ€์ง„ **๋ชฉ์ **๊ณผ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. -* ์งˆ๋ฌธ (๋Œ€๋ถ€๋ถ„์€ ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค)์ด **๋ช…ํ™•**ํ•œ์ง€ ํ™•์ธํ•˜์„ธ์š”. +* ๊ทธ๋Ÿฐ ๋‹ค์Œ ์งˆ๋ฌธ(๋Œ€๋ถ€๋ถ„์€ ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค)์ด **๋ช…ํ™•**ํ•œ์ง€ ํ™•์ธํ•˜์„ธ์š”. -* ๋งŽ์€ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์ •ํ•œ ํ•ด๊ฒฐ์ฑ…์— ๋Œ€ํ•œ ์งˆ๋ฌธ์„ ํ•˜์ง€๋งŒ, ๋” **์ข‹์€** ํ•ด๊ฒฐ์ฑ…์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ์™€ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋” ์ž˜ ์ดํ•ดํ•˜๋ฉด ๋” ๋‚˜์€ **๋Œ€์•ˆ์ ์ธ ํ•ด๊ฒฐ์ฑ…**์„ ์ œ์•ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ๋งŽ์€ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์ƒํ•œ ํ•ด๊ฒฐ์ฑ…์— ๋Œ€ํ•œ ์งˆ๋ฌธ์„ ํ•˜์ง€๋งŒ, ๋” **์ข‹์€** ํ•ด๊ฒฐ์ฑ…์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ์™€ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋” ์ž˜ ์ดํ•ดํ•˜๋ฉด ๋” ๋‚˜์€ **๋Œ€์•ˆ์ ์ธ ํ•ด๊ฒฐ์ฑ…**์„ ์ œ์•ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ์งˆ๋ฌธ์„ ์ดํ•ดํ•  ์ˆ˜ ์—†๋‹ค๋ฉด, ๋” **์ž์„ธํ•œ ์ •๋ณด**๋ฅผ ์š”์ฒญํ•˜์„ธ์š”. -### ๋ฌธ์ œ ์žฌํ˜„ํ•˜๊ธฐ +### ๋ฌธ์ œ ์žฌํ˜„ํ•˜๊ธฐ { #reproduce-the-problem } -๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ, ์งˆ๋ฌธ์€ ์งˆ๋ฌธ์ž์˜ **์›๋ณธ ์ฝ”๋“œ**์™€ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. +๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๊ทธ๋ฆฌ๊ณ  ๋Œ€๋ถ€๋ถ„์˜ ์งˆ๋ฌธ์—์„œ๋Š” ์งˆ๋ฌธ์ž์˜ **์›๋ณธ ์ฝ”๋“œ**์™€ ๊ด€๋ จ๋œ ๋‚ด์šฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŽ์€ ๊ฒฝ์šฐ, ์ฝ”๋“œ์˜ ์ผ๋ถ€๋งŒ ๋ณต์‚ฌํ•ด์„œ ์˜ฌ๋ฆฌ์ง€๋งŒ, ๊ทธ๊ฒƒ๋งŒ์œผ๋กœ๋Š” **๋ฌธ์ œ๋ฅผ ์žฌํ˜„**ํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -* ์งˆ๋ฌธ์ž์—๊ฒŒ <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">์ตœ์†Œํ•œ์˜ ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ์˜ˆ์ œ</a>๋ฅผ ์ œ๊ณตํ•ด๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•˜์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ **๋ณต์‚ฌ-๋ถ™์—ฌ๋„ฃ๊ธฐ**ํ•˜์—ฌ ์ง์ ‘ ์‹คํ–‰ํ•˜๊ณ , ๋™์ผํ•œ ์˜ค๋ฅ˜๋‚˜ ๋™์ž‘์„ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋” ์ž˜ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ์งˆ๋ฌธ์ž์—๊ฒŒ <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">์ตœ์†Œํ•œ์˜ ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ์˜ˆ์ œ</a>๋ฅผ ์ œ๊ณตํ•ด๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ **๋ณต์‚ฌ-๋ถ™์—ฌ๋„ฃ๊ธฐ**ํ•˜์—ฌ ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•˜๊ณ , ์งˆ๋ฌธ์ž๊ฐ€ ๋ณด๊ณ  ์žˆ๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ์˜ค๋ฅ˜๋‚˜ ๋™์ž‘์„ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋” ์ž˜ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* ๋„ˆ๊ทธ๋Ÿฌ์šด ๋งˆ์Œ์ด ๋“ ๋‹ค๋ฉด, ๋ฌธ์ œ ์„ค๋ช…๋งŒ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ง์ ‘ **์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์–ด**๋ณผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ด๋Š” ์‹œ๊ฐ„์ด ๋งŽ์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋จผ์ € ์งˆ๋ฌธ์„ ๋ช…ํ™•ํžˆ ํ•ด๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. +* ๋„ˆ๊ทธ๋Ÿฌ์šด ๋งˆ์Œ์ด ๋“ ๋‹ค๋ฉด, ๋ฌธ์ œ ์„ค๋ช…๋งŒ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ง์ ‘ **์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์–ด**๋ณผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ด๋Š” ์‹œ๊ฐ„์ด ๋งŽ์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋จผ์ € ๋ฌธ์ œ๋ฅผ ๋ช…ํ™•ํžˆ ํ•ด๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ์ด ๋” ๋‚˜์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. -### ํ•ด๊ฒฐ์ฑ… ์ œ์•ˆํ•˜๊ธฐ +### ํ•ด๊ฒฐ์ฑ… ์ œ์•ˆํ•˜๊ธฐ { #suggest-solutions } * ์งˆ๋ฌธ์„ ์ถฉ๋ถ„ํžˆ ์ดํ•ดํ•œ ํ›„์—๋Š” ๊ฐ€๋Šฅํ•œ **๋‹ต๋ณ€**์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* ๋งŽ์€ ๊ฒฝ์šฐ, ์งˆ๋ฌธ์ž์˜ **๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๋‚˜ ์‚ฌ์šฉ ์‚ฌ๋ก€**๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋“ค์ด ์‹œ๋„ํ•˜๋Š” ๋ฐฉ๋ฒ•๋ณด๋‹ค ๋” ๋‚˜์€ ํ•ด๊ฒฐ์ฑ…์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +* ๋งŽ์€ ๊ฒฝ์šฐ, ์งˆ๋ฌธ์ž์˜ **๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๋‚˜ ์‚ฌ์šฉ ์‚ฌ๋ก€**๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค. ๊ทธ๋“ค์ด ์‹œ๋„ํ•˜๋Š” ๋ฐฉ๋ฒ•๋ณด๋‹ค ๋” ๋‚˜์€ ํ•ด๊ฒฐ์ฑ…์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. -### ํ•ด๊ฒฐ ์š”์ฒญํ•˜๊ธฐ +### ์ข…๋ฃŒ ์š”์ฒญํ•˜๊ธฐ { #ask-to-close } -์งˆ๋ฌธ์ž๊ฐ€ ๋‹ต๋ณ€์„ ํ™•์ธํ•˜๊ณ  ๋‚˜๋ฉด, ๋‹น์‹ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค, **๋‹น์‹ ์€ ์˜์›…์ž…๋‹ˆ๋‹ค**! ๐Ÿฆธ +์งˆ๋ฌธ์ž๊ฐ€ ๋‹ต๋ณ€์„ ํ•˜๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค, **์—ฌ๋Ÿฌ๋ถ„์€ ์˜์›…์ž…๋‹ˆ๋‹ค**! ๐Ÿฆธ * ์ด์ œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ๋‹ค๋ฉด, ์งˆ๋ฌธ์ž์—๊ฒŒ ๋‹ค์Œ์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - * GitHub ๋””์Šค์ปค์…˜์—์„œ: ๋Œ“๊ธ€์„ **๋‹ต๋ณ€**์œผ๋กœ ํ‘œ์‹œํ•˜๋„๋ก ์š”์ฒญํ•˜์„ธ์š”. - * GitHub ์ด์Šˆ์—์„œ: ์ด์Šˆ๋ฅผ **๋‹ซ์•„๋‹ฌ๋ผ๊ณ ** ์š”์ฒญํ•˜์„ธ์š”. + * GitHub Discussions์—์„œ: ๋Œ“๊ธ€์„ **๋‹ต๋ณ€**์œผ๋กœ ํ‘œ์‹œํ•˜๋„๋ก ์š”์ฒญํ•˜์„ธ์š”. + * GitHub Issues์—์„œ: ์ด์Šˆ๋ฅผ **๋‹ซ์•„๋‹ฌ๋ผ๊ณ ** ์š”์ฒญํ•˜์„ธ์š”. -## GitHub ์ €์žฅ์†Œ ๋ณด๊ธฐ +## GitHub ์ €์žฅ์†Œ ๋ณด๊ธฐ { #watch-the-github-repository } -GitHub์—์„œ FastAPI๋ฅผ "watch"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ watch ๋ฒ„ํŠผ์„ ํด๋ฆญ): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. ๐Ÿ‘€ +GitHub์—์„œ FastAPI๋ฅผ "watch"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ "watch" ๋ฒ„ํŠผ์„ ํด๋ฆญ): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. ๐Ÿ‘€ -"Releases only" ๋Œ€์‹  "Watching"์„ ์„ ํƒํ•˜๋ฉด, ์ƒˆ๋กœ์šด ์ด์Šˆ๋‚˜ ์งˆ๋ฌธ์ด ์ƒ์„ฑ๋  ๋•Œ ์•Œ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ํŠน์ •ํ•˜๊ฒŒ ์ƒˆ๋กœ์šด ์ด์Šˆ, ๋””์Šค์ปค์…˜, PR ๋“ฑ๋งŒ ์•Œ๋ฆผ ๋ฐ›๋„๋ก ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +"Releases only" ๋Œ€์‹  "Watching"์„ ์„ ํƒํ•˜๋ฉด ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์ƒˆ ์ด์Šˆ๋‚˜ ์งˆ๋ฌธ์„ ๋งŒ๋“ค ๋•Œ ์•Œ๋ฆผ์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ƒˆ ์ด์Šˆ, ๋””์Šค์ปค์…˜, PR ๋“ฑ๋งŒ ์•Œ๋ฆผ์„ ๋ฐ›๋„๋ก ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด๋Ÿฐ ์ด์Šˆ๋“ค์„ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด๋Ÿฐ ์งˆ๋ฌธ๋“ค์„ ํ•ด๊ฒฐํ•˜๋„๋ก ๋„์™€์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ์ด์Šˆ ์ƒ์„ฑํ•˜๊ธฐ +## ์งˆ๋ฌธํ•˜๊ธฐ { #ask-questions } -GitHub ์ €์žฅ์†Œ์— <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">์ƒˆ๋กœ์šด ์ด์Šˆ ์ƒ์„ฑ</a>์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค, ์˜ˆ๋ฅผ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: +GitHub ์ €์žฅ์†Œ์—์„œ <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">์ƒˆ ์งˆ๋ฌธ์„ ์ƒ์„ฑ</a>ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: * **์งˆ๋ฌธ**์„ ํ•˜๊ฑฐ๋‚˜ **๋ฌธ์ œ**์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•ฉ๋‹ˆ๋‹ค. * ์ƒˆ๋กœ์šด **๊ธฐ๋Šฅ**์„ ์ œ์•ˆ ํ•ฉ๋‹ˆ๋‹ค. -**์ฐธ๊ณ **: ๋งŒ์•ฝ ์ด์Šˆ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค๋ฉด, ์ €๋Š” ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์„ ๋„์™€๋‹ฌ๋ผ๊ณ  ๋ถ€ํƒํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜‰ +**์ฐธ๊ณ **: ๋งŒ์•ฝ ์ด๋ ‡๊ฒŒ ํ•œ๋‹ค๋ฉด, ์ €๋Š” ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๋„ ๋„์™€๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜‰ -## Pull Requests ๋ฆฌ๋ทฐํ•˜๊ธฐ +## Pull Request ๋ฆฌ๋ทฐํ•˜๊ธฐ { #review-pull-requests } -๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ pull request๋ฅผ ๋ฆฌ๋ทฐํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ๋งŒ๋“  pull request๋ฅผ ๋ฆฌ๋ทฐํ•˜๋Š” ๋ฐ ์ €๋ฅผ ๋„์™€์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ํ•œ๋ฒˆ ๋งํ•˜์ง€๋งŒ, ์ตœ๋Œ€ํ•œ ์นœ์ ˆํ•˜๊ฒŒ ๋ฆฌ๋ทฐํ•ด ์ฃผ์„ธ์š”. ๐Ÿค— --- -Pull Rrquest๋ฅผ ๋ฆฌ๋ทฐํ•  ๋•Œ ๊ณ ๋ คํ•ด์•ผ ํ•  ์‚ฌํ•ญ๊ณผ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: +Pull request๋ฅผ ๋ฆฌ๋ทฐํ•  ๋•Œ ๊ณ ๋ คํ•ด์•ผ ํ•  ์‚ฌํ•ญ๊ณผ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: -### ๋ฌธ์ œ ์ดํ•ดํ•˜๊ธฐ +### ๋ฌธ์ œ ์ดํ•ดํ•˜๊ธฐ { #understand-the-problem } -* ๋จผ์ €, ํ•ด๋‹น pull request๊ฐ€ ํ•ด๊ฒฐํ•˜๋ ค๋Š” **๋ฌธ์ œ๋ฅผ ์ดํ•ดํ•˜๋Š”์ง€** ํ™•์ธํ•˜์„ธ์š”. GitHub ๋””์Šค์ปค์…˜ ๋˜๋Š” ์ด์Šˆ์—์„œ ๋” ๊ธด ๋…ผ์˜๊ฐ€ ์žˆ์—ˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +* ๋จผ์ €, ํ•ด๋‹น pull request๊ฐ€ ํ•ด๊ฒฐํ•˜๋ ค๋Š” **๋ฌธ์ œ๋ฅผ ์ดํ•ดํ•˜๋Š”์ง€** ํ™•์ธํ•˜์„ธ์š”. GitHub Discussion ๋˜๋Š” ์ด์Šˆ์—์„œ ๋” ๊ธด ๋…ผ์˜๊ฐ€ ์žˆ์—ˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -* Pull request๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฐ€๋Šฅ์„ฑ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. **๋‹ค๋ฅธ ๋ฐฉ์‹**์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๊ทธ ๋ฐฉ๋ฒ•์„ ์ œ์•ˆํ•˜๊ฑฐ๋‚˜ ์งˆ๋ฌธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* Pull request๊ฐ€ ์‹ค์ œ๋กœ ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฐ€๋Šฅ์„ฑ๋„ ํฝ๋‹ˆ๋‹ค. ๋ฌธ์ œ๊ฐ€ **๋‹ค๋ฅธ ๋ฐฉ์‹**์œผ๋กœ ํ•ด๊ฒฐ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ ๊ทธ ๋ฐฉ๋ฒ•์„ ์ œ์•ˆํ•˜๊ฑฐ๋‚˜ ์งˆ๋ฌธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### ์Šคํƒ€์ผ์— ๋„ˆ๋ฌด ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š๊ธฐ +### ์Šคํƒ€์ผ์— ๋„ˆ๋ฌด ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š๊ธฐ { #dont-worry-about-style } -* ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์Šคํƒ€์ผ ๊ฐ™์€ ๊ฒƒ์— ๋„ˆ๋ฌด ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. ์ €๋Š” ์ง์ ‘ ์ปค๋ฐ‹์„ ์ˆ˜์ •ํ•˜์—ฌ squash and merge๋ฅผ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +* ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์Šคํƒ€์ผ ๊ฐ™์€ ๊ฒƒ์— ๋„ˆ๋ฌด ์‹ ๊ฒฝ ์“ฐ์ง€ ๋งˆ์„ธ์š”. ์ €๋Š” ์ปค๋ฐ‹์„ ์ˆ˜๋™์œผ๋กœ ์กฐ์ •ํ•ด์„œ squash and merge๋ฅผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. * ์ฝ”๋“œ ์Šคํƒ€์ผ ๊ทœ์น™๋„ ๊ฑฑ์ •ํ•  ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ ์ž๋™ํ™”๋œ ๋„๊ตฌ๋“ค์ด ์ด๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -์Šคํƒ€์ผ์ด๋‚˜ ์ผ๊ด€์„ฑ ๊ด€๋ จ ์š”์ฒญ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ์ œ๊ฐ€ ์ง์ ‘ ์š”์ฒญํ•˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ถ”๊ฐ€ ์ปค๋ฐ‹์œผ๋กœ ์ˆ˜์ •ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ์Šคํƒ€์ผ์ด๋‚˜ ์ผ๊ด€์„ฑ ๊ด€๋ จ ํ•„์š” ์‚ฌํ•ญ์ด ์žˆ๋‹ค๋ฉด, ์ œ๊ฐ€ ์ง์ ‘ ์š”์ฒญํ•˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์œ„์— ์ปค๋ฐ‹์œผ๋กœ ์ถ”๊ฐ€ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. -### ์ฝ”๋“œ ํ™•์ธํ•˜๊ธฐ +### ์ฝ”๋“œ ํ™•์ธํ•˜๊ธฐ { #check-the-code } -* ์ฝ”๋“œ๋ฅผ ์ฝ๊ณ , **๋…ผ๋ฆฌ์ ์œผ๋กœ ํƒ€๋‹น**ํ•œ์ง€ ํ™•์ธํ•œ ํ›„ ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•˜์—ฌ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. +* ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜๊ณ  ์ฝ์–ด์„œ ๋ง์ด ๋˜๋Š”์ง€ ๋ณด๊ณ , **๋กœ์ปฌ์—์„œ ์‹คํ–‰**ํ•ด ์‹ค์ œ๋กœ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. -* ๊ทธ๋Ÿฐ ๋‹ค์Œ, ํ™•์ธํ–ˆ๋‹ค๊ณ  **๋Œ“๊ธ€**์„ ๋‚จ๊ฒจ ์ฃผ์„ธ์š”. ๊ทธ๋ž˜์•ผ ์ œ๊ฐ€ ๊ฒ€ํ† ํ–ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ๊ทธ๋Ÿฐ ๋‹ค์Œ ๊ทธ๋ ‡๊ฒŒ ํ–ˆ๋‹ค๊ณ  **๋Œ“๊ธ€**๋กœ ๋‚จ๊ฒจ ์ฃผ์„ธ์š”. ๊ทธ๋ž˜์•ผ ์ œ๊ฐ€ ์ •๋ง๋กœ ํ™•์ธํ–ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -/// info +/// info | ์ •๋ณด ๋ถˆํ–‰ํžˆ๋„, ์ œ๊ฐ€ ๋‹จ์ˆœํžˆ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์Šน์ธ๋งŒ์œผ๋กœ PR์„ ์‹ ๋ขฐํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. -3๊ฐœ, 5๊ฐœ ์ด์ƒ์˜ ์Šน์ธ์ด ๋‹ฌ๋ฆฐ PR์ด ์‹ค์ œ๋กœ๋Š” ๊นจ์ ธ ์žˆ๊ฑฐ๋‚˜, ๋ฒ„๊ทธ๊ฐ€ ์žˆ๊ฑฐ๋‚˜, ์ฃผ์žฅํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜… +์—ฌ๋Ÿฌ ๋ฒˆ, ์„ค๋ช…์ด ๊ทธ๋Ÿด๋“ฏํ•ด์„œ์ธ์ง€ 3๊ฐœ, 5๊ฐœ ์ด์ƒ์˜ ์Šน์ธ์ด ๋‹ฌ๋ฆฐ PR์ด ์žˆ์—ˆ์ง€๋งŒ, ์ œ๊ฐ€ ํ™•์ธํ•ด๋ณด๋ฉด ์‹ค์ œ๋กœ๋Š” ๊นจ์ ธ ์žˆ๊ฑฐ๋‚˜, ๋ฒ„๊ทธ๊ฐ€ ์žˆ๊ฑฐ๋‚˜, ์ฃผ์žฅํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜… ๋”ฐ๋ผ์„œ, ์ •๋ง๋กœ ์ฝ”๋“œ๋ฅผ ์ฝ๊ณ  ์‹คํ–‰ํ•œ ๋’ค, ๋Œ“๊ธ€๋กœ ํ™•์ธ ๋‚ด์šฉ์„ ๋‚จ๊ฒจ ์ฃผ๋Š” ๊ฒƒ์ด ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿค“ /// -* PR์„ ๋” ๋‹จ์ˆœํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ทธ๋ ‡๊ฒŒ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋„ˆ๋ฌด ๊นŒ๋‹ค๋กœ์šธ ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ฃผ๊ด€์ ์ธ ๊ฒฌํ•ด๊ฐ€ ๋งŽ์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค (๊ทธ๋ฆฌ๊ณ  ์ €๋„ ์ œ ๊ฒฌํ•ด๊ฐ€ ์žˆ์„ ๊ฑฐ์˜ˆ์š” ๐Ÿ™ˆ). ๋”ฐ๋ผ์„œ ํ•ต์‹ฌ์ ์ธ ๋ถ€๋ถ„์— ์ง‘์ค‘ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. +* PR์„ ๋” ๋‹จ์ˆœํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ทธ๋ ‡๊ฒŒ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋„ˆ๋ฌด ๊นŒ๋‹ค๋กœ์šธ ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ฃผ๊ด€์ ์ธ ๊ฒฌํ•ด๊ฐ€ ๋งŽ์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค(๊ทธ๋ฆฌ๊ณ  ์ €๋„ ์ œ ๊ฒฌํ•ด๊ฐ€ ์žˆ์„ ๊ฑฐ์˜ˆ์š” ๐Ÿ™ˆ). ๋”ฐ๋ผ์„œ ํ•ต์‹ฌ์ ์ธ ๋ถ€๋ถ„์— ์ง‘์ค‘ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. -### ํ…Œ์ŠคํŠธ +### ํ…Œ์ŠคํŠธ { #tests } * PR์— **ํ…Œ์ŠคํŠธ**๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ฃผ์„ธ์š”. -* PR์„ ์ ์šฉํ•˜๊ธฐ ์ „์— ํ…Œ์ŠคํŠธ๊ฐ€ **์‹คํŒจ**ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. ๐Ÿšจ +* PR ์ „์—๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ **์‹คํŒจ**ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. ๐Ÿšจ -* PR์„ ์ ์šฉํ•œ ํ›„ ํ…Œ์ŠคํŠธ๊ฐ€ **ํ†ต๊ณผ**ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. โœ… +* ๊ทธ๋Ÿฐ ๋‹ค์Œ PR ํ›„์—๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ **ํ†ต๊ณผ**ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. โœ… -* ๋งŽ์€ PR์—๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋„๋ก **์ƒ๊ธฐ**์‹œ์ผœ์ค„ ์ˆ˜๋„ ์žˆ๊ณ , ์ง์ ‘ ํ…Œ์ŠคํŠธ๋ฅผ **์ œ์•ˆ**ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์‹œ๊ฐ„์ด ๋งŽ์ด ์†Œ์š”๋˜๋Š” ๋ถ€๋ถ„ ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, ๊ทธ ๋ถ€๋ถ„์„ ๋งŽ์ด ๋„์™€์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ๋งŽ์€ PR์—๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋„๋ก **์ƒ๊ธฐ**์‹œ์ผœ์ค„ ์ˆ˜๋„ ์žˆ๊ณ , ์ง์ ‘ ํ…Œ์ŠคํŠธ๋ฅผ **์ œ์•ˆ**ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์‹œ๊ฐ„์ด ๊ฐ€์žฅ ๋งŽ์ด ๋“œ๋Š” ๊ฒƒ๋“ค ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, ๊ทธ ๋ถ€๋ถ„์„ ๋งŽ์ด ๋„์™€์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ๊ทธ๋ฆฌ๊ณ  ์‹œ๋„ํ•œ ๋‚ด์šฉ์„ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์„ธ์š”. ๊ทธ๋Ÿฌ๋ฉด ์ œ๊ฐ€ ํ™•์ธํ–ˆ๋‹ค๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ -## Pull Request๋ฅผ ๋งŒ๋“œ์‹ญ์‹œ์˜ค +## Pull Request ๋งŒ๋“ค๊ธฐ { #create-a-pull-request } -Pull Requests๋ฅผ ์ด์šฉํ•˜์—ฌ ์†Œ์Šค์ฝ”๋“œ์— [์ปจํŠธ๋ฆฌ๋ทฐํŠธ](contributing.md){.internal-link target=\_blank} ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: +Pull Requests๋ฅผ ์ด์šฉํ•˜์—ฌ ์†Œ์Šค ์ฝ”๋“œ์— [๊ธฐ์—ฌ](contributing.md){.internal-link target=_blank}ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: * ๋ฌธ์„œ์—์„œ ๋ฐœ๊ฒฌํ•œ ์˜คํƒ€๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ. -* FastAPI ๊ด€๋ จ ๋ฌธ์„œ, ๋น„๋””์˜ค ๋˜๋Š” ํŒŸ์บ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ๊ฑฐ๋‚˜ ๋ฐœ๊ฒฌํ•˜์—ฌ <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">์ด ํŒŒ์ผ์„ ํŽธ์ง‘ํ•˜์—ฌ</a> ๊ณต์œ ํ•  ๋•Œ. +* FastAPI์— ๋Œ€ํ•œ ๊ธ€, ๋น„๋””์˜ค, ํŒŸ์บ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ๊ฑฐ๋‚˜ ๋ฐœ๊ฒฌํ–ˆ๋‹ค๋ฉด <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">์ด ํŒŒ์ผ์„ ํŽธ์ง‘</a>ํ•˜์—ฌ ๊ณต์œ ํ•  ๋•Œ. * ํ•ด๋‹น ์„น์…˜์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์— ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -* ๋‹น์‹ ์˜ ์–ธ์–ด๋กœ [๋ฌธ์„œ ๋ฒˆ์—ญํ•˜๋Š”๋ฐ](contributing.md#translations){.internal-link target=\_blank} ๊ธฐ์—ฌํ•  ๋•Œ. +* ์—ฌ๋Ÿฌ๋ถ„์˜ ์–ธ์–ด๋กœ [๋ฌธ์„œ ๋ฒˆ์—ญ์—](contributing.md#translations){.internal-link target=_blank} ๋„์›€์„ ์ค„ ๋•Œ. * ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์ž‘์„ฑํ•œ ๋ฒˆ์—ญ์„ ๊ฒ€ํ† ํ•˜๋Š” ๊ฒƒ๋„ ๋„์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -* ์ƒˆ๋กœ์šด ๋ฌธ์„œ์˜ ์„น์…˜์„ ์ œ์•ˆํ•  ๋•Œ. -* ๊ธฐ์กด ๋ฌธ์ œ/๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ. +* ์ƒˆ๋กœ์šด ๋ฌธ์„œ ์„น์…˜์„ ์ œ์•ˆํ•  ๋•Œ. +* ๊ธฐ์กด ์ด์Šˆ/๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ. * ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -* ์ƒˆ๋กœ์šด feature๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ. +* ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ๋•Œ. * ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. - * ๊ด€๋ จ ๋ฌธ์„œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + * ๊ด€๋ จ ๋ฌธ์„œ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -## FastAPI ์œ ์ง€ ๊ด€๋ฆฌ์— ๋„์›€ ์ฃผ๊ธฐ +## FastAPI ์œ ์ง€ ๊ด€๋ฆฌ ๋•๊ธฐ { #help-maintain-fastapi } -**FastAPI**์˜ ์œ ์ง€ ๊ด€๋ฆฌ๋ฅผ ๋„์™€์ฃผ์„ธ์š”! ๐Ÿค“ +**FastAPI** ์œ ์ง€๋ฅผ ๋„์™€์ฃผ์„ธ์š”! ๐Ÿค“ -ํ•  ์ผ์ด ๋งŽ๊ณ , ๊ทธ ์ค‘ ๋Œ€๋ถ€๋ถ„์€ **์—ฌ๋Ÿฌ๋ถ„**์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ•  ์ผ์ด ๋งŽ๊ณ , ๊ทธ์ค‘ ๋Œ€๋ถ€๋ถ„์€ **์—ฌ๋Ÿฌ๋ถ„**์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ ํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ์š” ์ž‘์—…์€: -* [GitHub์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์งˆ๋ฌธ์— ๋„์›€ ์ฃผ๊ธฐ](#github_1){.internal-link target=_blank} (์œ„์˜ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”). -* [Pull Request ๋ฆฌ๋ทฐํ•˜๊ธฐ](#pull-requests){.internal-link target=_blank} (์œ„์˜ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”). +* [GitHub์—์„œ ์งˆ๋ฌธ์œผ๋กœ ๋‹ค๋ฅธ ์‚ฌ๋žŒ ๋•๊ธฐ](#help-others-with-questions-in-github){.internal-link target=_blank} (์œ„์˜ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”). +* [Pull Request ๋ฆฌ๋ทฐํ•˜๊ธฐ](#review-pull-requests){.internal-link target=_blank} (์œ„์˜ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”). -์ด ๋‘ ์ž‘์—…์ด **๊ฐ€์žฅ ๋งŽ์€ ์‹œ๊ฐ„์„ ์†Œ๋ชจ**ํ•˜๋Š” ์ผ์ž…๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์ด FastAPI ์œ ์ง€ ๊ด€๋ฆฌ์˜ ์ฃผ์š” ์ž‘์—…์ž…๋‹ˆ๋‹ค. +์ด ๋‘ ์ž‘์—…์ด **๊ฐ€์žฅ ๋งŽ์€ ์‹œ๊ฐ„์„ ์†Œ๋ชจ**ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด FastAPI๋ฅผ ์œ ์ง€ ๊ด€๋ฆฌํ•˜๋Š” ์ฃผ์š” ์ž‘์—…์ž…๋‹ˆ๋‹ค. -์ด ์ž‘์—…์„ ๋„์™€์ฃผ์‹ ๋‹ค๋ฉด, **FastAPI ์œ ์ง€ ๊ด€๋ฆฌ์— ๋„์›€์„ ์ฃผ๋Š” ๊ฒƒ**์ด๋ฉฐ ๊ทธ๊ฒƒ์ด **๋” ๋น ๋ฅด๊ณ  ๋” ์ž˜ ๋ฐœ์ „ํ•˜๋Š” ๊ฒƒ**์„ ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿš€ +์ด ์ž‘์—…์„ ๋„์™€์ฃผ์‹ ๋‹ค๋ฉด, **FastAPI ์œ ์ง€๋ฅผ ๋•๋Š” ๊ฒƒ**์ด๋ฉฐ FastAPI๊ฐ€ **๋” ๋น ๋ฅด๊ณ  ๋” ์ž˜ ๋ฐœ์ „ํ•˜๋Š” ๊ฒƒ**์„ ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿš€ -## ์ฑ„ํŒ…์— ์ฐธ์—ฌํ•˜์‹ญ์‹œ์˜ค +## ์ฑ„ํŒ…์— ์ฐธ์—ฌํ•˜๊ธฐ { #join-the-chat } -๐Ÿ‘ฅ <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">๋””์Šค์ฝ”๋“œ ์ฑ„ํŒ… ์„œ๋ฒ„</a> ๐Ÿ‘ฅ ์— ๊ฐ€์ž…ํ•˜๊ณ  FastAPI ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ์–ด์šธ๋ฆฌ์„ธ์š”. +๐Ÿ‘ฅ <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">Discord ์ฑ„ํŒ… ์„œ๋ฒ„</a> ๐Ÿ‘ฅ ์— ์ฐธ์—ฌํ•ด์„œ FastAPI ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ์–ด์šธ๋ฆฌ์„ธ์š”. -/// tip +/// tip | ํŒ -์งˆ๋ฌธ์ด ์žˆ๋Š” ๊ฒฝ์šฐ, <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub ๋””์Šค์ปค์…˜</a> ์—์„œ ์งˆ๋ฌธํ•˜์‹ญ์‹œ์˜ค, [FastAPI Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank} ์˜ ๋„์›€์„ ๋ฐ›์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. +์งˆ๋ฌธ์€ <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>์—์„œ ํ•˜์„ธ์š”. [FastAPI Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank}๋กœ๋ถ€ํ„ฐ ๋„์›€์„ ๋ฐ›์„ ๊ฐ€๋Šฅ์„ฑ์ด ํ›จ์”ฌ ๋†’์Šต๋‹ˆ๋‹ค. -๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ ๋Œ€ํ™”์—์„œ๋งŒ ์ฑ„ํŒ…์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. +์ฑ„ํŒ…์€ ๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ ๋Œ€ํ™”๋ฅผ ์œ„ํ•ด์„œ๋งŒ ์‚ฌ์šฉํ•˜์„ธ์š”. /// -### ์งˆ๋ฌธ์„ ์œ„ํ•ด ์ฑ„ํŒ…์„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค +### ์งˆ๋ฌธ์„ ์œ„ํ•ด ์ฑ„ํŒ…์„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š” { #dont-use-the-chat-for-questions } -์ฑ„ํŒ…์€ ๋” ๋งŽ์€ "์ž์œ ๋กœ์šด ๋Œ€ํ™”"๋ฅผ ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋„ˆ๋ฌด ์ผ๋ฐ˜์ ์ธ ์งˆ๋ฌธ์ด๋‚˜ ๋Œ€๋‹ตํ•˜๊ธฐ ์–ด๋ ค์šด ์งˆ๋ฌธ์„ ์‰ฝ๊ฒŒ ์งˆ๋ฌธ์„ ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋‹ต๋ณ€์„ ๋ฐ›์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ฑ„ํŒ…์€ ๋” ๋งŽ์€ "์ž์œ ๋กœ์šด ๋Œ€ํ™”"๋ฅผ ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋„ˆ๋ฌด ์ผ๋ฐ˜์ ์ธ ์งˆ๋ฌธ์ด๋‚˜ ๋‹ตํ•˜๊ธฐ ์–ด๋ ค์šด ์งˆ๋ฌธ์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์–ด ๋‹ต๋ณ€์„ ๋ฐ›์ง€ ๋ชปํ•  ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. -GitHub ์ด์Šˆ์—์„œ์˜ ํ…œํ”Œ๋ฆฟ์€ ์˜ฌ๋ฐ”๋ฅธ ์งˆ๋ฌธ์„ ์ž‘์„ฑํ•˜๋„๋ก ์•ˆ๋‚ดํ•˜์—ฌ ๋” ์‰ฝ๊ฒŒ ์ข‹์€ ๋‹ต๋ณ€์„ ์–ป๊ฑฐ๋‚˜ ์งˆ๋ฌธํ•˜๊ธฐ ์ „์— ์Šค์Šค๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  GitHub์—์„œ๋Š” ์‹œ๊ฐ„์ด ์กฐ๊ธˆ ๊ฑธ๋ฆฌ๋”๋ผ๋„ ํ•ญ์ƒ ๋ชจ๋“  ๊ฒƒ์— ๋‹ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—์„œ๋Š” ๊ฐœ์ธ์ ์œผ๋กœ ๊ทธ๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜… +GitHub์—์„œ๋Š” ํ…œํ”Œ๋ฆฟ์ด ์˜ฌ๋ฐ”๋ฅธ ์งˆ๋ฌธ์„ ์ž‘์„ฑํ•˜๋„๋ก ์•ˆ๋‚ดํ•˜์—ฌ ๋” ์‰ฝ๊ฒŒ ์ข‹์€ ๋‹ต๋ณ€์„ ์–ป๊ฑฐ๋‚˜, ์งˆ๋ฌธํ•˜๊ธฐ ์ „์— ์Šค์Šค๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  GitHub์—์„œ๋Š” ์‹œ๊ฐ„์ด ์กฐ๊ธˆ ๊ฑธ๋ฆฌ๋”๋ผ๋„ ์ œ๊ฐ€ ํ•ญ์ƒ ๋ชจ๋“  ๊ฒƒ์— ๋‹ตํ•˜๋„๋ก ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—์„œ๋Š” ์ œ๊ฐ€ ๊ฐœ์ธ์ ์œผ๋กœ ๊ทธ๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜… -์ฑ„ํŒ… ์‹œ์Šคํ…œ์—์„œ์˜ ๋Œ€ํ™” ๋˜ํ•œ GitHub์—์„œ ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€ํ™” ์ค‘์— ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€์ด ์†์‹ค๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  GitHub ์ด์Šˆ์— ์žˆ๋Š” ๊ฒƒ๋งŒ [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}๊ฐ€ ๋˜๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ๋˜๋ฏ€๋กœ, GitHub ์ด์Šˆ์—์„œ ๋” ๋งŽ์€ ๊ด€์‹ฌ์„ ๋ฐ›์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +์ฑ„ํŒ… ์‹œ์Šคํ…œ์—์„œ์˜ ๋Œ€ํ™” ๋˜ํ•œ GitHub๋งŒํผ ์‰ฝ๊ฒŒ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€์ด ๋Œ€ํ™” ์†์—์„œ ์‚ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  GitHub์— ์žˆ๋Š” ๊ฒƒ๋งŒ [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}๊ฐ€ ๋˜๋Š” ๊ฒƒ์œผ๋กœ ์ธ์ •๋˜๋ฏ€๋กœ, GitHub์—์„œ ๋” ๋งŽ์€ ๊ด€์‹ฌ์„ ๋ฐ›๊ฒŒ ๋  ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. -๋ฐ˜๋ฉด, ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—๋Š” ์ˆ˜์ฒœ ๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ฑฐ์˜ ํ•ญ์ƒ ๋Œ€ํ™” ์ƒ๋Œ€๋ฅผ ์ฐพ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ๐Ÿ˜„ +๋ฐ˜๋ฉด, ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—๋Š” ์ˆ˜์ฒœ ๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ, ๊ฑฐ์˜ ํ•ญ์ƒ ๋Œ€ํ™” ์ƒ๋Œ€๋ฅผ ์ฐพ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ๐Ÿ˜„ -## ๊ฐœ๋ฐœ์ž ์Šคํฐ์„œ๊ฐ€ ๋˜์‹ญ์‹œ์˜ค +## ๊ฐœ๋ฐœ์ž ์Šคํฐ์„œ ๋˜๊ธฐ { #sponsor-the-author } -<a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub ์Šคํฐ์„œ</a> ๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž๋ฅผ ๊ฒฝ์ œ์ ์œผ๋กœ ์ง€์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -๊ฐ์‚ฌํ•˜๋‹ค๋Š” ๋ง๋กœ ์ปคํ”ผ๋ฅผ โ˜•๏ธ ํ•œ์ž” ์‚ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜„ - -๋˜ํ•œ FastAPI์˜ ์‹ค๋ฒ„ ๋˜๋Š” ๊ณจ๋“œ ์Šคํฐ์„œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ…๐ŸŽ‰ - -## FastAPI๋ฅผ ๊ฐ•ํ™”ํ•˜๋Š” ๋„๊ตฌ์˜ ์Šคํฐ์„œ๊ฐ€ ๋˜์‹ญ์‹œ์˜ค - -๋ฌธ์„œ์—์„œ ๋ณด์•˜๋“ฏ์ด, FastAPI๋Š” Starlette๊ณผ Pydantic ๋ผ๋Š” ๊ฑฐ์ธ์˜ ์–ด๊นจ์— ํƒ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. - -๋‹ค์Œ์˜ ์Šคํฐ์„œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค - -* <a href="https://github.com/sponsors/samuelcolvin" class="external-link" target="_blank">Samuel Colvin (Pydantic)</a> -* <a href="https://github.com/sponsors/encode" class="external-link" target="_blank">Encode (Starlette, Uvicorn)</a> +์—ฌ๋Ÿฌ๋ถ„์˜ **์ œํ’ˆ/ํšŒ์‚ฌ**๊ฐ€ **FastAPI**์— ์˜์กดํ•˜๊ฑฐ๋‚˜ ๊ด€๋ จ๋˜์–ด ์žˆ๊ณ , FastAPI ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์•Œ๋ฆฌ๊ณ  ์‹ถ๋‹ค๋ฉด <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub sponsors</a>๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž(์ €)๋ฅผ ์Šคํฐ์„œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ‹ฐ์–ด์— ๋”ฐ๋ผ ๋ฌธ์„œ์— ๋ฐฐ์ง€ ๊ฐ™์€ ์ถ”๊ฐ€ ํ˜œํƒ์„ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐ŸŽ --- diff --git a/docs/ko/docs/history-design-future.md b/docs/ko/docs/history-design-future.md index 98f01d70d3..d972001215 100644 --- a/docs/ko/docs/history-design-future.md +++ b/docs/ko/docs/history-design-future.md @@ -1,81 +1,79 @@ -# ์—ญ์‚ฌ, ๋””์ž์ธ ๊ทธ๋ฆฌ๊ณ  ๋ฏธ๋ž˜ +# ์—ญ์‚ฌ, ๋””์ž์ธ ๊ทธ๋ฆฌ๊ณ  ๋ฏธ๋ž˜ { #history-design-and-future } -์–ด๋А ๋‚ , [ํ•œ FastAPI ์‚ฌ์šฉ์ž](https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920)๊ฐ€ ์ด๋ ‡๊ฒŒ ๋ฌผ์—ˆ์Šต๋‹ˆ๋‹ค: +์–ผ๋งˆ ์ „, <a href="https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920" class="external-link" target="_blank">ํ•œ **FastAPI** ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ ‡๊ฒŒ ๋ฌผ์—ˆ์Šต๋‹ˆ๋‹ค</a>: -> ์ด ํ”„๋กœ์ ํŠธ์˜ ์—ญ์‚ฌ๋ฅผ ์•Œ๋ ค ์ฃผ์‹ค ์ˆ˜ ์žˆ๋‚˜์š”? ๋ช‡ ์ฃผ ๋งŒ์— ๋ฉ‹์ง„ ๊ฒฐ๊ณผ๋ฅผ ๋‚ธ ๊ฒƒ ๊ฐ™์•„์š”. [...] +> ์ด ํ”„๋กœ์ ํŠธ์˜ ์—ญ์‚ฌ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”? ๋ช‡ ์ฃผ ๋งŒ์— ์•„๋ฌด ๋ฐ์„œ๋„ ๊ฐ‘์ž๊ธฐ ๋‚˜ํƒ€๋‚˜ ์—„์ฒญ๋‚˜๊ฒŒ ์ข‹์•„์ง„ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋„ค์š” [...] ์—ฌ๊ธฐ์„œ ๊ทธ ์—ญ์‚ฌ์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ---- +## ๋Œ€์•ˆ { #alternatives } -## ๋Œ€์•ˆ +์ €๋Š” ์—ฌ๋Ÿฌ ํ•ด ๋™์•ˆ ๋ณต์žกํ•œ ์š”๊ตฌ์‚ฌํ•ญ(๋จธ์‹ ๋Ÿฌ๋‹, ๋ถ„์‚ฐ ์‹œ์Šคํ…œ, ๋น„๋™๊ธฐ ์ž‘์—…, NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ)์„ ๊ฐ€์ง„ API๋ฅผ ๋งŒ๋“ค๋ฉด์„œ ์—ฌ๋Ÿฌ ๊ฐœ๋ฐœ ํŒ€์„ ์ด๋Œ์–ด ์™”์Šต๋‹ˆ๋‹ค. -์ €๋Š” ์—ฌ๋Ÿฌ ํ•ด ๋™์•ˆ ๋จธ์‹ ๋Ÿฌ๋‹, ๋ถ„์‚ฐ ์‹œ์Šคํ…œ, ๋น„๋™๊ธฐ ์ž‘์—…, NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ™์€ ๋ณต์žกํ•œ ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ฐ€์ง„ API๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉฐ ์—ฌ๋Ÿฌ ํŒ€์„ ์ด๋Œ์–ด ์™”์Šต๋‹ˆ๋‹ค. +๊ทธ ๊ณผ์ •์—์„œ ๋งŽ์€ ๋Œ€์•ˆ์„ ์กฐ์‚ฌํ•˜๊ณ , ํ…Œ์ŠคํŠธํ•˜๊ณ , ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. -์ด ๊ณผ์ •์—์„œ ๋งŽ์€ ๋Œ€์•ˆ์„ ์กฐ์‚ฌํ•˜๊ณ , ํ…Œ์ŠคํŠธํ•˜๋ฉฐ, ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. **FastAPI**์˜ ์—ญ์‚ฌ๋Š” ๊ทธ ์ด์ „์— ๋‚˜์™”๋˜ ์—ฌ๋Ÿฌ ๋„๊ตฌ์˜ ์—ญ์‚ฌ์™€ ๋ฐ€์ ‘ํ•˜๊ฒŒ ์—ฐ๊ด€๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +**FastAPI**์˜ ์—ญ์‚ฌ๋Š” ์ƒ๋‹น ๋ถ€๋ถ„ ๊ทธ ์ด์ „์— ์žˆ๋˜ ๋„๊ตฌ๋“ค์˜ ์—ญ์‚ฌ์ž…๋‹ˆ๋‹ค. [๋Œ€์•ˆ](alternatives.md){.internal-link target=_blank} ์„น์…˜์—์„œ ์–ธ๊ธ‰๋œ ๊ฒƒ์ฒ˜๋Ÿผ: -> **FastAPI**๋Š” ์ด์ „์— ๋‚˜์™”๋˜ ๋งŽ์€ ๋„๊ตฌ๋“ค์˜ ๋…ธ๋ ฅ ์—†์ด๋Š” ์กด์žฌํ•˜์ง€ ์•Š์•˜์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -> -> ์ด์ „์— ๊ฐœ๋ฐœ๋œ ์—ฌ๋Ÿฌ ๋„๊ตฌ๋“ค์ด ์ด ํ”„๋กœ์ ํŠธ์— ์˜๊ฐ์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. -> -> ์ €๋Š” ์˜ค๋žซ๋™์•ˆ ์ƒˆ๋กœ์šด ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ํ”ผํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” **FastAPI**๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค์„ ๋‹ค์–‘ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ํ”Œ๋Ÿฌ๊ทธ์ธ, ๋„๊ตฌ๋“ค์„ ์กฐํ•ฉํ•ด ํ•ด๊ฒฐํ•˜๋ ค ํ–ˆ์Šต๋‹ˆ๋‹ค. -> -> ํ•˜์ง€๋งŒ ๊ฒฐ๊ตญ์—๋Š” ์ด ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ํ†ตํ•ฉํ•˜๋Š” ๋„๊ตฌ๊ฐ€ ํ•„์š”ํ•ด์กŒ์Šต๋‹ˆ๋‹ค. ์ด์ „ ๋„๊ตฌ๋“ค๋กœ๋ถ€ํ„ฐ ์ตœ๊ณ ์˜ ์•„์ด๋””์–ด๋“ค์„ ๋ชจ์œผ๊ณ , ์ด๋ฅผ ์ตœ์ ์˜ ๋ฐฉ์‹์œผ๋กœ ์กฐํ•ฉํ•ด์•ผ๋งŒ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” :term:Python 3.6+ ํƒ€์ž… ํžŒํŠธ <type hints>์™€ ๊ฐ™์€, ์ด์ „์—๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์—ˆ๋˜ ์–ธ์–ด ๊ธฐ๋Šฅ์ด ๊ฐ€๋Šฅํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +<blockquote markdown="1"> ---- +**FastAPI**๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์ด์ „์— ํ•ด์˜จ ์ž‘์—…์ด ์—†์—ˆ๋‹ค๋ฉด ์กด์žฌํ•˜์ง€ ์•Š์•˜์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. -## ์กฐ์‚ฌ +๊ทธ ์ „์— ๋งŒ๋“ค์–ด์ง„ ๋งŽ์€ ๋„๊ตฌ๋“ค์ด ์ด๊ฒƒ์˜ ํƒ„์ƒ์— ์˜๊ฐ์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ ๋Œ€์•ˆ์„ ์‚ฌ์šฉํ•ด ๋ณด๋ฉฐ ๋‹ค์–‘ํ•œ ๋„๊ตฌ์—์„œ ๋ฐฐ์šด ์ ๋“ค์„ ๋ชจ์•„ ์ €์™€ ๊ฐœ๋ฐœํŒ€์—๊ฒŒ ๊ฐ€์žฅ ์ ํ•ฉํ•œ ๋ฐฉ์‹์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. +์ €๋Š” ์—ฌ๋Ÿฌ ํ•ด ๋™์•ˆ ์ƒˆ๋กœ์šด ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ํ”ผํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” **FastAPI**๊ฐ€ ๋‹ค๋ฃจ๋Š” ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์—ฌ๋Ÿฌ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ, ํ”Œ๋Ÿฌ๊ทธ์ธ, ๋„๊ตฌ๋“ค์„ ์‚ฌ์šฉํ•ด ํ•ด๊ฒฐํ•˜๋ ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค. -์˜ˆ๋ฅผ ๋“ค์–ด, ํ‘œ์ค€ :term:Python ํƒ€์ž… ํžŒํŠธ <type hints>์— ๊ธฐ๋ฐ˜ํ•˜๋Š” ๊ฒƒ์ด ์ด์ƒ์ ์ด๋ผ๋Š” ์ ์ด ๋ช…ํ™•ํ–ˆ์Šต๋‹ˆ๋‹ค. +ํ•˜์ง€๋งŒ ์–ด๋А ์‹œ์ ์—๋Š”, ์ด์ „ ๋„๊ตฌ๋“ค์˜ ์ตœ๊ณ ์˜ ์•„์ด๋””์–ด๋ฅผ ๊ฐ€์ ธ์™€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ตœ์„ ์˜ ๋ฐฉ์‹์œผ๋กœ ์กฐํ•ฉํ•˜๊ณ , ์ด์ „์—๋Š” ์กด์žฌํ•˜์ง€ ์•Š์•˜๋˜ ์–ธ์–ด ๊ธฐ๋Šฅ(Python 3.6+ type hints)์„ ์‚ฌ์šฉํ•ด ์ด ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ ์™ธ์—๋Š” ๋‹ค๋ฅธ ์„ ํƒ์ง€๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. -๋˜ํ•œ, ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํ‘œ์ค€์„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์€ ์ ‘๊ทผ๋ฒ•์ด๋ผ ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค. +</blockquote> -๊ทธ๋ž˜์„œ **FastAPI**์˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „์— ๋ช‡ ๋‹ฌ ๋™์•ˆ OpenAPI, JSON Schema, OAuth2 ๋ช…์„ธ๋ฅผ ์—ฐ๊ตฌํ•˜๋ฉฐ ์ด๋“ค์˜ ๊ด€๊ณ„์™€ ๊ฒน์น˜๋Š” ๋ถ€๋ถ„, ์ฐจ์ด์ ์„ ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค. +## ์กฐ์‚ฌ { #investigation } ---- +์ด์ „์˜ ๋ชจ๋“  ๋Œ€์•ˆ์„ ์‚ฌ์šฉํ•ด ๋ณด๋ฉด์„œ, ๊ฐ ๋„๊ตฌ๋กœ๋ถ€ํ„ฐ ๋ฐฐ์šธ ๊ธฐํšŒ๋ฅผ ์–ป์—ˆ๊ณ , ์•„์ด๋””์–ด๋ฅผ ๊ฐ€์ ธ์™€ ์ œ๊ฐ€ ์ผํ•ด์˜จ ๊ฐœ๋ฐœ ํŒ€๋“ค๊ณผ ์ € ์ž์‹ ์—๊ฒŒ ๊ฐ€์žฅ ์ ํ•ฉํ•˜๋‹ค๊ณ  ์ฐพ์€ ๋ฐฉ์‹์œผ๋กœ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. -## ๋””์ž์ธ +์˜ˆ๋ฅผ ๋“ค์–ด, ์ด์ƒ์ ์œผ๋กœ๋Š” ํ‘œ์ค€ Python ํƒ€์ž… ํžŒํŠธ์— ๊ธฐ๋ฐ˜ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ด ๋ถ„๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค. -๊ทธ ํ›„, **FastAPI** ์‚ฌ์šฉ์ž๊ฐ€ ๋  ๊ฐœ๋ฐœ์ž๋กœ์„œ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ๊ฐœ๋ฐœ์ž "API"๋ฅผ ๋””์ž์ธํ–ˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ, ๊ฐ€์žฅ ์ข‹์€ ์ ‘๊ทผ๋ฒ•์€ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํ‘œ์ค€์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. -[Python Developer Survey](https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools)์— ๋”ฐ๋ฅด๋ฉด ์•ฝ 80%์˜ Python ๊ฐœ๋ฐœ์ž๊ฐ€ PyCharm, VS Code, Jedi ๊ธฐ๋ฐ˜ ํŽธ์ง‘๊ธฐ ๋“ฑ์—์„œ ๊ฐœ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ์—ฌ๋Ÿฌ ์•„์ด๋””์–ด๋ฅผ ํ…Œ์ŠคํŠธํ–ˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ž˜์„œ **FastAPI**์˜ ์ฝ”๋”ฉ์„ ์‹œ์ž‘ํ•˜๊ธฐ๋„ ์ „์—, OpenAPI, JSON Schema, OAuth2 ๋“ฑ๊ณผ ๊ฐ™์€ ๋ช…์„ธ๋ฅผ ๋ช‡ ๋‹ฌ ๋™์•ˆ ๊ณต๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค์˜ ๊ด€๊ณ„, ๊ฒน์น˜๋Š” ๋ถ€๋ถ„, ์ฐจ์ด์ ์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ์˜€์Šต๋‹ˆ๋‹ค. -๋Œ€๋ถ€๋ถ„์˜ ๋‹ค๋ฅธ ํŽธ์ง‘๊ธฐ๋„ ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, **FastAPI**์˜ ์ด์ ์€ ๊ฑฐ์˜ ๋ชจ๋“  ํŽธ์ง‘๊ธฐ์—์„œ ๋ˆ„๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +## ๋””์ž์ธ { #design } -์ด ๊ณผ์ •์„ ํ†ตํ•ด ์ฝ”๋“œ ์ค‘๋ณต์„ ์ตœ์†Œํ™”ํ•˜๊ณ , ๋ชจ๋“  ๊ณณ์—์„œ ์ž๋™ ์™„์„ฑ, ํƒ€์ž… ๊ฒ€์‚ฌ, ์—๋Ÿฌ ํ™•์ธ ๊ธฐ๋Šฅ์ด ์ œ๊ณต๋˜๋Š” ์ตœ์ ์˜ ๋ฐฉ์‹์„ ์ฐพ์•„๋ƒˆ์Šต๋‹ˆ๋‹ค. +๊ทธ ๋‹ค์Œ์—๋Š” (FastAPI๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋กœ์„œ) ์‚ฌ์šฉ์ž๋กœ์„œ ๊ฐ–๊ณ  ์‹ถ์—ˆ๋˜ ๊ฐœ๋ฐœ์ž "API"๋ฅผ ๋””์ž์ธํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์„ ์ผ์Šต๋‹ˆ๋‹ค. -์ด ๋ชจ๋“  ๊ฒƒ์€ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ์ตœ๊ณ ์˜ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” Python ํŽธ์ง‘๊ธฐ๋“ค: PyCharm, VS Code, Jedi ๊ธฐ๋ฐ˜ ํŽธ์ง‘๊ธฐ์—์„œ ์—ฌ๋Ÿฌ ์•„์ด๋””์–ด๋ฅผ ํ…Œ์ŠคํŠธํ–ˆ์Šต๋‹ˆ๋‹ค. ---- +์•ฝ 80%์˜ ์‚ฌ์šฉ์ž๋ฅผ ํฌํ•จํ•˜๋Š” ์ตœ๊ทผ <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" class="external-link" target="_blank">Python Developer Survey</a>์— ๋”ฐ๋ฅด๋ฉด ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. -## ํ•„์š”์กฐ๊ฑด +์ฆ‰, **FastAPI**๋Š” Python ๊ฐœ๋ฐœ์ž์˜ 80%๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ง‘๊ธฐ๋“ค๋กœ ํŠน๋ณ„ํžˆ ํ…Œ์ŠคํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋Œ€๋ถ€๋ถ„์˜ ๋‹ค๋ฅธ ํŽธ์ง‘๊ธฐ๋„ ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์œผ๋ฏ€๋กœ, ๊ทธ ๋ชจ๋“  ์ด์ ์€ ์‚ฌ์‹ค์ƒ ๋ชจ๋“  ํŽธ์ง‘๊ธฐ์—์„œ ๋™์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -์—ฌ๋Ÿฌ ๋Œ€์•ˆ์„ ํ…Œ์ŠคํŠธํ•œ ํ›„, [Pydantic](https://docs.pydantic.dev/)์„ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. +๊ทธ๋ ‡๊ฒŒ ํ•ด์„œ ์ฝ”๋“œ ์ค‘๋ณต์„ ๊ฐ€๋Šฅํ•œ ํ•œ ๋งŽ์ด ์ค„์ด๊ณ , ์–ด๋””์„œ๋‚˜ ์ž๋™ ์™„์„ฑ, ํƒ€์ž… ๋ฐ ์—๋Ÿฌ ๊ฒ€์‚ฌ ๋“ฑ์„ ์ œ๊ณตํ•˜๋Š” ์ตœ์„ ์˜ ๋ฐฉ๋ฒ•์„ ์ฐพ์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. -์ดํ›„ ์ €๋Š” **Pydantic**์ด JSON Schema์™€ ์™„๋ฒฝํžˆ ํ˜ธํ™˜๋˜๋„๋ก ๊ฐœ์„ ํ•˜๊ณ , ๋‹ค์–‘ํ•œ ์ œ์•ฝ ์กฐ๊ฑด ์„ ์–ธ์„ ์ง€์›ํ•˜๋ฉฐ, ์—ฌ๋Ÿฌ ํŽธ์ง‘๊ธฐ์—์„œ์˜ ์ž๋™ ์™„์„ฑ๊ณผ ํƒ€์ž… ๊ฒ€์‚ฌ ๊ธฐ๋Šฅ์„ ํ–ฅ์ƒํ•˜๊ธฐ ์œ„ํ•ด ๊ธฐ์—ฌํ–ˆ์Šต๋‹ˆ๋‹ค. +๋ชจ๋“  ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์ตœ๊ณ ์˜ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ง์ž…๋‹ˆ๋‹ค. -๋˜ํ•œ, ๋˜ ๋‹ค๋ฅธ ์ฃผ์š” ํ•„์š”์กฐ๊ฑด์ด์—ˆ๋˜ [Starlette](https://www.starlette.dev/)์—๋„ ๊ธฐ์—ฌํ–ˆ์Šต๋‹ˆ๋‹ค. +## ํ•„์š”์กฐ๊ฑด { #requirements } ---- +์—ฌ๋Ÿฌ ๋Œ€์•ˆ์„ ํ…Œ์ŠคํŠธํ•œ ํ›„, ์žฅ์  ๋•Œ๋ฌธ์— <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">**Pydantic**</a>์„ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. -## ๊ฐœ๋ฐœ +๊ทธ ํ›„, JSON Schema๋ฅผ ์™„์ „ํžˆ ์ค€์ˆ˜ํ•˜๋„๋ก ํ•˜๊ณ , ์ œ์•ฝ ์กฐ๊ฑด ์„ ์–ธ์„ ์ •์˜ํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์„ ์ง€์›ํ•˜๋ฉฐ, ์—ฌ๋Ÿฌ ํŽธ์ง‘๊ธฐ์—์„œ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํŽธ์ง‘๊ธฐ ์ง€์›(ํƒ€์ž… ๊ฒ€์‚ฌ, ์ž๋™ ์™„์„ฑ)์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ๊ธฐ์—ฌํ–ˆ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋ฅผ ๊ฐœ๋ฐœํ•˜๊ธฐ ์‹œ์ž‘ํ•  ์ฆˆ์Œ์—๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์ค€๋น„๊ฐ€ ์ด๋ฏธ ์™„๋ฃŒ๋œ ์ƒํƒœ์˜€์Šต๋‹ˆ๋‹ค. ์„ค๊ณ„๊ฐ€ ์ •์˜๋˜์—ˆ๊ณ , ํ•„์š”์กฐ๊ฑด๊ณผ ๋„๊ตฌ๊ฐ€ ์ค€๋น„๋˜์—ˆ์œผ๋ฉฐ, ํ‘œ์ค€๊ณผ ๋ช…์„ธ์— ๋Œ€ํ•œ ์ง€์‹๋„ ์ถฉ๋ถ„ํ–ˆ์Šต๋‹ˆ๋‹ค. +๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ, ๋˜ ๋‹ค๋ฅธ ํ•ต์‹ฌ ํ•„์š”์กฐ๊ฑด์ธ <a href="https://www.starlette.dev/" class="external-link" target="_blank">**Starlette**</a>์—๋„ ๊ธฐ์—ฌํ–ˆ์Šต๋‹ˆ๋‹ค. ---- +## ๊ฐœ๋ฐœ { #development } -## ๋ฏธ๋ž˜ +**FastAPI** ์ž์ฒด๋ฅผ ๋งŒ๋“ค๊ธฐ ์‹œ์ž‘ํ–ˆ์„ ๋•Œ์ฏค์—๋Š”, ๋Œ€๋ถ€๋ถ„์˜ ์กฐ๊ฐ๋“ค์ด ์ด๋ฏธ ๊ฐ–์ถฐ์ ธ ์žˆ์—ˆ๊ณ , ๋””์ž์ธ์€ ์ •์˜๋˜์–ด ์žˆ์—ˆ์œผ๋ฉฐ, ํ•„์š”์กฐ๊ฑด๊ณผ ๋„๊ตฌ๋Š” ์ค€๋น„๋˜์–ด ์žˆ์—ˆ๊ณ , ํ‘œ์ค€๊ณผ ๋ช…์„ธ์— ๋Œ€ํ•œ ์ง€์‹๋„ ๋ช…ํ™•ํ•˜๊ณ  ์ตœ์‹  ์ƒํƒœ์˜€์Šต๋‹ˆ๋‹ค. -ํ˜„์‹œ์ ์—์„œ **FastAPI**๊ฐ€ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์œ ์šฉํ•˜๋‹ค๋Š” ๊ฒƒ์ด ๋ช…๋ฐฑํ•ด์กŒ์Šต๋‹ˆ๋‹ค. +## ๋ฏธ๋ž˜ { #future } -์—ฌ๋Ÿฌ ์šฉ๋„์— ๋” ์ ํ•ฉํ•œ ๋„๊ตฌ๋กœ์„œ ๊ธฐ์กด ๋Œ€์•ˆ๋ณด๋‹ค ์„ ํ˜ธ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -์ด๋ฏธ ๋งŽ์€ ๊ฐœ๋ฐœ์ž์™€ ํŒ€๋“ค์ด **FastAPI**์— ์˜์กดํ•ด ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค (์ €์™€ ์ œ ํŒ€๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค). +์ด ์‹œ์ ์—๋Š”, **FastAPI**๊ฐ€ ๊ทธ ์•„์ด๋””์–ด์™€ ํ•จ๊ป˜ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์œ ์šฉํ•˜๋‹ค๋Š” ๊ฒƒ์ด ์ด๋ฏธ ๋ถ„๋ช…ํ•ฉ๋‹ˆ๋‹ค. -ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ๊ฐœ์„ ํ•ด์•ผ ํ•  ์ ๊ณผ ์ถ”๊ฐ€ํ•  ๊ธฐ๋Šฅ๋“ค์ด ๋งŽ์ด ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค. +๋งŽ์€ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋” ์ž˜ ๋งž๊ธฐ ๋•Œ๋ฌธ์— ์ด์ „ ๋Œ€์•ˆ๋“ค๋ณด๋‹ค ์„ ํƒ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +๋งŽ์€ ๊ฐœ๋ฐœ์ž์™€ ํŒ€์ด ์ด๋ฏธ ์ž์‹ ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์œ„ํ•ด **FastAPI**์— ์˜์กดํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค(์ €์™€ ์ œ ํŒ€๋„ ํฌํ•จํ•ด์„œ์š”). + +ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ, ์•ž์œผ๋กœ ๋‚˜์˜ฌ ๊ฐœ์„  ์‚ฌํ•ญ๊ณผ ๊ธฐ๋Šฅ๋“ค์ด ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +**FastAPI**์˜ ๋ฏธ๋ž˜๋Š” ๋ฐ์Šต๋‹ˆ๋‹ค. -**FastAPI**๋Š” ๋ฐ์€ ๋ฏธ๋ž˜๋กœ ๋‚˜์•„๊ฐ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  [์—ฌ๋Ÿฌ๋ถ„์˜ ๋„์›€](help-fastapi.md){.internal-link target=_blank}์€ ํฐ ํž˜์ด ๋ฉ๋‹ˆ๋‹ค. From 442d007e761fdee34ec01d2365d138d0a746c971 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:16:33 +0000 Subject: [PATCH 108/110] =?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 f1321b7809..cf84ae9b53 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update translations for ko (update outdated, found by fixer tool). PR [#14738](https://github.com/fastapi/fastapi/pull/14738) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐ŸŒ Update translations for de (update-outdated). PR [#14690](https://github.com/fastapi/fastapi/pull/14690) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update LLM prompt for Russian translations. PR [#14733](https://github.com/fastapi/fastapi/pull/14733) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐ŸŒ Update translations for ru (update-outdated). PR [#14693](https://github.com/fastapi/fastapi/pull/14693) by [@tiangolo](https://github.com/tiangolo). From 7a0589466c1c0129ff7291ea0b663d52859d651d Mon Sep 17 00:00:00 2001 From: JUNG SEUNGHOON <seuthootdev@gmail.com> Date: Sun, 25 Jan 2026 06:17:54 +0900 Subject: [PATCH 109/110] =?UTF-8?q?=F0=9F=8C=90=20Update=20`llm-prompt.md`?= =?UTF-8?q?=20for=20Korean=20language=20(#14763)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(ko): refine 'burger' to 'ํ–„๋ฒ„๊ฑฐ' and update glossary * Update docs/ko/llm-prompt.md Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> * Add app and command to glossary Update glossary: add ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (app) and ๋ช…๋ น์–ด (command) --------- Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- docs/ko/llm-prompt.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/ko/llm-prompt.md b/docs/ko/llm-prompt.md index 533160eab9..be2f5be5de 100644 --- a/docs/ko/llm-prompt.md +++ b/docs/ko/llm-prompt.md @@ -33,6 +33,9 @@ Use the following preferred translations when they apply in documentation prose: - response (HTTP): ์‘๋‹ต - path operation: ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ - path operation function: ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ +- app: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ +- command: ๋ช…๋ น์–ด +- burger: ํ–„๋ฒ„๊ฑฐ (NOT ๋ฒ„๊ฑฐ) ### `///` admonitions From 8c32e91c10cbf13da8be4d27f03d9fdaa9a6730c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:18:15 +0000 Subject: [PATCH 110/110] =?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 cf84ae9b53..1fefe908ba 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Translations +* ๐ŸŒ Update `llm-prompt.md` for Korean language. PR [#14763](https://github.com/fastapi/fastapi/pull/14763) by [@seuthootDev](https://github.com/seuthootDev). * ๐ŸŒ Update translations for ko (update outdated, found by fixer tool). PR [#14738](https://github.com/fastapi/fastapi/pull/14738) by [@YuriiMotov](https://github.com/YuriiMotov). * ๐ŸŒ Update translations for de (update-outdated). PR [#14690](https://github.com/fastapi/fastapi/pull/14690) by [@tiangolo](https://github.com/tiangolo). * ๐ŸŒ Update LLM prompt for Russian translations. PR [#14733](https://github.com/fastapi/fastapi/pull/14733) by [@YuriiMotov](https://github.com/YuriiMotov).