From e93d15cf9a4820cfa69e6d2f62094720a71d619d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 17 Aug 2023 10:51:58 +0200 Subject: [PATCH 001/188] =?UTF-8?q?=F0=9F=94=A7=20Update=20sponsors,=20add?= =?UTF-8?q?=20Speakeasy=20(#10098)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ docs/en/data/sponsors.yml | 3 +++ docs/en/data/sponsors_badge.yml | 1 + docs/en/docs/img/sponsors/speakeasy.png | Bin 0 -> 4790 bytes 4 files changed, 6 insertions(+) create mode 100644 docs/en/docs/img/sponsors/speakeasy.png diff --git a/README.md b/README.md index 50f80ded6..266213426 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ The key features are: + @@ -56,6 +57,7 @@ The key features are: + diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index 6d9119520..0d9597f07 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -33,6 +33,9 @@ silver: - url: https://databento.com/ title: Pay as you go for market data img: https://fastapi.tiangolo.com/img/sponsors/databento.svg + - url: https://speakeasyapi.dev?utm_source=fastapi+repo&utm_medium=github+sponsorship + title: SDKs for your API | Speakeasy + img: https://fastapi.tiangolo.com/img/sponsors/speakeasy.png bronze: - url: https://www.exoflare.com/open-source/?utm_source=FastAPI&utm_campaign=open_source title: Biosecurity risk assessments made easy. diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 7c3bb2f47..7b605e0ff 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -19,3 +19,4 @@ logins: - Flint-company - porter-dev - fern-api + - ndimares diff --git a/docs/en/docs/img/sponsors/speakeasy.png b/docs/en/docs/img/sponsors/speakeasy.png new file mode 100644 index 0000000000000000000000000000000000000000..001b4b4caffe26d75892dd040064c5d5f4c1ff22 GIT binary patch literal 4790 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 JRI6we`aev(U(5gi literal 0 HcmV?d00001 From a6ae5af7d6c9e7e33490307cccff66a49671433b Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 17 Aug 2023 08:52:40 +0000 Subject: [PATCH 002/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9570bef36..6ef2e6a45 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🔧 Update sponsors, add Speakeasy. PR [#10098](https://github.com/tiangolo/fastapi/pull/10098) by [@tiangolo](https://github.com/tiangolo). ## 0.101.1 ### Fixes From d4201a49bc2e693afba554c1d73d9ce06a042383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 19 Aug 2023 15:11:35 +0200 Subject: [PATCH 003/188] =?UTF-8?q?=F0=9F=93=9D=20Restructure=20docs=20for?= =?UTF-8?q?=20cloud=20providers,=20include=20links=20to=20sponsors=20(#101?= =?UTF-8?q?10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + docs/em/docs/deployment/deta.md | 258 -------------------- docs/en/docs/deployment/cloud.md | 17 ++ docs/en/docs/deployment/deta.md | 391 ------------------------------- docs/en/mkdocs.yml | 2 +- docs/fr/docs/deployment/deta.md | 245 ------------------- docs/ja/docs/deployment/deta.md | 240 ------------------- docs/pt/docs/deployment/deta.md | 258 -------------------- 8 files changed, 21 insertions(+), 1393 deletions(-) delete mode 100644 docs/em/docs/deployment/deta.md create mode 100644 docs/en/docs/deployment/cloud.md delete mode 100644 docs/en/docs/deployment/deta.md delete mode 100644 docs/fr/docs/deployment/deta.md delete mode 100644 docs/ja/docs/deployment/deta.md delete mode 100644 docs/pt/docs/deployment/deta.md diff --git a/.gitignore b/.gitignore index d380d16b7..9be494cec 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ archive.zip *~ .*.sw? .cache + +# macOS +.DS_Store diff --git a/docs/em/docs/deployment/deta.md b/docs/em/docs/deployment/deta.md deleted file mode 100644 index 89b6c4bdb..000000000 --- a/docs/em/docs/deployment/deta.md +++ /dev/null @@ -1,258 +0,0 @@ -# 🛠️ FastAPI 🔛 🪔 - -👉 📄 👆 🔜 💡 ❔ 💪 🛠️ **FastAPI** 🈸 🔛 🪔 ⚙️ 🆓 📄. 👶 - -⚫️ 🔜 ✊ 👆 🔃 **1️⃣0️⃣ ⏲**. - -!!! info - 🪔 **FastAPI** 💰. 👶 - -## 🔰 **FastAPI** 📱 - -* ✍ 📁 👆 📱, 🖼, `./fastapideta/` & ⛔ 🔘 ⚫️. - -### FastAPI 📟 - -* ✍ `main.py` 📁 ⏮️: - -```Python -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int): - return {"item_id": item_id} -``` - -### 📄 - -🔜, 🎏 📁 ✍ 📁 `requirements.txt` ⏮️: - -```text -fastapi -``` - -!!! tip - 👆 🚫 💪 ❎ Uvicorn 🛠️ 🔛 🪔, 👐 👆 🔜 🎲 💚 ❎ ⚫️ 🌐 💯 👆 📱. - -### 📁 📊 - -👆 🔜 🔜 ✔️ 1️⃣ 📁 `./fastapideta/` ⏮️ 2️⃣ 📁: - -``` -. -└── main.py -└── requirements.txt -``` - -## ✍ 🆓 🪔 🏧 - -🔜 ✍ 🆓 🏧 🔛 🪔, 👆 💪 📧 & 🔐. - -👆 🚫 💪 💳. - -## ❎ ✳ - -🕐 👆 ✔️ 👆 🏧, ❎ 🪔 : - -=== "💾, 🇸🇻" - -
- - ```console - $ curl -fsSL https://get.deta.dev/cli.sh | sh - ``` - -
- -=== "🚪 📋" - -
- - ```console - $ iwr https://get.deta.dev/cli.ps1 -useb | iex - ``` - -
- -⏮️ ❎ ⚫️, 📂 🆕 📶 👈 ❎ ✳ 🔍. - -🆕 📶, ✔ 👈 ⚫️ ☑ ❎ ⏮️: - -
- -```console -$ deta --help - -Deta command line interface for managing deta micros. -Complete documentation available at https://docs.deta.sh - -Usage: - deta [flags] - deta [command] - -Available Commands: - auth Change auth settings for a deta micro - -... -``` - -
- -!!! tip - 🚥 👆 ✔️ ⚠ ❎ ✳, ✅ 🛂 🪔 🩺. - -## 💳 ⏮️ ✳ - -🔜 💳 🪔 ⚪️➡️ ✳ ⏮️: - -
- -```console -$ deta login - -Please, log in from the web page. Waiting.. -Logged in successfully. -``` - -
- -👉 🔜 📂 🕸 🖥 & 🔓 🔁. - -## 🛠️ ⏮️ 🪔 - -⏭, 🛠️ 👆 🈸 ⏮️ 🪔 ✳: - -
- -```console -$ deta new - -Successfully created a new micro - -// Notice the "endpoint" 🔍 - -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} - -Adding dependencies... - - ----> 100% - - -Successfully installed fastapi-0.61.1 pydantic-1.7.2 starlette-0.13.6 -``` - -
- -👆 🔜 👀 🎻 📧 🎏: - -```JSON hl_lines="4" -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} -``` - -!!! tip - 👆 🛠️ 🔜 ✔️ 🎏 `"endpoint"` 📛. - -## ✅ ⚫️ - -🔜 📂 👆 🖥 👆 `endpoint` 📛. 🖼 🔛 ⚫️ `https://qltnci.deta.dev`, ✋️ 👆 🔜 🎏. - -👆 🔜 👀 🎻 📨 ⚪️➡️ 👆 FastAPI 📱: - -```JSON -{ - "Hello": "World" -} -``` - -& 🔜 🚶 `/docs` 👆 🛠️, 🖼 🔛 ⚫️ 🔜 `https://qltnci.deta.dev/docs`. - -⚫️ 🔜 🎦 👆 🩺 💖: - - - -## 🛠️ 📢 🔐 - -🔢, 🪔 🔜 🍵 🤝 ⚙️ 🍪 👆 🏧. - -✋️ 🕐 👆 🔜, 👆 💪 ⚒ ⚫️ 📢 ⏮️: - -
- -```console -$ deta auth disable - -Successfully disabled http auth -``` - -
- -🔜 👆 💪 💰 👈 📛 ⏮️ 🙆 & 👫 🔜 💪 🔐 👆 🛠️. 👶 - -## 🇺🇸🔍 - -㊗ ❗ 👆 🛠️ 👆 FastAPI 📱 🪔 ❗ 👶 👶 - -, 👀 👈 🪔 ☑ 🍵 🇺🇸🔍 👆, 👆 🚫 ✔️ ✊ 💅 👈 & 💪 💭 👈 👆 👩‍💻 🔜 ✔️ 🔐 🗜 🔗. 👶 👶 - -## ✅ 🕶 - -⚪️➡️ 👆 🩺 🎚 (👫 🔜 📛 💖 `https://qltnci.deta.dev/docs`) 📨 📨 👆 *➡ 🛠️* `/items/{item_id}`. - -🖼 ⏮️ 🆔 `5`. - -🔜 🚶 https://web.deta.sh. - -👆 🔜 👀 📤 📄 ◀️ 🤙 "◾" ⏮️ 🔠 👆 📱. - -👆 🔜 👀 📑 ⏮️ "ℹ", & 📑 "🕶", 🚶 📑 "🕶". - -📤 👆 💪 ✔ ⏮️ 📨 📨 👆 📱. - -👆 💪 ✍ 👫 & 🏤-🤾 👫. - - - -## 💡 🌅 - -☝, 👆 🔜 🎲 💚 🏪 💽 👆 📱 🌌 👈 😣 🔘 🕰. 👈 👆 💪 ⚙️ 🪔 🧢, ⚫️ ✔️ 👍 **🆓 🎚**. - -👆 💪 ✍ 🌅 🪔 🩺. - -## 🛠️ 🔧 - -👟 🔙 🔧 👥 🔬 [🛠️ 🔧](./concepts.md){.internal-link target=_blank}, 📥 ❔ 🔠 👫 🔜 🍵 ⏮️ 🪔: - -* **🇺🇸🔍**: 🍵 🪔, 👫 🔜 🤝 👆 📁 & 🍵 🇺🇸🔍 🔁. -* **🏃‍♂ 🔛 🕴**: 🍵 🪔, 🍕 👫 🐕‍🦺. -* **⏏**: 🍵 🪔, 🍕 👫 🐕‍🦺. -* **🧬**: 🍵 🪔, 🍕 👫 🐕‍🦺. -* **💾**: 📉 🔁 🪔, 👆 💪 📧 👫 📈 ⚫️. -* **⏮️ 🔁 ⏭ ▶️**: 🚫 🔗 🐕‍🦺, 👆 💪 ⚒ ⚫️ 👷 ⏮️ 👫 💾 ⚙️ ⚖️ 🌖 ✍. - -!!! note - 🪔 🔧 ⚒ ⚫️ ⏩ (& 🆓) 🛠️ 🙅 🈸 🔜. - - ⚫️ 💪 📉 📚 ⚙️ 💼, ✋️ 🎏 🕰, ⚫️ 🚫 🐕‍🦺 🎏, 💖 ⚙️ 🔢 💽 (↖️ ⚪️➡️ 🪔 👍 ☁ 💽 ⚙️), 🛃 🕹 🎰, ♒️. - - 👆 💪 ✍ 🌅 ℹ 🪔 🩺 👀 🚥 ⚫️ ▶️️ ⚒ 👆. diff --git a/docs/en/docs/deployment/cloud.md b/docs/en/docs/deployment/cloud.md new file mode 100644 index 000000000..b2836aeb4 --- /dev/null +++ b/docs/en/docs/deployment/cloud.md @@ -0,0 +1,17 @@ +# Deploy FastAPI on Cloud Providers + +You can use virtually **any cloud provider** to deploy your FastAPI application. + +In most of the cases, the main cloud providers have guides to deploy FastAPI with them. + +## Cloud Providers - Sponsors + +Some cloud providers ✨ [**sponsor FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, this ensures the continued and healthy **development** of FastAPI and its **ecosystem**. + +And it shows their true commitment to FastAPI and its **community** (you), as they not only want to provide you a **good service** but also want to make sure you have a **good and healthy framework**, FastAPI. 🙇 + +You might want to try their services and follow their guides: + +* Platform.sh +* Porter +* Deta diff --git a/docs/en/docs/deployment/deta.md b/docs/en/docs/deployment/deta.md deleted file mode 100644 index 229d7fd5d..000000000 --- a/docs/en/docs/deployment/deta.md +++ /dev/null @@ -1,391 +0,0 @@ -# Deploy FastAPI on Deta Space - -In this section you will learn how to easily deploy a **FastAPI** application on Deta Space, for free. 🎁 - -It will take you about **10 minutes** to deploy an API that you can use. After that, you can optionally release it to anyone. - -Let's dive in. - -!!! info - Deta is a **FastAPI** sponsor. 🎉 - -## A simple **FastAPI** app - -* To start, create an empty directory with the name of your app, for example `./fastapi-deta/`, and then navigate into it. - -```console -$ mkdir fastapi-deta -$ cd fastapi-deta -``` - -### FastAPI code - -* Create a `main.py` file with: - -```Python -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int): - return {"item_id": item_id} -``` - -### Requirements - -Now, in the same directory create a file `requirements.txt` with: - -```text -fastapi -uvicorn[standard] -``` - -### Directory structure - -You will now have a directory `./fastapi-deta/` with two files: - -``` -. -└── main.py -└── requirements.txt -``` - -## Create a free **Deta Space** account - -Next, create a free account on Deta Space, you just need an email and password. - -You don't even need a credit card, but make sure **Developer Mode** is enabled when you sign up. - - -## Install the CLI - -Once you have your account, install the Deta Space CLI: - -=== "Linux, macOS" - -
- - ```console - $ curl -fsSL https://get.deta.dev/space-cli.sh | sh - ``` - -
- -=== "Windows PowerShell" - -
- - ```console - $ iwr https://get.deta.dev/space-cli.ps1 -useb | iex - ``` - -
- -After installing it, open a new terminal so that the installed CLI is detected. - -In a new terminal, confirm that it was correctly installed with: - -
- -```console -$ space --help - -Deta command line interface for managing deta micros. -Complete documentation available at https://deta.space/docs - -Usage: - space [flags] - space [command] - -Available Commands: - help Help about any command - link link code to project - login login to space - new create new project - push push code for project - release create release for a project - validate validate spacefile in dir - version Space CLI version -... -``` - -
- -!!! tip - If you have problems installing the CLI, check the official Deta Space Documentation. - -## Login with the CLI - -In order to authenticate your CLI with Deta Space, you will need an access token. - -To obtain this token, open your Deta Space Canvas, open the **Teletype** (command bar at the bottom of the Canvas), and then click on **Settings**. From there, select **Generate Token** and copy the resulting token. - - - -Now run `space login` from the Space CLI. Upon pasting the token into the CLI prompt and pressing enter, you should see a confirmation message. - -
- -```console -$ space login - -To authenticate the Space CLI with your Space account, generate a new access token in your Space settings and paste it below: - -# Enter access token (41 chars) >$ ***************************************** - -👍 Login Successful! -``` - -
- -## Create a new project in Space - -Now that you've authenticated with the Space CLI, use it to create a new Space Project: - -```console -$ space new - -# What is your project's name? >$ fastapi-deta -``` - -The Space CLI will ask you to name the project, we will call ours `fastapi-deta`. - -Then, it will try to automatically detect which framework or language you are using, showing you what it finds. In our case it will identify the Python app with the following message, prompting you to confirm: - -```console -⚙️ No Spacefile found, trying to auto-detect configuration ... -👇 Deta detected the following configuration: - -Micros: -name: fastapi-deta - L src: . - L engine: python3.9 - -# Do you want to bootstrap "fastapi-deta" with this configuration? (y/n)$ y -``` - -After you confirm, your project will be created in Deta Space inside a special app called Builder. Builder is a toolbox that helps you to create and manage your apps in Deta Space. - -The CLI will also create a `Spacefile` locally in the `fastapi-deta` directory. The Spacefile is a configuration file which tells Deta Space how to run your app. The `Spacefile` for your app will be as follows: - -```yaml -v: 0 -micros: - - name: fastapi-deta - src: . - engine: python3.9 -``` - -It is a `yaml` file, and you can use it to add features like scheduled tasks or modify how your app functions, which we'll do later. To learn more, read the `Spacefile` documentation. - -!!! tip - The Space CLI will also create a hidden `.space` folder in your local directory to link your local environment with Deta Space. This folder should not be included in your version control and will automatically be added to your `.gitignore` file, if you have initialized a Git repository. - -## Define the run command in the Spacefile - -The `run` command in the Spacefile tells Space what command should be executed to start your app. In this case it would be `uvicorn main:app`. - -```diff -v: 0 -micros: - - name: fastapi-deta - src: . - engine: python3.9 -+ run: uvicorn main:app -``` - -## Deploy to Deta Space - -To get your FastAPI live in the cloud, use one more CLI command: - -
- -```console -$ space push - ----> 100% - -build complete... created revision: satyr-jvjk - -✔ Successfully pushed your code and created a new Revision! -ℹ Updating your development instance with the latest Revision, it will be available on your Canvas shortly. -``` -
- -This command will package your code, upload all the necessary files to Deta Space, and run a remote build of your app, resulting in a **revision**. Whenever you run `space push` successfully, a live instance of your API is automatically updated with the latest revision. - -!!! tip - You can manage your revisions by opening your project in the Builder app. The live copy of your API will be visible under the **Develop** tab in Builder. - -## Check it - -The live instance of your API will also be added automatically to your Canvas (the dashboard) on Deta Space. - - - -Click on the new app called `fastapi-deta`, and it will open your API in a new browser tab on a URL like `https://fastapi-deta-gj7ka8.deta.app/`. - -You will get a JSON response from your FastAPI app: - -```JSON -{ - "Hello": "World" -} -``` - -And now you can head over to the `/docs` of your API. For this example, it would be `https://fastapi-deta-gj7ka8.deta.app/docs`. - - - -## Enable public access - -Deta will handle authentication for your account using cookies. By default, every app or API that you `push` or install to your Space is personal - it's only accessible to you. - -But you can also make your API public using the `Spacefile` from earlier. - -With a `public_routes` parameter, you can specify which paths of your API should be available to the public. - -Set your `public_routes` to `"*"` to open every route of your API to the public: - -```yaml -v: 0 -micros: - - name: fastapi-deta - src: . - engine: python3.9 - public_routes: - - "/*" -``` - -Then run `space push` again to update your live API on Deta Space. - -Once it deploys, you can share your URL with anyone and they will be able to access your API. 🚀 - -## HTTPS - -Congrats! You deployed your FastAPI app to Deta Space! 🎉 🍰 - -Also, notice that Deta Space correctly handles HTTPS for you, so you don't have to take care of that and can be sure that your users will have a secure encrypted connection. ✅ 🔒 - -## Create a release - -Space also allows you to publish your API. When you publish it, anyone else can install their own copy of your API, in their own Deta Space cloud. - -To do so, run `space release` in the Space CLI to create an **unlisted release**: - -
- -```console -$ space release - -# Do you want to use the latest revision (buzzard-hczt)? (y/n)$ y - -~ Creating a Release with the latest Revision - ----> 100% - -creating release... -publishing release in edge locations.. -completed... -released: fastapi-deta-exp-msbu -https://deta.space/discovery/r/5kjhgyxewkdmtotx - - Lift off -- successfully created a new Release! - Your Release is available globally on 5 Deta Edges - Anyone can install their own copy of your app. -``` -
- -This command publishes your revision as a release and gives you a link. Anyone you give this link to can install your API. - - -You can also make your app publicly discoverable by creating a **listed release** with `space release --listed` in the Space CLI: - -
- -```console -$ space release --listed - -# Do you want to use the latest revision (buzzard-hczt)? (y/n)$ y - -~ Creating a listed Release with the latest Revision ... - -creating release... -publishing release in edge locations.. -completed... -released: fastapi-deta-exp-msbu -https://deta.space/discovery/@user/fastapi-deta - - Lift off -- successfully created a new Release! - Your Release is available globally on 5 Deta Edges - Anyone can install their own copy of your app. - Listed on Discovery for others to find! -``` -
- -This will allow anyone to find and install your app via Deta Discovery. Read more about releasing your app in the docs. - -## Check runtime logs - -Deta Space also lets you inspect the logs of every app you build or install. - -Add some logging functionality to your app by adding a `print` statement to your `main.py` file. - -```py -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int): - print(item_id) - return {"item_id": item_id} -``` - -The code within the `read_item` function includes a print statement that will output the `item_id` that is included in the URL. Send a request to your _path operation_ `/items/{item_id}` from the docs UI (which will have a URL like `https://fastapi-deta-gj7ka8.deta.app/docs`), using an ID like `5` as an example. - -Now go to your Space's Canvas. Click on the context menu (`...`) of your live app instance, and then click on **View Logs**. Here you can view your app's logs, sorted by time. - - - -## Learn more - -At some point, you will probably want to store some data for your app in a way that persists through time. For that you can use Deta Base and Deta Drive, both of which have a generous **free tier**. - -You can also read more in the Deta Space Documentation. - -!!! tip - If you have any Deta related questions, comments, or feedback, head to the Deta Discord server. - - -## Deployment Concepts - -Coming back to the concepts we discussed in [Deployments Concepts](./concepts.md){.internal-link target=_blank}, here's how each of them would be handled with Deta Space: - -- **HTTPS**: Handled by Deta Space, they will give you a subdomain and handle HTTPS automatically. -- **Running on startup**: Handled by Deta Space, as part of their service. -- **Restarts**: Handled by Deta Space, as part of their service. -- **Replication**: Handled by Deta Space, as part of their service. -- **Authentication**: Handled by Deta Space, as part of their service. -- **Memory**: Limit predefined by Deta Space, you could contact them to increase it. -- **Previous steps before starting**: Can be configured using the `Spacefile`. - -!!! note - Deta Space is designed to make it easy and free to build cloud applications for yourself. Then you can optionally share them with anyone. - - It can simplify several use cases, but at the same time, it doesn't support others, like using external databases (apart from Deta's own NoSQL database system), custom virtual machines, etc. - - You can read more details in the Deta Space Documentation to see if it's the right choice for you. diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index a66b6c147..2a59be4b0 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -159,7 +159,7 @@ nav: - deployment/https.md - deployment/manually.md - deployment/concepts.md - - deployment/deta.md + - deployment/cloud.md - deployment/server-workers.md - deployment/docker.md - project-generation.md diff --git a/docs/fr/docs/deployment/deta.md b/docs/fr/docs/deployment/deta.md deleted file mode 100644 index cceb7b058..000000000 --- a/docs/fr/docs/deployment/deta.md +++ /dev/null @@ -1,245 +0,0 @@ -# Déployer FastAPI sur Deta - -Dans cette section, vous apprendrez à déployer facilement une application **FastAPI** sur Deta en utilisant le plan tarifaire gratuit. 🎁 - -Cela vous prendra environ **10 minutes**. - -!!! info - Deta sponsorise **FastAPI**. 🎉 - -## Une application **FastAPI** de base - -* Créez un répertoire pour votre application, par exemple `./fastapideta/` et déplacez-vous dedans. - -### Le code FastAPI - -* Créer un fichier `main.py` avec : - -```Python -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int): - return {"item_id": item_id} -``` - -### Dépendances - -Maintenant, dans le même répertoire, créez un fichier `requirements.txt` avec : - -```text -fastapi -``` - -!!! tip "Astuce" - Il n'est pas nécessaire d'installer Uvicorn pour déployer sur Deta, bien qu'il soit probablement souhaitable de l'installer localement pour tester votre application. - -### Structure du répertoire - -Vous aurez maintenant un répertoire `./fastapideta/` avec deux fichiers : - -``` -. -└── main.py -└── requirements.txt -``` - -## Créer un compte gratuit sur Deta - -Créez maintenant un compte gratuit -sur Deta, vous avez juste besoin d'une adresse email et d'un mot de passe. - -Vous n'avez même pas besoin d'une carte de crédit. - -## Installer le CLI (Interface en Ligne de Commande) - -Une fois que vous avez votre compte, installez le CLI de Deta : - -=== "Linux, macOS" - -
- - ```console - $ curl -fsSL https://get.deta.dev/cli.sh | sh - ``` - -
- -=== "Windows PowerShell" - -
- - ```console - $ iwr https://get.deta.dev/cli.ps1 -useb | iex - ``` - -
- -Après l'avoir installé, ouvrez un nouveau terminal afin que la nouvelle installation soit détectée. - -Dans un nouveau terminal, confirmez qu'il a été correctement installé avec : - -
- -```console -$ deta --help - -Deta command line interface for managing deta micros. -Complete documentation available at https://docs.deta.sh - -Usage: - deta [flags] - deta [command] - -Available Commands: - auth Change auth settings for a deta micro - -... -``` - -
- -!!! tip "Astuce" - Si vous rencontrez des problèmes pour installer le CLI, consultez la documentation officielle de Deta (en anglais). - -## Connexion avec le CLI - -Maintenant, connectez-vous à Deta depuis le CLI avec : - -
- -```console -$ deta login - -Please, log in from the web page. Waiting.. -Logged in successfully. -``` - -
- -Cela ouvrira un navigateur web et permettra une authentification automatique. - -## Déployer avec Deta - -Ensuite, déployez votre application avec le CLI de Deta : - -
- -```console -$ deta new - -Successfully created a new micro - -// Notice the "endpoint" 🔍 - -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} - -Adding dependencies... - - ----> 100% - - -Successfully installed fastapi-0.61.1 pydantic-1.7.2 starlette-0.13.6 -``` - -
- -Vous verrez un message JSON similaire à : - -```JSON hl_lines="4" -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} -``` - -!!! tip "Astuce" - Votre déploiement aura une URL `"endpoint"` différente. - -## Vérifiez - -Maintenant, dans votre navigateur ouvrez votre URL `endpoint`. Dans l'exemple ci-dessus, c'était -`https://qltnci.deta.dev`, mais la vôtre sera différente. - -Vous verrez la réponse JSON de votre application FastAPI : - -```JSON -{ - "Hello": "World" -} -``` - -Et maintenant naviguez vers `/docs` dans votre API, dans l'exemple ci-dessus ce serait `https://qltnci.deta.dev/docs`. - -Vous verrez votre documentation comme suit : - - - -## Activer l'accès public - -Par défaut, Deta va gérer l'authentification en utilisant des cookies pour votre compte. - -Mais une fois que vous êtes prêt, vous pouvez le rendre public avec : - -
- -```console -$ deta auth disable - -Successfully disabled http auth -``` - -
- -Maintenant, vous pouvez partager cette URL avec n'importe qui et ils seront en mesure d'accéder à votre API. 🚀 - -## HTTPS - -Félicitations ! Vous avez déployé votre application FastAPI sur Deta ! 🎉 🍰 - -Remarquez également que Deta gère correctement HTTPS pour vous, vous n'avez donc pas à vous en occuper et pouvez être sûr que vos clients auront une connexion cryptée sécurisée. ✅ 🔒 - -## Vérifiez le Visor - -À partir de l'interface graphique de votre documentation (dans une URL telle que `https://qltnci.deta.dev/docs`) -envoyez une requête à votre *opération de chemin* `/items/{item_id}`. - -Par exemple avec l'ID `5`. - -Allez maintenant sur https://web.deta.sh. - -Vous verrez qu'il y a une section à gauche appelée "Micros" avec chacune de vos applications. - -Vous verrez un onglet avec "Details", et aussi un onglet "Visor", allez à l'onglet "Visor". - -Vous pouvez y consulter les requêtes récentes envoyées à votre application. - -Vous pouvez également les modifier et les relancer. - - - -## En savoir plus - -À un moment donné, vous voudrez probablement stocker certaines données pour votre application d'une manière qui -persiste dans le temps. Pour cela, vous pouvez utiliser Deta Base, il dispose également d'un généreux **plan gratuit**. - -Vous pouvez également en lire plus dans la documentation Deta. diff --git a/docs/ja/docs/deployment/deta.md b/docs/ja/docs/deployment/deta.md deleted file mode 100644 index 723f169a0..000000000 --- a/docs/ja/docs/deployment/deta.md +++ /dev/null @@ -1,240 +0,0 @@ -# Deta にデプロイ - -このセクションでは、**FastAPI** アプリケーションを Deta の無料プランを利用して、簡単にデプロイする方法を学習します。🎁 - -所要時間は約**10分**です。 - -!!! info "備考" - Deta は **FastAPI** のスポンサーです。🎉 - -## ベーシックな **FastAPI** アプリ - -* アプリのためのディレクトリ (例えば `./fastapideta/`) を作成し、その中に入ってください。 - -### FastAPI のコード - -* 以下の `main.py` ファイルを作成してください: - -```Python -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int): - return {"item_id": item_id} -``` - -### Requirements - -では、同じディレクトリに以下の `requirements.txt` ファイルを作成してください: - -```text -fastapi -``` - -!!! tip "豆知識" - アプリのローカルテストのために Uvicorn をインストールしたくなるかもしれませんが、Deta へのデプロイには不要です。 - -### ディレクトリ構造 - -以下の2つのファイルと1つの `./fastapideta/` ディレクトリがあるはずです: - -``` -. -└── main.py -└── requirements.txt -``` - -## Detaの無料アカウントの作成 - -それでは、Detaの無料アカウントを作成しましょう。必要なものはメールアドレスとパスワードだけです。 - -クレジットカードさえ必要ありません。 - -## CLIのインストール - -アカウントを取得したら、Deta CLI をインストールしてください: - -=== "Linux, macOS" - -
- - ```console - $ curl -fsSL https://get.deta.dev/cli.sh | sh - ``` - -
- -=== "Windows PowerShell" - -
- - ```console - $ iwr https://get.deta.dev/cli.ps1 -useb | iex - ``` - -
- -インストールしたら、インストールした CLI を有効にするために新たなターミナルを開いてください。 - -新たなターミナル上で、正しくインストールされたか確認します: - -
- -```console -$ deta --help - -Deta command line interface for managing deta micros. -Complete documentation available at https://docs.deta.sh - -Usage: - deta [flags] - deta [command] - -Available Commands: - auth Change auth settings for a deta micro - -... -``` - -
- -!!! tip "豆知識" - CLI のインストールに問題が発生した場合は、Deta 公式ドキュメントを参照してください。 - -## CLIでログイン - -CLI から Deta にログインしてみましょう: - -
- -```console -$ deta login - -Please, log in from the web page. Waiting.. -Logged in successfully. -``` - -
- -自動的にウェブブラウザが開いて、認証処理が行われます。 - -## Deta でデプロイ - -次に、アプリケーションを Deta CLIでデプロイしましょう: - -
- -```console -$ deta new - -Successfully created a new micro - -// Notice the "endpoint" 🔍 - -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} - -Adding dependencies... - - ----> 100% - - -Successfully installed fastapi-0.61.1 pydantic-1.7.2 starlette-0.13.6 -``` - -
- -次のようなJSONメッセージが表示されます: - -```JSON hl_lines="4" -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} -``` - -!!! tip "豆知識" - あなたのデプロイでは異なる `"endpoint"` URLが表示されるでしょう。 - -## 確認 - -それでは、`endpoint` URLをブラウザで開いてみましょう。上記の例では `https://qltnci.deta.dev` ですが、あなたのURLは異なるはずです。 - -FastAPIアプリから返ってきたJSONレスポンスが表示されます: - -```JSON -{ - "Hello": "World" -} -``` - -そして `/docs` へ移動してください。上記の例では、`https://qltnci.deta.dev/docs` です。 - -次のようなドキュメントが表示されます: - - - -## パブリックアクセスの有効化 - -デフォルトでは、Deta はクッキーを用いてアカウントの認証を行います。 - -しかし、準備が整えば、以下の様に公開できます: - -
- -```console -$ deta auth disable - -Successfully disabled http auth -``` - -
- -ここで、URLを共有するとAPIにアクセスできるようになります。🚀 - -## HTTPS - -おめでとうございます!あなたの FastAPI アプリが Deta へデプロイされました!🎉 🍰 - -また、DetaがHTTPSを正しく処理するため、その処理を行う必要がなく、クライアントは暗号化された安全な通信が利用できます。✅ 🔒 - -## Visor を確認 - -ドキュメントUI (`https://qltnci.deta.dev/docs` のようなURLにある) は *path operation* `/items/{item_id}` へリクエストを送ることができます。 - -ID `5` の例を示します。 - -まず、https://web.deta.sh へアクセスします。 - -左側に各アプリの 「Micros」 というセクションが表示されます。 - -また、「Details」や「Visor」タブが表示されています。「Visor」タブへ移動してください。 - -そこでアプリに送られた直近のリクエストが調べられます。 - -また、それらを編集してリプレイできます。 - - - -## さらに詳しく知る - -様々な箇所で永続的にデータを保存したくなるでしょう。そのためには Deta Base を使用できます。惜しみない **無料利用枠** もあります。 - -詳しくは Deta ドキュメントを参照してください。 diff --git a/docs/pt/docs/deployment/deta.md b/docs/pt/docs/deployment/deta.md deleted file mode 100644 index 9271bba42..000000000 --- a/docs/pt/docs/deployment/deta.md +++ /dev/null @@ -1,258 +0,0 @@ -# Implantação FastAPI na Deta - -Nessa seção você aprenderá sobre como realizar a implantação de uma aplicação **FastAPI** na Deta utilizando o plano gratuito. 🎁 - -Isso tudo levará aproximadamente **10 minutos**. - -!!! info "Informação" - Deta é uma patrocinadora do **FastAPI**. 🎉 - -## Uma aplicação **FastAPI** simples - -* Crie e entre em um diretório para a sua aplicação, por exemplo, `./fastapideta/`. - -### Código FastAPI - -* Crie o arquivo `main.py` com: - -```Python -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int): - return {"item_id": item_id} -``` - -### Requisitos - -Agora, no mesmo diretório crie o arquivo `requirements.txt` com: - -```text -fastapi -``` - -!!! tip "Dica" - Você não precisa instalar Uvicorn para realizar a implantação na Deta, embora provavelmente queira instalá-lo para testar seu aplicativo localmente. - -### Estrutura de diretório - -Agora você terá o diretório `./fastapideta/` com dois arquivos: - -``` -. -└── main.py -└── requirements.txt -``` - -## Crie uma conta gratuita na Deta - -Agora crie uma conta gratuita na Deta, você precisará apenas de um email e senha. - -Você nem precisa de um cartão de crédito. - -## Instale a CLI - -Depois de ter sua conta criada, instale Deta CLI: - -=== "Linux, macOS" - -
- - ```console - $ curl -fsSL https://get.deta.dev/cli.sh | sh - ``` - -
- -=== "Windows PowerShell" - -
- - ```console - $ iwr https://get.deta.dev/cli.ps1 -useb | iex - ``` - -
- -Após a instalação, abra um novo terminal para que a CLI seja detectada. - -Em um novo terminal, confirme se foi instalado corretamente com: - -
- -```console -$ deta --help - -Deta command line interface for managing deta micros. -Complete documentation available at https://docs.deta.sh - -Usage: - deta [flags] - deta [command] - -Available Commands: - auth Change auth settings for a deta micro - -... -``` - -
- -!!! tip "Dica" - Se você tiver problemas ao instalar a CLI, verifique a documentação oficial da Deta. - -## Login pela CLI - -Agora faça login na Deta pela CLI com: - -
- -```console -$ deta login - -Please, log in from the web page. Waiting.. -Logged in successfully. -``` - -
- -Isso abrirá um navegador da Web e autenticará automaticamente. - -## Implantação com Deta - -Em seguida, implante seu aplicativo com a Deta CLI: - -
- -```console -$ deta new - -Successfully created a new micro - -// Notice the "endpoint" 🔍 - -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} - -Adding dependencies... - - ----> 100% - - -Successfully installed fastapi-0.61.1 pydantic-1.7.2 starlette-0.13.6 -``` - -
- -Você verá uma mensagem JSON semelhante a: - -```JSON hl_lines="4" -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} -``` - -!!! tip "Dica" - Sua implantação terá um URL `"endpoint"` diferente. - -## Confira - -Agora, abra seu navegador na URL do `endpoint`. No exemplo acima foi `https://qltnci.deta.dev`, mas o seu será diferente. - -Você verá a resposta JSON do seu aplicativo FastAPI: - -```JSON -{ - "Hello": "World" -} -``` - -Agora vá para o `/docs` da sua API, no exemplo acima seria `https://qltnci.deta.dev/docs`. - -Ele mostrará sua documentação como: - - - -## Permitir acesso público - -Por padrão, a Deta lidará com a autenticação usando cookies para sua conta. - -Mas quando estiver pronto, você pode torná-lo público com: - -
- -```console -$ deta auth disable - -Successfully disabled http auth -``` - -
- -Agora você pode compartilhar essa URL com qualquer pessoa e elas conseguirão acessar sua API. 🚀 - -## HTTPS - -Parabéns! Você realizou a implantação do seu app FastAPI na Deta! 🎉 🍰 - -Além disso, observe que a Deta lida corretamente com HTTPS para você, para que você não precise cuidar disso e tenha a certeza de que seus clientes terão uma conexão criptografada segura. ✅ 🔒 - -## Verifique o Visor - -Na UI da sua documentação (você estará em um URL como `https://qltnci.deta.dev/docs`) envie um request para *operação de rota* `/items/{item_id}`. - -Por exemplo com ID `5`. - -Agora vá para https://web.deta.sh. - -Você verá que há uma seção à esquerda chamada "Micros" com cada um dos seus apps. - -Você verá uma aba com "Detalhes", e também a aba "Visor", vá para "Visor". - -Lá você pode inspecionar as solicitações recentes enviadas ao seu aplicativo. - -Você também pode editá-los e reproduzi-los novamente. - - - -## Saiba mais - -Em algum momento, você provavelmente desejará armazenar alguns dados para seu aplicativo de uma forma que persista ao longo do tempo. Para isso você pode usar Deta Base, que também tem um generoso **nível gratuito**. - -Você também pode ler mais na documentação da Deta. - -## Conceitos de implantação - -Voltando aos conceitos que discutimos em [Deployments Concepts](./concepts.md){.internal-link target=_blank}, veja como cada um deles seria tratado com a Deta: - -* **HTTPS**: Realizado pela Deta, eles fornecerão um subdomínio e lidarão com HTTPS automaticamente. -* **Executando na inicialização**: Realizado pela Deta, como parte de seu serviço. -* **Reinicialização**: Realizado pela Deta, como parte de seu serviço. -* **Replicação**: Realizado pela Deta, como parte de seu serviço. -* **Memória**: Limite predefinido pela Deta, você pode contatá-los para aumentá-lo. -* **Etapas anteriores a inicialização**: Não suportado diretamente, você pode fazê-lo funcionar com o sistema Cron ou scripts adicionais. - -!!! note "Nota" - O Deta foi projetado para facilitar (e gratuitamente) a implantação rápida de aplicativos simples. - - Ele pode simplificar vários casos de uso, mas, ao mesmo tempo, não suporta outros, como o uso de bancos de dados externos (além do próprio sistema de banco de dados NoSQL da Deta), máquinas virtuais personalizadas, etc. - - Você pode ler mais detalhes na documentação da Deta para ver se é a escolha certa para você. From e04953a9e0c60a4afdf78652ac9f62bfce5a9349 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 19 Aug 2023 13:12:09 +0000 Subject: [PATCH 004/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6ef2e6a45..f55d8281a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Restructure docs for cloud providers, include links to sponsors. PR [#10110](https://github.com/tiangolo/fastapi/pull/10110) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update sponsors, add Speakeasy. PR [#10098](https://github.com/tiangolo/fastapi/pull/10098) by [@tiangolo](https://github.com/tiangolo). ## 0.101.1 From d1c0e5a89f07311384c4ea579d421dfa02f9f6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 19 Aug 2023 15:33:32 +0200 Subject: [PATCH 005/188] =?UTF-8?q?=F0=9F=93=9D=20Tweak=20MkDocs=20and=20a?= =?UTF-8?q?dd=20redirects=20(#10111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/mkdocs.yml | 16 +++++++++++----- requirements-docs.txt | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 2a59be4b0..22babe745 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -42,6 +42,9 @@ plugins: search: null markdownextradata: data: ../en/data + redirects: + redirect_maps: + deployment/deta.md: deployment/cloud.md nav: - FastAPI: index.md - Languages: @@ -60,6 +63,7 @@ nav: - ru: /ru/ - tr: /tr/ - uk: /uk/ + - ur: /ur/ - vi: /vi/ - zh: /zh/ - features.md @@ -178,9 +182,9 @@ markdown_extensions: guess_lang: false mdx_include: base_path: docs - admonition: - codehilite: - extra: + admonition: null + codehilite: null + extra: null pymdownx.superfences: custom_fences: - name: mermaid @@ -188,8 +192,8 @@ markdown_extensions: format: !!python/name:pymdownx.superfences.fence_code_format '' pymdownx.tabbed: alternate_style: true - attr_list: - md_in_html: + attr_list: null + md_in_html: null extra: analytics: provider: google @@ -240,6 +244,8 @@ extra: name: tr - Türkçe - link: /uk/ name: uk + - link: /ur/ + name: ur - link: /vi/ name: vi - Tiếng Việt - link: /zh/ diff --git a/requirements-docs.txt b/requirements-docs.txt index 220d1ec3a..2e667720e 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -2,6 +2,7 @@ mkdocs-material==9.1.21 mdx-include >=1.4.1,<2.0.0 mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0 +mkdocs-redirects>=1.2.1,<1.3.0 typer-cli >=0.0.13,<0.0.14 typer[all] >=0.6.1,<0.8.0 pyyaml >=5.3.1,<7.0.0 From 0fe434ca683e4c1786e4f190a62eaad7aea71240 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 19 Aug 2023 13:34:10 +0000 Subject: [PATCH 006/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f55d8281a..fe1153254 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Tweak MkDocs and add redirects. PR [#10111](https://github.com/tiangolo/fastapi/pull/10111) by [@tiangolo](https://github.com/tiangolo). * 📝 Restructure docs for cloud providers, include links to sponsors. PR [#10110](https://github.com/tiangolo/fastapi/pull/10110) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update sponsors, add Speakeasy. PR [#10098](https://github.com/tiangolo/fastapi/pull/10098) by [@tiangolo](https://github.com/tiangolo). ## 0.101.1 From 08feaf0cc43a76feef2bb02170792cb79dda7c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 19 Aug 2023 15:49:54 +0200 Subject: [PATCH 007/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20for=20ge?= =?UTF-8?q?nerating=20clients=20(#10112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/generate-clients.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/en/docs/advanced/generate-clients.md b/docs/en/docs/advanced/generate-clients.md index 3fed48b0b..f439ed93a 100644 --- a/docs/en/docs/advanced/generate-clients.md +++ b/docs/en/docs/advanced/generate-clients.md @@ -12,10 +12,18 @@ A common tool is openapi-typescript-codegen. -Another option you could consider for several languages is Fern. +## Client and SDK Generators - Sponsor -!!! info - Fern is also a FastAPI sponsor. 😎🎉 +There are also some **company-backed** Client and SDK generators based on OpenAPI (FastAPI), in some cases they can offer you **additional features** on top of high-quality generated SDKs/clients. + +Some of them also ✨ [**sponsor FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, this ensures the continued and healthy **development** of FastAPI and its **ecosystem**. + +And it shows their true commitment to FastAPI and its **community** (you), as they not only want to provide you a **good service** but also want to make sure you have a **good and healthy framework**, FastAPI. 🙇 + +You might want to try their services and follow their guides: + +* Fern +* Speakeasy ## Generate a TypeScript Frontend Client From 486cd139a94668a0a5a5b24aedb6c763aa26857e Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 19 Aug 2023 13:51:12 +0000 Subject: [PATCH 008/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index fe1153254..d7915d079 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Update docs for generating clients. PR [#10112](https://github.com/tiangolo/fastapi/pull/10112) by [@tiangolo](https://github.com/tiangolo). * 📝 Tweak MkDocs and add redirects. PR [#10111](https://github.com/tiangolo/fastapi/pull/10111) by [@tiangolo](https://github.com/tiangolo). * 📝 Restructure docs for cloud providers, include links to sponsors. PR [#10110](https://github.com/tiangolo/fastapi/pull/10110) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update sponsors, add Speakeasy. PR [#10098](https://github.com/tiangolo/fastapi/pull/10098) by [@tiangolo](https://github.com/tiangolo). From 8e382617873665cc7f2323e8742aa5eb04292600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 19 Aug 2023 16:08:16 +0200 Subject: [PATCH 009/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20Advanced=20docs?= =?UTF-8?q?,=20add=20links=20to=20sponsor=20courses=20(#10113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/index.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/en/docs/advanced/index.md b/docs/en/docs/advanced/index.md index 467f0833e..d8dcd4ca6 100644 --- a/docs/en/docs/advanced/index.md +++ b/docs/en/docs/advanced/index.md @@ -17,8 +17,17 @@ You could still use most of the features in **FastAPI** with the knowledge from And the next sections assume you already read it, and assume that you know those main ideas. -## TestDriven.io course +## External Courses -If you would like to take an advanced-beginner course to complement this section of the docs, you might want to check: Test-Driven Development with FastAPI and Docker by **TestDriven.io**. +Although the [Tutorial - User Guide](../tutorial/){.internal-link target=_blank} and this **Advanced User Guide** are written as a guided tutorial (like a book) and should be enough for you to **learn FastAPI**, you might want to complement it with additional courses. -They are currently donating 10% of all profits to the development of **FastAPI**. 🎉 😄 +Or it might be the case that you just prefer to take other courses because they adapt better to your learning style. + +Some course providers ✨ [**sponsor FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, this ensures the continued and healthy **development** of FastAPI and its **ecosystem**. + +And it shows their true commitment to FastAPI and its **community** (you), as they not only want to provide you a **good learning experience** but also want to make sure you have a **good and healthy framework**, FastAPI. 🙇 + +You might want to try their courses: + +* Talk Python Training +* Test-Driven Development From b406dd917486cbb429541930370b6a20d906cf99 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 19 Aug 2023 14:09:02 +0000 Subject: [PATCH 010/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d7915d079..1916564ae 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Update Advanced docs, add links to sponsor courses. PR [#10113](https://github.com/tiangolo/fastapi/pull/10113) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for generating clients. PR [#10112](https://github.com/tiangolo/fastapi/pull/10112) by [@tiangolo](https://github.com/tiangolo). * 📝 Tweak MkDocs and add redirects. PR [#10111](https://github.com/tiangolo/fastapi/pull/10111) by [@tiangolo](https://github.com/tiangolo). * 📝 Restructure docs for cloud providers, include links to sponsors. PR [#10110](https://github.com/tiangolo/fastapi/pull/10110) by [@tiangolo](https://github.com/tiangolo). From 7a06de2bb9de12727fe544b602f9d74f7a1b0bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 19 Aug 2023 20:47:59 +0200 Subject: [PATCH 011/188] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20tests?= =?UTF-8?q?=20for=20new=20Pydantic=202.2.1=20(#10115)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 4 +-- tests/test_multi_body_errors.py | 36 +++---------------- .../test_body_updates/test_tutorial001.py | 10 +++--- .../test_tutorial001_py310.py | 10 +++--- .../test_tutorial001_py39.py | 10 +++--- .../test_dataclasses/test_tutorial003.py | 10 +++--- .../test_tutorial004.py | 8 ++--- .../test_tutorial005.py | 8 ++--- .../test_tutorial005_py310.py | 8 ++--- .../test_tutorial005_py39.py | 8 ++--- 10 files changed, 44 insertions(+), 68 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a512a019..c9723b25b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-pydantic-v2-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v04 + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-pydantic-v2-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v05 - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' run: pip install -r requirements-tests.txt @@ -62,7 +62,7 @@ jobs: id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ matrix.pydantic-version }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v04 + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ matrix.pydantic-version }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v05 - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' run: pip install -r requirements-tests.txt diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py index 931f08fc1..a51ca7253 100644 --- a/tests/test_multi_body_errors.py +++ b/tests/test_multi_body_errors.py @@ -51,7 +51,7 @@ def test_jsonable_encoder_requiring_error(): "loc": ["body", 0, "age"], "msg": "Input should be greater than 0", "input": -1.0, - "ctx": {"gt": "0"}, + "ctx": {"gt": 0}, "url": match_pydantic_error_url("greater_than"), } ] @@ -84,25 +84,12 @@ def test_put_incorrect_body_multiple(): "input": {"age": "five"}, "url": match_pydantic_error_url("missing"), }, - { - "ctx": {"class": "Decimal"}, - "input": "five", - "loc": ["body", 0, "age", "is-instance[Decimal]"], - "msg": "Input should be an instance of Decimal", - "type": "is_instance_of", - "url": match_pydantic_error_url("is_instance_of"), - }, { "type": "decimal_parsing", - "loc": [ - "body", - 0, - "age", - "function-after[to_decimal(), " - "union[int,constrained-str,function-plain[str()]]]", - ], + "loc": ["body", 0, "age"], "msg": "Input should be a valid decimal", "input": "five", + "url": match_pydantic_error_url("decimal_parsing"), }, { "type": "missing", @@ -111,25 +98,12 @@ def test_put_incorrect_body_multiple(): "input": {"age": "six"}, "url": match_pydantic_error_url("missing"), }, - { - "ctx": {"class": "Decimal"}, - "input": "six", - "loc": ["body", 1, "age", "is-instance[Decimal]"], - "msg": "Input should be an instance of Decimal", - "type": "is_instance_of", - "url": match_pydantic_error_url("is_instance_of"), - }, { "type": "decimal_parsing", - "loc": [ - "body", - 1, - "age", - "function-after[to_decimal(), " - "union[int,constrained-str,function-plain[str()]]]", - ], + "loc": ["body", 1, "age"], "msg": "Input should be a valid decimal", "input": "six", + "url": match_pydantic_error_url("decimal_parsing"), }, ] } diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py index f1a46210a..58587885e 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py @@ -53,7 +53,7 @@ def test_openapi_schema(client: TestClient): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -87,7 +87,7 @@ def test_openapi_schema(client: TestClient): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -116,7 +116,7 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/ItemInput"} + "schema": {"$ref": "#/components/schemas/Item-Input"} } }, "required": True, @@ -126,7 +126,7 @@ def test_openapi_schema(client: TestClient): }, "components": { "schemas": { - "ItemInput": { + "Item-Input": { "title": "Item", "type": "object", "properties": { @@ -151,7 +151,7 @@ def test_openapi_schema(client: TestClient): }, }, }, - "ItemOutput": { + "Item-Output": { "title": "Item", "type": "object", "required": ["name", "description", "price", "tax", "tags"], diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py index ab696e4c8..d8a62502f 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py @@ -56,7 +56,7 @@ def test_openapi_schema(client: TestClient): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -90,7 +90,7 @@ def test_openapi_schema(client: TestClient): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -119,7 +119,7 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/ItemInput"} + "schema": {"$ref": "#/components/schemas/Item-Input"} } }, "required": True, @@ -129,7 +129,7 @@ def test_openapi_schema(client: TestClient): }, "components": { "schemas": { - "ItemInput": { + "Item-Input": { "title": "Item", "type": "object", "properties": { @@ -154,7 +154,7 @@ def test_openapi_schema(client: TestClient): }, }, }, - "ItemOutput": { + "Item-Output": { "title": "Item", "type": "object", "required": ["name", "description", "price", "tax", "tags"], diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py index 2ee6a5cb4..c604df6ec 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py @@ -56,7 +56,7 @@ def test_openapi_schema(client: TestClient): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -90,7 +90,7 @@ def test_openapi_schema(client: TestClient): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -119,7 +119,7 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/ItemInput"} + "schema": {"$ref": "#/components/schemas/Item-Input"} } }, "required": True, @@ -129,7 +129,7 @@ def test_openapi_schema(client: TestClient): }, "components": { "schemas": { - "ItemInput": { + "Item-Input": { "title": "Item", "type": "object", "properties": { @@ -154,7 +154,7 @@ def test_openapi_schema(client: TestClient): }, }, }, - "ItemOutput": { + "Item-Output": { "title": "Item", "type": "object", "required": ["name", "description", "price", "tax", "tags"], diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index 2e5809914..f2ca85823 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -79,7 +79,9 @@ def test_openapi_schema(): "schema": { "title": "Items", "type": "array", - "items": {"$ref": "#/components/schemas/ItemInput"}, + "items": { + "$ref": "#/components/schemas/Item-Input" + }, } } }, @@ -141,7 +143,7 @@ def test_openapi_schema(): "items": { "title": "Items", "type": "array", - "items": {"$ref": "#/components/schemas/ItemOutput"}, + "items": {"$ref": "#/components/schemas/Item-Output"}, }, }, }, @@ -156,7 +158,7 @@ def test_openapi_schema(): } }, }, - "ItemInput": { + "Item-Input": { "title": "Item", "required": ["name"], "type": "object", @@ -168,7 +170,7 @@ def test_openapi_schema(): }, }, }, - "ItemOutput": { + "Item-Output": { "title": "Item", "required": ["name", "description"], "type": "object", diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py index 3ffc0bca7..c5b2fb670 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py @@ -35,7 +35,7 @@ def test_openapi_schema(): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -57,7 +57,7 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/ItemInput"} + "schema": {"$ref": "#/components/schemas/Item-Input"} } }, "required": True, @@ -67,7 +67,7 @@ def test_openapi_schema(): }, "components": { "schemas": { - "ItemInput": { + "Item-Input": { "title": "Item", "required": ["name", "price"], "type": "object", @@ -91,7 +91,7 @@ def test_openapi_schema(): }, }, }, - "ItemOutput": { + "Item-Output": { "title": "Item", "required": ["name", "description", "price", "tax", "tags"], "type": "object", diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py index ff98295a6..458923b5a 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py @@ -35,7 +35,7 @@ def test_openapi_schema(): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -57,7 +57,7 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/ItemInput"} + "schema": {"$ref": "#/components/schemas/Item-Input"} } }, "required": True, @@ -67,7 +67,7 @@ def test_openapi_schema(): }, "components": { "schemas": { - "ItemInput": { + "Item-Input": { "title": "Item", "required": ["name", "price"], "type": "object", @@ -91,7 +91,7 @@ def test_openapi_schema(): }, }, }, - "ItemOutput": { + "Item-Output": { "title": "Item", "required": ["name", "description", "price", "tax", "tags"], "type": "object", diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py index ad1c09eae..1fcc5c4e0 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py @@ -42,7 +42,7 @@ def test_openapi_schema(client: TestClient): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -64,7 +64,7 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/ItemInput"} + "schema": {"$ref": "#/components/schemas/Item-Input"} } }, "required": True, @@ -74,7 +74,7 @@ def test_openapi_schema(client: TestClient): }, "components": { "schemas": { - "ItemInput": { + "Item-Input": { "title": "Item", "required": ["name", "price"], "type": "object", @@ -98,7 +98,7 @@ def test_openapi_schema(client: TestClient): }, }, }, - "ItemOutput": { + "Item-Output": { "title": "Item", "required": ["name", "description", "price", "tax", "tags"], "type": "object", diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py index 045d1d402..470fe032b 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py @@ -42,7 +42,7 @@ def test_openapi_schema(client: TestClient): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ItemOutput" + "$ref": "#/components/schemas/Item-Output" } } }, @@ -64,7 +64,7 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/ItemInput"} + "schema": {"$ref": "#/components/schemas/Item-Input"} } }, "required": True, @@ -74,7 +74,7 @@ def test_openapi_schema(client: TestClient): }, "components": { "schemas": { - "ItemInput": { + "Item-Input": { "title": "Item", "required": ["name", "price"], "type": "object", @@ -98,7 +98,7 @@ def test_openapi_schema(client: TestClient): }, }, }, - "ItemOutput": { + "Item-Output": { "title": "Item", "required": ["name", "description", "price", "tax", "tags"], "type": "object", From 3971c44a38fa703f56eb1801765d3f076a5c1b2c Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 19 Aug 2023 18:48:35 +0000 Subject: [PATCH 012/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1916564ae..48a809021 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ♻️ Refactor tests for new Pydantic 2.2.1. PR [#10115](https://github.com/tiangolo/fastapi/pull/10115) by [@tiangolo](https://github.com/tiangolo). * 📝 Update Advanced docs, add links to sponsor courses. PR [#10113](https://github.com/tiangolo/fastapi/pull/10113) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for generating clients. PR [#10112](https://github.com/tiangolo/fastapi/pull/10112) by [@tiangolo](https://github.com/tiangolo). * 📝 Tweak MkDocs and add redirects. PR [#10111](https://github.com/tiangolo/fastapi/pull/10111) by [@tiangolo](https://github.com/tiangolo). From 8cd7cfc2b622fad03455c324e2ae87014a3fd166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 19 Aug 2023 21:54:04 +0200 Subject: [PATCH 013/188] =?UTF-8?q?=F0=9F=93=9D=20Add=20new=20docs=20secti?= =?UTF-8?q?on,=20How=20To=20-=20Recipes,=20move=20docs=20that=20don't=20ha?= =?UTF-8?q?ve=20to=20be=20read=20by=20everyone=20to=20How=20To=20(#10114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📝 Start How To docs section, move Peewee, remove Peewee from dependencies * 🚚 Move em files to new locations * 🚚 Move and re-structure advanced docs, move relevant to How To * 🔧 Update MkDocs config, new files in How To * 📝 Move docs for Conditional OpenAPI for Japanese to How To * 📝 Move example source files for Extending OpenAPI into each of the new sections * ✅ Update tests with new locations for source files * 🔥 Remove init from Peewee examples --- docs/em/docs/advanced/extending-openapi.md | 314 ------------ .../conditional-openapi.md | 0 .../custom-request-and-route.md | 0 docs/em/docs/how-to/extending-openapi.md | 90 ++++ docs/em/docs/{advanced => how-to}/graphql.md | 0 .../sql-databases-peewee.md | 0 docs/en/docs/advanced/extending-openapi.md | 318 ------------ .../async-sql-encode-databases.md} | 2 +- .../conditional-openapi.md | 0 docs/en/docs/how-to/configure-swagger-ui.md | 78 +++ docs/en/docs/how-to/custom-docs-ui-assets.md | 199 ++++++++ .../custom-request-and-route.md | 0 docs/en/docs/how-to/extending-openapi.md | 87 ++++ docs/en/docs/how-to/general.md | 39 ++ docs/en/docs/{advanced => how-to}/graphql.md | 0 docs/en/docs/how-to/index.md | 11 + .../nosql-databases-couchbase.md} | 2 +- .../sql-databases-peewee.md | 2 + docs/en/mkdocs.yml | 26 +- .../conditional-openapi.md | 0 .../tutorial001.py} | 0 .../tutorial002.py} | 0 .../tutorial003.py} | 0 docs_src/custom_docs_ui/tutorial001.py | 38 ++ .../tutorial002.py | 0 requirements-tests.txt | 1 - .../test_configure_swagger_ui}/__init__.py | 0 .../test_tutorial001.py} | 2 +- .../test_tutorial002.py} | 2 +- .../test_tutorial003.py} | 2 +- .../__init__.py | 0 .../test_custom_docs_ui/test_tutorial001.py | 42 ++ .../test_tutorial002.py | 2 +- .../test_sql_databases_peewee.py | 454 ------------------ 34 files changed, 611 insertions(+), 1100 deletions(-) delete mode 100644 docs/em/docs/advanced/extending-openapi.md rename docs/em/docs/{advanced => how-to}/conditional-openapi.md (100%) rename docs/em/docs/{advanced => how-to}/custom-request-and-route.md (100%) create mode 100644 docs/em/docs/how-to/extending-openapi.md rename docs/em/docs/{advanced => how-to}/graphql.md (100%) rename docs/em/docs/{advanced => how-to}/sql-databases-peewee.md (100%) delete mode 100644 docs/en/docs/advanced/extending-openapi.md rename docs/en/docs/{advanced/async-sql-databases.md => how-to/async-sql-encode-databases.md} (98%) rename docs/en/docs/{advanced => how-to}/conditional-openapi.md (100%) create mode 100644 docs/en/docs/how-to/configure-swagger-ui.md create mode 100644 docs/en/docs/how-to/custom-docs-ui-assets.md rename docs/en/docs/{advanced => how-to}/custom-request-and-route.md (100%) create mode 100644 docs/en/docs/how-to/extending-openapi.md create mode 100644 docs/en/docs/how-to/general.md rename docs/en/docs/{advanced => how-to}/graphql.md (100%) create mode 100644 docs/en/docs/how-to/index.md rename docs/en/docs/{advanced/nosql-databases.md => how-to/nosql-databases-couchbase.md} (99%) rename docs/en/docs/{advanced => how-to}/sql-databases-peewee.md (99%) rename docs/ja/docs/{advanced => how-to}/conditional-openapi.md (100%) rename docs_src/{extending_openapi/tutorial003.py => configure_swagger_ui/tutorial001.py} (100%) rename docs_src/{extending_openapi/tutorial004.py => configure_swagger_ui/tutorial002.py} (100%) rename docs_src/{extending_openapi/tutorial005.py => configure_swagger_ui/tutorial003.py} (100%) create mode 100644 docs_src/custom_docs_ui/tutorial001.py rename docs_src/{extending_openapi => custom_docs_ui}/tutorial002.py (100%) rename {docs_src/sql_databases_peewee => tests/test_tutorial/test_configure_swagger_ui}/__init__.py (100%) rename tests/test_tutorial/{test_extending_openapi/test_tutorial003.py => test_configure_swagger_ui/test_tutorial001.py} (95%) rename tests/test_tutorial/{test_extending_openapi/test_tutorial004.py => test_configure_swagger_ui/test_tutorial002.py} (96%) rename tests/test_tutorial/{test_extending_openapi/test_tutorial005.py => test_configure_swagger_ui/test_tutorial003.py} (96%) rename tests/test_tutorial/{test_sql_databases_peewee => test_custom_docs_ui}/__init__.py (100%) create mode 100644 tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py rename tests/test_tutorial/{test_extending_openapi => test_custom_docs_ui}/test_tutorial002.py (95%) delete mode 100644 tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py diff --git a/docs/em/docs/advanced/extending-openapi.md b/docs/em/docs/advanced/extending-openapi.md deleted file mode 100644 index 496a8d9de..000000000 --- a/docs/em/docs/advanced/extending-openapi.md +++ /dev/null @@ -1,314 +0,0 @@ -# ↔ 🗄 - -!!! warning - 👉 👍 🏧 ⚒. 👆 🎲 💪 🚶 ⚫️. - - 🚥 👆 📄 🔰 - 👩‍💻 🦮, 👆 💪 🎲 🚶 👉 📄. - - 🚥 👆 ⏪ 💭 👈 👆 💪 🔀 🏗 🗄 🔗, 😣 👂. - -📤 💼 🌐❔ 👆 💪 💪 🔀 🏗 🗄 🔗. - -👉 📄 👆 🔜 👀 ❔. - -## 😐 🛠️ - -😐 (🔢) 🛠️, ⏩. - -`FastAPI` 🈸 (👐) ✔️ `.openapi()` 👩‍🔬 👈 📈 📨 🗄 🔗. - -🍕 🈸 🎚 🏗, *➡ 🛠️* `/openapi.json` (⚖️ ⚫️❔ 👆 ⚒ 👆 `openapi_url`) ®. - -⚫️ 📨 🎻 📨 ⏮️ 🏁 🈸 `.openapi()` 👩‍🔬. - -🔢, ⚫️❔ 👩‍🔬 `.openapi()` 🔨 ✅ 🏠 `.openapi_schema` 👀 🚥 ⚫️ ✔️ 🎚 & 📨 👫. - -🚥 ⚫️ 🚫, ⚫️ 🏗 👫 ⚙️ 🚙 🔢 `fastapi.openapi.utils.get_openapi`. - -& 👈 🔢 `get_openapi()` 📨 🔢: - -* `title`: 🗄 📛, 🎦 🩺. -* `version`: ⏬ 👆 🛠️, ✅ `2.5.0`. -* `openapi_version`: ⏬ 🗄 🔧 ⚙️. 🔢, ⏪: `3.0.2`. -* `description`: 📛 👆 🛠️. -* `routes`: 📇 🛣, 👫 🔠 ® *➡ 🛠️*. 👫 ✊ ⚪️➡️ `app.routes`. - -## 🔑 🔢 - -⚙️ ℹ 🔛, 👆 💪 ⚙️ 🎏 🚙 🔢 🏗 🗄 🔗 & 🔐 🔠 🍕 👈 👆 💪. - -🖼, ➡️ 🚮 📄 🗄 ↔ 🔌 🛃 🔱. - -### 😐 **FastAPI** - -🥇, ✍ 🌐 👆 **FastAPI** 🈸 🛎: - -```Python hl_lines="1 4 7-9" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### 🏗 🗄 🔗 - -⤴️, ⚙️ 🎏 🚙 🔢 🏗 🗄 🔗, 🔘 `custom_openapi()` 🔢: - -```Python hl_lines="2 15-20" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### 🔀 🗄 🔗 - -🔜 👆 💪 🚮 📄 ↔, ❎ 🛃 `x-logo` `info` "🎚" 🗄 🔗: - -```Python hl_lines="21-23" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### 💾 🗄 🔗 - -👆 💪 ⚙️ 🏠 `.openapi_schema` "💾", 🏪 👆 🏗 🔗. - -👈 🌌, 👆 🈸 🏆 🚫 ✔️ 🏗 🔗 🔠 🕰 👩‍💻 📂 👆 🛠️ 🩺. - -⚫️ 🔜 🏗 🕴 🕐, & ⤴️ 🎏 💾 🔗 🔜 ⚙️ ⏭ 📨. - -```Python hl_lines="13-14 24-25" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### 🔐 👩‍🔬 - -🔜 👆 💪 ❎ `.openapi()` 👩‍🔬 ⏮️ 👆 🆕 🔢. - -```Python hl_lines="28" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### ✅ ⚫️ - -🕐 👆 🚶 http://127.0.0.1:8000/redoc 👆 🔜 👀 👈 👆 ⚙️ 👆 🛃 🔱 (👉 🖼, **FastAPI**'Ⓜ 🔱): - - - -## 👤-🕸 🕸 & 🎚 🩺 - -🛠️ 🩺 ⚙️ **🦁 🎚** & **📄**, & 🔠 👈 💪 🕸 & 🎚 📁. - -🔢, 👈 📁 🍦 ⚪️➡️ 💲. - -✋️ ⚫️ 💪 🛃 ⚫️, 👆 💪 ⚒ 🎯 💲, ⚖️ 🍦 📁 👆. - -👈 ⚠, 🖼, 🚥 👆 💪 👆 📱 🚧 👷 ⏪ 📱, 🍵 📂 🕸 🔐, ⚖️ 🇧🇿 🕸. - -📥 👆 🔜 👀 ❔ 🍦 👈 📁 👆, 🎏 FastAPI 📱, & 🔗 🩺 ⚙️ 👫. - -### 🏗 📁 📊 - -➡️ 💬 👆 🏗 📁 📊 👀 💖 👉: - -``` -. -├── app -│ ├── __init__.py -│ ├── main.py -``` - -🔜 ✍ 📁 🏪 📚 🎻 📁. - -👆 🆕 📁 📊 💪 👀 💖 👉: - -``` -. -├── app -│   ├── __init__.py -│   ├── main.py -└── static/ -``` - -### ⏬ 📁 - -⏬ 🎻 📁 💪 🩺 & 🚮 👫 🔛 👈 `static/` 📁. - -👆 💪 🎲 ▶️️-🖊 🔠 🔗 & 🖊 🎛 🎏 `Save link as...`. - -**🦁 🎚** ⚙️ 📁: - -* `swagger-ui-bundle.js` -* `swagger-ui.css` - -& **📄** ⚙️ 📁: - -* `redoc.standalone.js` - -⏮️ 👈, 👆 📁 📊 💪 👀 💖: - -``` -. -├── app -│   ├── __init__.py -│   ├── main.py -└── static - ├── redoc.standalone.js - ├── swagger-ui-bundle.js - └── swagger-ui.css -``` - -### 🍦 🎻 📁 - -* 🗄 `StaticFiles`. -* "🗻" `StaticFiles()` 👐 🎯 ➡. - -```Python hl_lines="7 11" -{!../../../docs_src/extending_openapi/tutorial002.py!} -``` - -### 💯 🎻 📁 - -▶️ 👆 🈸 & 🚶 http://127.0.0.1:8000/static/redoc.standalone.js. - -👆 🔜 👀 📶 📏 🕸 📁 **📄**. - -⚫️ 💪 ▶️ ⏮️ 🕳 💖: - -```JavaScript -/*! - * ReDoc - OpenAPI/Swagger-generated API Reference Documentation - * ------------------------------------------------------------- - * Version: "2.0.0-rc.18" - * Repo: https://github.com/Redocly/redoc - */ -!function(e,t){"object"==typeof exports&&"object"==typeof m - -... -``` - -👈 ✔ 👈 👆 💆‍♂ 💪 🍦 🎻 📁 ⚪️➡️ 👆 📱, & 👈 👆 🥉 🎻 📁 🩺 ☑ 🥉. - -🔜 👥 💪 🔗 📱 ⚙️ 📚 🎻 📁 🩺. - -### ❎ 🏧 🩺 - -🥇 🔁 ❎ 🏧 🩺, 📚 ⚙️ 💲 🔢. - -❎ 👫, ⚒ 👫 📛 `None` 🕐❔ 🏗 👆 `FastAPI` 📱: - -```Python hl_lines="9" -{!../../../docs_src/extending_openapi/tutorial002.py!} -``` - -### 🔌 🛃 🩺 - -🔜 👆 💪 ✍ *➡ 🛠️* 🛃 🩺. - -👆 💪 🏤-⚙️ FastAPI 🔗 🔢 ✍ 🕸 📃 🩺, & 🚶‍♀️ 👫 💪 ❌: - -* `openapi_url`: 📛 🌐❔ 🕸 📃 🩺 💪 🤚 🗄 🔗 👆 🛠️. 👆 💪 ⚙️ 📥 🔢 `app.openapi_url`. -* `title`: 📛 👆 🛠️. -* `oauth2_redirect_url`: 👆 💪 ⚙️ `app.swagger_ui_oauth2_redirect_url` 📥 ⚙️ 🔢. -* `swagger_js_url`: 📛 🌐❔ 🕸 👆 🦁 🎚 🩺 💪 🤚 **🕸** 📁. 👉 1️⃣ 👈 👆 👍 📱 🔜 🍦. -* `swagger_css_url`: 📛 🌐❔ 🕸 👆 🦁 🎚 🩺 💪 🤚 **🎚** 📁. 👉 1️⃣ 👈 👆 👍 📱 🔜 🍦. - -& ➡ 📄... - -```Python hl_lines="2-6 14-22 25-27 30-36" -{!../../../docs_src/extending_openapi/tutorial002.py!} -``` - -!!! tip - *➡ 🛠️* `swagger_ui_redirect` 👩‍🎓 🕐❔ 👆 ⚙️ Oauth2️⃣. - - 🚥 👆 🛠️ 👆 🛠️ ⏮️ Oauth2️⃣ 🐕‍🦺, 👆 🔜 💪 🔓 & 👟 🔙 🛠️ 🩺 ⏮️ 📎 🎓. & 🔗 ⏮️ ⚫️ ⚙️ 🎰 Oauth2️⃣ 🤝. - - 🦁 🎚 🔜 🍵 ⚫️ ⛅ 🎑 👆, ✋️ ⚫️ 💪 👉 "❎" 👩‍🎓. - -### ✍ *➡ 🛠️* 💯 ⚫️ - -🔜, 💪 💯 👈 🌐 👷, ✍ *➡ 🛠️*: - -```Python hl_lines="39-41" -{!../../../docs_src/extending_openapi/tutorial002.py!} -``` - -### 💯 ⚫️ - -🔜, 👆 🔜 💪 🔌 👆 📻, 🚶 👆 🩺 http://127.0.0.1:8000/docs, & 🔃 📃. - -& 🍵 🕸, 👆 🔜 💪 👀 🩺 👆 🛠️ & 🔗 ⏮️ ⚫️. - -## 🛠️ 🦁 🎚 - -👆 💪 🔗 ➕ 🦁 🎚 🔢. - -🔗 👫, 🚶‍♀️ `swagger_ui_parameters` ❌ 🕐❔ 🏗 `FastAPI()` 📱 🎚 ⚖️ `get_swagger_ui_html()` 🔢. - -`swagger_ui_parameters` 📨 📖 ⏮️ 📳 🚶‍♀️ 🦁 🎚 🔗. - -FastAPI 🗜 📳 **🎻** ⚒ 👫 🔗 ⏮️ 🕸, 👈 ⚫️❔ 🦁 🎚 💪. - -### ❎ ❕ 🎦 - -🖼, 👆 💪 ❎ ❕ 🎦 🦁 🎚. - -🍵 🔀 ⚒, ❕ 🎦 🛠️ 🔢: - - - -✋️ 👆 💪 ❎ ⚫️ ⚒ `syntaxHighlight` `False`: - -```Python hl_lines="3" -{!../../../docs_src/extending_openapi/tutorial003.py!} -``` - -...& ⤴️ 🦁 🎚 🏆 🚫 🎦 ❕ 🎦 🚫🔜: - - - -### 🔀 🎢 - -🎏 🌌 👆 💪 ⚒ ❕ 🎦 🎢 ⏮️ 🔑 `"syntaxHighlight.theme"` (👀 👈 ⚫️ ✔️ ❣ 🖕): - -```Python hl_lines="3" -{!../../../docs_src/extending_openapi/tutorial004.py!} -``` - -👈 📳 🔜 🔀 ❕ 🎦 🎨 🎢: - - - -### 🔀 🔢 🦁 🎚 🔢 - -FastAPI 🔌 🔢 📳 🔢 ☑ 🌅 ⚙️ 💼. - -⚫️ 🔌 👫 🔢 📳: - -```Python -{!../../../fastapi/openapi/docs.py[ln:7-13]!} -``` - -👆 💪 🔐 🙆 👫 ⚒ 🎏 💲 ❌ `swagger_ui_parameters`. - -🖼, ❎ `deepLinking` 👆 💪 🚶‍♀️ 👉 ⚒ `swagger_ui_parameters`: - -```Python hl_lines="3" -{!../../../docs_src/extending_openapi/tutorial005.py!} -``` - -### 🎏 🦁 🎚 🔢 - -👀 🌐 🎏 💪 📳 👆 💪 ⚙️, ✍ 🛂 🩺 🦁 🎚 🔢. - -### 🕸-🕴 ⚒ - -🦁 🎚 ✔ 🎏 📳 **🕸-🕴** 🎚 (🖼, 🕸 🔢). - -FastAPI 🔌 👫 🕸-🕴 `presets` ⚒: - -```JavaScript -presets: [ - SwaggerUIBundle.presets.apis, - SwaggerUIBundle.SwaggerUIStandalonePreset -] -``` - -👫 **🕸** 🎚, 🚫 🎻, 👆 💪 🚫 🚶‍♀️ 👫 ⚪️➡️ 🐍 📟 🔗. - -🚥 👆 💪 ⚙️ 🕸-🕴 📳 💖 📚, 👆 💪 ⚙️ 1️⃣ 👩‍🔬 🔛. 🔐 🌐 🦁 🎚 *➡ 🛠️* & ❎ ✍ 🙆 🕸 👆 💪. diff --git a/docs/em/docs/advanced/conditional-openapi.md b/docs/em/docs/how-to/conditional-openapi.md similarity index 100% rename from docs/em/docs/advanced/conditional-openapi.md rename to docs/em/docs/how-to/conditional-openapi.md diff --git a/docs/em/docs/advanced/custom-request-and-route.md b/docs/em/docs/how-to/custom-request-and-route.md similarity index 100% rename from docs/em/docs/advanced/custom-request-and-route.md rename to docs/em/docs/how-to/custom-request-and-route.md diff --git a/docs/em/docs/how-to/extending-openapi.md b/docs/em/docs/how-to/extending-openapi.md new file mode 100644 index 000000000..6b3bc0075 --- /dev/null +++ b/docs/em/docs/how-to/extending-openapi.md @@ -0,0 +1,90 @@ +# ↔ 🗄 + +!!! warning + 👉 👍 🏧 ⚒. 👆 🎲 💪 🚶 ⚫️. + + 🚥 👆 📄 🔰 - 👩‍💻 🦮, 👆 💪 🎲 🚶 👉 📄. + + 🚥 👆 ⏪ 💭 👈 👆 💪 🔀 🏗 🗄 🔗, 😣 👂. + +📤 💼 🌐❔ 👆 💪 💪 🔀 🏗 🗄 🔗. + +👉 📄 👆 🔜 👀 ❔. + +## 😐 🛠️ + +😐 (🔢) 🛠️, ⏩. + +`FastAPI` 🈸 (👐) ✔️ `.openapi()` 👩‍🔬 👈 📈 📨 🗄 🔗. + +🍕 🈸 🎚 🏗, *➡ 🛠️* `/openapi.json` (⚖️ ⚫️❔ 👆 ⚒ 👆 `openapi_url`) ®. + +⚫️ 📨 🎻 📨 ⏮️ 🏁 🈸 `.openapi()` 👩‍🔬. + +🔢, ⚫️❔ 👩‍🔬 `.openapi()` 🔨 ✅ 🏠 `.openapi_schema` 👀 🚥 ⚫️ ✔️ 🎚 & 📨 👫. + +🚥 ⚫️ 🚫, ⚫️ 🏗 👫 ⚙️ 🚙 🔢 `fastapi.openapi.utils.get_openapi`. + +& 👈 🔢 `get_openapi()` 📨 🔢: + +* `title`: 🗄 📛, 🎦 🩺. +* `version`: ⏬ 👆 🛠️, ✅ `2.5.0`. +* `openapi_version`: ⏬ 🗄 🔧 ⚙️. 🔢, ⏪: `3.0.2`. +* `description`: 📛 👆 🛠️. +* `routes`: 📇 🛣, 👫 🔠 ® *➡ 🛠️*. 👫 ✊ ⚪️➡️ `app.routes`. + +## 🔑 🔢 + +⚙️ ℹ 🔛, 👆 💪 ⚙️ 🎏 🚙 🔢 🏗 🗄 🔗 & 🔐 🔠 🍕 👈 👆 💪. + +🖼, ➡️ 🚮 📄 🗄 ↔ 🔌 🛃 🔱. + +### 😐 **FastAPI** + +🥇, ✍ 🌐 👆 **FastAPI** 🈸 🛎: + +```Python hl_lines="1 4 7-9" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### 🏗 🗄 🔗 + +⤴️, ⚙️ 🎏 🚙 🔢 🏗 🗄 🔗, 🔘 `custom_openapi()` 🔢: + +```Python hl_lines="2 15-20" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### 🔀 🗄 🔗 + +🔜 👆 💪 🚮 📄 ↔, ❎ 🛃 `x-logo` `info` "🎚" 🗄 🔗: + +```Python hl_lines="21-23" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### 💾 🗄 🔗 + +👆 💪 ⚙️ 🏠 `.openapi_schema` "💾", 🏪 👆 🏗 🔗. + +👈 🌌, 👆 🈸 🏆 🚫 ✔️ 🏗 🔗 🔠 🕰 👩‍💻 📂 👆 🛠️ 🩺. + +⚫️ 🔜 🏗 🕴 🕐, & ⤴️ 🎏 💾 🔗 🔜 ⚙️ ⏭ 📨. + +```Python hl_lines="13-14 24-25" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### 🔐 👩‍🔬 + +🔜 👆 💪 ❎ `.openapi()` 👩‍🔬 ⏮️ 👆 🆕 🔢. + +```Python hl_lines="28" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### ✅ ⚫️ + +🕐 👆 🚶 http://127.0.0.1:8000/redoc 👆 🔜 👀 👈 👆 ⚙️ 👆 🛃 🔱 (👉 🖼, **FastAPI**'Ⓜ 🔱): + + diff --git a/docs/em/docs/advanced/graphql.md b/docs/em/docs/how-to/graphql.md similarity index 100% rename from docs/em/docs/advanced/graphql.md rename to docs/em/docs/how-to/graphql.md diff --git a/docs/em/docs/advanced/sql-databases-peewee.md b/docs/em/docs/how-to/sql-databases-peewee.md similarity index 100% rename from docs/em/docs/advanced/sql-databases-peewee.md rename to docs/em/docs/how-to/sql-databases-peewee.md diff --git a/docs/en/docs/advanced/extending-openapi.md b/docs/en/docs/advanced/extending-openapi.md deleted file mode 100644 index bec184dee..000000000 --- a/docs/en/docs/advanced/extending-openapi.md +++ /dev/null @@ -1,318 +0,0 @@ -# Extending OpenAPI - -!!! warning - This is a rather advanced feature. You probably can skip it. - - If you are just following the tutorial - user guide, you can probably skip this section. - - If you already know that you need to modify the generated OpenAPI schema, continue reading. - -There are some cases where you might need to modify the generated OpenAPI schema. - -In this section you will see how. - -## The normal process - -The normal (default) process, is as follows. - -A `FastAPI` application (instance) has an `.openapi()` method that is expected to return the OpenAPI schema. - -As part of the application object creation, a *path operation* for `/openapi.json` (or for whatever you set your `openapi_url`) is registered. - -It just returns a JSON response with the result of the application's `.openapi()` method. - -By default, what the method `.openapi()` does is check the property `.openapi_schema` to see if it has contents and return them. - -If it doesn't, it generates them using the utility function at `fastapi.openapi.utils.get_openapi`. - -And that function `get_openapi()` receives as parameters: - -* `title`: The OpenAPI title, shown in the docs. -* `version`: The version of your API, e.g. `2.5.0`. -* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.1.0`. -* `summary`: A short summary of the API. -* `description`: The description of your API, this can include markdown and will be shown in the docs. -* `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`. - -!!! info - The parameter `summary` is available in OpenAPI 3.1.0 and above, supported by FastAPI 0.99.0 and above. - -## Overriding the defaults - -Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need. - -For example, let's add ReDoc's OpenAPI extension to include a custom logo. - -### Normal **FastAPI** - -First, write all your **FastAPI** application as normally: - -```Python hl_lines="1 4 7-9" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### Generate the OpenAPI schema - -Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function: - -```Python hl_lines="2 15-21" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### Modify the OpenAPI schema - -Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema: - -```Python hl_lines="22-24" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### Cache the OpenAPI schema - -You can use the property `.openapi_schema` as a "cache", to store your generated schema. - -That way, your application won't have to generate the schema every time a user opens your API docs. - -It will be generated only once, and then the same cached schema will be used for the next requests. - -```Python hl_lines="13-14 25-26" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### Override the method - -Now you can replace the `.openapi()` method with your new function. - -```Python hl_lines="29" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` - -### Check it - -Once you go to http://127.0.0.1:8000/redoc you will see that you are using your custom logo (in this example, **FastAPI**'s logo): - - - -## Self-hosting JavaScript and CSS for docs - -The API docs use **Swagger UI** and **ReDoc**, and each of those need some JavaScript and CSS files. - -By default, those files are served from a CDN. - -But it's possible to customize it, you can set a specific CDN, or serve the files yourself. - -That's useful, for example, if you need your app to keep working even while offline, without open Internet access, or in a local network. - -Here you'll see how to serve those files yourself, in the same FastAPI app, and configure the docs to use them. - -### Project file structure - -Let's say your project file structure looks like this: - -``` -. -├── app -│ ├── __init__.py -│ ├── main.py -``` - -Now create a directory to store those static files. - -Your new file structure could look like this: - -``` -. -├── app -│   ├── __init__.py -│   ├── main.py -└── static/ -``` - -### Download the files - -Download the static files needed for the docs and put them on that `static/` directory. - -You can probably right-click each link and select an option similar to `Save link as...`. - -**Swagger UI** uses the files: - -* `swagger-ui-bundle.js` -* `swagger-ui.css` - -And **ReDoc** uses the file: - -* `redoc.standalone.js` - -After that, your file structure could look like: - -``` -. -├── app -│   ├── __init__.py -│   ├── main.py -└── static - ├── redoc.standalone.js - ├── swagger-ui-bundle.js - └── swagger-ui.css -``` - -### Serve the static files - -* Import `StaticFiles`. -* "Mount" a `StaticFiles()` instance in a specific path. - -```Python hl_lines="7 11" -{!../../../docs_src/extending_openapi/tutorial002.py!} -``` - -### Test the static files - -Start your application and go to http://127.0.0.1:8000/static/redoc.standalone.js. - -You should see a very long JavaScript file for **ReDoc**. - -It could start with something like: - -```JavaScript -/*! - * ReDoc - OpenAPI/Swagger-generated API Reference Documentation - * ------------------------------------------------------------- - * Version: "2.0.0-rc.18" - * Repo: https://github.com/Redocly/redoc - */ -!function(e,t){"object"==typeof exports&&"object"==typeof m - -... -``` - -That confirms that you are being able to serve static files from your app, and that you placed the static files for the docs in the correct place. - -Now we can configure the app to use those static files for the docs. - -### Disable the automatic docs - -The first step is to disable the automatic docs, as those use the CDN by default. - -To disable them, set their URLs to `None` when creating your `FastAPI` app: - -```Python hl_lines="9" -{!../../../docs_src/extending_openapi/tutorial002.py!} -``` - -### Include the custom docs - -Now you can create the *path operations* for the custom docs. - -You can re-use FastAPI's internal functions to create the HTML pages for the docs, and pass them the needed arguments: - -* `openapi_url`: the URL where the HTML page for the docs can get the OpenAPI schema for your API. You can use here the attribute `app.openapi_url`. -* `title`: the title of your API. -* `oauth2_redirect_url`: you can use `app.swagger_ui_oauth2_redirect_url` here to use the default. -* `swagger_js_url`: the URL where the HTML for your Swagger UI docs can get the **JavaScript** file. This is the one that your own app is now serving. -* `swagger_css_url`: the URL where the HTML for your Swagger UI docs can get the **CSS** file. This is the one that your own app is now serving. - -And similarly for ReDoc... - -```Python hl_lines="2-6 14-22 25-27 30-36" -{!../../../docs_src/extending_openapi/tutorial002.py!} -``` - -!!! tip - The *path operation* for `swagger_ui_redirect` is a helper for when you use OAuth2. - - If you integrate your API with an OAuth2 provider, you will be able to authenticate and come back to the API docs with the acquired credentials. And interact with it using the real OAuth2 authentication. - - Swagger UI will handle it behind the scenes for you, but it needs this "redirect" helper. - -### Create a *path operation* to test it - -Now, to be able to test that everything works, create a *path operation*: - -```Python hl_lines="39-41" -{!../../../docs_src/extending_openapi/tutorial002.py!} -``` - -### Test it - -Now, you should be able to disconnect your WiFi, go to your docs at http://127.0.0.1:8000/docs, and reload the page. - -And even without Internet, you would be able to see the docs for your API and interact with it. - -## Configuring Swagger UI - -You can configure some extra Swagger UI parameters. - -To configure them, pass the `swagger_ui_parameters` argument when creating the `FastAPI()` app object or to the `get_swagger_ui_html()` function. - -`swagger_ui_parameters` receives a dictionary with the configurations passed to Swagger UI directly. - -FastAPI converts the configurations to **JSON** to make them compatible with JavaScript, as that's what Swagger UI needs. - -### Disable Syntax Highlighting - -For example, you could disable syntax highlighting in Swagger UI. - -Without changing the settings, syntax highlighting is enabled by default: - - - -But you can disable it by setting `syntaxHighlight` to `False`: - -```Python hl_lines="3" -{!../../../docs_src/extending_openapi/tutorial003.py!} -``` - -...and then Swagger UI won't show the syntax highlighting anymore: - - - -### Change the Theme - -The same way you could set the syntax highlighting theme with the key `"syntaxHighlight.theme"` (notice that it has a dot in the middle): - -```Python hl_lines="3" -{!../../../docs_src/extending_openapi/tutorial004.py!} -``` - -That configuration would change the syntax highlighting color theme: - - - -### Change Default Swagger UI Parameters - -FastAPI includes some default configuration parameters appropriate for most of the use cases. - -It includes these default configurations: - -```Python -{!../../../fastapi/openapi/docs.py[ln:7-13]!} -``` - -You can override any of them by setting a different value in the argument `swagger_ui_parameters`. - -For example, to disable `deepLinking` you could pass these settings to `swagger_ui_parameters`: - -```Python hl_lines="3" -{!../../../docs_src/extending_openapi/tutorial005.py!} -``` - -### Other Swagger UI Parameters - -To see all the other possible configurations you can use, read the official docs for Swagger UI parameters. - -### JavaScript-only settings - -Swagger UI also allows other configurations to be **JavaScript-only** objects (for example, JavaScript functions). - -FastAPI also includes these JavaScript-only `presets` settings: - -```JavaScript -presets: [ - SwaggerUIBundle.presets.apis, - SwaggerUIBundle.SwaggerUIStandalonePreset -] -``` - -These are **JavaScript** objects, not strings, so you can't pass them from Python code directly. - -If you need to use JavaScript-only configurations like those, you can use one of the methods above. Override all the Swagger UI *path operation* and manually write any JavaScript you need. diff --git a/docs/en/docs/advanced/async-sql-databases.md b/docs/en/docs/how-to/async-sql-encode-databases.md similarity index 98% rename from docs/en/docs/advanced/async-sql-databases.md rename to docs/en/docs/how-to/async-sql-encode-databases.md index 12549a190..697167f79 100644 --- a/docs/en/docs/advanced/async-sql-databases.md +++ b/docs/en/docs/how-to/async-sql-encode-databases.md @@ -1,4 +1,4 @@ -# Async SQL (Relational) Databases +# Async SQL (Relational) Databases with Encode/Databases !!! info These docs are about to be updated. 🎉 diff --git a/docs/en/docs/advanced/conditional-openapi.md b/docs/en/docs/how-to/conditional-openapi.md similarity index 100% rename from docs/en/docs/advanced/conditional-openapi.md rename to docs/en/docs/how-to/conditional-openapi.md diff --git a/docs/en/docs/how-to/configure-swagger-ui.md b/docs/en/docs/how-to/configure-swagger-ui.md new file mode 100644 index 000000000..f36ba5ba8 --- /dev/null +++ b/docs/en/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,78 @@ +# Configure Swagger UI + +You can configure some extra Swagger UI parameters. + +To configure them, pass the `swagger_ui_parameters` argument when creating the `FastAPI()` app object or to the `get_swagger_ui_html()` function. + +`swagger_ui_parameters` receives a dictionary with the configurations passed to Swagger UI directly. + +FastAPI converts the configurations to **JSON** to make them compatible with JavaScript, as that's what Swagger UI needs. + +## Disable Syntax Highlighting + +For example, you could disable syntax highlighting in Swagger UI. + +Without changing the settings, syntax highlighting is enabled by default: + + + +But you can disable it by setting `syntaxHighlight` to `False`: + +```Python hl_lines="3" +{!../../../docs_src/configure_swagger_ui/tutorial001.py!} +``` + +...and then Swagger UI won't show the syntax highlighting anymore: + + + +## Change the Theme + +The same way you could set the syntax highlighting theme with the key `"syntaxHighlight.theme"` (notice that it has a dot in the middle): + +```Python hl_lines="3" +{!../../../docs_src/configure_swagger_ui/tutorial002.py!} +``` + +That configuration would change the syntax highlighting color theme: + + + +## Change Default Swagger UI Parameters + +FastAPI includes some default configuration parameters appropriate for most of the use cases. + +It includes these default configurations: + +```Python +{!../../../fastapi/openapi/docs.py[ln:7-13]!} +``` + +You can override any of them by setting a different value in the argument `swagger_ui_parameters`. + +For example, to disable `deepLinking` you could pass these settings to `swagger_ui_parameters`: + +```Python hl_lines="3" +{!../../../docs_src/configure_swagger_ui/tutorial003.py!} +``` + +## Other Swagger UI Parameters + +To see all the other possible configurations you can use, read the official docs for Swagger UI parameters. + +## JavaScript-only settings + +Swagger UI also allows other configurations to be **JavaScript-only** objects (for example, JavaScript functions). + +FastAPI also includes these JavaScript-only `presets` settings: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +These are **JavaScript** objects, not strings, so you can't pass them from Python code directly. + +If you need to use JavaScript-only configurations like those, you can use one of the methods above. Override all the Swagger UI *path operation* and manually write any JavaScript you need. diff --git a/docs/en/docs/how-to/custom-docs-ui-assets.md b/docs/en/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 000000000..f26324869 --- /dev/null +++ b/docs/en/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,199 @@ +# Custom Docs UI Static Assets (Self-Hosting) + +The API docs use **Swagger UI** and **ReDoc**, and each of those need some JavaScript and CSS files. + +By default, those files are served from a CDN. + +But it's possible to customize it, you can set a specific CDN, or serve the files yourself. + +## Custom CDN for JavaScript and CSS + +Let's say that you want to use a different CDN, for example you want to use `https://unpkg.com/`. + +This could be useful if for example you live in a country that restricts some URLs. + +### Disable the automatic docs + +The first step is to disable the automatic docs, as by default, those use the default CDN. + +To disable them, set their URLs to `None` when creating your `FastAPI` app: + +```Python hl_lines="8" +{!../../../docs_src/custom_docs_ui/tutorial001.py!} +``` + +### Include the custom docs + +Now you can create the *path operations* for the custom docs. + +You can re-use FastAPI's internal functions to create the HTML pages for the docs, and pass them the needed arguments: + +* `openapi_url`: the URL where the HTML page for the docs can get the OpenAPI schema for your API. You can use here the attribute `app.openapi_url`. +* `title`: the title of your API. +* `oauth2_redirect_url`: you can use `app.swagger_ui_oauth2_redirect_url` here to use the default. +* `swagger_js_url`: the URL where the HTML for your Swagger UI docs can get the **JavaScript** file. This is the custom CDN URL. +* `swagger_css_url`: the URL where the HTML for your Swagger UI docs can get the **CSS** file. This is the custom CDN URL. + +And similarly for ReDoc... + +```Python hl_lines="2-6 11-19 22-24 27-33" +{!../../../docs_src/custom_docs_ui/tutorial001.py!} +``` + +!!! tip + The *path operation* for `swagger_ui_redirect` is a helper for when you use OAuth2. + + If you integrate your API with an OAuth2 provider, you will be able to authenticate and come back to the API docs with the acquired credentials. And interact with it using the real OAuth2 authentication. + + Swagger UI will handle it behind the scenes for you, but it needs this "redirect" helper. + +### Create a *path operation* to test it + +Now, to be able to test that everything works, create a *path operation*: + +```Python hl_lines="36-38" +{!../../../docs_src/custom_docs_ui/tutorial001.py!} +``` + +### Test it + +Now, you should be able to go to your docs at http://127.0.0.1:8000/docs, and reload the page, it will load those assets from the new CDN. + +## Self-hosting JavaScript and CSS for docs + +Self-hosting the JavaScript and CSS could be useful if, for example, you need your app to keep working even while offline, without open Internet access, or in a local network. + +Here you'll see how to serve those files yourself, in the same FastAPI app, and configure the docs to use them. + +### Project file structure + +Let's say your project file structure looks like this: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +Now create a directory to store those static files. + +Your new file structure could look like this: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### Download the files + +Download the static files needed for the docs and put them on that `static/` directory. + +You can probably right-click each link and select an option similar to `Save link as...`. + +**Swagger UI** uses the files: + +* `swagger-ui-bundle.js` +* `swagger-ui.css` + +And **ReDoc** uses the file: + +* `redoc.standalone.js` + +After that, your file structure could look like: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### Serve the static files + +* Import `StaticFiles`. +* "Mount" a `StaticFiles()` instance in a specific path. + +```Python hl_lines="7 11" +{!../../../docs_src/custom_docs_ui/tutorial002.py!} +``` + +### Test the static files + +Start your application and go to http://127.0.0.1:8000/static/redoc.standalone.js. + +You should see a very long JavaScript file for **ReDoc**. + +It could start with something like: + +```JavaScript +/*! + * ReDoc - OpenAPI/Swagger-generated API Reference Documentation + * ------------------------------------------------------------- + * Version: "2.0.0-rc.18" + * Repo: https://github.com/Redocly/redoc + */ +!function(e,t){"object"==typeof exports&&"object"==typeof m + +... +``` + +That confirms that you are being able to serve static files from your app, and that you placed the static files for the docs in the correct place. + +Now we can configure the app to use those static files for the docs. + +### Disable the automatic docs for static files + +The same as when using a custom CDN, the first step is to disable the automatic docs, as those use the CDN by default. + +To disable them, set their URLs to `None` when creating your `FastAPI` app: + +```Python hl_lines="9" +{!../../../docs_src/custom_docs_ui/tutorial002.py!} +``` + +### Include the custom docs for static files + +And the same way as with a custom CDN, now you can create the *path operations* for the custom docs. + +Again, you can re-use FastAPI's internal functions to create the HTML pages for the docs, and pass them the needed arguments: + +* `openapi_url`: the URL where the HTML page for the docs can get the OpenAPI schema for your API. You can use here the attribute `app.openapi_url`. +* `title`: the title of your API. +* `oauth2_redirect_url`: you can use `app.swagger_ui_oauth2_redirect_url` here to use the default. +* `swagger_js_url`: the URL where the HTML for your Swagger UI docs can get the **JavaScript** file. **This is the one that your own app is now serving**. +* `swagger_css_url`: the URL where the HTML for your Swagger UI docs can get the **CSS** file. **This is the one that your own app is now serving**. + +And similarly for ReDoc... + +```Python hl_lines="2-6 14-22 25-27 30-36" +{!../../../docs_src/custom_docs_ui/tutorial002.py!} +``` + +!!! tip + The *path operation* for `swagger_ui_redirect` is a helper for when you use OAuth2. + + If you integrate your API with an OAuth2 provider, you will be able to authenticate and come back to the API docs with the acquired credentials. And interact with it using the real OAuth2 authentication. + + Swagger UI will handle it behind the scenes for you, but it needs this "redirect" helper. + +### Create a *path operation* to test static files + +Now, to be able to test that everything works, create a *path operation*: + +```Python hl_lines="39-41" +{!../../../docs_src/custom_docs_ui/tutorial002.py!} +``` + +### Test Static Files UI + +Now, you should be able to disconnect your WiFi, go to your docs at http://127.0.0.1:8000/docs, and reload the page. + +And even without Internet, you would be able to see the docs for your API and interact with it. diff --git a/docs/en/docs/advanced/custom-request-and-route.md b/docs/en/docs/how-to/custom-request-and-route.md similarity index 100% rename from docs/en/docs/advanced/custom-request-and-route.md rename to docs/en/docs/how-to/custom-request-and-route.md diff --git a/docs/en/docs/how-to/extending-openapi.md b/docs/en/docs/how-to/extending-openapi.md new file mode 100644 index 000000000..a18fd737e --- /dev/null +++ b/docs/en/docs/how-to/extending-openapi.md @@ -0,0 +1,87 @@ +# Extending OpenAPI + +There are some cases where you might need to modify the generated OpenAPI schema. + +In this section you will see how. + +## The normal process + +The normal (default) process, is as follows. + +A `FastAPI` application (instance) has an `.openapi()` method that is expected to return the OpenAPI schema. + +As part of the application object creation, a *path operation* for `/openapi.json` (or for whatever you set your `openapi_url`) is registered. + +It just returns a JSON response with the result of the application's `.openapi()` method. + +By default, what the method `.openapi()` does is check the property `.openapi_schema` to see if it has contents and return them. + +If it doesn't, it generates them using the utility function at `fastapi.openapi.utils.get_openapi`. + +And that function `get_openapi()` receives as parameters: + +* `title`: The OpenAPI title, shown in the docs. +* `version`: The version of your API, e.g. `2.5.0`. +* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.1.0`. +* `summary`: A short summary of the API. +* `description`: The description of your API, this can include markdown and will be shown in the docs. +* `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`. + +!!! info + The parameter `summary` is available in OpenAPI 3.1.0 and above, supported by FastAPI 0.99.0 and above. + +## Overriding the defaults + +Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need. + +For example, let's add ReDoc's OpenAPI extension to include a custom logo. + +### Normal **FastAPI** + +First, write all your **FastAPI** application as normally: + +```Python hl_lines="1 4 7-9" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Generate the OpenAPI schema + +Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function: + +```Python hl_lines="2 15-21" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Modify the OpenAPI schema + +Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema: + +```Python hl_lines="22-24" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Cache the OpenAPI schema + +You can use the property `.openapi_schema` as a "cache", to store your generated schema. + +That way, your application won't have to generate the schema every time a user opens your API docs. + +It will be generated only once, and then the same cached schema will be used for the next requests. + +```Python hl_lines="13-14 25-26" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Override the method + +Now you can replace the `.openapi()` method with your new function. + +```Python hl_lines="29" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Check it + +Once you go to http://127.0.0.1:8000/redoc you will see that you are using your custom logo (in this example, **FastAPI**'s logo): + + diff --git a/docs/en/docs/how-to/general.md b/docs/en/docs/how-to/general.md new file mode 100644 index 000000000..04367c6b7 --- /dev/null +++ b/docs/en/docs/how-to/general.md @@ -0,0 +1,39 @@ +# General - How To - Recipes + +Here are several pointers to other places in the docs, for general or frequent questions. + +## Filter Data - Security + +To ensure that you don't return more data than you should, read the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}. + +## Documentation Tags - OpenAPI + +To add tags to your *path operations*, and group them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. + +## Documentation Summary and Description - OpenAPI + +To add a summary and description to your *path operations*, and show them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Summary and Description](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. + +## Documentation Response description - OpenAPI + +To define the description of the response, shown in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Response description](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. + +## Documentation Deprecate a *Path Operation* - OpenAPI + +To deprecate a *path operation*, and show it in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Deprecation](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. + +## Convert any Data to JSON-compatible + +To convert any data to JSON-compatible, read the docs for [Tutorial - JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. + +## OpenAPI Metadata - Docs + +To add metadata to your OpenAPI schema, including a license, version, contact, etc, read the docs for [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md){.internal-link target=_blank}. + +## OpenAPI Custom URL + +To customize the OpenAPI URL (or remove it), read the docs for [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. + +## OpenAPI Docs URLs + +To update the URLs used for the automatically generated docs user interfaces, read the docs for [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. diff --git a/docs/en/docs/advanced/graphql.md b/docs/en/docs/how-to/graphql.md similarity index 100% rename from docs/en/docs/advanced/graphql.md rename to docs/en/docs/how-to/graphql.md diff --git a/docs/en/docs/how-to/index.md b/docs/en/docs/how-to/index.md new file mode 100644 index 000000000..ec7fd38f8 --- /dev/null +++ b/docs/en/docs/how-to/index.md @@ -0,0 +1,11 @@ +# How To - Recipes + +Here you will see different recipes or "how to" guides for **several topics**. + +Most of these ideas would be more or less **independent**, and in most cases you should only need to study them if they apply directly to **your project**. + +If something seems interesting and useful to your project, go ahead and check it, but otherwise, you might probably just skip them. + +!!! tip + + If you want to **learn FastAPI** in a structured way (recommended), go and read the [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} chapter by chapter instead. diff --git a/docs/en/docs/advanced/nosql-databases.md b/docs/en/docs/how-to/nosql-databases-couchbase.md similarity index 99% rename from docs/en/docs/advanced/nosql-databases.md rename to docs/en/docs/how-to/nosql-databases-couchbase.md index 606db35c7..ae6ad604b 100644 --- a/docs/en/docs/advanced/nosql-databases.md +++ b/docs/en/docs/how-to/nosql-databases-couchbase.md @@ -1,4 +1,4 @@ -# NoSQL (Distributed / Big Data) Databases +# NoSQL (Distributed / Big Data) Databases with Couchbase !!! info These docs are about to be updated. 🎉 diff --git a/docs/en/docs/advanced/sql-databases-peewee.md b/docs/en/docs/how-to/sql-databases-peewee.md similarity index 99% rename from docs/en/docs/advanced/sql-databases-peewee.md rename to docs/en/docs/how-to/sql-databases-peewee.md index 6a469634f..bf2f2e714 100644 --- a/docs/en/docs/advanced/sql-databases-peewee.md +++ b/docs/en/docs/how-to/sql-databases-peewee.md @@ -12,6 +12,8 @@ Because Pewee doesn't play well with anything async and there are better alternatives, I won't update these docs for Pydantic v2, they are kept for now only for historical purposes. + The examples here are no longer tested in CI (as they were before). + If you are starting a project from scratch, you are probably better off with SQLAlchemy ORM ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}), or any other async ORM. If you already have a code base that uses Peewee ORM, you can check here how to use it with **FastAPI**. diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 22babe745..f75b84ff5 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -45,6 +45,13 @@ plugins: redirects: redirect_maps: deployment/deta.md: deployment/cloud.md + advanced/sql-databases-peewee.md: how-to/sql-databases-peewee.md + advanced/async-sql-databases.md: how-to/async-sql-encode-databases.md + advanced/nosql-databases.md: how-to/nosql-databases-couchbase.md + advanced/graphql.md: how-to/graphql.md + advanced/custom-request-and-route.md: how-to/custom-request-and-route.md + advanced/conditional-openapi.md: how-to/conditional-openapi.md + advanced/extending-openapi.md: how-to/extending-openapi.md nav: - FastAPI: index.md - Languages: @@ -134,24 +141,17 @@ nav: - advanced/using-request-directly.md - advanced/dataclasses.md - advanced/middleware.md - - advanced/sql-databases-peewee.md - - advanced/async-sql-databases.md - - advanced/nosql-databases.md - advanced/sub-applications.md - advanced/behind-a-proxy.md - advanced/templates.md - - advanced/graphql.md - advanced/websockets.md - advanced/events.md - - advanced/custom-request-and-route.md - advanced/testing-websockets.md - advanced/testing-events.md - advanced/testing-dependencies.md - advanced/testing-database.md - advanced/async-tests.md - advanced/settings.md - - advanced/conditional-openapi.md - - advanced/extending-openapi.md - advanced/openapi-callbacks.md - advanced/openapi-webhooks.md - advanced/wsgi.md @@ -166,6 +166,18 @@ nav: - deployment/cloud.md - deployment/server-workers.md - deployment/docker.md +- How To - Recipes: + - how-to/index.md + - how-to/general.md + - how-to/sql-databases-peewee.md + - how-to/async-sql-encode-databases.md + - how-to/nosql-databases-couchbase.md + - how-to/graphql.md + - how-to/custom-request-and-route.md + - how-to/conditional-openapi.md + - how-to/extending-openapi.md + - how-to/custom-docs-ui-assets.md + - how-to/configure-swagger-ui.md - project-generation.md - alternatives.md - history-design-future.md diff --git a/docs/ja/docs/advanced/conditional-openapi.md b/docs/ja/docs/how-to/conditional-openapi.md similarity index 100% rename from docs/ja/docs/advanced/conditional-openapi.md rename to docs/ja/docs/how-to/conditional-openapi.md diff --git a/docs_src/extending_openapi/tutorial003.py b/docs_src/configure_swagger_ui/tutorial001.py similarity index 100% rename from docs_src/extending_openapi/tutorial003.py rename to docs_src/configure_swagger_ui/tutorial001.py diff --git a/docs_src/extending_openapi/tutorial004.py b/docs_src/configure_swagger_ui/tutorial002.py similarity index 100% rename from docs_src/extending_openapi/tutorial004.py rename to docs_src/configure_swagger_ui/tutorial002.py diff --git a/docs_src/extending_openapi/tutorial005.py b/docs_src/configure_swagger_ui/tutorial003.py similarity index 100% rename from docs_src/extending_openapi/tutorial005.py rename to docs_src/configure_swagger_ui/tutorial003.py diff --git a/docs_src/custom_docs_ui/tutorial001.py b/docs_src/custom_docs_ui/tutorial001.py new file mode 100644 index 000000000..f7ceb0c2f --- /dev/null +++ b/docs_src/custom_docs_ui/tutorial001.py @@ -0,0 +1,38 @@ +from fastapi import FastAPI +from fastapi.openapi.docs import ( + get_redoc_html, + get_swagger_ui_html, + get_swagger_ui_oauth2_redirect_html, +) + +app = FastAPI(docs_url=None, redoc_url=None) + + +@app.get("/docs", include_in_schema=False) +async def custom_swagger_ui_html(): + return get_swagger_ui_html( + openapi_url=app.openapi_url, + title=app.title + " - Swagger UI", + oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, + swagger_js_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js", + swagger_css_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css", + ) + + +@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False) +async def swagger_ui_redirect(): + return get_swagger_ui_oauth2_redirect_html() + + +@app.get("/redoc", include_in_schema=False) +async def redoc_html(): + return get_redoc_html( + openapi_url=app.openapi_url, + title=app.title + " - ReDoc", + redoc_js_url="https://unpkg.com/redoc@next/bundles/redoc.standalone.js", + ) + + +@app.get("/users/{username}") +async def read_user(username: str): + return {"message": f"Hello {username}"} diff --git a/docs_src/extending_openapi/tutorial002.py b/docs_src/custom_docs_ui/tutorial002.py similarity index 100% rename from docs_src/extending_openapi/tutorial002.py rename to docs_src/custom_docs_ui/tutorial002.py diff --git a/requirements-tests.txt b/requirements-tests.txt index 0113b6f7a..6f7f4ac23 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -11,7 +11,6 @@ dirty-equals ==0.6.0 # TODO: once removing databases from tutorial, upgrade SQLAlchemy # probably when including SQLModel sqlalchemy >=1.3.18,<1.4.43 -peewee >=3.13.3,<4.0.0 databases[sqlite] >=0.3.2,<0.7.0 orjson >=3.2.1,<4.0.0 ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0 diff --git a/docs_src/sql_databases_peewee/__init__.py b/tests/test_tutorial/test_configure_swagger_ui/__init__.py similarity index 100% rename from docs_src/sql_databases_peewee/__init__.py rename to tests/test_tutorial/test_configure_swagger_ui/__init__.py diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial003.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py similarity index 95% rename from tests/test_tutorial/test_extending_openapi/test_tutorial003.py rename to tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py index 0184dd9f8..72db54bd2 100644 --- a/tests/test_tutorial/test_extending_openapi/test_tutorial003.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.extending_openapi.tutorial003 import app +from docs_src.configure_swagger_ui.tutorial001 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial004.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py similarity index 96% rename from tests/test_tutorial/test_extending_openapi/test_tutorial004.py rename to tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py index 4f7615126..166901188 100644 --- a/tests/test_tutorial/test_extending_openapi/test_tutorial004.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.extending_openapi.tutorial004 import app +from docs_src.configure_swagger_ui.tutorial002 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial005.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py similarity index 96% rename from tests/test_tutorial/test_extending_openapi/test_tutorial005.py rename to tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py index 24aeb93db..187e89ace 100644 --- a/tests/test_tutorial/test_extending_openapi/test_tutorial005.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.extending_openapi.tutorial005 import app +from docs_src.configure_swagger_ui.tutorial003 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_sql_databases_peewee/__init__.py b/tests/test_tutorial/test_custom_docs_ui/__init__.py similarity index 100% rename from tests/test_tutorial/test_sql_databases_peewee/__init__.py rename to tests/test_tutorial/test_custom_docs_ui/__init__.py diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py new file mode 100644 index 000000000..aff070d74 --- /dev/null +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py @@ -0,0 +1,42 @@ +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" + print(static_dir) + static_dir.mkdir(exist_ok=True) + from docs_src.custom_docs_ui.tutorial001 import app + + with TestClient(app) as client: + yield client + static_dir.rmdir() + + +def test_swagger_ui_html(client: TestClient): + response = client.get("/docs") + assert response.status_code == 200, response.text + assert "https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js" in response.text + assert "https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" in response.text + + +def test_swagger_ui_oauth2_redirect_html(client: TestClient): + response = client.get("/docs/oauth2-redirect") + assert response.status_code == 200, response.text + assert "window.opener.swaggerUIRedirectOauth2" in response.text + + +def test_redoc_html(client: TestClient): + response = client.get("/redoc") + assert response.status_code == 200, response.text + assert "https://unpkg.com/redoc@next/bundles/redoc.standalone.js" in response.text + + +def test_api(client: TestClient): + response = client.get("/users/john") + assert response.status_code == 200, response.text + assert response.json()["message"] == "Hello john" diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial002.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py similarity index 95% rename from tests/test_tutorial/test_extending_openapi/test_tutorial002.py rename to tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py index 654db2e4c..712618807 100644 --- a/tests/test_tutorial/test_extending_openapi/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py @@ -10,7 +10,7 @@ def client(): static_dir: Path = Path(os.getcwd()) / "static" print(static_dir) static_dir.mkdir(exist_ok=True) - from docs_src.extending_openapi.tutorial002 import app + from docs_src.custom_docs_ui.tutorial002 import app with TestClient(app) as client: yield client diff --git a/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py b/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py deleted file mode 100644 index 4350567d1..000000000 --- a/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py +++ /dev/null @@ -1,454 +0,0 @@ -import time -from pathlib import Path -from unittest.mock import MagicMock - -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_pydanticv1 - - -@pytest.fixture(scope="module") -def client(): - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases_peewee.sql_app.main import app - - test_db = Path("./test.db") - with TestClient(app) as c: - yield c - test_db.unlink() - - -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -@needs_pydanticv1 -def test_inexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -time.sleep = MagicMock() - - -@needs_pydanticv1 -def test_get_slowusers(client): - response = client.get("/slowusers/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - - -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -@needs_pydanticv1 -def test_openapi_schema(client): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/users/{user_id}": { - "get": { - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/users/{user_id}/items/": { - "post": { - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "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" - } - } - }, - }, - }, - } - }, - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/slowusers/": { - "get": { - "summary": "Read Slow Users", - "operationId": "read_slow_users_slowusers__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Slow Users Slowusers Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "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"}, - }, - }, - } - }, - } From 10a127ea4adfe59b7d275fa0bc764b23910aea5c Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 19 Aug 2023 19:54:40 +0000 Subject: [PATCH 014/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 48a809021..61ec91b4a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Add new docs section, How To - Recipes, move docs that don't have to be read by everyone to How To. PR [#10114](https://github.com/tiangolo/fastapi/pull/10114) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor tests for new Pydantic 2.2.1. PR [#10115](https://github.com/tiangolo/fastapi/pull/10115) by [@tiangolo](https://github.com/tiangolo). * 📝 Update Advanced docs, add links to sponsor courses. PR [#10113](https://github.com/tiangolo/fastapi/pull/10113) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for generating clients. PR [#10112](https://github.com/tiangolo/fastapi/pull/10112) by [@tiangolo](https://github.com/tiangolo). From ea43f227e5c7128c09e3e2d5dceea212552b19dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 25 Aug 2023 21:10:22 +0200 Subject: [PATCH 015/188] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20disab?= =?UTF-8?q?ling=20the=20separation=20of=20input=20and=20output=20JSON=20Sc?= =?UTF-8?q?hemas=20in=20OpenAPI=20with=20Pydantic=20v2=20(#10145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📝 Add docs for Separate OpenAPI Schemas for Input and Output * 🔧 Add new docs page to MkDocs config * ✨ Add separate_input_output_schemas parameter to FastAPI class * 📝 Add source examples for separating OpenAPI schemas * ✅ Add tests for separated OpenAPI schemas * 📝 Add source examples for Python 3.10, 3.9, and 3.7+ * 📝 Update docs for Separate OpenAPI Schemas with new multi-version examples * ✅ Add and update tests for different Python versions * ✅ Add tests for corner cases with separate_input_output_schemas * 📝 Update tutorial to use Union instead of Optional * 🐛 Fix type annotations * 🐛 Fix correct import in test * 💄 Add CSS to simulate browser windows for screenshots * ➕ Add playwright as a dev dependency to automate generating screenshots * 🔨 Add Playwright scripts to generate screenshots for new docs * 📝 Update docs, tweak text to match screenshots * 🍱 Add screenshots for new docs --- docs/en/docs/css/custom.css | 36 ++ .../docs/how-to/separate-openapi-schemas.md | 228 ++++++++ .../separate-openapi-schemas/image01.png | Bin 0 -> 81548 bytes .../separate-openapi-schemas/image02.png | Bin 0 -> 92417 bytes .../separate-openapi-schemas/image03.png | Bin 0 -> 85171 bytes .../separate-openapi-schemas/image04.png | Bin 0 -> 58285 bytes .../separate-openapi-schemas/image05.png | Bin 0 -> 45618 bytes docs/en/mkdocs.yml | 1 + .../separate_openapi_schemas/tutorial001.py | 28 + .../tutorial001_py310.py | 26 + .../tutorial001_py39.py | 28 + .../separate_openapi_schemas/tutorial002.py | 28 + .../tutorial002_py310.py | 26 + .../tutorial002_py39.py | 28 + fastapi/_compat.py | 15 +- fastapi/applications.py | 3 + fastapi/openapi/utils.py | 14 + requirements.txt | 2 + .../separate_openapi_schemas/image01.py | 29 ++ .../separate_openapi_schemas/image02.py | 30 ++ .../separate_openapi_schemas/image03.py | 30 ++ .../separate_openapi_schemas/image04.py | 29 ++ .../separate_openapi_schemas/image05.py | 29 ++ ...t_openapi_separate_input_output_schemas.py | 490 ++++++++++++++++++ .../test_separate_openapi_schemas/__init__.py | 0 .../test_tutorial001.py | 147 ++++++ .../test_tutorial001_py310.py | 150 ++++++ .../test_tutorial001_py39.py | 150 ++++++ .../test_tutorial002.py | 133 +++++ .../test_tutorial002_py310.py | 136 +++++ .../test_tutorial002_py39.py | 136 +++++ 31 files changed, 1950 insertions(+), 2 deletions(-) create mode 100644 docs/en/docs/how-to/separate-openapi-schemas.md create mode 100644 docs/en/docs/img/tutorial/separate-openapi-schemas/image01.png create mode 100644 docs/en/docs/img/tutorial/separate-openapi-schemas/image02.png create mode 100644 docs/en/docs/img/tutorial/separate-openapi-schemas/image03.png create mode 100644 docs/en/docs/img/tutorial/separate-openapi-schemas/image04.png create mode 100644 docs/en/docs/img/tutorial/separate-openapi-schemas/image05.png create mode 100644 docs_src/separate_openapi_schemas/tutorial001.py create mode 100644 docs_src/separate_openapi_schemas/tutorial001_py310.py create mode 100644 docs_src/separate_openapi_schemas/tutorial001_py39.py create mode 100644 docs_src/separate_openapi_schemas/tutorial002.py create mode 100644 docs_src/separate_openapi_schemas/tutorial002_py310.py create mode 100644 docs_src/separate_openapi_schemas/tutorial002_py39.py create mode 100644 scripts/playwright/separate_openapi_schemas/image01.py create mode 100644 scripts/playwright/separate_openapi_schemas/image02.py create mode 100644 scripts/playwright/separate_openapi_schemas/image03.py create mode 100644 scripts/playwright/separate_openapi_schemas/image04.py create mode 100644 scripts/playwright/separate_openapi_schemas/image05.py create mode 100644 tests/test_openapi_separate_input_output_schemas.py create mode 100644 tests/test_tutorial/test_separate_openapi_schemas/__init__.py create mode 100644 tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py create mode 100644 tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py create mode 100644 tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py create mode 100644 tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py create mode 100644 tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css index 066b51725..187040792 100644 --- a/docs/en/docs/css/custom.css +++ b/docs/en/docs/css/custom.css @@ -144,3 +144,39 @@ code { margin-top: 2em; margin-bottom: 2em; } + +/* Screenshots */ +/* +Simulate a browser window frame. +Inspired by Termynal's CSS tricks with modifications +*/ + +.screenshot { + display: block; + background-color: #d3e0de; + border-radius: 4px; + padding: 45px 5px 5px; + position: relative; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.screenshot img { + display: block; + border-radius: 2px; +} + +.screenshot:before { + content: ''; + position: absolute; + top: 15px; + left: 15px; + display: inline-block; + width: 15px; + height: 15px; + border-radius: 50%; + /* A little hack to display the window buttons in one pseudo element. */ + background: #d9515d; + -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; + box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; +} diff --git a/docs/en/docs/how-to/separate-openapi-schemas.md b/docs/en/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 000000000..39d96ea39 --- /dev/null +++ b/docs/en/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,228 @@ +# Separate OpenAPI Schemas for Input and Output or Not + +When using **Pydantic v2**, the generated OpenAPI is a bit more exact and **correct** than before. 😎 + +In fact, in some cases, it will even have **two JSON Schemas** in OpenAPI for the same Pydantic model, for input and output, depending on if they have **default values**. + +Let's see how that works and how to change it if you need to do that. + +## Pydantic Models for Input and Output + +Let's say you have a Pydantic model with default values, like this one: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py[ln:1-7]!} + + # Code below omitted 👇 + ``` + +
+ 👀 Full file preview + + ```Python + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} + ``` + +
+ +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py[ln:1-9]!} + + # Code below omitted 👇 + ``` + +
+ 👀 Full file preview + + ```Python + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} + ``` + +
+ +=== "Python 3.7+" + + ```Python hl_lines="9" + {!> ../../../docs_src/separate_openapi_schemas/tutorial001.py[ln:1-9]!} + + # Code below omitted 👇 + ``` + +
+ 👀 Full file preview + + ```Python + {!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!} + ``` + +
+ +### Model for Input + +If you use this model as an input like here: + +=== "Python 3.10+" + + ```Python hl_lines="14" + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py[ln:1-15]!} + + # Code below omitted 👇 + ``` + +
+ 👀 Full file preview + + ```Python + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} + ``` + +
+ +=== "Python 3.9+" + + ```Python hl_lines="16" + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py[ln:1-17]!} + + # Code below omitted 👇 + ``` + +
+ 👀 Full file preview + + ```Python + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} + ``` + +
+ +=== "Python 3.7+" + + ```Python hl_lines="16" + {!> ../../../docs_src/separate_openapi_schemas/tutorial001.py[ln:1-17]!} + + # Code below omitted 👇 + ``` + +
+ 👀 Full file preview + + ```Python + {!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!} + ``` + +
+ +...then the `description` field will **not be required**. Because it has a default value of `None`. + +### Input Model in Docs + +You can confirm that in the docs, the `description` field doesn't have a **red asterisk**, it's not marked as required: + +
+ +
+ +### Model for Output + +But if you use the same model as an output, like here: + +=== "Python 3.10+" + + ```Python hl_lines="19" + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="21" + {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} + ``` + +=== "Python 3.7+" + + ```Python hl_lines="21" + {!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!} + ``` + +...then because `description` has a default value, if you **don't return anything** for that field, it will still have that **default value**. + +### Model for Output Response Data + +If you interact with the docs and check the response, even though the code didn't add anything in one of the `description` fields, the JSON response contains the default value (`null`): + +
+ +
+ +This means that it will **always have a value**, it's just that sometimes the value could be `None` (or `null` in JSON). + +That means that, clients using your API don't have to check if the value exists or not, they can **asume the field will always be there**, but just that in some cases it will have the default value of `None`. + +The way to describe this in OpenAPI, is to mark that field as **required**, because it will always be there. + +Because of that, the JSON Schema for a model can be different depending on if it's used for **input or output**: + +* for **input** the `description` will **not be required** +* for **output** it will be **required** (and possibly `None`, or in JSON terms, `null`) + +### Model for Output in Docs + +You can check the output model in the docs too, **both** `name` and `description` are marked as **required** with a **red asterisk**: + +
+ +
+ +### Model for Input and Output in Docs + +And if you check all the available Schemas (JSON Schemas) in OpenAPI, you will see that there are two, one `Item-Input` and one `Item-Output`. + +For `Item-Input`, `description` is **not required**, it doesn't have a red asterisk. + +But for `Item-Output`, `description` is **required**, it has a red asterisk. + +
+ +
+ +With this feature from **Pydantic v2**, your API documentation is more **precise**, and if you have autogenerated clients and SDKs, they will be more precise too, with a better **developer experience** and consistency. 🎉 + +## Do not Separate Schemas + +Now, there are some cases where you might want to have the **same schema for input and output**. + +Probably the main use case for this is if you already have some autogenerated client code/SDKs and you don't want to update all the autogenerated client code/SDKs yet, you probably will want to do it at some point, but maybe not right now. + +In that case, you can disable this feature in **FastAPI**, with the parameter `separate_input_output_schemas=False`. + +=== "Python 3.10+" + + ```Python hl_lines="10" + {!> ../../../docs_src/separate_openapi_schemas/tutorial002_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="12" + {!> ../../../docs_src/separate_openapi_schemas/tutorial002_py39.py!} + ``` + +=== "Python 3.7+" + + ```Python hl_lines="12" + {!> ../../../docs_src/separate_openapi_schemas/tutorial002.py!} + ``` + +### Same Schema for Input and Output Models in Docs + +And now there will be one single schema for input and output for the model, only `Item`, and it will have `description` as **not required**: + +
+ +
+ +This is the same behavior as in Pydantic v1. 🤓 diff --git a/docs/en/docs/img/tutorial/separate-openapi-schemas/image01.png b/docs/en/docs/img/tutorial/separate-openapi-schemas/image01.png new file mode 100644 index 0000000000000000000000000000000000000000..aa085f88df45e53aaddb11e35b346075eeff2670 GIT binary patch literal 81548 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¥b$&#Qf4~ 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 literal 0 HcmV?d00001 diff --git a/docs/en/docs/img/tutorial/separate-openapi-schemas/image02.png b/docs/en/docs/img/tutorial/separate-openapi-schemas/image02.png new file mode 100644 index 0000000000000000000000000000000000000000..672ef1d2b71df96e9046fd3080ba5d2837721e41 GIT binary patch literal 92417 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%0A=&HIJw6ofo2IQv*&ppKc@i)9TDSQ1L41B(-m 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 ZmdSXHb5bb@l<>c~vlh+_pKh`HKLGA#a;E?Q literal 0 HcmV?d00001 diff --git a/docs/en/docs/img/tutorial/separate-openapi-schemas/image03.png b/docs/en/docs/img/tutorial/separate-openapi-schemas/image03.png new file mode 100644 index 0000000000000000000000000000000000000000..81340fbece9420cb01981aeb401fc3a6f532e047 GIT binary patch literal 85171 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 literal 0 HcmV?d00001 diff --git a/docs/en/docs/img/tutorial/separate-openapi-schemas/image04.png b/docs/en/docs/img/tutorial/separate-openapi-schemas/image04.png new file mode 100644 index 0000000000000000000000000000000000000000..fc2302aa77ce27ef23caeb7ef0144725c919738a GIT binary patch literal 58285 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 literal 0 HcmV?d00001 diff --git a/docs/en/docs/img/tutorial/separate-openapi-schemas/image05.png b/docs/en/docs/img/tutorial/separate-openapi-schemas/image05.png new file mode 100644 index 0000000000000000000000000000000000000000..674dd0b2e273078ffea526c0dd8572547a4c04f2 GIT binary patch literal 45618 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 z List[Item]: + return [ + Item( + name="Portal Gun", + description="Device to travel through the multi-rick-verse", + ), + Item(name="Plumbus"), + ] diff --git a/docs_src/separate_openapi_schemas/tutorial001_py310.py b/docs_src/separate_openapi_schemas/tutorial001_py310.py new file mode 100644 index 000000000..289cb54ed --- /dev/null +++ b/docs_src/separate_openapi_schemas/tutorial001_py310.py @@ -0,0 +1,26 @@ +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: str | None = None + + +app = FastAPI() + + +@app.post("/items/") +def create_item(item: Item): + return item + + +@app.get("/items/") +def read_items() -> list[Item]: + return [ + Item( + name="Portal Gun", + description="Device to travel through the multi-rick-verse", + ), + Item(name="Plumbus"), + ] diff --git a/docs_src/separate_openapi_schemas/tutorial001_py39.py b/docs_src/separate_openapi_schemas/tutorial001_py39.py new file mode 100644 index 000000000..63cffd1e3 --- /dev/null +++ b/docs_src/separate_openapi_schemas/tutorial001_py39.py @@ -0,0 +1,28 @@ +from typing import Optional + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + + +app = FastAPI() + + +@app.post("/items/") +def create_item(item: Item): + return item + + +@app.get("/items/") +def read_items() -> list[Item]: + return [ + Item( + name="Portal Gun", + description="Device to travel through the multi-rick-verse", + ), + Item(name="Plumbus"), + ] diff --git a/docs_src/separate_openapi_schemas/tutorial002.py b/docs_src/separate_openapi_schemas/tutorial002.py new file mode 100644 index 000000000..7df93783b --- /dev/null +++ b/docs_src/separate_openapi_schemas/tutorial002.py @@ -0,0 +1,28 @@ +from typing import List, Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + + +app = FastAPI(separate_input_output_schemas=False) + + +@app.post("/items/") +def create_item(item: Item): + return item + + +@app.get("/items/") +def read_items() -> List[Item]: + return [ + Item( + name="Portal Gun", + description="Device to travel through the multi-rick-verse", + ), + Item(name="Plumbus"), + ] diff --git a/docs_src/separate_openapi_schemas/tutorial002_py310.py b/docs_src/separate_openapi_schemas/tutorial002_py310.py new file mode 100644 index 000000000..5db210872 --- /dev/null +++ b/docs_src/separate_openapi_schemas/tutorial002_py310.py @@ -0,0 +1,26 @@ +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: str | None = None + + +app = FastAPI(separate_input_output_schemas=False) + + +@app.post("/items/") +def create_item(item: Item): + return item + + +@app.get("/items/") +def read_items() -> list[Item]: + return [ + Item( + name="Portal Gun", + description="Device to travel through the multi-rick-verse", + ), + Item(name="Plumbus"), + ] diff --git a/docs_src/separate_openapi_schemas/tutorial002_py39.py b/docs_src/separate_openapi_schemas/tutorial002_py39.py new file mode 100644 index 000000000..50d997d92 --- /dev/null +++ b/docs_src/separate_openapi_schemas/tutorial002_py39.py @@ -0,0 +1,28 @@ +from typing import Optional + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + + +app = FastAPI(separate_input_output_schemas=False) + + +@app.post("/items/") +def create_item(item: Item): + return item + + +@app.get("/items/") +def read_items() -> list[Item]: + return [ + Item( + name="Portal Gun", + description="Device to travel through the multi-rick-verse", + ), + Item(name="Plumbus"), + ] diff --git a/fastapi/_compat.py b/fastapi/_compat.py index 9ffcaf409..eb55b08f2 100644 --- a/fastapi/_compat.py +++ b/fastapi/_compat.py @@ -181,9 +181,13 @@ if PYDANTIC_V2: field_mapping: Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], + separate_input_output_schemas: bool = True, ) -> Dict[str, Any]: + override_mode: Union[Literal["validation"], None] = ( + None if separate_input_output_schemas else "validation" + ) # This expects that GenerateJsonSchema was already used to generate the definitions - json_schema = field_mapping[(field, field.mode)] + json_schema = field_mapping[(field, override_mode or field.mode)] if "$ref" not in json_schema: # TODO remove when deprecating Pydantic v1 # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207 @@ -200,14 +204,19 @@ if PYDANTIC_V2: fields: List[ModelField], schema_generator: GenerateJsonSchema, model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, ) -> Tuple[ Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], Dict[str, Dict[str, Any]], ]: + override_mode: Union[Literal["validation"], None] = ( + None if separate_input_output_schemas else "validation" + ) inputs = [ - (field, field.mode, field._type_adapter.core_schema) for field in fields + (field, override_mode or field.mode, field._type_adapter.core_schema) + for field in fields ] field_mapping, definitions = schema_generator.generate_definitions( inputs=inputs @@ -429,6 +438,7 @@ else: field_mapping: Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], + separate_input_output_schemas: bool = True, ) -> Dict[str, Any]: # This expects that GenerateJsonSchema was already used to generate the definitions return field_schema( # type: ignore[no-any-return] @@ -444,6 +454,7 @@ else: fields: List[ModelField], schema_generator: GenerateJsonSchema, model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, ) -> Tuple[ Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue diff --git a/fastapi/applications.py b/fastapi/applications.py index e32cfa03d..b681e50b3 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -92,6 +92,7 @@ class FastAPI(Starlette): generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), + separate_input_output_schemas: bool = True, **extra: Any, ) -> None: self.debug = debug @@ -111,6 +112,7 @@ class FastAPI(Starlette): self.swagger_ui_init_oauth = swagger_ui_init_oauth self.swagger_ui_parameters = swagger_ui_parameters self.servers = servers or [] + self.separate_input_output_schemas = separate_input_output_schemas self.extra = extra self.openapi_version = "3.1.0" self.openapi_schema: Optional[Dict[str, Any]] = None @@ -227,6 +229,7 @@ class FastAPI(Starlette): webhooks=self.webhooks.routes, tags=self.openapi_tags, servers=self.servers, + separate_input_output_schemas=self.separate_input_output_schemas, ) return self.openapi_schema diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index e295361e6..9498375fe 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -95,6 +95,7 @@ def get_openapi_operation_parameters( field_mapping: Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], + separate_input_output_schemas: bool = True, ) -> List[Dict[str, Any]]: parameters = [] for param in all_route_params: @@ -107,6 +108,7 @@ def get_openapi_operation_parameters( schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, ) parameter = { "name": param.alias, @@ -132,6 +134,7 @@ def get_openapi_operation_request_body( field_mapping: Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], + separate_input_output_schemas: bool = True, ) -> Optional[Dict[str, Any]]: if not body_field: return None @@ -141,6 +144,7 @@ def get_openapi_operation_request_body( schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, ) field_info = cast(Body, body_field.field_info) request_media_type = field_info.media_type @@ -211,6 +215,7 @@ def get_openapi_path( field_mapping: Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], + separate_input_output_schemas: bool = True, ) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]: path = {} security_schemes: Dict[str, Any] = {} @@ -242,6 +247,7 @@ def get_openapi_path( schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, ) parameters.extend(operation_parameters) if parameters: @@ -263,6 +269,7 @@ def get_openapi_path( schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, ) if request_body_oai: operation["requestBody"] = request_body_oai @@ -280,6 +287,7 @@ def get_openapi_path( schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, ) callbacks[callback.name] = {callback.path: cb_path} operation["callbacks"] = callbacks @@ -310,6 +318,7 @@ def get_openapi_path( schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, ) else: response_schema = {} @@ -343,6 +352,7 @@ def get_openapi_path( schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, ) media_type = route_response_media_type or "application/json" additional_schema = ( @@ -433,6 +443,7 @@ def get_openapi( terms_of_service: Optional[str] = None, contact: Optional[Dict[str, Union[str, Any]]] = None, license_info: Optional[Dict[str, Union[str, Any]]] = None, + separate_input_output_schemas: bool = True, ) -> Dict[str, Any]: info: Dict[str, Any] = {"title": title, "version": version} if summary: @@ -459,6 +470,7 @@ def get_openapi( fields=all_fields, schema_generator=schema_generator, model_name_map=model_name_map, + separate_input_output_schemas=separate_input_output_schemas, ) for route in routes or []: if isinstance(route, routing.APIRoute): @@ -468,6 +480,7 @@ def get_openapi( schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, ) if result: path, security_schemes, path_definitions = result @@ -487,6 +500,7 @@ def get_openapi( schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, ) if result: path, security_schemes, path_definitions = result diff --git a/requirements.txt b/requirements.txt index 7e746016a..ef25ec483 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,5 @@ -r requirements-docs.txt uvicorn[standard] >=0.12.0,<0.23.0 pre-commit >=2.17.0,<4.0.0 +# For generating screenshots +playwright diff --git a/scripts/playwright/separate_openapi_schemas/image01.py b/scripts/playwright/separate_openapi_schemas/image01.py new file mode 100644 index 000000000..0b40f3bbc --- /dev/null +++ b/scripts/playwright/separate_openapi_schemas/image01.py @@ -0,0 +1,29 @@ +import subprocess + +from playwright.sync_api import Playwright, sync_playwright + + +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_text("POST/items/Create Item").click() + page.get_by_role("tab", name="Schema").first.click() + page.screenshot( + path="docs/en/docs/img/tutorial/separate-openapi-schemas/image01.png" + ) + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["uvicorn", "docs_src.separate_openapi_schemas.tutorial001:app"] +) +try: + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/separate_openapi_schemas/image02.py b/scripts/playwright/separate_openapi_schemas/image02.py new file mode 100644 index 000000000..f76af7ee2 --- /dev/null +++ b/scripts/playwright/separate_openapi_schemas/image02.py @@ -0,0 +1,30 @@ +import subprocess + +from playwright.sync_api import Playwright, sync_playwright + + +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_text("GET/items/Read Items").click() + page.get_by_role("button", name="Try it out").click() + page.get_by_role("button", name="Execute").click() + page.screenshot( + path="docs/en/docs/img/tutorial/separate-openapi-schemas/image02.png" + ) + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["uvicorn", "docs_src.separate_openapi_schemas.tutorial001:app"] +) +try: + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/separate_openapi_schemas/image03.py b/scripts/playwright/separate_openapi_schemas/image03.py new file mode 100644 index 000000000..127f5c428 --- /dev/null +++ b/scripts/playwright/separate_openapi_schemas/image03.py @@ -0,0 +1,30 @@ +import subprocess + +from playwright.sync_api import Playwright, sync_playwright + + +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_text("GET/items/Read Items").click() + page.get_by_role("tab", name="Schema").click() + page.get_by_label("Schema").get_by_role("button", name="Expand all").click() + page.screenshot( + path="docs/en/docs/img/tutorial/separate-openapi-schemas/image03.png" + ) + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["uvicorn", "docs_src.separate_openapi_schemas.tutorial001:app"] +) +try: + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/separate_openapi_schemas/image04.py b/scripts/playwright/separate_openapi_schemas/image04.py new file mode 100644 index 000000000..208eaf8a0 --- /dev/null +++ b/scripts/playwright/separate_openapi_schemas/image04.py @@ -0,0 +1,29 @@ +import subprocess + +from playwright.sync_api import Playwright, sync_playwright + + +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("button", name="Item-Input").click() + page.get_by_role("button", name="Item-Output").click() + page.set_viewport_size({"width": 960, "height": 820}) + page.screenshot( + path="docs/en/docs/img/tutorial/separate-openapi-schemas/image04.png" + ) + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["uvicorn", "docs_src.separate_openapi_schemas.tutorial001:app"] +) +try: + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/separate_openapi_schemas/image05.py b/scripts/playwright/separate_openapi_schemas/image05.py new file mode 100644 index 000000000..83966b449 --- /dev/null +++ b/scripts/playwright/separate_openapi_schemas/image05.py @@ -0,0 +1,29 @@ +import subprocess + +from playwright.sync_api import Playwright, sync_playwright + + +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("button", name="Item", exact=True).click() + page.set_viewport_size({"width": 960, "height": 700}) + page.screenshot( + path="docs/en/docs/img/tutorial/separate-openapi-schemas/image05.png" + ) + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["uvicorn", "docs_src.separate_openapi_schemas.tutorial002:app"] +) +try: + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py new file mode 100644 index 000000000..70f4b90d7 --- /dev/null +++ b/tests/test_openapi_separate_input_output_schemas.py @@ -0,0 +1,490 @@ +from typing import List, Optional + +from fastapi import FastAPI +from fastapi.testclient import TestClient +from pydantic import BaseModel + +from .utils import needs_pydanticv2 + + +class SubItem(BaseModel): + subname: str + sub_description: Optional[str] = None + tags: List[str] = [] + + +class Item(BaseModel): + name: str + description: Optional[str] = None + sub: Optional[SubItem] = None + + +def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: + app = FastAPI(separate_input_output_schemas=separate_input_output_schemas) + + @app.post("/items/") + def create_item(item: Item): + return item + + @app.post("/items-list/") + def create_item_list(item: List[Item]): + return item + + @app.get("/items/") + def read_items() -> List[Item]: + return [ + Item( + name="Portal Gun", + description="Device to travel through the multi-rick-verse", + sub=SubItem(subname="subname"), + ), + Item(name="Plumbus"), + ] + + client = TestClient(app) + return client + + +def test_create_item(): + client = get_app_client() + client_no = get_app_client(separate_input_output_schemas=False) + response = client.post("/items/", json={"name": "Plumbus"}) + response2 = client_no.post("/items/", json={"name": "Plumbus"}) + assert response.status_code == response2.status_code == 200, response.text + assert ( + response.json() + == response2.json() + == {"name": "Plumbus", "description": None, "sub": None} + ) + + +def test_create_item_with_sub(): + client = get_app_client() + client_no = get_app_client(separate_input_output_schemas=False) + data = { + "name": "Plumbus", + "sub": {"subname": "SubPlumbus", "sub_description": "Sub WTF"}, + } + response = client.post("/items/", json=data) + response2 = client_no.post("/items/", json=data) + assert response.status_code == response2.status_code == 200, response.text + assert ( + response.json() + == response2.json() + == { + "name": "Plumbus", + "description": None, + "sub": {"subname": "SubPlumbus", "sub_description": "Sub WTF", "tags": []}, + } + ) + + +def test_create_item_list(): + client = get_app_client() + client_no = get_app_client(separate_input_output_schemas=False) + data = [ + {"name": "Plumbus"}, + { + "name": "Portal Gun", + "description": "Device to travel through the multi-rick-verse", + }, + ] + response = client.post("/items-list/", json=data) + response2 = client_no.post("/items-list/", json=data) + assert response.status_code == response2.status_code == 200, response.text + assert ( + response.json() + == response2.json() + == [ + {"name": "Plumbus", "description": None, "sub": None}, + { + "name": "Portal Gun", + "description": "Device to travel through the multi-rick-verse", + "sub": None, + }, + ] + ) + + +def test_read_items(): + client = get_app_client() + client_no = get_app_client(separate_input_output_schemas=False) + response = client.get("/items/") + response2 = client_no.get("/items/") + assert response.status_code == response2.status_code == 200, response.text + assert ( + response.json() + == response2.json() + == [ + { + "name": "Portal Gun", + "description": "Device to travel through the multi-rick-verse", + "sub": {"subname": "subname", "sub_description": None, "tags": []}, + }, + {"name": "Plumbus", "description": None, "sub": None}, + ] + ) + + +@needs_pydanticv2 +def test_openapi_schema(): + client = get_app_client() + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item-Output" + }, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item-Input"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/items-list/": { + "post": { + "summary": "Create Item List", + "operationId": "create_item_list_items_list__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item-Input" + }, + "type": "array", + "title": "Item", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": {"$ref": "#/components/schemas/ValidationError"}, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item-Input": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "sub": { + "anyOf": [ + {"$ref": "#/components/schemas/SubItem-Input"}, + {"type": "null"}, + ] + }, + }, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "Item-Output": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "sub": { + "anyOf": [ + {"$ref": "#/components/schemas/SubItem-Output"}, + {"type": "null"}, + ] + }, + }, + "type": "object", + "required": ["name", "description", "sub"], + "title": "Item", + }, + "SubItem-Input": { + "properties": { + "subname": {"type": "string", "title": "Subname"}, + "sub_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Sub Description", + }, + "tags": { + "items": {"type": "string"}, + "type": "array", + "title": "Tags", + "default": [], + }, + }, + "type": "object", + "required": ["subname"], + "title": "SubItem", + }, + "SubItem-Output": { + "properties": { + "subname": {"type": "string", "title": "Subname"}, + "sub_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Sub Description", + }, + "tags": { + "items": {"type": "string"}, + "type": "array", + "title": "Tags", + "default": [], + }, + }, + "type": "object", + "required": ["subname", "sub_description", "tags"], + "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", + }, + } + }, + } + + +@needs_pydanticv2 +def test_openapi_schema_no_separate(): + client = get_app_client(separate_input_output_schemas=False) + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/items-list/": { + "post": { + "summary": "Create Item List", + "operationId": "create_item_list_items_list__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Item", + } + } + }, + "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": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "sub": { + "anyOf": [ + {"$ref": "#/components/schemas/SubItem"}, + {"type": "null"}, + ] + }, + }, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "SubItem": { + "properties": { + "subname": {"type": "string", "title": "Subname"}, + "sub_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Sub Description", + }, + "tags": { + "items": {"type": "string"}, + "type": "array", + "title": "Tags", + "default": [], + }, + }, + "type": "object", + "required": ["subname"], + "title": "SubItem", + }, + "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_separate_openapi_schemas/__init__.py b/tests/test_tutorial/test_separate_openapi_schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py new file mode 100644 index 000000000..8079c1134 --- /dev/null +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py @@ -0,0 +1,147 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_pydanticv2 + + +@pytest.fixture(name="client") +def get_client() -> TestClient: + from docs_src.separate_openapi_schemas.tutorial001 import app + + client = TestClient(app) + return client + + +def test_create_item(client: TestClient) -> None: + response = client.post("/items/", json={"name": "Foo"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "description": None} + + +def test_read_items(client: TestClient) -> None: + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [ + { + "name": "Portal Gun", + "description": "Device to travel through the multi-rick-verse", + }, + {"name": "Plumbus", "description": None}, + ] + + +@needs_pydanticv2 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item-Output" + }, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item-Input"} + } + }, + "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-Input": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + }, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "Item-Output": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + }, + "type": "object", + "required": ["name", "description"], + "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_separate_openapi_schemas/test_tutorial001_py310.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py new file mode 100644 index 000000000..4fa98ccbe --- /dev/null +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py @@ -0,0 +1,150 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310, needs_pydanticv2 + + +@pytest.fixture(name="client") +def get_client() -> TestClient: + from docs_src.separate_openapi_schemas.tutorial001_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_create_item(client: TestClient) -> None: + response = client.post("/items/", json={"name": "Foo"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "description": None} + + +@needs_py310 +def test_read_items(client: TestClient) -> None: + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [ + { + "name": "Portal Gun", + "description": "Device to travel through the multi-rick-verse", + }, + {"name": "Plumbus", "description": None}, + ] + + +@needs_py310 +@needs_pydanticv2 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item-Output" + }, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item-Input"} + } + }, + "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-Input": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + }, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "Item-Output": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + }, + "type": "object", + "required": ["name", "description"], + "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_separate_openapi_schemas/test_tutorial001_py39.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py new file mode 100644 index 000000000..ad36582ed --- /dev/null +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py @@ -0,0 +1,150 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39, needs_pydanticv2 + + +@pytest.fixture(name="client") +def get_client() -> TestClient: + from docs_src.separate_openapi_schemas.tutorial001_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_create_item(client: TestClient) -> None: + response = client.post("/items/", json={"name": "Foo"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "description": None} + + +@needs_py39 +def test_read_items(client: TestClient) -> None: + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [ + { + "name": "Portal Gun", + "description": "Device to travel through the multi-rick-verse", + }, + {"name": "Plumbus", "description": None}, + ] + + +@needs_py39 +@needs_pydanticv2 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item-Output" + }, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item-Input"} + } + }, + "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-Input": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + }, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "Item-Output": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + }, + "type": "object", + "required": ["name", "description"], + "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_separate_openapi_schemas/test_tutorial002.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py new file mode 100644 index 000000000..d2cf7945b --- /dev/null +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py @@ -0,0 +1,133 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_pydanticv2 + + +@pytest.fixture(name="client") +def get_client() -> TestClient: + from docs_src.separate_openapi_schemas.tutorial002 import app + + client = TestClient(app) + return client + + +def test_create_item(client: TestClient) -> None: + response = client.post("/items/", json={"name": "Foo"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "description": None} + + +def test_read_items(client: TestClient) -> None: + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [ + { + "name": "Portal Gun", + "description": "Device to travel through the multi-rick-verse", + }, + {"name": "Plumbus", "description": None}, + ] + + +@needs_pydanticv2 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "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", + }, + }, + "type": "object", + "required": ["name"], + "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_separate_openapi_schemas/test_tutorial002_py310.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py new file mode 100644 index 000000000..89c9ce977 --- /dev/null +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py @@ -0,0 +1,136 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310, needs_pydanticv2 + + +@pytest.fixture(name="client") +def get_client() -> TestClient: + from docs_src.separate_openapi_schemas.tutorial002_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_create_item(client: TestClient) -> None: + response = client.post("/items/", json={"name": "Foo"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "description": None} + + +@needs_py310 +def test_read_items(client: TestClient) -> None: + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [ + { + "name": "Portal Gun", + "description": "Device to travel through the multi-rick-verse", + }, + {"name": "Plumbus", "description": None}, + ] + + +@needs_py310 +@needs_pydanticv2 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "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", + }, + }, + "type": "object", + "required": ["name"], + "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_separate_openapi_schemas/test_tutorial002_py39.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py new file mode 100644 index 000000000..6ac3d8f79 --- /dev/null +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py @@ -0,0 +1,136 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39, needs_pydanticv2 + + +@pytest.fixture(name="client") +def get_client() -> TestClient: + from docs_src.separate_openapi_schemas.tutorial002_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_create_item(client: TestClient) -> None: + response = client.post("/items/", json={"name": "Foo"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "description": None} + + +@needs_py39 +def test_read_items(client: TestClient) -> None: + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [ + { + "name": "Portal Gun", + "description": "Device to travel through the multi-rick-verse", + }, + {"name": "Plumbus", "description": None}, + ] + + +@needs_py39 +@needs_pydanticv2 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "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", + }, + }, + "type": "object", + "required": ["name"], + "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", + }, + } + }, + } From 098778e07f8d04d3fbf3a21f812ca97ab7daa46e Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 25 Aug 2023 19:11:02 +0000 Subject: [PATCH 016/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 61ec91b4a..3858f47da 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ Add support for disabling the separation of input and output JSON Schemas in OpenAPI with Pydantic v2. PR [#10145](https://github.com/tiangolo/fastapi/pull/10145) by [@tiangolo](https://github.com/tiangolo). * 📝 Add new docs section, How To - Recipes, move docs that don't have to be read by everyone to How To. PR [#10114](https://github.com/tiangolo/fastapi/pull/10114) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor tests for new Pydantic 2.2.1. PR [#10115](https://github.com/tiangolo/fastapi/pull/10115) by [@tiangolo](https://github.com/tiangolo). * 📝 Update Advanced docs, add links to sponsor courses. PR [#10113](https://github.com/tiangolo/fastapi/pull/10113) by [@tiangolo](https://github.com/tiangolo). From 859d40407caa3850862526ee689e2d22177cf19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 25 Aug 2023 21:18:09 +0200 Subject: [PATCH 017/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 3858f47da..517553c9a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,14 +2,28 @@ ## Latest Changes -* ✨ Add support for disabling the separation of input and output JSON Schemas in OpenAPI with Pydantic v2. PR [#10145](https://github.com/tiangolo/fastapi/pull/10145) by [@tiangolo](https://github.com/tiangolo). -* 📝 Add new docs section, How To - Recipes, move docs that don't have to be read by everyone to How To. PR [#10114](https://github.com/tiangolo/fastapi/pull/10114) by [@tiangolo](https://github.com/tiangolo). + +### Features + +* ✨ Add support for disabling the separation of input and output JSON Schemas in OpenAPI with Pydantic v2 with `separate_input_output_schemas=False`. PR [#10145](https://github.com/tiangolo/fastapi/pull/10145) by [@tiangolo](https://github.com/tiangolo). + * New docs [Separate OpenAPI Schemas for Input and Output or Not](https://fastapi.tiangolo.com/how-to/separate-openapi-schemas/). + +### Refactors + * ♻️ Refactor tests for new Pydantic 2.2.1. PR [#10115](https://github.com/tiangolo/fastapi/pull/10115) by [@tiangolo](https://github.com/tiangolo). + +### Docs + +* 📝 Add new docs section, How To - Recipes, move docs that don't have to be read by everyone to How To. PR [#10114](https://github.com/tiangolo/fastapi/pull/10114) by [@tiangolo](https://github.com/tiangolo). * 📝 Update Advanced docs, add links to sponsor courses. PR [#10113](https://github.com/tiangolo/fastapi/pull/10113) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for generating clients. PR [#10112](https://github.com/tiangolo/fastapi/pull/10112) by [@tiangolo](https://github.com/tiangolo). * 📝 Tweak MkDocs and add redirects. PR [#10111](https://github.com/tiangolo/fastapi/pull/10111) by [@tiangolo](https://github.com/tiangolo). * 📝 Restructure docs for cloud providers, include links to sponsors. PR [#10110](https://github.com/tiangolo/fastapi/pull/10110) by [@tiangolo](https://github.com/tiangolo). + +### Internal + * 🔧 Update sponsors, add Speakeasy. PR [#10098](https://github.com/tiangolo/fastapi/pull/10098) by [@tiangolo](https://github.com/tiangolo). + ## 0.101.1 ### Fixes From 9cf9e1084dceb45e50946e4130801012ac0c0c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 25 Aug 2023 21:18:38 +0200 Subject: [PATCH 018/188] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.10?= =?UTF-8?q?2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + fastapi/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 517553c9a..2375bfee5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +## 0.102.0 ### Features diff --git a/fastapi/__init__.py b/fastapi/__init__.py index d8abf2103..6979ec5fb 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.101.1" +__version__ = "0.102.0" from starlette import status as status From f3ab547c0c6e5b42b81e3669aaea753f742b742c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 25 Aug 2023 21:23:44 +0200 Subject: [PATCH 019/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2375bfee5..1a9721e2c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -8,6 +8,7 @@ * ✨ Add support for disabling the separation of input and output JSON Schemas in OpenAPI with Pydantic v2 with `separate_input_output_schemas=False`. PR [#10145](https://github.com/tiangolo/fastapi/pull/10145) by [@tiangolo](https://github.com/tiangolo). * New docs [Separate OpenAPI Schemas for Input and Output or Not](https://fastapi.tiangolo.com/how-to/separate-openapi-schemas/). + * This PR also includes a new setup (internal tools) for generating screenshots for the docs. ### Refactors From 594b1ae0c38550ddfdedab314276c1a659ce4719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 26 Aug 2023 15:20:04 +0200 Subject: [PATCH 020/188] =?UTF-8?q?=F0=9F=93=9D=20Add=20note=20to=20docs?= =?UTF-8?q?=20about=20Separate=20Input=20and=20Output=20Schemas=20with=20F?= =?UTF-8?q?astAPI=20version=20(#10150)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/how-to/separate-openapi-schemas.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/en/docs/how-to/separate-openapi-schemas.md b/docs/en/docs/how-to/separate-openapi-schemas.md index 39d96ea39..d289391ca 100644 --- a/docs/en/docs/how-to/separate-openapi-schemas.md +++ b/docs/en/docs/how-to/separate-openapi-schemas.md @@ -199,6 +199,9 @@ Probably the main use case for this is if you already have some autogenerated cl In that case, you can disable this feature in **FastAPI**, with the parameter `separate_input_output_schemas=False`. +!!! info + Support for `separate_input_output_schemas` was added in FastAPI `0.102.0`. 🤓 + === "Python 3.10+" ```Python hl_lines="10" From 5f855b1179d06c8b82f479013e5888c48e68c716 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 26 Aug 2023 13:20:54 +0000 Subject: [PATCH 021/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1a9721e2c..5d15e1c25 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Add note to docs about Separate Input and Output Schemas with FastAPI version. PR [#10150](https://github.com/tiangolo/fastapi/pull/10150) by [@tiangolo](https://github.com/tiangolo). ## 0.102.0 ### Features From 1b714b317732df1e08983e4a8ffe2e4e02c995e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 26 Aug 2023 20:03:13 +0200 Subject: [PATCH 022/188] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20`open?= =?UTF-8?q?api=5Fexamples`=20in=20all=20FastAPI=20parameters=20(#10152)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ Refactor model for OpenAPI Examples to use a reusable TypedDict * ✨ Add support for openapi_examples in parameters * 📝 Add new docs examples for new parameter openapi_examples * 📝 Update docs for Schema Extra to include OpenAPI examples * ✅ Add tests for new source examples, for openapi_examples * ✅ Add tests for openapi_examples corner cases and all parameters * 💡 Tweak and ignore type annotation checks for custom TypedDict --- docs/en/docs/tutorial/schema-extra-example.md | 105 +++- docs_src/schema_extra_example/tutorial005.py | 51 ++ .../schema_extra_example/tutorial005_an.py | 55 +++ .../tutorial005_an_py310.py | 54 +++ .../tutorial005_an_py39.py | 54 +++ .../schema_extra_example/tutorial005_py310.py | 49 ++ fastapi/openapi/models.py | 16 +- fastapi/openapi/utils.py | 10 +- fastapi/param_functions.py | 15 + fastapi/params.py | 17 + tests/test_openapi_examples.py | 455 ++++++++++++++++++ .../test_tutorial005.py | 166 +++++++ .../test_tutorial005_an.py | 166 +++++++ .../test_tutorial005_an_py310.py | 170 +++++++ .../test_tutorial005_an_py39.py | 170 +++++++ .../test_tutorial005_py310.py | 170 +++++++ 16 files changed, 1695 insertions(+), 28 deletions(-) create mode 100644 docs_src/schema_extra_example/tutorial005.py create mode 100644 docs_src/schema_extra_example/tutorial005_an.py create mode 100644 docs_src/schema_extra_example/tutorial005_an_py310.py create mode 100644 docs_src/schema_extra_example/tutorial005_an_py39.py create mode 100644 docs_src/schema_extra_example/tutorial005_py310.py create mode 100644 tests/test_openapi_examples.py create mode 100644 tests/test_tutorial/test_schema_extra_example/test_tutorial005.py create mode 100644 tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py create mode 100644 tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py create mode 100644 tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py create mode 100644 tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py diff --git a/docs/en/docs/tutorial/schema-extra-example.md b/docs/en/docs/tutorial/schema-extra-example.md index 39d184763..8cf1b9c09 100644 --- a/docs/en/docs/tutorial/schema-extra-example.md +++ b/docs/en/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ When using `Field()` with Pydantic models, you can also declare additional `exam {!> ../../../docs_src/schema_extra_example/tutorial002.py!} ``` -## `examples` in OpenAPI +## `examples` in JSON Schema - OpenAPI When using any of: @@ -86,7 +86,7 @@ When using any of: * `Form()` * `File()` -you can also declare a group of `examples` with additional information that will be added to **OpenAPI**. +you can also declare a group of `examples` with additional information that will be added to their **JSON Schemas** inside of **OpenAPI**. ### `Body` with `examples` @@ -174,9 +174,84 @@ You can of course also pass multiple `examples`: {!> ../../../docs_src/schema_extra_example/tutorial004.py!} ``` -### Examples in the docs UI +When you do this, the examples will be part of the internal **JSON Schema** for that body data. -With `examples` added to `Body()` the `/docs` would look like: +Nevertheless, at the time of writing this, Swagger UI, the tool in charge of showing the docs UI, doesn't support showing multiple examples for the data in **JSON Schema**. But read below for a workaround. + +### OpenAPI-specific `examples` + +Since before **JSON Schema** supported `examples` OpenAPI had support for a different field also called `examples`. + +This **OpenAPI-specific** `examples` goes in another section in the OpenAPI specification. It goes in the **details for each *path operation***, not inside each JSON Schema. + +And Swagger UI has supported this particular `examples` field for a while. So, you can use it to **show** different **examples in the docs UI**. + +The shape of this OpenAPI-specific field `examples` is a `dict` with **multiple examples** (instead of a `list`), each with extra information that will be added to **OpenAPI** too. + +This doesn't go inside of each JSON Schema contained in OpenAPI, this goes outside, in the *path operation* directly. + +### Using the `openapi_examples` Parameter + +You can declare the OpenAPI-specific `examples` in FastAPI with the parameter `openapi_examples` for: + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +The keys of the `dict` identify each example, and each value is another `dict`. + +Each specific example `dict` in the `examples` can contain: + +* `summary`: Short description for the example. +* `description`: A long description that can contain Markdown text. +* `value`: This is the actual example shown, e.g. a `dict`. +* `externalValue`: alternative to `value`, a URL pointing to the example. Although this might not be supported by as many tools as `value`. + +You can use it like this: + +=== "Python 3.10+" + + ```Python hl_lines="23-49" + {!> ../../../docs_src/schema_extra_example/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="23-49" + {!> ../../../docs_src/schema_extra_example/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="24-50" + {!> ../../../docs_src/schema_extra_example/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="19-45" + {!> ../../../docs_src/schema_extra_example/tutorial005_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="21-47" + {!> ../../../docs_src/schema_extra_example/tutorial005.py!} + ``` + +### OpenAPI Examples in the Docs UI + +With `openapi_examples` added to `Body()` the `/docs` would look like: @@ -210,20 +285,8 @@ OpenAPI also added `example` and `examples` fields to other parts of the specifi * `File()` * `Form()` -### OpenAPI's `examples` field - -The shape of this field `examples` from OpenAPI is a `dict` with **multiple examples**, each with extra information that will be added to **OpenAPI** too. - -The keys of the `dict` identify each example, and each value is another `dict`. - -Each specific example `dict` in the `examples` can contain: - -* `summary`: Short description for the example. -* `description`: A long description that can contain Markdown text. -* `value`: This is the actual example shown, e.g. a `dict`. -* `externalValue`: alternative to `value`, a URL pointing to the example. Although this might not be supported by as many tools as `value`. - -This applies to those other parts of the OpenAPI specification apart from JSON Schema. +!!! info + This old OpenAPI-specific `examples` parameter is now `openapi_examples` since FastAPI `0.103.0`. ### JSON Schema's `examples` field @@ -250,6 +313,12 @@ In versions of FastAPI before 0.99.0 (0.99.0 and above use the newer OpenAPI 3.1 But now that FastAPI 0.99.0 and above uses OpenAPI 3.1.0, that uses JSON Schema 2020-12, and Swagger UI 5.0.0 and above, everything is more consistent and the examples are included in JSON Schema. +### Swagger UI and OpenAPI-specific `examples` + +Now, as Swagger UI didn't support multiple JSON Schema examples (as of 2023-08-26), users didn't have a way to show multiple examples in the docs. + +To solve that, FastAPI `0.103.0` **added support** for declaring the same old **OpenAPI-specific** `examples` field with the new parameter `openapi_examples`. 🤓 + ### Summary I used to say I didn't like history that much... and look at me now giving "tech history" lessons. 😅 diff --git a/docs_src/schema_extra_example/tutorial005.py b/docs_src/schema_extra_example/tutorial005.py new file mode 100644 index 000000000..b8217c27e --- /dev/null +++ b/docs_src/schema_extra_example/tutorial005.py @@ -0,0 +1,51 @@ +from typing import Union + +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +@app.put("/items/{item_id}") +async def update_item( + *, + item_id: int, + item: Item = Body( + openapi_examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": { + "name": "Bar", + "price": "35.4", + }, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + ), +): + results = {"item_id": item_id, "item": item} + return results diff --git a/docs_src/schema_extra_example/tutorial005_an.py b/docs_src/schema_extra_example/tutorial005_an.py new file mode 100644 index 000000000..4b2d9c662 --- /dev/null +++ b/docs_src/schema_extra_example/tutorial005_an.py @@ -0,0 +1,55 @@ +from typing import Union + +from fastapi import Body, FastAPI +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +@app.put("/items/{item_id}") +async def update_item( + *, + item_id: int, + item: Annotated[ + Item, + Body( + openapi_examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": { + "name": "Bar", + "price": "35.4", + }, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + ), + ], +): + results = {"item_id": item_id, "item": item} + return results diff --git a/docs_src/schema_extra_example/tutorial005_an_py310.py b/docs_src/schema_extra_example/tutorial005_an_py310.py new file mode 100644 index 000000000..64dc2cf90 --- /dev/null +++ b/docs_src/schema_extra_example/tutorial005_an_py310.py @@ -0,0 +1,54 @@ +from typing import Annotated + +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + + +@app.put("/items/{item_id}") +async def update_item( + *, + item_id: int, + item: Annotated[ + Item, + Body( + openapi_examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": { + "name": "Bar", + "price": "35.4", + }, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + ), + ], +): + results = {"item_id": item_id, "item": item} + return results diff --git a/docs_src/schema_extra_example/tutorial005_an_py39.py b/docs_src/schema_extra_example/tutorial005_an_py39.py new file mode 100644 index 000000000..edeb1affc --- /dev/null +++ b/docs_src/schema_extra_example/tutorial005_an_py39.py @@ -0,0 +1,54 @@ +from typing import Annotated, Union + +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +@app.put("/items/{item_id}") +async def update_item( + *, + item_id: int, + item: Annotated[ + Item, + Body( + openapi_examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": { + "name": "Bar", + "price": "35.4", + }, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + ), + ], +): + results = {"item_id": item_id, "item": item} + return results diff --git a/docs_src/schema_extra_example/tutorial005_py310.py b/docs_src/schema_extra_example/tutorial005_py310.py new file mode 100644 index 000000000..eef973343 --- /dev/null +++ b/docs_src/schema_extra_example/tutorial005_py310.py @@ -0,0 +1,49 @@ +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + + +@app.put("/items/{item_id}") +async def update_item( + *, + item_id: int, + item: Item = Body( + openapi_examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": { + "name": "Bar", + "price": "35.4", + }, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + ), +): + results = {"item_id": item_id, "item": item} + return results diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index 2268dd229..3d982eb9a 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -11,7 +11,7 @@ from fastapi._compat import ( ) from fastapi.logger import logger from pydantic import AnyUrl, BaseModel, Field -from typing_extensions import Annotated, Literal +from typing_extensions import Annotated, Literal, TypedDict from typing_extensions import deprecated as typing_deprecated try: @@ -267,14 +267,14 @@ class Schema(BaseModel): SchemaOrBool = Union[Schema, bool] -class Example(BaseModel): - summary: Optional[str] = None - description: Optional[str] = None - value: Optional[Any] = None - externalValue: Optional[AnyUrl] = None +class Example(TypedDict, total=False): + summary: Optional[str] + description: Optional[str] + value: Optional[Any] + externalValue: Optional[AnyUrl] - if PYDANTIC_V2: - model_config = {"extra": "allow"} + if PYDANTIC_V2: # type: ignore [misc] + __pydantic_config__ = {"extra": "allow"} else: diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 9498375fe..5bfb5acef 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -118,7 +118,9 @@ def get_openapi_operation_parameters( } if field_info.description: parameter["description"] = field_info.description - if field_info.example != Undefined: + if field_info.openapi_examples: + parameter["examples"] = jsonable_encoder(field_info.openapi_examples) + elif field_info.example != Undefined: parameter["example"] = jsonable_encoder(field_info.example) if field_info.deprecated: parameter["deprecated"] = field_info.deprecated @@ -153,7 +155,11 @@ def get_openapi_operation_request_body( if required: request_body_oai["required"] = required request_media_content: Dict[str, Any] = {"schema": body_schema} - if field_info.example != Undefined: + if field_info.openapi_examples: + request_media_content["examples"] = jsonable_encoder( + field_info.openapi_examples + ) + elif field_info.example != Undefined: request_media_content["example"] = jsonable_encoder(field_info.example) request_body_oai["content"] = {request_media_type: request_media_content} return request_body_oai diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index a43afaf31..63914d1d6 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -2,6 +2,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Union from fastapi import params from fastapi._compat import Undefined +from fastapi.openapi.models import Example from typing_extensions import Annotated, deprecated _Unset: Any = Undefined @@ -46,6 +47,7 @@ def Path( # noqa: N802 "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -76,6 +78,7 @@ def Path( # noqa: N802 decimal_places=decimal_places, example=example, examples=examples, + openapi_examples=openapi_examples, deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, @@ -122,6 +125,7 @@ def Query( # noqa: N802 "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -152,6 +156,7 @@ def Query( # noqa: N802 decimal_places=decimal_places, example=example, examples=examples, + openapi_examples=openapi_examples, deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, @@ -199,6 +204,7 @@ def Header( # noqa: N802 "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -230,6 +236,7 @@ def Header( # noqa: N802 decimal_places=decimal_places, example=example, examples=examples, + openapi_examples=openapi_examples, deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, @@ -276,6 +283,7 @@ def Cookie( # noqa: N802 "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -306,6 +314,7 @@ def Cookie( # noqa: N802 decimal_places=decimal_places, example=example, examples=examples, + openapi_examples=openapi_examples, deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, @@ -354,6 +363,7 @@ def Body( # noqa: N802 "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -386,6 +396,7 @@ def Body( # noqa: N802 decimal_places=decimal_places, example=example, examples=examples, + openapi_examples=openapi_examples, deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, @@ -433,6 +444,7 @@ def Form( # noqa: N802 "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -464,6 +476,7 @@ def Form( # noqa: N802 decimal_places=decimal_places, example=example, examples=examples, + openapi_examples=openapi_examples, deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, @@ -511,6 +524,7 @@ def File( # noqa: N802 "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -542,6 +556,7 @@ def File( # noqa: N802 decimal_places=decimal_places, example=example, examples=examples, + openapi_examples=openapi_examples, deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, diff --git a/fastapi/params.py b/fastapi/params.py index 2d8100650..b40944dba 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -2,6 +2,7 @@ import warnings from enum import Enum from typing import Any, Callable, Dict, List, Optional, Sequence, Union +from fastapi.openapi.models import Example from pydantic.fields import FieldInfo from typing_extensions import Annotated, deprecated @@ -61,6 +62,7 @@ class Param(FieldInfo): "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -75,6 +77,7 @@ class Param(FieldInfo): ) self.example = example self.include_in_schema = include_in_schema + self.openapi_examples = openapi_examples kwargs = dict( default=default, default_factory=default_factory, @@ -170,6 +173,7 @@ class Path(Param): "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -204,6 +208,7 @@ class Path(Param): deprecated=deprecated, example=example, examples=examples, + openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, **extra, @@ -254,6 +259,7 @@ class Query(Param): "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -286,6 +292,7 @@ class Query(Param): deprecated=deprecated, example=example, examples=examples, + openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, **extra, @@ -337,6 +344,7 @@ class Header(Param): "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -370,6 +378,7 @@ class Header(Param): deprecated=deprecated, example=example, examples=examples, + openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, **extra, @@ -420,6 +429,7 @@ class Cookie(Param): "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -452,6 +462,7 @@ class Cookie(Param): deprecated=deprecated, example=example, examples=examples, + openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, **extra, @@ -502,6 +513,7 @@ class Body(FieldInfo): "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -518,6 +530,7 @@ class Body(FieldInfo): ) self.example = example self.include_in_schema = include_in_schema + self.openapi_examples = openapi_examples kwargs = dict( default=default, default_factory=default_factory, @@ -613,6 +626,7 @@ class Form(Body): "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -647,6 +661,7 @@ class Form(Body): deprecated=deprecated, example=example, examples=examples, + openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, **extra, @@ -696,6 +711,7 @@ class File(Form): "although still supported. Use examples instead." ), ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, json_schema_extra: Union[Dict[str, Any], None] = None, @@ -729,6 +745,7 @@ class File(Form): 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/tests/test_openapi_examples.py b/tests/test_openapi_examples.py new file mode 100644 index 000000000..d0e35953e --- /dev/null +++ b/tests/test_openapi_examples.py @@ -0,0 +1,455 @@ +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 + +app = FastAPI() + + +class Item(BaseModel): + data: str + + +@app.post("/examples/") +def examples( + item: Item = Body( + examples=[ + {"data": "Data in Body examples, example1"}, + ], + openapi_examples={ + "Example One": { + "summary": "Example One Summary", + "description": "Example One Description", + "value": {"data": "Data in Body examples, example1"}, + }, + "Example Two": { + "value": {"data": "Data in Body examples, example2"}, + }, + }, + ) +): + return item + + +@app.get("/path_examples/{item_id}") +def path_examples( + item_id: str = Path( + examples=[ + "json_schema_item_1", + "json_schema_item_2", + ], + openapi_examples={ + "Path One": { + "summary": "Path One Summary", + "description": "Path One Description", + "value": "item_1", + }, + "Path Two": { + "value": "item_2", + }, + }, + ), +): + return item_id + + +@app.get("/query_examples/") +def query_examples( + data: Union[str, None] = Query( + default=None, + examples=[ + "json_schema_query1", + "json_schema_query2", + ], + openapi_examples={ + "Query One": { + "summary": "Query One Summary", + "description": "Query One Description", + "value": "query1", + }, + "Query Two": { + "value": "query2", + }, + }, + ), +): + return data + + +@app.get("/header_examples/") +def header_examples( + data: Union[str, None] = Header( + default=None, + examples=[ + "json_schema_header1", + "json_schema_header2", + ], + openapi_examples={ + "Header One": { + "summary": "Header One Summary", + "description": "Header One Description", + "value": "header1", + }, + "Header Two": { + "value": "header2", + }, + }, + ), +): + return data + + +@app.get("/cookie_examples/") +def cookie_examples( + data: Union[str, None] = Cookie( + default=None, + examples=["json_schema_cookie1", "json_schema_cookie2"], + openapi_examples={ + "Cookie One": { + "summary": "Cookie One Summary", + "description": "Cookie One Description", + "value": "cookie1", + }, + "Cookie Two": { + "value": "cookie2", + }, + }, + ), +): + return data + + +client = TestClient(app) + + +def test_call_api(): + response = client.get("/path_examples/foo") + assert response.status_code == 200, response.text + + response = client.get("/query_examples/") + assert response.status_code == 200, response.text + + response = client.get("/header_examples/") + assert response.status_code == 200, response.text + + response = client.get("/cookie_examples/") + assert response.status_code == 200, response.text + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/examples/": { + "post": { + "summary": "Examples", + "operationId": "examples_examples__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [{"$ref": "#/components/schemas/Item"}], + "title": "Item", + "examples": [ + {"data": "Data in Body examples, example1"} + ], + }, + "examples": { + "Example One": { + "summary": "Example One Summary", + "description": "Example One Description", + "value": { + "data": "Data in Body examples, example1" + }, + }, + "Example Two": { + "value": { + "data": "Data in Body examples, example2" + } + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/path_examples/{item_id}": { + "get": { + "summary": "Path Examples", + "operationId": "path_examples_path_examples__item_id__get", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": { + "type": "string", + "examples": [ + "json_schema_item_1", + "json_schema_item_2", + ], + "title": "Item Id", + }, + "examples": { + "Path One": { + "summary": "Path One Summary", + "description": "Path One Description", + "value": "item_1", + }, + "Path Two": {"value": "item_2"}, + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/query_examples/": { + "get": { + "summary": "Query Examples", + "operationId": "query_examples_query_examples__get", + "parameters": [ + { + "name": "data", + "in": "query", + "required": False, + "schema": 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", + } + ), + "examples": { + "Query One": { + "summary": "Query One Summary", + "description": "Query One Description", + "value": "query1", + }, + "Query Two": {"value": "query2"}, + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/header_examples/": { + "get": { + "summary": "Header Examples", + "operationId": "header_examples_header_examples__get", + "parameters": [ + { + "name": "data", + "in": "header", + "required": False, + "schema": 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", + } + ), + "examples": { + "Header One": { + "summary": "Header One Summary", + "description": "Header One Description", + "value": "header1", + }, + "Header Two": {"value": "header2"}, + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/cookie_examples/": { + "get": { + "summary": "Cookie Examples", + "operationId": "cookie_examples_cookie_examples__get", + "parameters": [ + { + "name": "data", + "in": "cookie", + "required": False, + "schema": 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", + } + ), + "examples": { + "Cookie One": { + "summary": "Cookie One Summary", + "description": "Cookie One Description", + "value": "cookie1", + }, + "Cookie Two": {"value": "cookie2"}, + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": {"$ref": "#/components/schemas/ValidationError"}, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": {"data": {"type": "string", "title": "Data"}}, + "type": "object", + "required": ["data"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "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_tutorial005.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py new file mode 100644 index 000000000..94a40ed5a --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py @@ -0,0 +1,166 @@ +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.schema_extra_example.tutorial005 import app + + client = TestClient(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) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": IsDict({"$ref": "#/components/schemas/Item"}) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + ), + "examples": { + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": {"name": "Bar", "price": "35.4"}, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": IsDict( + { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Description", "type": "string"} + ), + "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"} + ), + }, + }, + "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_schema_extra_example/test_tutorial005_an.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py new file mode 100644 index 000000000..da92f98f6 --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py @@ -0,0 +1,166 @@ +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.schema_extra_example.tutorial005_an import app + + client = TestClient(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) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": IsDict({"$ref": "#/components/schemas/Item"}) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + ), + "examples": { + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": {"name": "Bar", "price": "35.4"}, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": IsDict( + { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Description", "type": "string"} + ), + "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"} + ), + }, + }, + "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_schema_extra_example/test_tutorial005_an_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py new file mode 100644 index 000000000..9109cb14e --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py @@ -0,0 +1,170 @@ +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.schema_extra_example.tutorial005_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +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_py310 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": IsDict({"$ref": "#/components/schemas/Item"}) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + ), + "examples": { + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": {"name": "Bar", "price": "35.4"}, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": IsDict( + { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Description", "type": "string"} + ), + "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"} + ), + }, + }, + "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_schema_extra_example/test_tutorial005_an_py39.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py new file mode 100644 index 000000000..fd4ec0575 --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py @@ -0,0 +1,170 @@ +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.schema_extra_example.tutorial005_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +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_py39 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": IsDict({"$ref": "#/components/schemas/Item"}) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + ), + "examples": { + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": {"name": "Bar", "price": "35.4"}, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": IsDict( + { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Description", "type": "string"} + ), + "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"} + ), + }, + }, + "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_schema_extra_example/test_tutorial005_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py new file mode 100644 index 000000000..05df53422 --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py @@ -0,0 +1,170 @@ +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.schema_extra_example.tutorial005_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +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_py310 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": IsDict({"$ref": "#/components/schemas/Item"}) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + ), + "examples": { + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": {"name": "Bar", "price": "35.4"}, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": IsDict( + { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Description", "type": "string"} + ), + "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"} + ), + }, + }, + "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"}, + }, + }, + } + }, + } From df16699dd8a5909b7de2d8c79f53f7153d922625 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 26 Aug 2023 18:03:56 +0000 Subject: [PATCH 023/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5d15e1c25..4021532d2 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ Add support for `openapi_examples` in all FastAPI parameters. PR [#10152](https://github.com/tiangolo/fastapi/pull/10152) by [@tiangolo](https://github.com/tiangolo). * 📝 Add note to docs about Separate Input and Output Schemas with FastAPI version. PR [#10150](https://github.com/tiangolo/fastapi/pull/10150) by [@tiangolo](https://github.com/tiangolo). ## 0.102.0 From bd32bca55cdbf3ca3e9de66b0e790772e019b55f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 26 Aug 2023 20:09:59 +0200 Subject: [PATCH 024/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4021532d2..f61d1360d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,8 +2,14 @@ ## Latest Changes +### Features + * ✨ Add support for `openapi_examples` in all FastAPI parameters. PR [#10152](https://github.com/tiangolo/fastapi/pull/10152) by [@tiangolo](https://github.com/tiangolo). + +### Docs + * 📝 Add note to docs about Separate Input and Output Schemas with FastAPI version. PR [#10150](https://github.com/tiangolo/fastapi/pull/10150) by [@tiangolo](https://github.com/tiangolo). + ## 0.102.0 ### Features From 415eb1405a5bc93a32e14c15d690517c95d26743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 26 Aug 2023 20:10:27 +0200 Subject: [PATCH 025/188] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.10?= =?UTF-8?q?3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 3 +++ fastapi/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f61d1360d..91b29e772 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,9 @@ ## Latest Changes + +## 0.103.0 + ### Features * ✨ Add support for `openapi_examples` in all FastAPI parameters. PR [#10152](https://github.com/tiangolo/fastapi/pull/10152) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 6979ec5fb..ff8b98d3b 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.102.0" +__version__ = "0.103.0" from starlette import status as status From a3f1689d78abef3d5d54ba655ec87cef81bf7384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 26 Aug 2023 20:14:42 +0200 Subject: [PATCH 026/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 91b29e772..b60e024e5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -8,6 +8,7 @@ ### Features * ✨ Add support for `openapi_examples` in all FastAPI parameters. PR [#10152](https://github.com/tiangolo/fastapi/pull/10152) by [@tiangolo](https://github.com/tiangolo). + * New docs: [OpenAPI-specific examples](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#openapi-specific-examples). ### Docs From 37d46e6b6c58c0e38747d91c4a0cdc0ea76c9d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 1 Sep 2023 23:36:08 +0200 Subject: [PATCH 027/188] =?UTF-8?q?=E2=9C=85=20Add=20missing=20test=20for?= =?UTF-8?q?=20OpenAPI=20examples,=20it=20was=20missing=20in=20coverage=20(?= =?UTF-8?q?#10188)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Add missing test for OpenAPI examples, it seems it was discovered in coverage by an upgrade in AnyIO --- tests/test_openapi_examples.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py index d0e35953e..70664a8a4 100644 --- a/tests/test_openapi_examples.py +++ b/tests/test_openapi_examples.py @@ -125,6 +125,9 @@ client = TestClient(app) def test_call_api(): + response = client.post("/examples/", json={"data": "example1"}) + assert response.status_code == 200, response.text + response = client.get("/path_examples/foo") assert response.status_code == 200, response.text From 7a63d11093d17de34a62dd10f81b1cf149c2e37b Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 1 Sep 2023 21:36:46 +0000 Subject: [PATCH 028/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b60e024e5..198bf3823 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✅ Add missing test for OpenAPI examples, it was missing in coverage. PR [#10188](https://github.com/tiangolo/fastapi/pull/10188) by [@tiangolo](https://github.com/tiangolo). ## 0.103.0 From 4bfe83bd2706536fcb0fa911c0e1e0bb95ba39ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 2 Sep 2023 01:32:40 +0200 Subject: [PATCH 029/188] =?UTF-8?q?=F0=9F=91=A5=20Update=20FastAPI=20Peopl?= =?UTF-8?q?e=20(#10186)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions --- docs/en/data/github_sponsors.yml | 80 ++++++++++------- docs/en/data/people.yml | 148 +++++++++++++++---------------- 2 files changed, 120 insertions(+), 108 deletions(-) diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml index 3a68ba62b..1ec71dd49 100644 --- a/docs/en/data/github_sponsors.yml +++ b/docs/en/data/github_sponsors.yml @@ -2,6 +2,9 @@ sponsors: - - login: cryptapi avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4 url: https://github.com/cryptapi + - login: porter-dev + avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4 + url: https://github.com/porter-dev - login: fern-api avatarUrl: https://avatars.githubusercontent.com/u/102944815?v=4 url: https://github.com/fern-api @@ -17,24 +20,21 @@ sponsors: - - login: mikeckennedy avatarUrl: https://avatars.githubusercontent.com/u/2035561?u=1bb18268bcd4d9249e1f783a063c27df9a84c05b&v=4 url: https://github.com/mikeckennedy + - login: ndimares + avatarUrl: https://avatars.githubusercontent.com/u/6267663?u=cfb27efde7a7212be8142abb6c058a1aeadb41b1&v=4 + url: https://github.com/ndimares - login: deta avatarUrl: https://avatars.githubusercontent.com/u/47275976?v=4 url: https://github.com/deta - login: deepset-ai avatarUrl: https://avatars.githubusercontent.com/u/51827949?v=4 url: https://github.com/deepset-ai - - login: svix - avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4 - url: https://github.com/svix - login: databento-bot avatarUrl: https://avatars.githubusercontent.com/u/98378480?u=494f679996e39427f7ddb1a7de8441b7c96fb670&v=4 url: https://github.com/databento-bot - login: VincentParedes avatarUrl: https://avatars.githubusercontent.com/u/103889729?v=4 url: https://github.com/VincentParedes -- - login: arcticfly - avatarUrl: https://avatars.githubusercontent.com/u/41524992?u=03c88529a86cf51f7a380e890d84d84c71468848&v=4 - url: https://github.com/arcticfly - - login: getsentry avatarUrl: https://avatars.githubusercontent.com/u/1396951?v=4 url: https://github.com/getsentry @@ -89,18 +89,18 @@ sponsors: - - login: povilasb avatarUrl: https://avatars.githubusercontent.com/u/1213442?u=b11f58ed6ceea6e8297c9b310030478ebdac894d&v=4 url: https://github.com/povilasb + - login: mukulmantosh + avatarUrl: https://avatars.githubusercontent.com/u/15572034?u=006f0a33c0e15bb2de57474a2cf7d2f8ee05f5a0&v=4 + url: https://github.com/mukulmantosh - login: primer-io avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4 url: https://github.com/primer-io - - login: indeedeng avatarUrl: https://avatars.githubusercontent.com/u/2905043?v=4 url: https://github.com/indeedeng - - login: iguit0 - avatarUrl: https://avatars.githubusercontent.com/u/12905770?u=63a1a96d1e6c27d85c4f946b84836599de047f65&v=4 - url: https://github.com/iguit0 - - login: JacobKochems - avatarUrl: https://avatars.githubusercontent.com/u/41692189?u=a75f62ddc0d060ee6233a91e19c433d2687b8eb6&v=4 - url: https://github.com/JacobKochems + - login: NateXVI + avatarUrl: https://avatars.githubusercontent.com/u/48195620?u=4bc8751ae50cb087c40c1fe811764aa070b9eea6&v=4 + url: https://github.com/NateXVI - - login: Kludex avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 url: https://github.com/Kludex @@ -200,12 +200,12 @@ sponsors: - login: hiancdtrsnm avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4 url: https://github.com/hiancdtrsnm - - login: Shackelford-Arden - avatarUrl: https://avatars.githubusercontent.com/u/7362263?v=4 - url: https://github.com/Shackelford-Arden - login: wdwinslow avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&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: joeds13 avatarUrl: https://avatars.githubusercontent.com/u/13631604?u=628eb122e08bef43767b3738752b883e8e7f6259&v=4 url: https://github.com/joeds13 @@ -227,9 +227,6 @@ sponsors: - login: Filimoa avatarUrl: https://avatars.githubusercontent.com/u/21352040?u=0be845711495bbd7b756e13fcaeb8efc1ebd78ba&v=4 url: https://github.com/Filimoa - - login: LarryGF - avatarUrl: https://avatars.githubusercontent.com/u/26148349?u=431bb34d36d41c172466252242175281ae132152&v=4 - url: https://github.com/LarryGF - login: BrettskiPy avatarUrl: https://avatars.githubusercontent.com/u/30988215?u=d8a94a67e140d5ee5427724b292cc52d8827087a&v=4 url: https://github.com/BrettskiPy @@ -239,6 +236,9 @@ sponsors: - login: Leay15 avatarUrl: https://avatars.githubusercontent.com/u/32212558?u=c4aa9c1737e515959382a5515381757b1fd86c53&v=4 url: https://github.com/Leay15 + - login: dvlpjrs + avatarUrl: https://avatars.githubusercontent.com/u/32254642?u=fbd6ad0324d4f1eb6231cf775be1c7bd4404e961&v=4 + url: https://github.com/dvlpjrs - login: ygorpontelo avatarUrl: https://avatars.githubusercontent.com/u/32963605?u=35f7103f9c4c4c2589ae5737ee882e9375ef072e&v=4 url: https://github.com/ygorpontelo @@ -284,15 +284,15 @@ sponsors: - login: DelfinaCare avatarUrl: https://avatars.githubusercontent.com/u/83734439?v=4 url: https://github.com/DelfinaCare + - login: hbakri + avatarUrl: https://avatars.githubusercontent.com/u/92298226?u=36eee6d75db1272264d3ee92f1871f70b8917bd8&v=4 + url: https://github.com/hbakri - login: osawa-koki avatarUrl: https://avatars.githubusercontent.com/u/94336223?u=59c6fe6945bcbbaff87b2a794238671b060620d2&v=4 url: https://github.com/osawa-koki - login: pyt3h avatarUrl: https://avatars.githubusercontent.com/u/99658549?v=4 url: https://github.com/pyt3h - - login: Dagmaara - avatarUrl: https://avatars.githubusercontent.com/u/115501964?v=4 - url: https://github.com/Dagmaara - - login: SebTota avatarUrl: https://avatars.githubusercontent.com/u/25122511?v=4 url: https://github.com/SebTota @@ -329,6 +329,9 @@ sponsors: - login: janfilips avatarUrl: https://avatars.githubusercontent.com/u/870699?u=80702ec63f14e675cd4cdcc6ce3821d2ed207fd7&v=4 url: https://github.com/janfilips + - login: dodo5522 + avatarUrl: https://avatars.githubusercontent.com/u/1362607?u=9bf1e0e520cccc547c046610c468ce6115bbcf9f&v=4 + url: https://github.com/dodo5522 - login: WillHogan avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=7036c064cf29781470573865264ec8e60b6b809f&v=4 url: https://github.com/WillHogan @@ -374,9 +377,6 @@ sponsors: - login: katnoria avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4 url: https://github.com/katnoria - - login: mattwelke - avatarUrl: https://avatars.githubusercontent.com/u/7719209?u=80f02a799323b1472b389b836d95957c93a6d856&v=4 - url: https://github.com/mattwelke - login: harsh183 avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4 url: https://github.com/harsh183 @@ -422,6 +422,9 @@ sponsors: - login: jangia avatarUrl: https://avatars.githubusercontent.com/u/17927101?u=9261b9bb0c3e3bb1ecba43e8915dc58d8c9a077e&v=4 url: https://github.com/jangia + - login: timzaz + avatarUrl: https://avatars.githubusercontent.com/u/19709244?u=e6658f6b0b188294ce2db20dad94a678fcea718d&v=4 + url: https://github.com/timzaz - login: shuheng-liu avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4 url: https://github.com/shuheng-liu @@ -431,6 +434,9 @@ sponsors: - login: kxzk avatarUrl: https://avatars.githubusercontent.com/u/25046261?u=e185e58080090f9e678192cd214a14b14a2b232b&v=4 url: https://github.com/kxzk + - login: nisutec + avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4 + url: https://github.com/nisutec - login: hoenie-ams avatarUrl: https://avatars.githubusercontent.com/u/25708487?u=cda07434f0509ac728d9edf5e681117c0f6b818b&v=4 url: https://github.com/hoenie-ams @@ -450,7 +456,7 @@ 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=527044d90b5ebb7f8dad517db5da1f45253b774b&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=1a104991a2ea90bfe304bc0b9ef191c7e4891a0e&v=4 url: https://github.com/bnkc - login: declon avatarUrl: https://avatars.githubusercontent.com/u/36180226?v=4 @@ -482,6 +488,15 @@ sponsors: - login: 0417taehyun avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4 url: https://github.com/0417taehyun + - login: romabozhanovgithub + avatarUrl: https://avatars.githubusercontent.com/u/67696229?u=e4b921eef096415300425aca249348f8abb78ad7&v=4 + url: https://github.com/romabozhanovgithub + - login: mnicolleUTC + avatarUrl: https://avatars.githubusercontent.com/u/68548924?u=1fdd7f436bb1f4857c3415e62aa8fbc055fc1a76&v=4 + url: https://github.com/mnicolleUTC + - login: mbukeRepo + avatarUrl: https://avatars.githubusercontent.com/u/70356088?u=e125069c6ac5c49355e2b3ca2aa66a445fc4cccd&v=4 + url: https://github.com/mbukeRepo - - login: ssbarnea avatarUrl: https://avatars.githubusercontent.com/u/102495?u=b4bf6818deefe59952ac22fec6ed8c76de1b8f7c&v=4 url: https://github.com/ssbarnea @@ -494,21 +509,18 @@ sponsors: - login: sadikkuzu avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4 url: https://github.com/sadikkuzu - - login: ruizdiazever - avatarUrl: https://avatars.githubusercontent.com/u/29817086?u=2df54af55663d246e3a4dc8273711c37f1adb117&v=4 - url: https://github.com/ruizdiazever - login: samnimoh avatarUrl: https://avatars.githubusercontent.com/u/33413170?u=147bc516be6cb647b28d7e3b3fea3a018a331145&v=4 url: https://github.com/samnimoh - login: danburonline avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=2cad4388c1544e539ecb732d656e42fb07b4ff2d&v=4 url: https://github.com/danburonline - - login: iharshgor - avatarUrl: https://avatars.githubusercontent.com/u/35490011?u=2dea054476e752d9e92c9d71a9a7cc919b1c2f8e&v=4 - url: https://github.com/iharshgor + - login: koalawangyang + avatarUrl: https://avatars.githubusercontent.com/u/40114367?u=3bb94010f0473b8d4c2edea5a8c1cab61e6a1b22&v=4 + url: https://github.com/koalawangyang - login: rwxd avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=cd04a39e3655923be4f25c2ba8a5a07b3da3230a&v=4 url: https://github.com/rwxd - - login: ThomasPalma1 - avatarUrl: https://avatars.githubusercontent.com/u/66331874?u=5763f7402d784ba189b60d704ff5849b4d0a63fb&v=4 - url: https://github.com/ThomasPalma1 + - login: lodine-software + avatarUrl: https://avatars.githubusercontent.com/u/133536046?v=4 + url: https://github.com/lodine-software diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml index 89d189564..8ebd2f84c 100644 --- a/docs/en/data/people.yml +++ b/docs/en/data/people.yml @@ -1,16 +1,16 @@ maintainers: - login: tiangolo - answers: 1849 - prs: 466 + answers: 1866 + prs: 486 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=740f11212a731f56798f558ceddb0bd07642afa7&v=4 url: https://github.com/tiangolo experts: - login: Kludex - count: 463 + count: 480 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 url: https://github.com/Kludex - login: dmontagu - count: 239 + count: 240 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 url: https://github.com/dmontagu - login: Mause @@ -26,7 +26,7 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 url: https://github.com/JarroVGIT - login: jgould22 - count: 157 + count: 164 avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 url: https://github.com/jgould22 - login: euri10 @@ -38,7 +38,7 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4 url: https://github.com/phy25 - login: iudeen - count: 121 + count: 122 avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 url: https://github.com/iudeen - login: raphaelauv @@ -61,34 +61,34 @@ experts: count: 49 avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 url: https://github.com/sm-Fifteen -- login: insomnes - count: 45 - avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 - url: https://github.com/insomnes - login: yinziyan1206 count: 45 avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 url: https://github.com/yinziyan1206 -- login: acidjunk +- login: insomnes count: 45 - avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 - url: https://github.com/acidjunk + avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 + url: https://github.com/insomnes - login: Dustyposa count: 45 avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 url: https://github.com/Dustyposa +- login: acidjunk + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 + url: https://github.com/acidjunk - login: adriangb count: 44 avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 url: https://github.com/adriangb -- login: frankie567 - count: 43 - avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 - url: https://github.com/frankie567 - login: odiseo0 count: 43 avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=241a71f6b7068738b81af3e57f45ffd723538401&v=4 url: https://github.com/odiseo0 +- login: frankie567 + count: 43 + avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=c159fe047727aedecbbeeaa96a1b03ceb9d39add&v=4 + url: https://github.com/frankie567 - login: includeamin count: 40 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 @@ -97,14 +97,14 @@ experts: count: 37 avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 url: https://github.com/STeveShary +- login: chbndrhnns + count: 36 + avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 + url: https://github.com/chbndrhnns - login: krishnardt count: 35 avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4 url: https://github.com/krishnardt -- login: chbndrhnns - count: 35 - avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 - url: https://github.com/chbndrhnns - login: panla count: 32 avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 @@ -117,26 +117,30 @@ experts: count: 26 avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4 url: https://github.com/dbanty +- login: n8sty + count: 25 + avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 + url: https://github.com/n8sty - login: wshayes count: 25 avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 url: https://github.com/wshayes -- login: acnebs - count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/9054108?v=4 - url: https://github.com/acnebs - login: SirTelemak count: 23 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 url: https://github.com/SirTelemak +- login: acnebs + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/9054108?v=4 + url: https://github.com/acnebs - login: rafsaf count: 21 - avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=f8f0d6d6e90fac39fa786228158ba7f013c74271&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=5fe59a56e1f2f9ccd8005d71752a8276f133ae1a&v=4 url: https://github.com/rafsaf -- login: n8sty +- login: JavierSanchezCastro count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 - url: https://github.com/n8sty + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro - login: nsidnev count: 20 avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4 @@ -173,55 +177,51 @@ experts: count: 16 avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4 url: https://github.com/jonatasoli +- login: ebottos94 + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/100039558?u=e2c672da5a7977fd24d87ce6ab35f8bf5b1ed9fa&v=4 + url: https://github.com/ebottos94 - login: dstlny count: 16 avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 url: https://github.com/dstlny -- login: abhint +- login: chrisK824 count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 - url: https://github.com/abhint + avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 + url: https://github.com/chrisK824 - login: nymous count: 15 avatarUrl: https://avatars.githubusercontent.com/u/4216559?u=360a36fb602cded27273cbfc0afc296eece90662&v=4 url: https://github.com/nymous -- login: ghost +- login: abhint count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/10137?u=b1951d34a583cf12ec0d3b0781ba19be97726318&v=4 - url: https://github.com/ghost -- login: simondale00 - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/33907262?v=4 - url: https://github.com/simondale00 -- login: jorgerpo - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4 - url: https://github.com/jorgerpo + avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 + url: https://github.com/abhint last_month_active: +- login: JavierSanchezCastro + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro - login: Kludex - count: 24 + count: 10 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 url: https://github.com/Kludex - login: jgould22 - count: 17 + count: 8 avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 url: https://github.com/jgould22 -- login: arjwilliams - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/22227620?v=4 - url: https://github.com/arjwilliams -- login: Ahmed-Abdou14 - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/104530599?u=05365b155a1ff911532e8be316acfad2e0736f98&v=4 - url: https://github.com/Ahmed-Abdou14 -- login: iudeen +- login: romabozhanovgithub + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/67696229?u=e4b921eef096415300425aca249348f8abb78ad7&v=4 + url: https://github.com/romabozhanovgithub +- login: n8sty + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 + url: https://github.com/n8sty +- login: chrisK824 count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 - url: https://github.com/iudeen -- login: mikeedjones - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/4087139?u=cc4a242896ac2fcf88a53acfaf190d0fe0a1f0c9&v=4 - url: https://github.com/mikeedjones + avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 + url: https://github.com/chrisK824 top_contributors: - login: waynerv count: 25 @@ -235,22 +235,22 @@ top_contributors: count: 21 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 url: https://github.com/Kludex +- login: dmontagu + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 + url: https://github.com/dmontagu - login: jaystone776 count: 17 avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 url: https://github.com/jaystone776 -- login: dmontagu - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 - url: https://github.com/dmontagu +- login: Xewus + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 + url: https://github.com/Xewus - login: euri10 count: 13 avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4 url: https://github.com/euri10 -- login: Xewus - count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 - url: https://github.com/Xewus - login: mariacamilagl count: 12 avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 @@ -360,6 +360,10 @@ top_reviewers: count: 78 avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=d7062cbc6eb7671d5dc9cc0e32a24ae335e0f225&v=4 url: https://github.com/yezz123 +- login: iudeen + count: 53 + avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 + url: https://github.com/iudeen - login: tokusumi count: 51 avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 @@ -372,10 +376,6 @@ top_reviewers: count: 47 avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4 url: https://github.com/Laineyzhang55 -- login: iudeen - count: 46 - avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 - url: https://github.com/iudeen - login: ycd count: 45 avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4 @@ -385,7 +385,7 @@ top_reviewers: avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4 url: https://github.com/cikay - login: Xewus - count: 38 + count: 39 avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 url: https://github.com/Xewus - login: JarroVGIT @@ -482,7 +482,7 @@ top_reviewers: url: https://github.com/sh0nk - login: peidrao count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=5b94b548ef0002ef3219d7c07ac0fac17c6201a2&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=a66902b40c13647d0ed0e573d598128240a4dd04&v=4 url: https://github.com/peidrao - login: r0b2g1t count: 13 From ee0b28a398db5e4f7bfe8d53284e9c0575c9543d Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 1 Sep 2023 23:33:31 +0000 Subject: [PATCH 030/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 198bf3823..3b10fe405 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 👥 Update FastAPI People. PR [#10186](https://github.com/tiangolo/fastapi/pull/10186) by [@tiangolo](https://github.com/tiangolo). * ✅ Add missing test for OpenAPI examples, it was missing in coverage. PR [#10188](https://github.com/tiangolo/fastapi/pull/10188) by [@tiangolo](https://github.com/tiangolo). ## 0.103.0 From bf952d345c36807b5931ce6f74c40aa4e7308ccf Mon Sep 17 00:00:00 2001 From: PieCat <53287533+funny-cat-happy@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:18:30 +0800 Subject: [PATCH 031/188] =?UTF-8?q?=F0=9F=8C=90=20Add=20Chinese=20translat?= =?UTF-8?q?ion=20for=20`docs/zh/docs/advanced/generate-clients.md`=20(#988?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: xzmeng Co-authored-by: Sebastián Ramírez --- docs/zh/docs/advanced/generate-clients.md | 266 ++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 docs/zh/docs/advanced/generate-clients.md diff --git a/docs/zh/docs/advanced/generate-clients.md b/docs/zh/docs/advanced/generate-clients.md new file mode 100644 index 000000000..f3a58c062 --- /dev/null +++ b/docs/zh/docs/advanced/generate-clients.md @@ -0,0 +1,266 @@ +# 生成客户端 + +因为 **FastAPI** 是基于OpenAPI规范的,自然您可以使用许多相匹配的工具,包括自动生成API文档 (由 Swagger UI 提供)。 + +一个不太明显而又特别的优势是,你可以为你的API针对不同的**编程语言**来**生成客户端**(有时候被叫做 **SDKs** )。 + +## OpenAPI 客户端生成 + +有许多工具可以从**OpenAPI**生成客户端。 + +一个常见的工具是 OpenAPI Generator。 + +如果您正在开发**前端**,一个非常有趣的替代方案是 openapi-typescript-codegen。 + +## 生成一个 TypeScript 前端客户端 + +让我们从一个简单的 FastAPI 应用开始: + +=== "Python 3.9+" + + ```Python hl_lines="7-9 12-13 16-17 21" + {!> ../../../docs_src/generate_clients/tutorial001_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9-11 14-15 18 19 23" + {!> ../../../docs_src/generate_clients/tutorial001.py!} + ``` + +请注意,*路径操作* 定义了他们所用于请求数据和回应数据的模型,所使用的模型是`Item` 和 `ResponseMessage`。 + +### API 文档 + +如果您访问API文档,您将看到它具有在请求中发送和在响应中接收数据的**模式(schemas)**: + + + +您可以看到这些模式,因为它们是用程序中的模型声明的。 + +那些信息可以在应用的 **OpenAPI模式** 被找到,然后显示在API文档中(通过Swagger UI)。 + +OpenAPI中所包含的模型里有相同的信息可以用于 **生成客户端代码**。 + +### 生成一个TypeScript 客户端 + +现在我们有了带有模型的应用,我们可以为前端生成客户端代码。 + +#### 安装 `openapi-typescript-codegen` + +您可以使用以下工具在前端代码中安装 `openapi-typescript-codegen`: + +
+ +```console +$ npm install openapi-typescript-codegen --save-dev + +---> 100% +``` + +
+ +#### 生成客户端代码 + +要生成客户端代码,您可以使用现在将要安装的命令行应用程序 `openapi`。 + +因为它安装在本地项目中,所以您可能无法直接使用此命令,但您可以将其放在 `package.json` 文件中。 + +它可能看起来是这样的: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi --input http://localhost:8000/openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "openapi-typescript-codegen": "^0.20.1", + "typescript": "^4.6.2" + } +} +``` + +在这里添加 NPM `generate-client` 脚本后,您可以使用以下命令运行它: + +
+ +```console +$ npm run generate-client + +frontend-app@1.0.0 generate-client /home/user/code/frontend-app +> openapi --input http://localhost:8000/openapi.json --output ./src/client --client axios +``` + +
+ +此命令将在 `./src/client` 中生成代码,并将在其内部使用 `axios`(前端HTTP库)。 + +### 尝试客户端代码 + +现在您可以导入并使用客户端代码,它可能看起来像这样,请注意,您可以为这些方法使用自动补全: + + + +您还将自动补全要发送的数据: + + + +!!! tip + 请注意, `name` 和 `price` 的自动补全,是通过其在`Item`模型(FastAPI)中的定义实现的。 + +如果发送的数据字段不符,你也会看到编辑器的错误提示: + + + +响应(response)对象也拥有自动补全: + + + +## 带有标签的 FastAPI 应用 + +在许多情况下,你的FastAPI应用程序会更复杂,你可能会使用标签来分隔不同组的*路径操作(path operations)*。 + +例如,您可以有一个用 `items` 的部分和另一个用于 `users` 的部分,它们可以用标签来分隔: + +=== "Python 3.9+" + + ```Python hl_lines="21 26 34" + {!> ../../../docs_src/generate_clients/tutorial002_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="23 28 36" + {!> ../../../docs_src/generate_clients/tutorial002.py!} + ``` + +### 生成带有标签的 TypeScript 客户端 + +如果您使用标签为FastAPI应用生成客户端,它通常也会根据标签分割客户端代码。 + +通过这种方式,您将能够为客户端代码进行正确地排序和分组: + + + +在这个案例中,您有: + +* `ItemsService` +* `UsersService` + +### 客户端方法名称 + +现在生成的方法名像 `createItemItemsPost` 看起来不太简洁: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +...这是因为客户端生成器为每个 *路径操作* 使用OpenAPI的内部 **操作 ID(operation ID)**。 + +OpenAPI要求每个操作 ID 在所有 *路径操作* 中都是唯一的,因此 FastAPI 使用**函数名**、**路径**和**HTTP方法/操作**来生成此操作ID,因为这样可以确保这些操作 ID 是唯一的。 + +但接下来我会告诉你如何改进。 🤓 + +## 自定义操作ID和更好的方法名 + +您可以**修改**这些操作ID的**生成**方式,以使其更简洁,并在客户端中具有**更简洁的方法名称**。 + +在这种情况下,您必须确保每个操作ID在其他方面是**唯一**的。 + +例如,您可以确保每个*路径操作*都有一个标签,然后根据**标签**和*路径操作***名称**(函数名)来生成操作ID。 + +### 自定义生成唯一ID函数 + +FastAPI为每个*路径操作*使用一个**唯一ID**,它用于**操作ID**,也用于任何所需自定义模型的名称,用于请求或响应。 + +你可以自定义该函数。它接受一个 `APIRoute` 对象作为输入,并输出一个字符串。 + +例如,以下是一个示例,它使用第一个标签(你可能只有一个标签)和*路径操作*名称(函数名)。 + +然后,你可以将这个自定义函数作为 `generate_unique_id_function` 参数传递给 **FastAPI**: + +=== "Python 3.9+" + + ```Python hl_lines="6-7 10" + {!> ../../../docs_src/generate_clients/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8-9 12" + {!> ../../../docs_src/generate_clients/tutorial003.py!} + ``` + +### 使用自定义操作ID生成TypeScript客户端 + +现在,如果你再次生成客户端,你会发现它具有改善的方法名称: + + + +正如你所见,现在方法名称中只包含标签和函数名,不再包含URL路径和HTTP操作的信息。 + +### 预处理用于客户端生成器的OpenAPI规范 + +生成的代码仍然存在一些**重复的信息**。 + +我们已经知道该方法与 **items** 相关,因为它在 `ItemsService` 中(从标签中获取),但方法名中仍然有标签名作为前缀。😕 + +一般情况下对于OpenAPI,我们可能仍然希望保留它,因为这将确保操作ID是**唯一的**。 + +但对于生成的客户端,我们可以在生成客户端之前**修改** OpenAPI 操作ID,以使方法名称更加美观和**简洁**。 + +我们可以将 OpenAPI JSON 下载到一个名为`openapi.json`的文件中,然后使用以下脚本**删除此前缀的标签**: + +```Python +{!../../../docs_src/generate_clients/tutorial004.py!} +``` + +通过这样做,操作ID将从类似于 `items-get_items` 的名称重命名为 `get_items` ,这样客户端生成器就可以生成更简洁的方法名称。 + +### 使用预处理的OpenAPI生成TypeScript客户端 + +现在,由于最终结果保存在文件openapi.json中,你可以修改 package.json 文件以使用此本地文件,例如: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi --input ./openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "openapi-typescript-codegen": "^0.20.1", + "typescript": "^4.6.2" + } +} +``` + +生成新的客户端之后,你现在将拥有**清晰的方法名称**,具备**自动补全**、**错误提示**等功能: + + + +## 优点 + +当使用自动生成的客户端时,你将获得以下的自动补全功能: + +* 方法。 +* 请求体中的数据、查询参数等。 +* 响应数据。 + +你还将获得针对所有内容的错误提示。 + +每当你更新后端代码并**重新生成**前端代码时,新的*路径操作*将作为方法可用,旧的方法将被删除,并且其他任何更改将反映在生成的代码中。 🤓 + +这也意味着如果有任何更改,它将自动**反映**在客户端代码中。如果你**构建**客户端,在使用的数据上存在**不匹配**时,它将报错。 + +因此,你将在开发周期的早期**检测到许多错误**,而不必等待错误在生产环境中向最终用户展示,然后尝试调试问题所在。 ✨ From 82ff9a6920115f6b2c00acdf03452a297b926604 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 15:19:08 +0000 Subject: [PATCH 032/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 3b10fe405..5852de5d0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add Chinese translation for `docs/zh/docs/advanced/generate-clients.md`. PR [#9883](https://github.com/tiangolo/fastapi/pull/9883) by [@funny-cat-happy](https://github.com/funny-cat-happy). * 👥 Update FastAPI People. PR [#10186](https://github.com/tiangolo/fastapi/pull/10186) by [@tiangolo](https://github.com/tiangolo). * ✅ Add missing test for OpenAPI examples, it was missing in coverage. PR [#10188](https://github.com/tiangolo/fastapi/pull/10188) by [@tiangolo](https://github.com/tiangolo). From d8f2f39f6ddc07aa9bc25f7eea8cb752330d7374 Mon Sep 17 00:00:00 2001 From: xzmeng Date: Sat, 2 Sep 2023 23:22:24 +0800 Subject: [PATCH 033/188] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20typos=20in?= =?UTF-8?q?=20`docs/en/docs/how-to/separate-openapi-schemas.md`=20and=20`d?= =?UTF-8?q?ocs/en/docs/tutorial/schema-extra-example.md`=20(#10189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/how-to/separate-openapi-schemas.md | 2 +- docs/en/docs/tutorial/schema-extra-example.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/docs/how-to/separate-openapi-schemas.md b/docs/en/docs/how-to/separate-openapi-schemas.md index d289391ca..d38be3c59 100644 --- a/docs/en/docs/how-to/separate-openapi-schemas.md +++ b/docs/en/docs/how-to/separate-openapi-schemas.md @@ -160,7 +160,7 @@ If you interact with the docs and check the response, even though the code didn' This means that it will **always have a value**, it's just that sometimes the value could be `None` (or `null` in JSON). -That means that, clients using your API don't have to check if the value exists or not, they can **asume the field will always be there**, but just that in some cases it will have the default value of `None`. +That means that, clients using your API don't have to check if the value exists or not, they can **assume the field will always be there**, but just that in some cases it will have the default value of `None`. The way to describe this in OpenAPI, is to mark that field as **required**, because it will always be there. diff --git a/docs/en/docs/tutorial/schema-extra-example.md b/docs/en/docs/tutorial/schema-extra-example.md index 8cf1b9c09..9eeb3f1f2 100644 --- a/docs/en/docs/tutorial/schema-extra-example.md +++ b/docs/en/docs/tutorial/schema-extra-example.md @@ -38,13 +38,13 @@ That extra info will be added as-is to the output **JSON Schema** for that model In Pydantic version 2, you would use the attribute `model_config`, that takes a `dict` as described in Pydantic's docs: Model Config. - You can set `"json_schema_extra"` with a `dict` containing any additonal data you would like to show up in the generated JSON Schema, including `examples`. + You can set `"json_schema_extra"` with a `dict` containing any additional data you would like to show up in the generated JSON Schema, including `examples`. === "Pydantic v1" In Pydantic version 1, you would use an internal class `Config` and `schema_extra`, as described in Pydantic's docs: Schema customization. - You can set `schema_extra` with a `dict` containing any additonal data you would like to show up in the generated JSON Schema, including `examples`. + You can set `schema_extra` with a `dict` containing any additional data you would like to show up in the generated JSON Schema, including `examples`. !!! tip You could use the same technique to extend the JSON Schema and add your own custom extra info. From 8cb33e9b477a32ccac867bd5be655e9426d4ccb9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 15:24:05 +0000 Subject: [PATCH 034/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5852de5d0..357adc8d3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏️ Fix typos in `docs/en/docs/how-to/separate-openapi-schemas.md` and `docs/en/docs/tutorial/schema-extra-example.md`. PR [#10189](https://github.com/tiangolo/fastapi/pull/10189) by [@xzmeng](https://github.com/xzmeng). * 🌐 Add Chinese translation for `docs/zh/docs/advanced/generate-clients.md`. PR [#9883](https://github.com/tiangolo/fastapi/pull/9883) by [@funny-cat-happy](https://github.com/funny-cat-happy). * 👥 Update FastAPI People. PR [#10186](https://github.com/tiangolo/fastapi/pull/10186) by [@tiangolo](https://github.com/tiangolo). * ✅ Add missing test for OpenAPI examples, it was missing in coverage. PR [#10188](https://github.com/tiangolo/fastapi/pull/10188) by [@tiangolo](https://github.com/tiangolo). From 1d688a062e59b11b160191d77a9f11971b316cb5 Mon Sep 17 00:00:00 2001 From: Rostyslav Date: Sat, 2 Sep 2023 18:29:36 +0300 Subject: [PATCH 035/188] =?UTF-8?q?=F0=9F=8C=90=20Add=20Ukrainian=20transl?= =?UTF-8?q?ation=20for=20`docs/uk/docs/tutorial/index.md`=20(#10079)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/uk/docs/tutorial/index.md | 80 ++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 docs/uk/docs/tutorial/index.md diff --git a/docs/uk/docs/tutorial/index.md b/docs/uk/docs/tutorial/index.md new file mode 100644 index 000000000..e5bae74bc --- /dev/null +++ b/docs/uk/docs/tutorial/index.md @@ -0,0 +1,80 @@ +# Туторіал - Посібник користувача + +У цьому посібнику показано, як користуватися **FastAPI** з більшістю його функцій, крок за кроком. + +Кожен розділ поступово надбудовується на попередні, але він структурований на окремі теми, щоб ви могли перейти безпосередньо до будь-якої конкретної, щоб вирішити ваші конкретні потреби API. + +Він також створений як довідник для роботи у майбутньому. + +Тож ви можете повернутися і побачити саме те, що вам потрібно. + +## Запустіть код + +Усі блоки коду можна скопіювати та використовувати безпосередньо (це фактично перевірені файли Python). + +Щоб запустити будь-який із прикладів, скопіюйте код у файл `main.py` і запустіть `uvicorn` за допомогою: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +**ДУЖЕ радимо** написати або скопіювати код, відредагувати його та запустити локально. + +Використання його у своєму редакторі – це те, що дійсно показує вам переваги FastAPI, бачите, як мало коду вам потрібно написати, всі перевірки типів, автозаповнення тощо. + +--- + +## Встановлення FastAPI + +Першим кроком є встановлення FastAPI. + +Для туторіалу ви можете встановити його з усіма необов’язковими залежностями та функціями: + +
+ +```console +$ pip install "fastapi[all]" + +---> 100% +``` + +
+ +...який також включає `uvicorn`, який ви можете використовувати як сервер, який запускає ваш код. + +!!! note + Ви також можете встановити його частина за частиною. + + Це те, що ви, ймовірно, зробили б, коли захочете розгорнути свою програму у виробничому середовищі: + + ``` + pip install fastapi + ``` + + Також встановіть `uvicorn`, щоб він працював як сервер: + + ``` + pip install "uvicorn[standard]" + ``` + + І те саме для кожної з опціональних залежностей, які ви хочете використовувати. + +## Розширений посібник користувача + +Існує також **Розширений посібник користувача**, який ви зможете прочитати пізніше після цього **Туторіал - Посібник користувача**. + +**Розширений посібник користувача** засновано на цьому, використовує ті самі концепції та навчає вас деяким додатковим функціям. + +Але вам слід спочатку прочитати **Туторіал - Посібник користувача** (те, що ви зараз читаєте). + +Він розроблений таким чином, що ви можете створити повну програму лише за допомогою **Туторіал - Посібник користувача**, а потім розширити її різними способами, залежно від ваших потреб, використовуючи деякі з додаткових ідей з **Розширеного посібника користувача** . From 48f6ccfe7d852867ca1be3aa0e0d782e613cfe61 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 15:31:37 +0000 Subject: [PATCH 036/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 357adc8d3..09f19f0f6 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/index.md`. PR [#10079](https://github.com/tiangolo/fastapi/pull/10079) by [@rostik1410](https://github.com/rostik1410). * ✏️ Fix typos in `docs/en/docs/how-to/separate-openapi-schemas.md` and `docs/en/docs/tutorial/schema-extra-example.md`. PR [#10189](https://github.com/tiangolo/fastapi/pull/10189) by [@xzmeng](https://github.com/xzmeng). * 🌐 Add Chinese translation for `docs/zh/docs/advanced/generate-clients.md`. PR [#9883](https://github.com/tiangolo/fastapi/pull/9883) by [@funny-cat-happy](https://github.com/funny-cat-happy). * 👥 Update FastAPI People. PR [#10186](https://github.com/tiangolo/fastapi/pull/10186) by [@tiangolo](https://github.com/tiangolo). From ad76dd1aa806fb62ad64a2b5c14fe00393ed2589 Mon Sep 17 00:00:00 2001 From: whysage <67018871+whysage@users.noreply.github.com> Date: Sat, 2 Sep 2023 18:32:30 +0300 Subject: [PATCH 037/188] =?UTF-8?q?=F0=9F=8C=90=20Add=20Ukrainian=20transl?= =?UTF-8?q?ation=20for=20`docs/uk/docs/alternatives.md`=20(#10060)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/uk/docs/alternatives.md | 412 +++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 docs/uk/docs/alternatives.md diff --git a/docs/uk/docs/alternatives.md b/docs/uk/docs/alternatives.md new file mode 100644 index 000000000..e71257976 --- /dev/null +++ b/docs/uk/docs/alternatives.md @@ -0,0 +1,412 @@ +# Альтернативи, натхнення та порівняння + +Що надихнуло на створення **FastAPI**, який він у порінянні з іншими альтернативами та чого він у них навчився. + +## Вступ + +**FastAPI** не існувало б, якби не попередні роботи інших. + +Раніше було створено багато інструментів, які надихнули на його створення. + +Я кілька років уникав створення нового фреймворку. Спочатку я спробував вирішити всі функції, охоплені **FastAPI**, використовуючи багато різних фреймворків, плагінів та інструментів. + +Але в якийсь момент не було іншого виходу, окрім створення чогось, що надавало б усі ці функції, взявши найкращі ідеї з попередніх інструментів і поєднавши їх найкращим чином, використовуючи мовні функції, які навіть не були доступні раніше (Python 3.6+ підказки типів). + +## Попередні інструменти + +### Django + +Це найпопулярніший фреймворк Python, який користується широкою довірою. Він використовується для створення таких систем, як Instagram. + +Він відносно тісно пов’язаний з реляційними базами даних (наприклад, MySQL або PostgreSQL), тому мати базу даних NoSQL (наприклад, Couchbase, MongoDB, Cassandra тощо) як основний механізм зберігання не дуже просто. + +Він був створений для створення HTML у серверній частині, а не для створення API, які використовуються сучасним інтерфейсом (як-от React, Vue.js і Angular) або іншими системами (як-от IoT пристрої), які спілкуються з ним. + +### Django REST Framework + +Фреймворк Django REST був створений як гнучкий інструментарій для створення веб-інтерфейсів API використовуючи Django в основі, щоб покращити його можливості API. + +Його використовують багато компаній, включаючи Mozilla, Red Hat і Eventbrite. + +Це був один із перших прикладів **автоматичної документації API**, і саме це була одна з перших ідей, яка надихнула на «пошук» **FastAPI**. + +!!! Примітка + Django REST Framework створив Том Крісті. Той самий творець Starlette і Uvicorn, на яких базується **FastAPI**. + + +!!! Перегляньте "Надихнуло **FastAPI** на" + Мати автоматичний веб-інтерфейс документації API. + +### Flask + +Flask — це «мікрофреймворк», він не включає інтеграцію бази даних, а також багато речей, які за замовчуванням є в Django. + +Ця простота та гнучкість дозволяють використовувати бази даних NoSQL як основну систему зберігання даних. + +Оскільки він дуже простий, він порівняно легкий та інтуїтивний для освоєння, хоча в деяких моментах документація стає дещо технічною. + +Він також зазвичай використовується для інших програм, яким не обов’язково потрібна база даних, керування користувачами або будь-яка з багатьох функцій, які є попередньо вбудованими в Django. Хоча багато з цих функцій можна додати за допомогою плагінів. + +Відокремлення частин було ключовою особливістю, яку я хотів зберегти, при цьому залишаючись «мікрофреймворком», який можна розширити, щоб охопити саме те, що потрібно. + +Враховуючи простоту Flask, він здавався хорошим підходом для створення API. Наступним, що знайшов, був «Django REST Framework» для Flask. + +!!! Переглянте "Надихнуло **FastAPI** на" + Бути мікрофреймоворком. Зробити легким комбінування та поєднання необхідних інструментів та частин. + + Мати просту та легку у використанні систему маршрутизації. + + +### Requests + +**FastAPI** насправді не є альтернативою **Requests**. Сфера їх застосування дуже різна. + +Насправді цілком звична річ використовувати Requests *всередині* програми FastAPI. + +Але все ж FastAPI черпав натхнення з Requests. + +**Requests** — це бібліотека для *взаємодії* з API (як клієнт), а **FastAPI** — це бібліотека для *створення* API (як сервер). + +Вони більш-менш знаходяться на протилежних кінцях, доповнюючи одна одну. + +Requests мають дуже простий та інтуїтивно зрозумілий дизайн, дуже простий у використанні, з розумними параметрами за замовчуванням. Але в той же час він дуже потужний і налаштовується. + +Ось чому, як сказано на офіційному сайті: + +> Requests є одним із найбільш завантажуваних пакетів Python усіх часів + +Використовувати його дуже просто. Наприклад, щоб виконати запит `GET`, ви повинні написати: + +```Python +response = requests.get("http://example.com/some/url") +``` + +Відповідна операція *роуту* API FastAPI може виглядати так: + +```Python hl_lines="1" +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +Зверніть увагу на схожість у `requests.get(...)` і `@app.get(...)`. + +!!! Перегляньте "Надихнуло **FastAPI** на" + * Майте простий та інтуїтивно зрозумілий API. + * Використовуйте імена (операції) методів HTTP безпосередньо, простим та інтуїтивно зрозумілим способом. + * Розумні параметри за замовчуванням, але потужні налаштування. + + +### Swagger / OpenAPI + +Головною функцією, яку я хотів від Django REST Framework, була автоматична API документація. + +Потім я виявив, що існує стандарт для документування API з використанням JSON (або YAML, розширення JSON) під назвою Swagger. + +І вже був створений веб-інтерфейс користувача для Swagger API. Отже, можливість генерувати документацію Swagger для API дозволить використовувати цей веб-інтерфейс автоматично. + +У якийсь момент Swagger було передано Linux Foundation, щоб перейменувати його на OpenAPI. + +Тому, коли говорять про версію 2.0, прийнято говорити «Swagger», а про версію 3+ «OpenAPI». + +!!! Перегляньте "Надихнуло **FastAPI** на" + Прийняти і використовувати відкритий стандарт для специфікацій API замість спеціальної схеми. + + Інтегрувати інструменти інтерфейсу на основі стандартів: + + * Інтерфейс Swagger + * ReDoc + + Ці два було обрано через те, що вони досить популярні та стабільні, але, виконавши швидкий пошук, ви можете знайти десятки додаткових альтернативних інтерфейсів для OpenAPI (які можна використовувати з **FastAPI**). + +### Фреймворки REST для Flask + +Існує кілька фреймворків Flask REST, але, витративши час і роботу на їх дослідження, я виявив, що багато з них припинено або залишено, з кількома постійними проблемами, які зробили їх непридатними. + +### Marshmallow + +Однією з головних функцій, необхідних для систем API, є "серіалізація", яка бере дані з коду (Python) і перетворює їх на щось, що можна надіслати через мережу. Наприклад, перетворення об’єкта, що містить дані з бази даних, на об’єкт JSON. Перетворення об’єктів `datetime` на строки тощо. + +Іншою важливою функцією, необхідною для API, є перевірка даних, яка забезпечує дійсність даних за певними параметрами. Наприклад, що деяке поле є `int`, а не деяка випадкова строка. Це особливо корисно для вхідних даних. + +Без системи перевірки даних вам довелося б виконувати всі перевірки вручну, у коді. + +Marshmallow створено для забезпечення цих функцій. Це чудова бібліотека, і я часто нею користувався раніше. + +Але він був створений до того, як існували підказки типу Python. Отже, щоб визначити кожну схему, вам потрібно використовувати спеціальні утиліти та класи, надані Marshmallow. + +!!! Перегляньте "Надихнуло **FastAPI** на" + Використовувати код для автоматичного визначення "схем", які надають типи даних і перевірку. + +### Webargs + +Іншою важливою функцією, необхідною для API, є аналіз даних із вхідних запитів. + +Webargs — це інструмент, створений, щоб забезпечити це поверх кількох фреймворків, включаючи Flask. + +Він використовує Marshmallow в основі для перевірки даних. І створений тими ж розробниками. + +Це чудовий інструмент, і я також часто використовував його, перш ніж створити **FastAPI**. + +!!! Інформація + Webargs був створений тими ж розробниками Marshmallow. + +!!! Перегляньте "Надихнуло **FastAPI** на" + Мати автоматичну перевірку даних вхідного запиту. + +### APISpec + +Marshmallow і Webargs забезпечують перевірку, аналіз і серіалізацію як плагіни. + +Але документація досі відсутня. Потім було створено APISpec. + +Це плагін для багатьох фреймворків (також є плагін для Starlette). + +Принцип роботи полягає в тому, що ви пишете визначення схеми, використовуючи формат YAML, у docstring кожної функції, що обробляє маршрут. + +І він генерує схеми OpenAPI. + +Так це працює у Flask, Starlette, Responder тощо. + +Але потім ми знову маємо проблему наявності мікросинтаксису всередині Python строки (великий YAML). + +Редактор тут нічим не може допомогти. І якщо ми змінимо параметри чи схеми Marshmallow і забудемо також змінити цю строку документа YAML, згенерована схема буде застарілою. + +!!! Інформація + APISpec був створений тими ж розробниками Marshmallow. + + +!!! Перегляньте "Надихнуло **FastAPI** на" + Підтримувати відкритий стандарт API, OpenAPI. + +### Flask-apispec + +Це плагін Flask, який об’єднує Webargs, Marshmallow і APISpec. + +Він використовує інформацію з Webargs і Marshmallow для автоматичного створення схем OpenAPI за допомогою APISpec. + +Це чудовий інструмент, дуже недооцінений. Він має бути набагато популярнішим, ніж багато плагінів Flask. Це може бути пов’язано з тим, що його документація надто стисла й абстрактна. + +Це вирішило необхідність писати YAML (інший синтаксис) всередині рядків документів Python. + +Ця комбінація Flask, Flask-apispec із Marshmallow і Webargs була моїм улюбленим бекенд-стеком до створення **FastAPI**. + +Їі використання призвело до створення кількох генераторів повного стека Flask. Це основний стек, який я (та кілька зовнішніх команд) використовував досі: + +* https://github.com/tiangolo/full-stack +* https://github.com/tiangolo/full-stack-flask-couchbase +* https://github.com/tiangolo/full-stack-flask-couchdb + +І ці самі генератори повного стеку були основою [**FastAPI** генераторів проектів](project-generation.md){.internal-link target=_blank}. + +!!! Інформація + Flask-apispec був створений тими ж розробниками Marshmallow. + +!!! Перегляньте "Надихнуло **FastAPI** на" + Створення схеми OpenAPI автоматично з того самого коду, який визначає серіалізацію та перевірку. + +### NestJS (та Angular) + +Це навіть не Python, NestJS — це фреймворк NodeJS JavaScript (TypeScript), натхненний Angular. + +Це досягає чогось подібного до того, що можна зробити з Flask-apispec. + +Він має інтегровану систему впровадження залежностей, натхненну Angular two. Він потребує попередньої реєстрації «injectables» (як і всі інші системи впровадження залежностей, які я знаю), тому це збільшує багатослівність та повторення коду. + +Оскільки параметри описані за допомогою типів TypeScript (подібно до підказок типу Python), підтримка редактора досить хороша. + +Але оскільки дані TypeScript не зберігаються після компіляції в JavaScript, вони не можуть покладатися на типи для визначення перевірки, серіалізації та документації одночасно. Через це та деякі дизайнерські рішення, щоб отримати перевірку, серіалізацію та автоматичну генерацію схеми, потрібно додати декоратори в багатьох місцях. Таким чином код стає досить багатослівним. + +Він не дуже добре обробляє вкладені моделі. Отже, якщо тіло JSON у запиті є об’єктом JSON із внутрішніми полями, які, у свою чергу, є вкладеними об’єктами JSON, його неможливо належним чином задокументувати та перевірити. + +!!! Перегляньте "Надихнуло **FastAPI** на" + Використовувати типи Python, щоб мати чудову підтримку редактора. + + Мати потужну систему впровадження залежностей. Знайдіть спосіб звести до мінімуму повторення коду. + +### Sanic + +Це був один із перших надзвичайно швидких фреймворків Python на основі `asyncio`. Він був дуже схожий на Flask. + +!!! Примітка "Технічні деталі" + Він використовував `uvloop` замість стандартного циклу Python `asyncio`. Ось що зробило його таким швидким. + + Це явно надихнуло Uvicorn і Starlette, які зараз швидші за Sanic у відкритих тестах. + +!!! Перегляньте "Надихнуло **FastAPI** на" + Знайти спосіб отримати божевільну продуктивність. + + Ось чому **FastAPI** базується на Starlette, оскільки це найшвидша доступна структура (перевірена тестами сторонніх розробників). + +### Falcon + +Falcon — ще один високопродуктивний фреймворк Python, він розроблений як мінімальний і працює як основа інших фреймворків, таких як Hug. + +Він розроблений таким чином, щоб мати функції, які отримують два параметри, один «запит» і один «відповідь». Потім ви «читаєте» частини запиту та «записуєте» частини у відповідь. Через такий дизайн неможливо оголосити параметри запиту та тіла за допомогою стандартних підказок типу Python як параметри функції. + +Таким чином, перевірка даних, серіалізація та документація повинні виконуватися в коді, а не автоматично. Або вони повинні бути реалізовані як фреймворк поверх Falcon, як Hug. Така сама відмінність спостерігається в інших фреймворках, натхненних дизайном Falcon, що мають один об’єкт запиту та один об’єкт відповіді як параметри. + +!!! Перегляньте "Надихнуло **FastAPI** на" + Знайти способи отримати чудову продуктивність. + + Разом із Hug (оскільки Hug базується на Falcon) надихнув **FastAPI** оголосити параметр `response` у функціях. + + Хоча у FastAPI це необов’язково, і використовується в основному для встановлення заголовків, файлів cookie та альтернативних кодів стану. + +### Molten + +Я відкрив для себе Molten на перших етапах створення **FastAPI**. І він має досить схожі ідеї: + +* Базується на підказках типу Python. +* Перевірка та документація цих типів. +* Система впровадження залежностей. + +Він не використовує перевірку даних, серіалізацію та бібліотеку документації сторонніх розробників, як Pydantic, він має свою власну. Таким чином, ці визначення типів даних не можна було б використовувати повторно так легко. + +Це вимагає трохи більш докладних конфігурацій. І оскільки він заснований на WSGI (замість ASGI), він не призначений для використання високопродуктивних інструментів, таких як Uvicorn, Starlette і Sanic. + +Система впровадження залежностей вимагає попередньої реєстрації залежностей, і залежності вирішуються на основі оголошених типів. Отже, неможливо оголосити більше ніж один «компонент», який надає певний тип. + +Маршрути оголошуються в одному місці з використанням функцій, оголошених в інших місцях (замість використання декораторів, які можна розмістити безпосередньо поверх функції, яка обробляє кінцеву точку). Це ближче до того, як це робить Django, ніж до Flask (і Starlette). Він розділяє в коді речі, які відносно тісно пов’язані. + +!!! Перегляньте "Надихнуло **FastAPI** на" + Визначити додаткові перевірки для типів даних, використовуючи значення "за замовчуванням" атрибутів моделі. Це покращує підтримку редактора, а раніше вона була недоступна в Pydantic. + + Це фактично надихнуло оновити частини Pydantic, щоб підтримувати той самий стиль оголошення перевірки (всі ці функції вже доступні в Pydantic). + +### Hug + +Hug був одним із перших фреймворків, який реалізував оголошення типів параметрів API за допомогою підказок типу Python. Це була чудова ідея, яка надихнула інші інструменти зробити те саме. + +Він використовував спеціальні типи у своїх оголошеннях замість стандартних типів Python, але це все одно був величезний крок вперед. + +Це також був один із перших фреймворків, який генерував спеціальну схему, що оголошувала весь API у JSON. + +Він не базувався на таких стандартах, як OpenAPI та JSON Schema. Тому було б непросто інтегрувати його з іншими інструментами, як-от Swagger UI. Але знову ж таки, це була дуже інноваційна ідея. + +Він має цікаву незвичайну функцію: використовуючи ту саму структуру, можна створювати API, а також CLI. + +Оскільки він заснований на попередньому стандарті для синхронних веб-фреймворків Python (WSGI), він не може працювати з Websockets та іншими речами, хоча він також має високу продуктивність. + +!!! Інформація + Hug створив Тімоті Крослі, той самий творець `isort`, чудовий інструмент для автоматичного сортування імпорту у файлах Python. + +!!! Перегляньте "Надихнуло **FastAPI** на" + Hug надихнув частину APIStar і був одним із найбільш перспективних інструментів, поряд із APIStar. + + Hug надихнув **FastAPI** на використання підказок типу Python для оголошення параметрів і автоматичного створення схеми, що визначає API. + + Hug надихнув **FastAPI** оголосити параметр `response` у функціях для встановлення заголовків і файлів cookie. + +### APIStar (<= 0,5) + +Безпосередньо перед тим, як вирішити створити **FastAPI**, я знайшов сервер **APIStar**. Він мав майже все, що я шукав, і мав чудовий дизайн. + +Це була одна з перших реалізацій фреймворку, що використовує підказки типу Python для оголошення параметрів і запитів, яку я коли-небудь бачив (до NestJS і Molten). Я знайшов його більш-менш одночасно з Hug. Але APIStar використовував стандарт OpenAPI. + +Він мав автоматичну перевірку даних, серіалізацію даних і генерацію схеми OpenAPI на основі підказок того самого типу в кількох місцях. + +Визначення схеми тіла не використовували ті самі підказки типу Python, як Pydantic, воно було трохи схоже на Marshmallow, тому підтримка редактора була б не такою хорошою, але все ж APIStar був найкращим доступним варіантом. + +Він мав найкращі показники продуктивності на той час (перевершив лише Starlette). + +Спочатку він не мав автоматичного веб-інтерфейсу документації API, але я знав, що можу додати до нього інтерфейс користувача Swagger. + +Він мав систему введення залежностей. Він вимагав попередньої реєстрації компонентів, як і інші інструменти, розглянуті вище. Але все одно це була чудова функція. + +Я ніколи не міг використовувати його в повноцінному проекті, оскільки він не мав інтеграції безпеки, тому я не міг замінити всі функції, які мав, генераторами повного стеку на основі Flask-apispec. У моїх невиконаних проектах я мав створити запит на вилучення, додавши цю функцію. + +Але потім фокус проекту змінився. + +Це вже не був веб-фреймворк API, оскільки творцю потрібно було зосередитися на Starlette. + +Тепер APIStar — це набір інструментів для перевірки специфікацій OpenAPI, а не веб-фреймворк. + +!!! Інформація + APIStar створив Том Крісті. Той самий хлопець, який створив: + + * Django REST Framework + * Starlette (на якому базується **FastAPI**) + * Uvicorn (використовується Starlette і **FastAPI**) + +!!! Перегляньте "Надихнуло **FastAPI** на" + Існувати. + + Ідею оголошення кількох речей (перевірки даних, серіалізації та документації) за допомогою тих самих типів Python, які в той же час забезпечували чудову підтримку редактора, я вважав геніальною ідеєю. + + І після тривалого пошуку подібної структури та тестування багатьох різних альтернатив, APIStar став найкращим доступним варіантом. + + Потім APIStar перестав існувати як сервер, і було створено Starlette, який став новою кращою основою для такої системи. Це стало останнім джерелом натхнення для створення **FastAPI**. Я вважаю **FastAPI** «духовним спадкоємцем» APIStar, удосконалюючи та розширюючи функції, систему введення тексту та інші частини на основі досвіду, отриманого від усіх цих попередніх інструментів. + +## Використовується **FastAPI** + +### Pydantic + +Pydantic — це бібліотека для визначення перевірки даних, серіалізації та документації (за допомогою схеми JSON) на основі підказок типу Python. + +Це робить його надзвичайно інтуїтивним. + +Його можна порівняти з Marshmallow. Хоча він швидший за Marshmallow у тестах. Оскільки він базується на тих самих підказках типу Python, підтримка редактора чудова. + +!!! Перегляньте "**FastAPI** використовує його для" + Виконання перевірки всіх даних, серіалізації даних і автоматичної документацію моделі (на основі схеми JSON). + + Потім **FastAPI** бере ці дані схеми JSON і розміщує їх у OpenAPI, окремо від усіх інших речей, які він робить. + +### Starlette + +Starlette — це легкий фреймворк/набір інструментів ASGI, який ідеально підходить для створення високопродуктивних asyncio сервісів. + +Він дуже простий та інтуїтивно зрозумілий. Його розроблено таким чином, щоб його можна було легко розширювати та мати модульні компоненти. + +Він має: + +* Серйозно вражаючу продуктивність. +* Підтримку WebSocket. +* Фонові завдання в процесі. +* Події запуску та завершення роботи. +* Тестового клієнта, побудований на HTTPX. +* CORS, GZip, статичні файли, потокові відповіді. +* Підтримку сеансів і файлів cookie. +* 100% покриття тестом. +* 100% анотовану кодову базу. +* Кілька жорстких залежностей. + +Starlette наразі є найшвидшим фреймворком Python із перевірених. Перевершує лише Uvicorn, який є не фреймворком, а сервером. + +Starlette надає всі основні функції веб-мікрофреймворку. + +Але він не забезпечує автоматичної перевірки даних, серіалізації чи документації. + +Це одна з головних речей, які **FastAPI** додає зверху, все на основі підказок типу Python (з використанням Pydantic). Це, а також система впровадження залежностей, утиліти безпеки, створення схеми OpenAPI тощо. + +!!! Примітка "Технічні деталі" + ASGI — це новий «стандарт», який розробляється членами основної команди Django. Це ще не «стандарт Python» (PEP), хоча вони в процесі цього. + + Тим не менш, він уже використовується як «стандарт» кількома інструментами. Це значно покращує сумісність, оскільки ви можете переключити Uvicorn на будь-який інший сервер ASGI (наприклад, Daphne або Hypercorn), або ви можете додати інструменти, сумісні з ASGI, як-от `python-socketio`. + +!!! Перегляньте "**FastAPI** використовує його для" + Керування всіма основними веб-частинами. Додавання функцій зверху. + + Сам клас `FastAPI` безпосередньо успадковує клас `Starlette`. + + Отже, усе, що ви можете робити зі Starlette, ви можете робити це безпосередньо за допомогою **FastAPI**, оскільки це, по суті, Starlette на стероїдах. + +### Uvicorn + +Uvicorn — це блискавичний сервер ASGI, побудований на uvloop і httptools. + +Це не веб-фреймворк, а сервер. Наприклад, він не надає інструментів для маршрутизації. Це те, що фреймворк на кшталт Starlette (або **FastAPI**) забезпечить поверх нього. + +Це рекомендований сервер для Starlette і **FastAPI**. + +!!! Перегляньте "**FastAPI** рекомендує це як" + Основний веб-сервер для запуску програм **FastAPI**. + + Ви можете поєднати його з Gunicorn, щоб мати асинхронний багатопроцесний сервер. + + Додаткову інформацію див. у розділі [Розгортання](deployment/index.md){.internal-link target=_blank}. + +## Орієнтири та швидкість + +Щоб зрозуміти, порівняти та побачити різницю між Uvicorn, Starlette і FastAPI, перегляньте розділ про [Бенчмарки](benchmarks.md){.internal-link target=_blank}. From 4e93f8e0bc94f72adc79cf1517061f2a1bd17677 Mon Sep 17 00:00:00 2001 From: Ragul K <78537172+ragul-kachiappan@users.noreply.github.com> Date: Sat, 2 Sep 2023 21:02:48 +0530 Subject: [PATCH 038/188] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20typo=20in=20?= =?UTF-8?q?`docs/en/docs/tutorial/dependencies/dependencies-in-path-operat?= =?UTF-8?q?ion-decorators.md`=20(#10172)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- .../dependencies/dependencies-in-path-operation-decorators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 935555339..ccef5aef4 100644 --- a/docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -35,7 +35,7 @@ It should be a `list` of `Depends()`: {!> ../../../docs_src/dependencies/tutorial006.py!} ``` -These dependencies will be executed/solved the same way normal dependencies. But their value (if they return any) won't be passed to your *path operation function*. +These dependencies will be executed/solved the same way as normal dependencies. But their value (if they return any) won't be passed to your *path operation function*. !!! tip Some editors check for unused function parameters, and show them as errors. From 59cbeccac02cf66cbdc55fd1d7ec2675fa5e8e9e Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 15:36:34 +0000 Subject: [PATCH 039/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 09f19f0f6..a74499d88 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add Ukrainian translation for `docs/uk/docs/alternatives.md`. PR [#10060](https://github.com/tiangolo/fastapi/pull/10060) by [@whysage](https://github.com/whysage). * 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/index.md`. PR [#10079](https://github.com/tiangolo/fastapi/pull/10079) by [@rostik1410](https://github.com/rostik1410). * ✏️ Fix typos in `docs/en/docs/how-to/separate-openapi-schemas.md` and `docs/en/docs/tutorial/schema-extra-example.md`. PR [#10189](https://github.com/tiangolo/fastapi/pull/10189) by [@xzmeng](https://github.com/xzmeng). * 🌐 Add Chinese translation for `docs/zh/docs/advanced/generate-clients.md`. PR [#9883](https://github.com/tiangolo/fastapi/pull/9883) by [@funny-cat-happy](https://github.com/funny-cat-happy). From 9fc33f8565be369ed7b40d8202a8cf4a49c951b6 Mon Sep 17 00:00:00 2001 From: Ahsan Sheraz Date: Sat, 2 Sep 2023 20:37:40 +0500 Subject: [PATCH 040/188] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20typos=20in?= =?UTF-8?q?=20comment=20in=20`fastapi/applications.py`=20(#10045)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/applications.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/fastapi/applications.py b/fastapi/applications.py index b681e50b3..5cc568292 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -189,20 +189,20 @@ class FastAPI(Starlette): # contextvars. # This needs to happen after user middlewares because those create a # new contextvars context copy by using a new AnyIO task group. - # The initial part of dependencies with yield is executed in the - # FastAPI code, inside all the middlewares, but the teardown part - # (after yield) is executed in the AsyncExitStack in this middleware, - # if the AsyncExitStack lived outside of the custom middlewares and - # contextvars were set in a dependency with yield in that internal + # The initial part of dependencies with 'yield' is executed in the + # FastAPI code, inside all the middlewares. However, the teardown part + # (after 'yield') is executed in the AsyncExitStack in this middleware. + # If the AsyncExitStack lived outside of the custom middlewares and + # contextvars were set in a dependency with 'yield' in that internal # contextvars context, the values would not be available in the - # outside context of the AsyncExitStack. - # By putting the middleware and the AsyncExitStack here, inside all - # user middlewares, the code before and after yield in dependencies - # with yield is executed in the same contextvars context, so all values - # set in contextvars before yield is still available after yield as - # would be expected. + # outer context of the AsyncExitStack. + # By placing the middleware and the AsyncExitStack here, inside all + # user middlewares, the code before and after 'yield' in dependencies + # with 'yield' is executed in the same contextvars context. Thus, all values + # set in contextvars before 'yield' are still available after 'yield,' as + # expected. # Additionally, by having this AsyncExitStack here, after the - # ExceptionMiddleware, now dependencies can catch handled exceptions, + # ExceptionMiddleware, dependencies can now catch handled exceptions, # e.g. HTTPException, to customize the teardown code (e.g. DB session # rollback). Middleware(AsyncExitStackMiddleware), From a55f3204ef2577045a0ab0585dd58c40686a2da2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 15:37:56 +0000 Subject: [PATCH 041/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a74499d88..2a9ffc954 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏️ Fix typo in `docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#10172](https://github.com/tiangolo/fastapi/pull/10172) by [@ragul-kachiappan](https://github.com/ragul-kachiappan). * 🌐 Add Ukrainian translation for `docs/uk/docs/alternatives.md`. PR [#10060](https://github.com/tiangolo/fastapi/pull/10060) by [@whysage](https://github.com/whysage). * 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/index.md`. PR [#10079](https://github.com/tiangolo/fastapi/pull/10079) by [@rostik1410](https://github.com/rostik1410). * ✏️ Fix typos in `docs/en/docs/how-to/separate-openapi-schemas.md` and `docs/en/docs/tutorial/schema-extra-example.md`. PR [#10189](https://github.com/tiangolo/fastapi/pull/10189) by [@xzmeng](https://github.com/xzmeng). From b2562c5c73440c992e2bd9a1ad07312f3358361f Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 15:41:53 +0000 Subject: [PATCH 042/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2a9ffc954..8af275623 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏️ Fix typos in comment in `fastapi/applications.py`. PR [#10045](https://github.com/tiangolo/fastapi/pull/10045) by [@AhsanSheraz](https://github.com/AhsanSheraz). * ✏️ Fix typo in `docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#10172](https://github.com/tiangolo/fastapi/pull/10172) by [@ragul-kachiappan](https://github.com/ragul-kachiappan). * 🌐 Add Ukrainian translation for `docs/uk/docs/alternatives.md`. PR [#10060](https://github.com/tiangolo/fastapi/pull/10060) by [@whysage](https://github.com/whysage). * 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/index.md`. PR [#10079](https://github.com/tiangolo/fastapi/pull/10079) by [@rostik1410](https://github.com/rostik1410). From 2e3295719814fdfb957c31d0720c578053c230fc Mon Sep 17 00:00:00 2001 From: Poupapaa <3238986+poupapaa@users.noreply.github.com> Date: Sat, 2 Sep 2023 22:43:16 +0700 Subject: [PATCH 043/188] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20typo=20in=20?= =?UTF-8?q?`docs/en/docs/tutorial/handling-errors.md`=20(#10170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/tutorial/handling-errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/tutorial/handling-errors.md b/docs/en/docs/tutorial/handling-errors.md index 8c30326ce..a03029e81 100644 --- a/docs/en/docs/tutorial/handling-errors.md +++ b/docs/en/docs/tutorial/handling-errors.md @@ -1,6 +1,6 @@ # Handling Errors -There are many situations in where you need to notify an error to a client that is using your API. +There are many situations in which you need to notify an error to a client that is using your API. This client could be a browser with a frontend, a code from someone else, an IoT device, etc. From 8979166bc308067951f5361df0cf85b0b73fb1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Kh=E1=BA=AFc=20Th=C3=A0nh?= Date: Sat, 2 Sep 2023 22:44:17 +0700 Subject: [PATCH 044/188] =?UTF-8?q?=F0=9F=8C=90=20Add=20Vietnamese=20trans?= =?UTF-8?q?lations=20for=20`docs/vi/docs/tutorial/first-steps.md`=20and=20?= =?UTF-8?q?`docs/vi/docs/tutorial/index.md`=20(#10088)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- docs/vi/docs/tutorial/first-steps.md | 333 +++++++++++++++++++++++++++ docs/vi/docs/tutorial/index.md | 80 +++++++ 2 files changed, 413 insertions(+) create mode 100644 docs/vi/docs/tutorial/first-steps.md create mode 100644 docs/vi/docs/tutorial/index.md diff --git a/docs/vi/docs/tutorial/first-steps.md b/docs/vi/docs/tutorial/first-steps.md new file mode 100644 index 000000000..712f00852 --- /dev/null +++ b/docs/vi/docs/tutorial/first-steps.md @@ -0,0 +1,333 @@ +# Những bước đầu tiên + +Tệp tin FastAPI đơn giản nhất có thể trông như này: + +```Python +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Sao chép sang một tệp tin `main.py`. + +Chạy live server: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +!!! note + Câu lệnh `uvicorn main:app` được giải thích như sau: + + * `main`: tệp tin `main.py` (một Python "mô đun"). + * `app`: một object được tạo ra bên trong `main.py` với dòng `app = FastAPI()`. + * `--reload`: làm server khởi động lại sau mỗi lần thay đổi. Chỉ sử dụng trong môi trường phát triển. + +Trong output, có một dòng giống như: + +```hl_lines="4" +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +Dòng đó cho thấy URL, nơi mà app của bạn đang được chạy, trong máy local của bạn. + +### Kiểm tra + +Mở trình duyệt của bạn tại http://127.0.0.1:8000. + +Bạn sẽ thấy một JSON response như: + +```JSON +{"message": "Hello World"} +``` + +### Tài liệu tương tác API + +Bây giờ tới http://127.0.0.1:8000/docs. + +Bạn sẽ thấy một tài liệu tương tác API (cung cấp bởi Swagger UI): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### Phiên bản thay thế của tài liệu API + +Và bây giờ tới http://127.0.0.1:8000/redoc. + +Bạn sẽ thấy một bản thay thế của tài liệu (cung cấp bởi ReDoc): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +### OpenAPI + +**FastAPI** sinh một "schema" với tất cả API của bạn sử dụng tiêu chuẩn **OpenAPI** cho định nghĩa các API. + +#### "Schema" + +Một "schema" là một định nghĩa hoặc mô tả thứ gì đó. Không phải code triển khai của nó, nhưng chỉ là một bản mô tả trừu tượng. + +#### API "schema" + +Trong trường hợp này, OpenAPI là một bản mô tả bắt buộc cơ chế định nghĩa API của bạn. + +Định nghĩa cấu trúc này bao gồm những đường dẫn API của bạn, các tham số có thể có,... + +#### "Cấu trúc" dữ liệu + +Thuật ngữ "cấu trúc" (schema) cũng có thể được coi như là hình dạng của dữ liệu, tương tự như một JSON content. + +Trong trường hợp đó, nó có nghĩa là các thuộc tính JSON và các kiểu dữ liệu họ có,... + +#### OpenAPI và JSON Schema + +OpenAPI định nghĩa một cấu trúc API cho API của bạn. Và cấu trúc đó bao gồm các dịnh nghĩa (or "schema") về dữ liệu được gửi đi và nhận về bởi API của bạn, sử dụng **JSON Schema**, một tiêu chuẩn cho cấu trúc dữ liệu JSON. + +#### Kiểm tra `openapi.json` + +Nếu bạn tò mò về việc cấu trúc OpenAPI nhìn như thế nào thì FastAPI tự động sinh một JSON (schema) với các mô tả cho tất cả API của bạn. + +Bạn có thể thấy nó trực tiếp tại: http://127.0.0.1:8000/openapi.json. + +Nó sẽ cho thấy một JSON bắt đầu giống như: + +```JSON +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... +``` + +#### OpenAPI dùng để làm gì? + +Cấu trúc OpenAPI là sức mạnh của tài liệu tương tác. + +Và có hàng tá các bản thay thế, tất cả đều dựa trên OpenAPI. Bạn có thể dễ dàng thêm bất kì bản thay thế bào cho ứng dụng của bạn được xây dựng với **FastAPI**. + +Bạn cũng có thể sử dụng nó để sinh code tự động, với các client giao viết qua API của bạn. Ví dụ, frontend, mobile hoặc các ứng dụng IoT. + +## Tóm lại, từng bước một + +### Bước 1: import `FastAPI` + +```Python hl_lines="1" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`FastAPI` là một Python class cung cấp tất cả chức năng cho API của bạn. + +!!! note "Chi tiết kĩ thuật" + `FastAPI` là một class kế thừa trực tiếp `Starlette`. + + Bạn cũng có thể sử dụng tất cả Starlette chức năng với `FastAPI`. + +### Bước 2: Tạo một `FastAPI` "instance" + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Biến `app` này là một "instance" của class `FastAPI`. + +Đây sẽ là điểm cốt lõi để tạo ra tất cả API của bạn. + +`app` này chính là điều được nhắc tới bởi `uvicorn` trong câu lệnh: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Nếu bạn tạo ứng dụng của bạn giống như: + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial002.py!} +``` + +Và đặt nó trong một tệp tin `main.py`, sau đó bạn sẽ gọi `uvicorn` giống như: + +
+ +```console +$ uvicorn main:my_awesome_api --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Bước 3: tạo một *đường dẫn toán tử* + +#### Đường dẫn + +"Đường dẫn" ở đây được nhắc tới là phần cuối cùng của URL bắt đầu từ `/`. + +Do đó, trong một URL nhìn giống như: + +``` +https://example.com/items/foo +``` + +...đường dẫn sẽ là: + +``` +/items/foo +``` + +!!! info + Một đường dẫn cũng là một cách gọi chung cho một "endpoint" hoặc một "route". + +Trong khi xây dựng một API, "đường dẫn" là các chính để phân tách "mối quan hệ" và "tài nguyên". + +#### Toán tử (Operation) + +"Toán tử" ở đây được nhắc tới là một trong các "phương thức" HTTP. + +Một trong những: + +* `POST` +* `GET` +* `PUT` +* `DELETE` + +...và một trong những cái còn lại: + +* `OPTIONS` +* `HEAD` +* `PATCH` +* `TRACE` + +Trong giao thức HTTP, bạn có thể giao tiếp trong mỗi đường dẫn sử dụng một (hoặc nhiều) trong các "phương thức này". + +--- + +Khi xây dựng các API, bạn thường sử dụng cụ thể các phương thức HTTP này để thực hiện một hành động cụ thể. + +Thông thường, bạn sử dụng + +* `POST`: để tạo dữ liệu. +* `GET`: để đọc dữ liệu. +* `PUT`: để cập nhật dữ liệu. +* `DELETE`: để xóa dữ liệu. + +Do đó, trong OpenAPI, mỗi phương thức HTTP được gọi là một "toán tử (operation)". + +Chúng ta cũng sẽ gọi chúng là "**các toán tử**". + +#### Định nghĩa moojt *decorator cho đường dẫn toán tử* + +```Python hl_lines="6" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`@app.get("/")` nói **FastAPI** rằng hàm bên dưới có trách nhiệm xử lí request tới: + +* đường dẫn `/` +* sử dụng một toán tửget + +!!! info Thông tin về "`@decorator`" + Cú pháp `@something` trong Python được gọi là một "decorator". + + Bạn đặt nó trên một hàm. Giống như một chiếc mũ xinh xắn (Tôi ddonas đó là lí do mà thuật ngữ này ra đời). + + Một "decorator" lấy một hàm bên dưới và thực hiện một vài thứ với nó. + + Trong trường hợp của chúng ta, decorator này nói **FastAPI** rằng hàm bên dưới ứng với **đường dẫn** `/` và một **toán tử** `get`. + + Nó là một "**decorator đường dẫn toán tử**". + +Bạn cũng có thể sử dụng với các toán tử khác: + +* `@app.post()` +* `@app.put()` +* `@app.delete()` + +Và nhiều hơn với các toán tử còn lại: + +* `@app.options()` +* `@app.head()` +* `@app.patch()` +* `@app.trace()` + +!!! tip + Bạn thoải mái sử dụng mỗi toán tử (phương thức HTTP) như bạn mơ ước. + + **FastAPI** không bắt buộc bất kì ý nghĩa cụ thể nào. + + Thông tin ở đây được biểu thị như là một chỉ dẫn, không phải là một yêu cầu bắt buộc. + + Ví dụ, khi sử dụng GraphQL bạn thông thường thực hiện tất cả các hành động chỉ bằng việc sử dụng các toán tử `POST`. + +### Step 4: Định nghĩa **hàm cho đường dẫn toán tử** + +Đây là "**hàm cho đường dẫn toán tử**": + +* **đường dẫn**: là `/`. +* **toán tử**: là `get`. +* **hàm**: là hàm bên dưới "decorator" (bên dưới `@app.get("/")`). + +```Python hl_lines="7" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Đây là một hàm Python. + +Nó sẽ được gọi bởi **FastAPI** bất cứ khi nào nó nhận một request tới URL "`/`" sử dụng một toán tử `GET`. + +Trong trường hợp này, nó là một hàm `async`. + +--- + +Bạn cũng có thể định nghĩa nó như là một hàm thông thường thay cho `async def`: + +```Python hl_lines="7" +{!../../../docs_src/first_steps/tutorial003.py!} +``` + +!!! note + Nếu bạn không biết sự khác nhau, kiểm tra [Async: *"Trong khi vội vàng?"*](../async.md#in-a-hurry){.internal-link target=_blank}. + +### Bước 5: Nội dung trả về + +```Python hl_lines="8" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Bạn có thể trả về một `dict`, `list`, một trong những giá trị đơn như `str`, `int`,... + +Bạn cũng có thể trả về Pydantic model (bạn sẽ thấy nhiều hơn về nó sau). + +Có nhiều object và model khác nhau sẽ được tự động chuyển đổi sang JSON (bao gồm cả ORM,...). Thử sử dụng loại ưa thích của bạn, nó có khả năng cao đã được hỗ trợ. + +## Tóm lại + +* Import `FastAPI`. +* Tạo một `app` instance. +* Viết một **decorator cho đường dẫn toán tử** (giống như `@app.get("/")`). +* Viết một **hàm cho đường dẫn toán tử** (giống như `def root(): ...` ở trên). +* Chạy server trong môi trường phát triển (giống như `uvicorn main:app --reload`). diff --git a/docs/vi/docs/tutorial/index.md b/docs/vi/docs/tutorial/index.md new file mode 100644 index 000000000..e8a93fe40 --- /dev/null +++ b/docs/vi/docs/tutorial/index.md @@ -0,0 +1,80 @@ +# Hướng dẫn sử dụng + +Hướng dẫn này cho bạn thấy từng bước cách sử dụng **FastAPI** đa số các tính năng của nó. + +Mỗi phần được xây dựng từ những phần trước đó, nhưng nó được cấu trúc thành các chủ đề riêng biệt, do đó bạn có thể xem trực tiếp từng phần cụ thể bất kì để giải quyết những API cụ thể mà bạn cần. + +Nó cũng được xây dựng để làm việc như một tham chiếu trong tương lai. + +Do đó bạn có thể quay lại và tìm chính xác những gì bạn cần. + +## Chạy mã + +Tất cả các code block có thể được sao chép và sử dụng trực tiếp (chúng thực chất là các tệp tin Python đã được kiểm thử). + +Để chạy bất kì ví dụ nào, sao chép code tới tệp tin `main.py`, và bắt đầu `uvicorn` với: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +**Khuyến khích** bạn viết hoặc sao chép code, sửa và chạy nó ở local. + +Sử dụng nó trong trình soạn thảo của bạn thực sự cho bạn thấy những lợi ích của FastAPI, thấy được cách bạn viết code ít hơn, tất cả đều được type check, autocompletion,... + +--- + +## Cài đặt FastAPI + +Bước đầu tiên là cài đặt FastAPI. + +Với hướng dẫn này, bạn có thể muốn cài đặt nó với tất cả các phụ thuộc và tính năng tùy chọn: + +
+ +```console +$ pip install "fastapi[all]" + +---> 100% +``` + +
+ +...dó cũng bao gồm `uvicorn`, bạn có thể sử dụng như một server để chạy code của bạn. + +!!! note + Bạn cũng có thể cài đặt nó từng phần. + + Đây là những gì bạn có thể sẽ làm một lần duy nhất bạn muốn triển khai ứng dụng của bạn lên production: + + ``` + pip install fastapi + ``` + + Cũng cài đặt `uvicorn` để làm việc như một server: + + ``` + pip install "uvicorn[standard]" + ``` + + Và tương tự với từng phụ thuộc tùy chọn mà bạn muốn sử dụng. + +## Hướng dẫn nâng cao + +Cũng có một **Hướng dẫn nâng cao** mà bạn có thể đọc nó sau **Hướng dẫn sử dụng**. + +**Hướng dẫn sử dụng nâng cao**, xây dựng dựa trên cái này, sử dụng các khái niệm tương tự, và dạy bạn những tính năng mở rộng. + +Nhưng bạn nên đọc **Hướng dẫn sử dụng** đầu tiên (những gì bạn đang đọc). + +Nó được thiết kế do đó bạn có thể xây dựng một ứng dụng hoàn chỉnh chỉ với **Hướng dẫn sử dụng**, và sau đó mở rộng nó theo các cách khác nhau, phụ thuộc vào những gì bạn cần, sử dụng một vài ý tưởng bổ sung từ **Hướng dẫn sử dụng nâng cao**. From e6c4785959609097631fa37f393cd9f977377dfa Mon Sep 17 00:00:00 2001 From: Rostyslav Date: Sat, 2 Sep 2023 18:48:03 +0300 Subject: [PATCH 045/188] =?UTF-8?q?=F0=9F=8C=90=20Add=20Ukrainian=20transl?= =?UTF-8?q?ation=20for=20`docs/uk/docs/python-types.md`=20(#10080)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/uk/docs/python-types.md | 448 +++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 docs/uk/docs/python-types.md diff --git a/docs/uk/docs/python-types.md b/docs/uk/docs/python-types.md new file mode 100644 index 000000000..f792e83a8 --- /dev/null +++ b/docs/uk/docs/python-types.md @@ -0,0 +1,448 @@ +# Вступ до типів Python + +Python підтримує додаткові "підказки типу" ("type hints") (також звані "анотаціями типу" ("type annotations")). + +Ці **"type hints"** є спеціальним синтаксисом, що дозволяє оголошувати тип змінної. + +За допомогою оголошення типів для ваших змінних, редактори та інструменти можуть надати вам кращу підтримку. + +Це просто **швидкий посібник / нагадування** про анотації типів у Python. Він покриває лише мінімум, необхідний щоб використовувати їх з **FastAPI**... що насправді дуже мало. + +**FastAPI** повністю базується на цих анотаціях типів, вони дають йому багато переваг. + +Але навіть якщо ви ніколи не використаєте **FastAPI**, вам буде корисно дізнатись трохи про них. + +!!! note + Якщо ви експерт у Python і ви вже знаєте усе про анотації типів - перейдіть до наступного розділу. + +## Мотивація + +Давайте почнемо з простого прикладу: + +```Python +{!../../../docs_src/python_types/tutorial001.py!} +``` + +Виклик цієї програми виводить: + +``` +John Doe +``` + +Функція виконує наступне: + +* Бере `first_name` та `last_name`. +* Конвертує кожну літеру кожного слова у верхній регістр за допомогою `title()`. +* Конкатенує їх разом із пробілом по середині. + +```Python hl_lines="2" +{!../../../docs_src/python_types/tutorial001.py!} +``` + +### Редагуйте це + +Це дуже проста програма. + +Але тепер уявіть, що ви писали це з нуля. + +У певний момент ви розпочали б визначення функції, у вас були б готові параметри... + +Але тоді вам потрібно викликати "той метод, який переводить першу літеру у верхній регістр". + +Це буде `upper`? Чи `uppercase`? `first_uppercase`? `capitalize`? + +Тоді ви спробуєте давнього друга програміста - автозаповнення редактора коду. + +Ви надрукуєте перший параметр функції, `first_name`, тоді крапку (`.`), а тоді натиснете `Ctrl+Space`, щоб запустити автозаповнення. + +Але, на жаль, ви не отримаєте нічого корисного: + + + +### Додайте типи + +Давайте змінимо один рядок з попередньої версії. + +Ми змінимо саме цей фрагмент, параметри функції, з: + +```Python + first_name, last_name +``` + +на: + +```Python + first_name: str, last_name: str +``` + +Ось і все. + +Це "type hints": + +```Python hl_lines="1" +{!../../../docs_src/python_types/tutorial002.py!} +``` + +Це не те саме, що оголошення значень за замовчуванням, як це було б з: + +```Python + first_name="john", last_name="doe" +``` + +Це зовсім інше. + +Ми використовуємо двокрапку (`:`), не дорівнює (`=`). + +І додавання анотації типу зазвичай не змінює того, що сталось би без них. + +Але тепер, уявіть що ви посеред процесу створення функції, але з анотаціями типів. + +В цей же момент, ви спробуєте викликати автозаповнення з допомогою `Ctrl+Space` і побачите: + + + +Разом з цим, ви можете прокручувати, переглядати опції, допоки ви не знайдете одну, що звучить схоже: + + + +## Більше мотивації + +Перевірте цю функцію, вона вже має анотацію типу: + +```Python hl_lines="1" +{!../../../docs_src/python_types/tutorial003.py!} +``` + +Оскільки редактор знає типи змінних, ви не тільки отримаєте автозаповнення, ви також отримаєте перевірку помилок: + + + +Тепер ви знаєте, щоб виправити це, вам потрібно перетворити `age` у строку з допомогою `str(age)`: + +```Python hl_lines="2" +{!../../../docs_src/python_types/tutorial004.py!} +``` + +## Оголошення типів + +Щойно ви побачили основне місце для оголошення анотацій типу. Як параметри функції. + +Це також основне місце, де ви б їх використовували у **FastAPI**. + +### Прості типи + +Ви можете оголошувати усі стандартні типи у Python, не тільки `str`. + +Ви можете використовувати, наприклад: + +* `int` +* `float` +* `bool` +* `bytes` + +```Python hl_lines="1" +{!../../../docs_src/python_types/tutorial005.py!} +``` + +### Generic-типи з параметрами типів + +Існують деякі структури даних, які можуть містити інші значення, наприклад `dict`, `list`, `set` та `tuple`. І внутрішні значення також можуть мати свій тип. + +Ці типи, які мають внутрішні типи, називаються "**generic**" типами. І оголосити їх можна навіть із внутрішніми типами. + +Щоб оголосити ці типи та внутрішні типи, ви можете використовувати стандартний модуль Python `typing`. Він існує спеціально для підтримки анотацій типів. + +#### Новіші версії Python + +Синтаксис із використанням `typing` **сумісний** з усіма версіями, від Python 3.6 до останніх, включаючи Python 3.9, Python 3.10 тощо. + +У міру розвитку Python **новіші версії** мають покращену підтримку анотацій типів і в багатьох випадках вам навіть не потрібно буде імпортувати та використовувати модуль `typing` для оголошення анотацій типу. + +Якщо ви можете вибрати новішу версію Python для свого проекту, ви зможете скористатися цією додатковою простотою. Дивіться кілька прикладів нижче. + +#### List (список) + +Наприклад, давайте визначимо змінну, яка буде `list` із `str`. + +=== "Python 3.6 і вище" + + З модуля `typing`, імпортуємо `List` (з великої літери `L`): + + ``` Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial006.py!} + ``` + + Оголосимо змінну з тим самим синтаксисом двокрапки (`:`). + + Як тип вкажемо `List`, який ви імпортували з `typing`. + + Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки: + + ```Python hl_lines="4" + {!> ../../../docs_src/python_types/tutorial006.py!} + ``` + +=== "Python 3.9 і вище" + + Оголосимо змінну з тим самим синтаксисом двокрапки (`:`). + + Як тип вкажемо `list`. + + Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки: + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial006_py39.py!} + ``` + +!!! info + Ці внутрішні типи в квадратних дужках називаються "параметрами типу". + + У цьому випадку, `str` це параметр типу переданий у `List` (або `list` у Python 3.9 і вище). + +Це означає: "змінна `items` це `list`, і кожен з елементів у цьому списку - `str`". + +!!! tip + Якщо ви використовуєте Python 3.9 і вище, вам не потрібно імпортувати `List` з `typing`, ви можете використовувати натомість тип `list`. + +Зробивши це, ваш редактор може надати підтримку навіть під час обробки елементів зі списку: + + + +Без типів цього майже неможливо досягти. + +Зверніть увагу, що змінна `item` є одним із елементів у списку `items`. + +І все ж редактор знає, що це `str`, і надає підтримку для цього. + +#### Tuple and Set (кортеж та набір) + +Ви повинні зробити те ж саме, щоб оголосити `tuple` і `set`: + +=== "Python 3.6 і вище" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial007.py!} + ``` + +=== "Python 3.9 і вище" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial007_py39.py!} + ``` + +Це означає: + +* Змінна `items_t` це `tuple` з 3 елементами, `int`, ще `int`, та `str`. +* Змінна `items_s` це `set`, і кожен його елемент типу `bytes`. + +#### Dict (словник) + +Щоб оголосити `dict`, вам потрібно передати 2 параметри типу, розділені комами. + +Перший параметр типу для ключа у `dict`. + +Другий параметр типу для значення у `dict`: + +=== "Python 3.6 і вище" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial008.py!} + ``` + +=== "Python 3.9 і вище" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial008_py39.py!} + ``` + +Це означає: + +* Змінна `prices` це `dict`: + * Ключі цього `dict` типу `str` (наприклад, назва кожного елементу). + * Значення цього `dict` типу `float` (наприклад, ціна кожного елементу). + +#### Union (об'єднання) + +Ви можете оголосити, що змінна може бути будь-яким із **кількох типів**, наприклад, `int` або `str`. + +У Python 3.6 і вище (включаючи Python 3.10) ви можете використовувати тип `Union` з `typing` і вставляти в квадратні дужки можливі типи, які можна прийняти. + +У Python 3.10 також є **альтернативний синтаксис**, у якому ви можете розділити можливі типи за допомогою вертикальної смуги (`|`). + +=== "Python 3.6 і вище" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial008b.py!} + ``` + +=== "Python 3.10 і вище" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial008b_py310.py!} + ``` + +В обох випадках це означає, що `item` може бути `int` або `str`. + +#### Possibly `None` (Optional) + +Ви можете оголосити, що значення може мати тип, наприклад `str`, але також може бути `None`. + +У Python 3.6 і вище (включаючи Python 3.10) ви можете оголосити його, імпортувавши та використовуючи `Optional` з модуля `typing`. + +```Python hl_lines="1 4" +{!../../../docs_src/python_types/tutorial009.py!} +``` + +Використання `Optional[str]` замість просто `str` дозволить редактору допомогти вам виявити помилки, коли ви могли б вважати, що значенням завжди є `str`, хоча насправді воно також може бути `None`. + +`Optional[Something]` насправді є скороченням для `Union[Something, None]`, вони еквівалентні. + +Це також означає, що в Python 3.10 ви можете використовувати `Something | None`: + +=== "Python 3.6 і вище" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial009.py!} + ``` + +=== "Python 3.6 і вище - альтернатива" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial009b.py!} + ``` + +=== "Python 3.10 і вище" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial009_py310.py!} + ``` + +#### Generic типи + +Ці типи, які приймають параметри типу у квадратних дужках, називаються **Generic types** or **Generics**, наприклад: + +=== "Python 3.6 і вище" + + * `List` + * `Tuple` + * `Set` + * `Dict` + * `Union` + * `Optional` + * ...та інші. + +=== "Python 3.9 і вище" + + Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині): + + * `list` + * `tuple` + * `set` + * `dict` + + І те саме, що й у Python 3.6, із модуля `typing`: + + * `Union` + * `Optional` + * ...та інші. + +=== "Python 3.10 і вище" + + Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині): + + * `list` + * `tuple` + * `set` + * `dict` + + І те саме, що й у Python 3.6, із модуля `typing`: + + * `Union` + * `Optional` (так само як у Python 3.6) + * ...та інші. + + У Python 3.10, як альтернатива використанню `Union` та `Optional`, ви можете використовувати вертикальну смугу (`|`) щоб оголосити об'єднання типів. + +### Класи як типи + +Ви також можете оголосити клас як тип змінної. + +Скажімо, у вас є клас `Person` з імʼям: + +```Python hl_lines="1-3" +{!../../../docs_src/python_types/tutorial010.py!} +``` + +Потім ви можете оголосити змінну типу `Person`: + +```Python hl_lines="6" +{!../../../docs_src/python_types/tutorial010.py!} +``` + +І знову ж таки, ви отримуєте всю підтримку редактора: + + + +## Pydantic моделі + +Pydantic це бібліотека Python для валідації даних. + +Ви оголошуєте «форму» даних як класи з атрибутами. + +І кожен атрибут має тип. + +Потім ви створюєте екземпляр цього класу з деякими значеннями, і він перевірить ці значення, перетворить їх у відповідний тип (якщо є потреба) і надасть вам об’єкт з усіма даними. + +І ви отримуєте всю підтримку редактора з цим отриманим об’єктом. + +Приклад з документації Pydantic: + +=== "Python 3.6 і вище" + + ```Python + {!> ../../../docs_src/python_types/tutorial011.py!} + ``` + +=== "Python 3.9 і вище" + + ```Python + {!> ../../../docs_src/python_types/tutorial011_py39.py!} + ``` + +=== "Python 3.10 і вище" + + ```Python + {!> ../../../docs_src/python_types/tutorial011_py310.py!} + ``` + +!!! info + Щоб дізнатись більше про Pydantic, перегляньте його документацію. + +**FastAPI** повністю базується на Pydantic. + +Ви побачите набагато більше цього всього на практиці в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}. + +## Анотації типів у **FastAPI** + +**FastAPI** використовує ці підказки для виконання кількох речей. + +З **FastAPI** ви оголошуєте параметри з підказками типу, і отримуєте: + +* **Підтримку редактора**. +* **Перевірку типів**. + +...і **FastAPI** використовує ті самі оголошення для: + +* **Визначення вимог**: з параметрів шляху запиту, параметрів запиту, заголовків, тіл, залежностей тощо. +* **Перетворення даних**: із запиту в необхідний тип. +* **Перевірка даних**: що надходять від кожного запиту: + * Генерування **автоматичних помилок**, що повертаються клієнту, коли дані недійсні. +* **Документування** API за допомогою OpenAPI: + * який потім використовується для автоматичної інтерактивної документації користувальницьких інтерфейсів. + +Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в [Туторіал - Посібник користувача](tutorial/index.md){.internal-link target=_blank}. + +Важливо те, що за допомогою стандартних типів Python в одному місці (замість того, щоб додавати більше класів, декораторів тощо), **FastAPI** зробить багато роботи за вас. + +!!! info + Якщо ви вже пройшли весь навчальний посібник і повернулися, щоб дізнатися більше про типи, ось хороший ресурс "шпаргалка" від `mypy`. From 1866abffc1485bd09971d3c6f4a98fcfe4d96b0a Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 15:49:31 +0000 Subject: [PATCH 046/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 8af275623..44143af63 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏️ Fix typo in `docs/en/docs/tutorial/handling-errors.md`. PR [#10170](https://github.com/tiangolo/fastapi/pull/10170) by [@poupapaa](https://github.com/poupapaa). * ✏️ Fix typos in comment in `fastapi/applications.py`. PR [#10045](https://github.com/tiangolo/fastapi/pull/10045) by [@AhsanSheraz](https://github.com/AhsanSheraz). * ✏️ Fix typo in `docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#10172](https://github.com/tiangolo/fastapi/pull/10172) by [@ragul-kachiappan](https://github.com/ragul-kachiappan). * 🌐 Add Ukrainian translation for `docs/uk/docs/alternatives.md`. PR [#10060](https://github.com/tiangolo/fastapi/pull/10060) by [@whysage](https://github.com/whysage). From 28bf4abf1fd81a6a930cca415604ebfa90898727 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 15:50:11 +0000 Subject: [PATCH 047/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 44143af63..57fea97ef 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add Vietnamese translations for `docs/vi/docs/tutorial/first-steps.md` and `docs/vi/docs/tutorial/index.md`. PR [#10088](https://github.com/tiangolo/fastapi/pull/10088) by [@magiskboy](https://github.com/magiskboy). * ✏️ Fix typo in `docs/en/docs/tutorial/handling-errors.md`. PR [#10170](https://github.com/tiangolo/fastapi/pull/10170) by [@poupapaa](https://github.com/poupapaa). * ✏️ Fix typos in comment in `fastapi/applications.py`. PR [#10045](https://github.com/tiangolo/fastapi/pull/10045) by [@AhsanSheraz](https://github.com/AhsanSheraz). * ✏️ Fix typo in `docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#10172](https://github.com/tiangolo/fastapi/pull/10172) by [@ragul-kachiappan](https://github.com/ragul-kachiappan). From 7fe952f52255ebc2eef151fa9bb6c3a6f9d30909 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 15:54:22 +0000 Subject: [PATCH 048/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 57fea97ef..7861a35ab 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add Ukrainian translation for `docs/uk/docs/python-types.md`. PR [#10080](https://github.com/tiangolo/fastapi/pull/10080) by [@rostik1410](https://github.com/rostik1410). * 🌐 Add Vietnamese translations for `docs/vi/docs/tutorial/first-steps.md` and `docs/vi/docs/tutorial/index.md`. PR [#10088](https://github.com/tiangolo/fastapi/pull/10088) by [@magiskboy](https://github.com/magiskboy). * ✏️ Fix typo in `docs/en/docs/tutorial/handling-errors.md`. PR [#10170](https://github.com/tiangolo/fastapi/pull/10170) by [@poupapaa](https://github.com/poupapaa). * ✏️ Fix typos in comment in `fastapi/applications.py`. PR [#10045](https://github.com/tiangolo/fastapi/pull/10045) by [@AhsanSheraz](https://github.com/AhsanSheraz). From 0ea23e2a8de9aad5c675f02f3ec54b9dd756a877 Mon Sep 17 00:00:00 2001 From: Hasnat Sajid <86589885+hasnatsajid@users.noreply.github.com> Date: Sat, 2 Sep 2023 20:55:41 +0500 Subject: [PATCH 049/188] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20link=20to=20?= =?UTF-8?q?Pydantic=20docs=20in=20`docs/en/docs/tutorial/extra-data-types.?= =?UTF-8?q?md`=20(#10155)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/tutorial/extra-data-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/tutorial/extra-data-types.md b/docs/en/docs/tutorial/extra-data-types.md index 7d6ffbc78..b34ccd26f 100644 --- a/docs/en/docs/tutorial/extra-data-types.md +++ b/docs/en/docs/tutorial/extra-data-types.md @@ -49,7 +49,7 @@ Here are some of the additional data types you can use: * `Decimal`: * Standard Python `Decimal`. * In requests and responses, handled the same as a `float`. -* You can check all the valid pydantic data types here: Pydantic data types. +* You can check all the valid pydantic data types here: Pydantic data types. ## Example From 0242ca756670e66aeb534054c9251176f08876bd Mon Sep 17 00:00:00 2001 From: Rahul Salgare Date: Sat, 2 Sep 2023 21:26:35 +0530 Subject: [PATCH 050/188] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20Pydantic=20e?= =?UTF-8?q?xamples=20in=20tutorial=20for=20Python=20types=20(#9961)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs_src/python_types/tutorial011.py | 2 +- docs_src/python_types/tutorial011_py310.py | 2 +- docs_src/python_types/tutorial011_py39.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs_src/python_types/tutorial011.py b/docs_src/python_types/tutorial011.py index c8634cbff..297a84db6 100644 --- a/docs_src/python_types/tutorial011.py +++ b/docs_src/python_types/tutorial011.py @@ -6,7 +6,7 @@ from pydantic import BaseModel class User(BaseModel): id: int - name = "John Doe" + name: str = "John Doe" signup_ts: Union[datetime, None] = None friends: List[int] = [] diff --git a/docs_src/python_types/tutorial011_py310.py b/docs_src/python_types/tutorial011_py310.py index 7f173880f..842760c60 100644 --- a/docs_src/python_types/tutorial011_py310.py +++ b/docs_src/python_types/tutorial011_py310.py @@ -5,7 +5,7 @@ from pydantic import BaseModel class User(BaseModel): id: int - name = "John Doe" + name: str = "John Doe" signup_ts: datetime | None = None friends: list[int] = [] diff --git a/docs_src/python_types/tutorial011_py39.py b/docs_src/python_types/tutorial011_py39.py index 468496f51..4eb40b405 100644 --- a/docs_src/python_types/tutorial011_py39.py +++ b/docs_src/python_types/tutorial011_py39.py @@ -6,7 +6,7 @@ from pydantic import BaseModel class User(BaseModel): id: int - name = "John Doe" + name: str = "John Doe" signup_ts: Union[datetime, None] = None friends: list[int] = [] From aa43afa4c0aeba4615d3fde9ac96abf7a1a2e08a Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 16:00:21 +0000 Subject: [PATCH 051/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7861a35ab..c540d2426 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏️ Fix link to Pydantic docs in `docs/en/docs/tutorial/extra-data-types.md`. PR [#10155](https://github.com/tiangolo/fastapi/pull/10155) by [@hasnatsajid](https://github.com/hasnatsajid). * 🌐 Add Ukrainian translation for `docs/uk/docs/python-types.md`. PR [#10080](https://github.com/tiangolo/fastapi/pull/10080) by [@rostik1410](https://github.com/rostik1410). * 🌐 Add Vietnamese translations for `docs/vi/docs/tutorial/first-steps.md` and `docs/vi/docs/tutorial/index.md`. PR [#10088](https://github.com/tiangolo/fastapi/pull/10088) by [@magiskboy](https://github.com/magiskboy). * ✏️ Fix typo in `docs/en/docs/tutorial/handling-errors.md`. PR [#10170](https://github.com/tiangolo/fastapi/pull/10170) by [@poupapaa](https://github.com/poupapaa). From 34028290f5386011f4638ea4e36f1619d60b9b0c Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 16:03:22 +0000 Subject: [PATCH 052/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c540d2426..622f6ddc5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏️ Fix Pydantic examples in tutorial for Python types. PR [#9961](https://github.com/tiangolo/fastapi/pull/9961) by [@rahulsalgare](https://github.com/rahulsalgare). * ✏️ Fix link to Pydantic docs in `docs/en/docs/tutorial/extra-data-types.md`. PR [#10155](https://github.com/tiangolo/fastapi/pull/10155) by [@hasnatsajid](https://github.com/hasnatsajid). * 🌐 Add Ukrainian translation for `docs/uk/docs/python-types.md`. PR [#10080](https://github.com/tiangolo/fastapi/pull/10080) by [@rostik1410](https://github.com/rostik1410). * 🌐 Add Vietnamese translations for `docs/vi/docs/tutorial/first-steps.md` and `docs/vi/docs/tutorial/index.md`. PR [#10088](https://github.com/tiangolo/fastapi/pull/10088) by [@magiskboy](https://github.com/magiskboy). From 1711c1e95f3eec71c9d29050c9901137117b54aa Mon Sep 17 00:00:00 2001 From: Olaoluwa Afolabi Date: Sat, 2 Sep 2023 17:12:44 +0100 Subject: [PATCH 053/188] =?UTF-8?q?=F0=9F=8C=90=20Add=20Yoruba=20translati?= =?UTF-8?q?on=20for=20`docs/yo/docs/index.md`=20(#10033)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- docs/en/mkdocs.yml | 3 + docs/yo/docs/index.md | 470 ++++++++++++++++++++++++++++++++++++++++++ docs/yo/mkdocs.yml | 1 + 3 files changed, 474 insertions(+) create mode 100644 docs/yo/docs/index.md create mode 100644 docs/yo/mkdocs.yml diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index c56e4c942..ba1ac7924 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -72,6 +72,7 @@ nav: - uk: /uk/ - ur: /ur/ - vi: /vi/ + - yo: /yo/ - zh: /zh/ - features.md - fastapi-people.md @@ -261,6 +262,8 @@ extra: name: ur - link: /vi/ name: vi - Tiếng Việt + - link: /yo/ + name: yo - Yorùbá - link: /zh/ name: zh - 汉语 extra_css: diff --git a/docs/yo/docs/index.md b/docs/yo/docs/index.md new file mode 100644 index 000000000..ca75a6b13 --- /dev/null +++ b/docs/yo/docs/index.md @@ -0,0 +1,470 @@ +

+ FastAPI +

+

+ Ìlànà wẹ́ẹ́bù FastAPI, iṣẹ́ gíga, ó rọrùn láti kọ̀, o yára láti kóòdù, ó sì ṣetán fún iṣelọpọ ní lílo +

+

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

+ +--- + +**Àkọsílẹ̀**: https://fastapi.tiangolo.com + +**Orisun Kóòdù**: https://github.com/tiangolo/fastapi + +--- + +FastAPI jẹ́ ìgbàlódé, tí ó yára (iṣẹ-giga), ìlànà wẹ́ẹ́bù fún kikọ àwọn API pẹ̀lú Python 3.7+ èyí tí ó da lori àwọn ìtọ́kasí àmì irúfẹ́ Python. + +Àwọn ẹya pàtàkì ni: + +* **Ó yára**: Iṣẹ tí ó ga púpọ̀, tí ó wa ni ibamu pẹ̀lú **NodeJS** àti **Go** (ọpẹ si Starlette àti Pydantic). [Ọkan nínú àwọn ìlànà Python ti o yára jùlọ ti o wa](#performance). +* **Ó yára láti kóòdù**: O mu iyara pọ si láti kọ àwọn ẹya tuntun kóòdù nipasẹ "Igba ìdá ọgọ́rùn-ún" (i.e. 200%) si "ọ̀ọ́dúrún ìdá ọgọ́rùn-ún" (i.e. 300%). +* **Àìtọ́ kékeré**: O n din aṣiṣe ku bi ọgbon ìdá ọgọ́rùn-ún (i.e. 40%) ti eda eniyan (oṣiṣẹ kóòdù) fa. * +* **Ọgbọ́n àti ìmọ̀**: Atilẹyin olootu nla. Ìparí nibi gbogbo. Àkókò díẹ̀ nipa wíwá ibi tí ìṣòro kóòdù wà. +* **Irọrun**: A kọ kí ó le rọrun láti lo àti láti kọ ẹkọ nínú rè. Ó máa fún ọ ní àkókò díẹ̀ látı ka àkọsílẹ. +* **Ó kúkurú ní kikọ**: Ó dín àtúnkọ àti àtúntò kóòdù kù. Ìkéde àṣàyàn kọ̀ọ̀kan nínú rẹ̀ ní ọ̀pọ̀lọpọ̀ àwọn ìlò. O ṣe iranlọwọ láti má ṣe ní ọ̀pọ̀lọpọ̀ àṣìṣe. +* **Ó lágbára**: Ó ń ṣe àgbéjáde kóòdù tí ó ṣetán fún ìṣelọ́pọ̀. Pẹ̀lú àkọsílẹ̀ tí ó máa ṣàlàyé ara rẹ̀ fún ẹ ní ìbáṣepọ̀ aládàáṣiṣẹ́ pẹ̀lú rè. +* **Ajohunše/Ìtọ́kasí**: Ó da lori (àti ibamu ni kikun pẹ̀lú) àwọn ìmọ ajohunše/ìtọ́kasí fún àwọn API: OpenAPI (èyí tí a mọ tẹlẹ si Swagger) àti JSON Schema. + +* iṣiro yi da lori àwọn idanwo tí ẹgbẹ ìdàgbàsókè FastAPI ṣe, nígbàtí wọn kọ àwọn ohun elo iṣelọpọ kóòdù pẹ̀lú rẹ. + +## Àwọn onígbọ̀wọ́ + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} + +{% endfor -%} +{%- for sponsor in sponsors.silver -%} + +{% endfor %} +{% endif %} + + + +Àwọn onígbọ̀wọ́ míràn + +## Àwọn ero àti èsì + +"_[...] Mò ń lo **FastAPI** púpọ̀ ní lẹ́nu àìpẹ́ yìí. [...] Mo n gbero láti lo o pẹ̀lú àwọn ẹgbẹ mi fún gbogbo iṣẹ **ML wa ni Microsoft**. Diẹ nínú wọn ni afikun ti ifilelẹ àwọn ẹya ara ti ọja **Windows** wa pẹ̀lú àwọn ti **Office**._" + +
Kabir Khan - Microsoft (ref)
+ +--- + +"_A gba àwọn ohun èlò ìwé afọwọkọ **FastAPI** tí kò yí padà láti ṣẹ̀dá olùpín **REST** tí a lè béèrè lọ́wọ́ rẹ̀ láti gba **àsọtẹ́lẹ̀**. [fún Ludwig]_" + +
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
+ +--- + +"_**Netflix** ni inudidun láti kede itusilẹ orisun kóòdù ti ìlànà iṣọkan **iṣakoso Ìṣòro** wa: **Ìfiránṣẹ́**! [a kọ pẹ̀lú **FastAPI**]_" + +
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
+ +--- + +"_Inú mi dùn púpọ̀ nípa **FastAPI**. Ó mú inú ẹnì dùn púpọ̀!_" + +
Brian Okken - Python Bytes podcast host (ref)
+ +--- + +"_Ní tòótọ́, ohun tí o kọ dára ó sì tún dán. Ní ọ̀pọ̀lọpọ̀ ọ̀nà, ohun tí mo fẹ́ kí **Hug** jẹ́ nìyẹn - ó wúni lórí gan-an láti rí ẹnìkan tí ó kọ́ nǹkan bí èyí._" + +
Timothy Crosley - Hug creator (ref)
+ +--- + +"_Ti o ba n wa láti kọ ọkan **ìlànà igbalode** fún kikọ àwọn REST API, ṣayẹwo **FastAPI** [...] Ó yára, ó rọrùn láti lò, ó sì rọrùn láti kọ́[...]_" + +"_A ti yipada si **FastAPI** fún **APIs** wa [...] Mo lérò pé wà á fẹ́ràn rẹ̀ [...]_" + +
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
+ +--- + +"_Ti ẹnikẹni ba n wa láti kọ iṣelọpọ API pẹ̀lú Python, èmi yóò ṣe'dúró fún **FastAPI**. Ó jẹ́ ohun tí **àgbékalẹ̀ rẹ̀ lẹ́wà**, **ó rọrùn láti lò** àti wipe ó ni **ìwọ̀n gíga**, o tí dí **bọtini paati** nínú alakọkọ API ìdàgbàsókè kikọ fún wa, àti pe o ni ipa lori adaṣiṣẹ àti àwọn iṣẹ gẹ́gẹ́ bíi Onímọ̀-ẹ̀rọ TAC tí órí Íńtánẹ́ẹ̀tì_" + +
Deon Pillsbury - Cisco (ref)
+ +--- + +## **Typer**, FastAPI ti CLIs + + + +Ti o ba n kọ ohun èlò CLI láti ṣeé lọ nínú ohun èlò lori ebute kọmputa dipo API, ṣayẹwo **Typer**. + +**Typer** jẹ́ àbúrò ìyá FastAPI kékeré. Àti pé wọ́n kọ́ láti jẹ́ **FastAPI ti CLIs**. ⌨️ 🚀 + +## Èròjà + +Python 3.7+ + +FastAPI dúró lórí àwọn èjìká tí àwọn òmíràn: + +* Starlette fún àwọn ẹ̀yà ayélujára. +* Pydantic fún àwọn ẹ̀yà àkójọf'áyẹ̀wò. + +## Fifi sórí ẹrọ + +
+ +```console +$ pip install fastapi + +---> 100% +``` + +
+Iwọ yóò tún nílò olupin ASGI, fún iṣelọpọ bii Uvicorn tabi Hypercorn. + +
+ +```console +$ pip install "uvicorn[standard]" + +---> 100% +``` + +
+ +## Àpẹẹrẹ + +### Ṣẹ̀dá rẹ̀ + +* Ṣẹ̀dá fáìlì `main.py (èyí tíí ṣe, akọkọ.py)` pẹ̀lú: + +```Python +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +
+Tàbí lò async def... + +Tí kóòdù rẹ̀ bá ń lò `async` / `await`, lò `async def`: + +```Python hl_lines="9 14" +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +**Akiyesi**: + +Tí o kò bá mọ̀, ṣàyẹ̀wò ibi tí a ti ní _"In a hurry?"_ (i.e. _"Ní kíákíá?"_) nípa `async` and `await` nínú àkọsílẹ̀. + +
+ +### Mu ṣiṣẹ + +Mú olupin ṣiṣẹ pẹ̀lú: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +
+Nipa aṣẹ kóòdù náà uvicorn main:app --reload... + +Àṣẹ `uvicorn main:app` ń tọ́ka sí: + +* `main`: fáìlì náà 'main.py' (Python "module"). +* `app` jẹ object( i.e. nǹkan) tí a ṣẹ̀dá nínú `main.py` pẹ̀lú ilà `app = FastAPI()`. +* `--reload`: èyí yóò jẹ́ ki olupin tún bẹ̀rẹ̀ lẹ́hìn àwọn àyípadà kóòdù. Jọ̀wọ́, ṣe èyí fún ìdàgbàsókè kóòdù nìkan, má ṣe é ṣe lori àgbéjáde kóòdù tabi fún iṣelọpọ kóòdù. + + +
+ +### Ṣayẹwo rẹ + +Ṣii aṣàwákiri kọ̀ǹpútà rẹ ni http://127.0.0.1:8000/items/5?q=somequery. + +Ìwọ yóò sì rí ìdáhùn JSON bíi: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +O tí ṣẹ̀dá API èyí tí yóò: + +* Gbà àwọn ìbéèrè HTTP ni àwọn _ipa ọ̀nà_ `/` àti `/items/{item_id}`. +* Èyí tí àwọn _ipa ọ̀nà_ (i.e. _paths_) méjèèjì gbà àwọn iṣẹ `GET` (a tun mọ si _àwọn ọna_ HTTP). +* Èyí tí _ipa ọ̀nà_ (i.e. _paths_) `/items/{item_id}` ní _àwọn ohun-ini ipa ọ̀nà_ tí ó yẹ kí ó jẹ́ `int` i.e. `ÒǸKÀ`. +* Èyí tí _ipa ọ̀nà_ (i.e. _paths_) `/items/{item_id}` ní àṣàyàn `str` _àwọn ohun-ini_ (i.e. _query parameter_) `q`. + +### Ìbáṣepọ̀ àkọsílẹ̀ API + +Ní báyìí, lọ sí http://127.0.0.1:8000/docs. + +Lẹ́yìn náà, iwọ yóò rí ìdáhùn àkọsílẹ̀ API tí ó jẹ́ ìbáṣepọ̀ alaifọwọyi/aládàáṣiṣẹ́ (tí a pèṣè nípaṣẹ̀ Swagger UI): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### Ìdàkejì àkọsílẹ̀ API + +Ní báyìí, lọ sí http://127.0.0.1:8000/redoc. + +Wà á rí àwọn àkọsílẹ̀ aládàáṣiṣẹ́ mìíràn (tí a pese nipasẹ ReDoc): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## Àpẹẹrẹ ìgbésókè mìíràn + +Ní báyìí ṣe àtúnṣe fáìlì `main.py` láti gba kókó èsì láti inú ìbéèrè `PUT`. + +Ní báyìí, ṣe ìkéde kókó èsì API nínú kóòdù rẹ nipa lílo àwọn ìtọ́kasí àmì irúfẹ́ Python, ọpẹ́ pàtàkìsi sí Pydantic. + +```Python hl_lines="4 9-12 25-27" +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +Olupin yóò tún ṣe àtúnṣe laifọwọyi/aládàáṣiṣẹ́ (nítorí wípé ó se àfikún `-reload` si àṣẹ kóòdù `uvicorn` lókè). + +### Ìbáṣepọ̀ ìgbésókè àkọsílẹ̀ API + +Ní báyìí, lọ sí http://127.0.0.1:8000/docs. + +* Ìbáṣepọ̀ àkọsílẹ̀ API yóò ṣe imudojuiwọn àkọsílẹ̀ API laifọwọyi, pẹ̀lú kókó èsì ìdáhùn API tuntun: + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* Tẹ bọtini "Gbiyanju rẹ" i.e. "Try it out", yóò gbà ọ́ láàyè láti jẹ́ kí ó tẹ́ àlàyé tí ó nílò kí ó le sọ̀rọ̀ tààrà pẹ̀lú API: + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) + +* Lẹhinna tẹ bọtini "Ṣiṣe" i.e. "Execute", olùmúlò (i.e. user interface) yóò sọrọ pẹ̀lú API rẹ, yóò ṣe afiranṣẹ àwọn èròjà, pàápàá jùlọ yóò gba àwọn àbájáde yóò si ṣafihan wọn loju ìbòjú: + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) + +### Ìdàkejì ìgbésókè àkọsílẹ̀ API + +Ní báyìí, lọ sí http://127.0.0.1:8000/redoc. + +* Ìdàkejì àkọsílẹ̀ API yóò ṣ'afihan ìbéèrè èròjà/pàrámítà tuntun àti kókó èsì ti API: + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### Àtúnyẹ̀wò + +Ni akopọ, ìwọ yóò kéde ni **kete** àwọn iru èròjà/pàrámítà, kókó èsì API, abbl (i.e. àti bẹbẹ lọ), bi àwọn èròjà iṣẹ. + +O ṣe ìyẹn pẹ̀lú irúfẹ́ àmì ìtọ́kasí ìgbàlódé Python. + +O ò nílò láti kọ́ síńtáàsì tuntun, ìlànà tàbí ọ̀wọ́ kíláàsì kan pàtó, abbl (i.e. àti bẹbẹ lọ). + +Ìtọ́kasí **Python 3.7+** + +Fún àpẹẹrẹ, fún `int`: + +```Python +item_id: int +``` + +tàbí fún àwòṣe `Item` tí ó nira díẹ̀ síi: + +```Python +item: Item +``` + +... àti pẹ̀lú ìkéde kan ṣoṣo yẹn ìwọ yóò gbà: + +* Atilẹyin olootu, pẹ̀lú: + * Pipari. + * Àyẹ̀wò irúfẹ́ àmì ìtọ́kasí. +* Ìfọwọ́sí àkójọf'áyẹ̀wò (i.e. data): + * Aṣiṣe alaifọwọyi/aládàáṣiṣẹ́ àti aṣiṣe ti ó hàn kedere nígbàtí àwọn àkójọf'áyẹ̀wò (i.e. data) kò wulo tabi tí kò fẹsẹ̀ múlẹ̀. + * Ìfọwọ́sí fún ohun elo JSON tí ó jìn gan-an. +* Ìyípadà tí input àkójọf'áyẹ̀wò: tí ó wà láti nẹtiwọọki si àkójọf'áyẹ̀wò àti irúfẹ́ àmì ìtọ́kasí Python. Ó ń ka láti: + * JSON. + * èròjà ọ̀nà tí ò gbé gbà. + * èròjà ìbéèrè. + * Àwọn Kúkì + * Àwọn Àkọlé + * Àwọn Fọọmu + * Àwọn Fáìlì +* Ìyípadà èsì àkójọf'áyẹ̀wò: yíyípadà láti àkójọf'áyẹ̀wò àti irúfẹ́ àmì ìtọ́kasí Python si nẹtiwọọki (gẹ́gẹ́ bí JSON): + * Yí irúfẹ́ àmì ìtọ́kasí padà (`str`, `int`, `float`, `bool`, `list`, abbl i.e. àti bèbè ló). + * Àwọn ohun èlò `datetime`. + * Àwọn ohun èlò `UUID`. + * Àwọn awoṣẹ́ ibi ìpamọ́ àkójọf'áyẹ̀wò. + * ...àti ọ̀pọ̀lọpọ̀ díẹ̀ síi. +* Ìbáṣepọ̀ àkọsílẹ̀ API aládàáṣiṣẹ́, pẹ̀lú ìdàkejì àgbékalẹ̀-àwọn-olùmúlò (i.e user interfaces) méjì: + * Àgbékalẹ̀-olùmúlò Swagger. + * ReDoc. + +--- + +Nisinsin yi, tí ó padà sí àpẹẹrẹ ti tẹ́lẹ̀, **FastAPI** yóò: + +* Fọwọ́ sí i pé `item_id` wà nínú ọ̀nà ìbéèrè HTTP fún `GET` àti `PUT`. +* Fọwọ́ sí i pé `item_id` jẹ́ irúfẹ́ àmì ìtọ́kasí `int` fún ìbéèrè HTTP `GET` àti `PUT`. + * Tí kìí bá ṣe bẹ, oníbàárà yóò ríi àṣìṣe tí ó wúlò, kedere. +* Ṣàyẹ̀wò bóyá ìbéèrè àṣàyàn pàrámítà kan wà tí orúkọ rẹ̀ ń jẹ́ `q` (gẹ́gẹ́ bíi `http://127.0.0.1:8000/items/foo?q=somequery`) fún ìbéèrè HTTP `GET`. + * Bí wọ́n ṣe kéde pàrámítà `q` pẹ̀lú `= None`, ó jẹ́ àṣàyàn (i.e optional). + * Láìsí `None` yóò nílò (gẹ́gẹ́ bí kókó èsì ìbéèrè HTTP ṣe wà pẹ̀lú `PUT`). +* Fún àwọn ìbéèrè HTTP `PUT` sí `/items/{item_id}`, kà kókó èsì ìbéèrè HTTP gẹ́gẹ́ bí JSON: + * Ṣàyẹ̀wò pé ó ní àbùdá tí ó nílò èyí tíí ṣe `name` i.e. `orúkọ` tí ó yẹ kí ó jẹ́ `str`. + * Ṣàyẹ̀wò pé ó ní àbùdá tí ó nílò èyí tíí ṣe `price` i.e. `iye` tí ó gbọ́dọ̀ jẹ́ `float`. + * Ṣàyẹ̀wò pé ó ní àbùdá àṣàyàn `is_offer`, tí ó yẹ kí ó jẹ́ `bool`, tí ó bá wà níbẹ̀. + * Gbogbo èyí yóò tún ṣiṣẹ́ fún àwọn ohun èlò JSON tí ó jìn gidi gan-an. +* Yìí padà láti àti sí JSON lai fi ọwọ́ yi. +* Ṣe àkọsílẹ̀ ohun gbogbo pẹ̀lú OpenAPI, èyí tí yóò wà ní lílo nípaṣẹ̀: + * Àwọn ètò àkọsílẹ̀ ìbáṣepọ̀. + * Aládàáṣiṣẹ́ oníbárà èlètò tíí ṣẹ̀dá kóòdù, fún ọ̀pọ̀lọpọ̀ àwọn èdè. +* Pese àkọsílẹ̀ òní ìbáṣepọ̀ ti àwọn àgbékalẹ̀ ayélujára méjì tààrà. + +--- + +A ń ṣẹ̀ṣẹ̀ ń mú ẹyẹ bọ́ làpò ní, ṣùgbọ́n ó ti ni òye bí gbogbo rẹ̀ ṣe ń ṣiṣẹ́. + +Gbiyanju láti yí ìlà padà pẹ̀lú: + +```Python + return {"item_name": item.name, "item_id": item_id} +``` + +...láti: + +```Python + ... "item_name": item.name ... +``` + +...ṣí: + +```Python + ... "item_price": item.price ... +``` + +.. kí o sì wo bí olóòtú rẹ yóò ṣe parí àwọn àbùdá náà fúnra rẹ̀, yóò sì mọ irúfẹ́ wọn: + +![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) + +Fún àpẹẹrẹ pípé síi pẹ̀lú àwọn àbùdá mìíràn, wo Ìdánilẹ́kọ̀ọ́ - Ìtọ́sọ́nà Olùmúlò. + +**Itaniji gẹ́gẹ́ bí isọ'ye**: ìdánilẹ́kọ̀ọ́ - itọsọna olùmúlò pẹ̀lú: + +* Ìkéde àṣàyàn **pàrámítà** láti àwọn oriṣiriṣi ibòmíràn gẹ́gẹ́ bíi: àwọn **àkọlé èsì API**, **kúkì**, **ààyè fọọmu**, àti **fáìlì**. +* Bíi ó ṣe lé ṣètò **àwọn ìdíwọ́ ìfọwọ́sí** bí `maximum_length` tàbí `regex`. +* Ó lágbára púpọ̀ ó sì rọrùn láti lo ètò **Àfikún Ìgbẹ́kẹ̀lé Kóòdù**. +* Ààbò àti ìfọwọ́sowọ́pọ̀, pẹ̀lú àtìlẹ́yìn fún **OAuth2** pẹ̀lú **àmì JWT** àti **HTTP Ipilẹ ìfọwọ́sowọ́pọ̀**. +* Àwọn ìlànà ìlọsíwájú (ṣùgbọ́n tí ó rọrùn bákan náà) fún ìkéde **àwọn àwòṣe JSON tó jinlẹ̀** (ọpẹ́ pàtàkìsi sí Pydantic). +* Iṣọpọ **GraphQL** pẹ̀lú Strawberry àti àwọn ohun èlò ìwé kóòdù afọwọkọ mìíràn tí kò yí padà. +* Ọpọlọpọ àwọn àfikún àwọn ẹ̀yà (ọpẹ́ pàtàkìsi sí Starlette) bí: + * **WebSockets** + * àwọn ìdánwò tí ó rọrùn púpọ̀ lórí HTTPX àti `pytest` + * **CORS** + * **Cookie Sessions** + * ...àti síwájú síi. + +## Ìṣesí + +Àwọn àlá TechEmpower fi hàn pé **FastAPI** ń ṣiṣẹ́ lábẹ́ Uvicorn gẹ́gẹ́ bí ọ̀kan lára àwọn ìlànà Python tí ó yára jùlọ tí ó wà, ní ìsàlẹ̀ Starlette àti Uvicorn fúnra wọn (tí FastAPI ń lò fúnra rẹ̀). (*) + +Láti ní òye síi nípa rẹ̀, wo abala àwọn Àlá. + +## Àṣàyàn Àwọn Àfikún Ìgbẹ́kẹ̀lé Kóòdù + +Èyí tí Pydantic ń lò: + +* email_validator - fún ifọwọsi ímeèlì. +* pydantic-settings - fún ètò ìsàkóso. +* pydantic-extra-types - fún àfikún oríṣi láti lọ pẹ̀lú Pydantic. + +Èyí tí Starlette ń lò: + +* httpx - Nílò tí ó bá fẹ́ láti lọ `TestClient`. +* jinja2 - Nílò tí ó bá fẹ́ láti lọ iṣeto awoṣe aiyipada. +* python-multipart - Nílò tí ó bá fẹ́ láti ṣe àtìlẹ́yìn fún "àyẹ̀wò" fọọmu, pẹ̀lú `request.form()`. +* itsdangerous - Nílò fún àtìlẹ́yìn `SessionMiddleware`. +* pyyaml - Nílò fún àtìlẹ́yìn Starlette's `SchemaGenerator` (ó ṣe ṣe kí ó má nílò rẹ̀ fún FastAPI). +* ujson - Nílò tí ó bá fẹ́ láti lọ `UJSONResponse`. + +Èyí tí FastAPI / Starlette ń lò: + +* uvicorn - Fún olupin tí yóò sẹ́ àmúyẹ àti tí yóò ṣe ìpèsè fún iṣẹ́ rẹ tàbí ohun èlò rẹ. +* orjson - Nílò tí ó bá fẹ́ láti lọ `ORJSONResponse`. + +Ó lè fi gbogbo àwọn wọ̀nyí sórí ẹrọ pẹ̀lú `pip install "fastapi[all]"`. + +## Iwe-aṣẹ + +Iṣẹ́ yìí ni iwe-aṣẹ lábẹ́ àwọn òfin tí iwe-aṣẹ MIT. diff --git a/docs/yo/mkdocs.yml b/docs/yo/mkdocs.yml new file mode 100644 index 000000000..de18856f4 --- /dev/null +++ b/docs/yo/mkdocs.yml @@ -0,0 +1 @@ +INHERIT: ../en/mkdocs.yml From a6d893fe981f270be660c3e8bceab888d38786c5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 16:16:38 +0000 Subject: [PATCH 054/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 622f6ddc5..21b3be50e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add Yoruba translation for `docs/yo/docs/index.md`. PR [#10033](https://github.com/tiangolo/fastapi/pull/10033) by [@AfolabiOlaoluwa](https://github.com/AfolabiOlaoluwa). * ✏️ Fix Pydantic examples in tutorial for Python types. PR [#9961](https://github.com/tiangolo/fastapi/pull/9961) by [@rahulsalgare](https://github.com/rahulsalgare). * ✏️ Fix link to Pydantic docs in `docs/en/docs/tutorial/extra-data-types.md`. PR [#10155](https://github.com/tiangolo/fastapi/pull/10155) by [@hasnatsajid](https://github.com/hasnatsajid). * 🌐 Add Ukrainian translation for `docs/uk/docs/python-types.md`. PR [#10080](https://github.com/tiangolo/fastapi/pull/10080) by [@rostik1410](https://github.com/rostik1410). From caf0b688cd7e71da0a207ab5d611a57ce4ac5f13 Mon Sep 17 00:00:00 2001 From: Yusuke Tamura <62091034+tamtam-fitness@users.noreply.github.com> Date: Sun, 3 Sep 2023 01:55:26 +0900 Subject: [PATCH 055/188] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20indent=20for?= =?UTF-8?q?mat=20in=20`docs/en/docs/deployment/server-workers.md`=20(#1006?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/en/docs/deployment/server-workers.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/docs/deployment/server-workers.md b/docs/en/docs/deployment/server-workers.md index 4ccd9d9f6..2df9f3d43 100644 --- a/docs/en/docs/deployment/server-workers.md +++ b/docs/en/docs/deployment/server-workers.md @@ -90,7 +90,9 @@ Let's see what each of those options mean: ``` * So, the colon in `main:app` would be equivalent to the Python `import` part in `from main import app`. + * `--workers`: The number of worker processes to use, each will run a Uvicorn worker, in this case, 4 workers. + * `--worker-class`: The Gunicorn-compatible worker class to use in the worker processes. * Here we pass the class that Gunicorn can import and use with: From 7802454131866a1af7b509702fb6de369f33c71d Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 16:56:04 +0000 Subject: [PATCH 056/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 21b3be50e..624fc736b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏️ Fix indent format in `docs/en/docs/deployment/server-workers.md`. PR [#10066](https://github.com/tiangolo/fastapi/pull/10066) by [@tamtam-fitness](https://github.com/tamtam-fitness). * 🌐 Add Yoruba translation for `docs/yo/docs/index.md`. PR [#10033](https://github.com/tiangolo/fastapi/pull/10033) by [@AfolabiOlaoluwa](https://github.com/AfolabiOlaoluwa). * ✏️ Fix Pydantic examples in tutorial for Python types. PR [#9961](https://github.com/tiangolo/fastapi/pull/9961) by [@rahulsalgare](https://github.com/rahulsalgare). * ✏️ Fix link to Pydantic docs in `docs/en/docs/tutorial/extra-data-types.md`. PR [#10155](https://github.com/tiangolo/fastapi/pull/10155) by [@hasnatsajid](https://github.com/hasnatsajid). From 23511f1fdf5de1b47575c6d1e305c17a3851fbae Mon Sep 17 00:00:00 2001 From: Alex Rocha <62669972+LecoOliveira@users.noreply.github.com> Date: Sat, 2 Sep 2023 14:01:06 -0300 Subject: [PATCH 057/188] =?UTF-8?q?=F0=9F=8C=90=20Remove=20duplicate=20lin?= =?UTF-8?q?e=20in=20translation=20for=20`docs/pt/docs/tutorial/path-params?= =?UTF-8?q?.md`=20(#10126)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/pt/docs/tutorial/path-params.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/pt/docs/tutorial/path-params.md b/docs/pt/docs/tutorial/path-params.md index 5de3756ed..cd8c18858 100644 --- a/docs/pt/docs/tutorial/path-params.md +++ b/docs/pt/docs/tutorial/path-params.md @@ -236,7 +236,6 @@ Então, você poderia usar ele com: Com o **FastAPI**, usando as declarações de tipo do Python, você obtém: * Suporte no editor: verificação de erros, e opção de autocompletar, etc. -* Parsing de dados * "Parsing" de dados * Validação de dados * Anotação da API e documentação automática From 7f1dedac2c8f57aa1e976aaa492f5cbd77d25ab1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 17:01:44 +0000 Subject: [PATCH 058/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 624fc736b..54a00d9ed 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Remove duplicate line in translation for `docs/pt/docs/tutorial/path-params.md`. PR [#10126](https://github.com/tiangolo/fastapi/pull/10126) by [@LecoOliveira](https://github.com/LecoOliveira). * ✏️ Fix indent format in `docs/en/docs/deployment/server-workers.md`. PR [#10066](https://github.com/tiangolo/fastapi/pull/10066) by [@tamtam-fitness](https://github.com/tamtam-fitness). * 🌐 Add Yoruba translation for `docs/yo/docs/index.md`. PR [#10033](https://github.com/tiangolo/fastapi/pull/10033) by [@AfolabiOlaoluwa](https://github.com/AfolabiOlaoluwa). * ✏️ Fix Pydantic examples in tutorial for Python types. PR [#9961](https://github.com/tiangolo/fastapi/pull/9961) by [@rahulsalgare](https://github.com/rahulsalgare). From c502197d7cffc6fe3f310fc2a72dd3148bcaa016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Dorr=C3=ADo=20V=C3=A1zquez?= <64154120+pablodorrio@users.noreply.github.com> Date: Sat, 2 Sep 2023 19:02:26 +0200 Subject: [PATCH 059/188] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20validation?= =?UTF-8?q?=20parameter=20name=20in=20docs,=20from=20`regex`=20to=20`patte?= =?UTF-8?q?rn`=20(#10085)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/query-params-str-validations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index f87adddcb..5d1c08add 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -932,7 +932,7 @@ Validations specific for strings: * `min_length` * `max_length` -* `regex` +* `pattern` In these examples you saw how to declare validations for `str` values. From e1a1a367a74a64c330e11fb63a870a2c77a29a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 2 Sep 2023 19:03:43 +0200 Subject: [PATCH 060/188] =?UTF-8?q?=F0=9F=93=8C=20Pin=20AnyIO=20to=20<=204?= =?UTF-8?q?.0.0=20to=20handle=20an=20incompatibility=20while=20upgrading?= =?UTF-8?q?=20to=20Starlette=200.31.1=20(#10194)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9b7cca9c9..2870b31a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,8 @@ dependencies = [ "starlette>=0.27.0,<0.28.0", "pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0", "typing-extensions>=4.5.0", + # TODO: remove this pin after upgrading Starlette 0.31.1 + "anyio>=3.7.1,<4.0.0", ] dynamic = ["version"] From 8562cae44b18d0f1638bb6d338a85754468fc559 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 17:05:59 +0000 Subject: [PATCH 061/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 54a00d9ed..24dd82085 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📌 Pin AnyIO to < 4.0.0 to handle an incompatibility while upgrading to Starlette 0.31.1. PR [#10194](https://github.com/tiangolo/fastapi/pull/10194) by [@tiangolo](https://github.com/tiangolo). * 🌐 Remove duplicate line in translation for `docs/pt/docs/tutorial/path-params.md`. PR [#10126](https://github.com/tiangolo/fastapi/pull/10126) by [@LecoOliveira](https://github.com/LecoOliveira). * ✏️ Fix indent format in `docs/en/docs/deployment/server-workers.md`. PR [#10066](https://github.com/tiangolo/fastapi/pull/10066) by [@tamtam-fitness](https://github.com/tamtam-fitness). * 🌐 Add Yoruba translation for `docs/yo/docs/index.md`. PR [#10033](https://github.com/tiangolo/fastapi/pull/10033) by [@AfolabiOlaoluwa](https://github.com/AfolabiOlaoluwa). From 118010ad5ebb98f602deee7e69c3bff94aadf16a Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Sep 2023 17:06:22 +0000 Subject: [PATCH 062/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 24dd82085..45d845f25 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏️ Fix validation parameter name in docs, from `regex` to `pattern`. PR [#10085](https://github.com/tiangolo/fastapi/pull/10085) by [@pablodorrio](https://github.com/pablodorrio). * 📌 Pin AnyIO to < 4.0.0 to handle an incompatibility while upgrading to Starlette 0.31.1. PR [#10194](https://github.com/tiangolo/fastapi/pull/10194) by [@tiangolo](https://github.com/tiangolo). * 🌐 Remove duplicate line in translation for `docs/pt/docs/tutorial/path-params.md`. PR [#10126](https://github.com/tiangolo/fastapi/pull/10126) by [@LecoOliveira](https://github.com/LecoOliveira). * ✏️ Fix indent format in `docs/en/docs/deployment/server-workers.md`. PR [#10066](https://github.com/tiangolo/fastapi/pull/10066) by [@tamtam-fitness](https://github.com/tamtam-fitness). From ce8ee1410ae4ba37744ed818be05e2cc187601e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 2 Sep 2023 19:09:47 +0200 Subject: [PATCH 063/188] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 45d845f25..4f333119c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,25 +2,39 @@ ## Latest Changes -* ✏️ Fix validation parameter name in docs, from `regex` to `pattern`. PR [#10085](https://github.com/tiangolo/fastapi/pull/10085) by [@pablodorrio](https://github.com/pablodorrio). +### Fixes + * 📌 Pin AnyIO to < 4.0.0 to handle an incompatibility while upgrading to Starlette 0.31.1. PR [#10194](https://github.com/tiangolo/fastapi/pull/10194) by [@tiangolo](https://github.com/tiangolo). -* 🌐 Remove duplicate line in translation for `docs/pt/docs/tutorial/path-params.md`. PR [#10126](https://github.com/tiangolo/fastapi/pull/10126) by [@LecoOliveira](https://github.com/LecoOliveira). + +### Docs + +* ✏️ Fix validation parameter name in docs, from `regex` to `pattern`. PR [#10085](https://github.com/tiangolo/fastapi/pull/10085) by [@pablodorrio](https://github.com/pablodorrio). * ✏️ Fix indent format in `docs/en/docs/deployment/server-workers.md`. PR [#10066](https://github.com/tiangolo/fastapi/pull/10066) by [@tamtam-fitness](https://github.com/tamtam-fitness). -* 🌐 Add Yoruba translation for `docs/yo/docs/index.md`. PR [#10033](https://github.com/tiangolo/fastapi/pull/10033) by [@AfolabiOlaoluwa](https://github.com/AfolabiOlaoluwa). * ✏️ Fix Pydantic examples in tutorial for Python types. PR [#9961](https://github.com/tiangolo/fastapi/pull/9961) by [@rahulsalgare](https://github.com/rahulsalgare). * ✏️ Fix link to Pydantic docs in `docs/en/docs/tutorial/extra-data-types.md`. PR [#10155](https://github.com/tiangolo/fastapi/pull/10155) by [@hasnatsajid](https://github.com/hasnatsajid). +* ✏️ Fix typo in `docs/en/docs/tutorial/handling-errors.md`. PR [#10170](https://github.com/tiangolo/fastapi/pull/10170) by [@poupapaa](https://github.com/poupapaa). +* ✏️ Fix typo in `docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#10172](https://github.com/tiangolo/fastapi/pull/10172) by [@ragul-kachiappan](https://github.com/ragul-kachiappan). + +### Translations + +* 🌐 Remove duplicate line in translation for `docs/pt/docs/tutorial/path-params.md`. PR [#10126](https://github.com/tiangolo/fastapi/pull/10126) by [@LecoOliveira](https://github.com/LecoOliveira). +* 🌐 Add Yoruba translation for `docs/yo/docs/index.md`. PR [#10033](https://github.com/tiangolo/fastapi/pull/10033) by [@AfolabiOlaoluwa](https://github.com/AfolabiOlaoluwa). * 🌐 Add Ukrainian translation for `docs/uk/docs/python-types.md`. PR [#10080](https://github.com/tiangolo/fastapi/pull/10080) by [@rostik1410](https://github.com/rostik1410). * 🌐 Add Vietnamese translations for `docs/vi/docs/tutorial/first-steps.md` and `docs/vi/docs/tutorial/index.md`. PR [#10088](https://github.com/tiangolo/fastapi/pull/10088) by [@magiskboy](https://github.com/magiskboy). -* ✏️ Fix typo in `docs/en/docs/tutorial/handling-errors.md`. PR [#10170](https://github.com/tiangolo/fastapi/pull/10170) by [@poupapaa](https://github.com/poupapaa). -* ✏️ Fix typos in comment in `fastapi/applications.py`. PR [#10045](https://github.com/tiangolo/fastapi/pull/10045) by [@AhsanSheraz](https://github.com/AhsanSheraz). -* ✏️ Fix typo in `docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#10172](https://github.com/tiangolo/fastapi/pull/10172) by [@ragul-kachiappan](https://github.com/ragul-kachiappan). * 🌐 Add Ukrainian translation for `docs/uk/docs/alternatives.md`. PR [#10060](https://github.com/tiangolo/fastapi/pull/10060) by [@whysage](https://github.com/whysage). * 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/index.md`. PR [#10079](https://github.com/tiangolo/fastapi/pull/10079) by [@rostik1410](https://github.com/rostik1410). * ✏️ Fix typos in `docs/en/docs/how-to/separate-openapi-schemas.md` and `docs/en/docs/tutorial/schema-extra-example.md`. PR [#10189](https://github.com/tiangolo/fastapi/pull/10189) by [@xzmeng](https://github.com/xzmeng). * 🌐 Add Chinese translation for `docs/zh/docs/advanced/generate-clients.md`. PR [#9883](https://github.com/tiangolo/fastapi/pull/9883) by [@funny-cat-happy](https://github.com/funny-cat-happy). -* 👥 Update FastAPI People. PR [#10186](https://github.com/tiangolo/fastapi/pull/10186) by [@tiangolo](https://github.com/tiangolo). + +### Refactors + +* ✏️ Fix typos in comment in `fastapi/applications.py`. PR [#10045](https://github.com/tiangolo/fastapi/pull/10045) by [@AhsanSheraz](https://github.com/AhsanSheraz). * ✅ Add missing test for OpenAPI examples, it was missing in coverage. PR [#10188](https://github.com/tiangolo/fastapi/pull/10188) by [@tiangolo](https://github.com/tiangolo). +### Internal + +* 👥 Update FastAPI People. PR [#10186](https://github.com/tiangolo/fastapi/pull/10186) by [@tiangolo](https://github.com/tiangolo). + ## 0.103.0 ### Features From bfde8f3ef20dc338bbce8a0ff0f4b441c54d5b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 2 Sep 2023 19:10:19 +0200 Subject: [PATCH 064/188] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.10?= =?UTF-8?q?3.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 3 +++ fastapi/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4f333119c..f03d54a9a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,9 @@ ## Latest Changes + +## 0.103.1 + ### Fixes * 📌 Pin AnyIO to < 4.0.0 to handle an incompatibility while upgrading to Starlette 0.31.1. PR [#10194](https://github.com/tiangolo/fastapi/pull/10194) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index ff8b98d3b..329477e41 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.103.0" +__version__ = "0.103.1" from starlette import status as status From 766dfb5b38a4751c0706b1ef4a73dc276e81572b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 10 Sep 2023 12:18:26 +0200 Subject: [PATCH 065/188] =?UTF-8?q?=F0=9F=94=A7=20Update=20sponsors,=20add?= =?UTF-8?q?=20Bump.sh=20(#10227)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/en/data/sponsors.yml | 3 +++ docs/en/docs/img/sponsors/bump-sh-banner.png | Bin 0 -> 10139 bytes docs/en/docs/img/sponsors/bump-sh.png | Bin 0 -> 18609 bytes docs/en/overrides/main.html | 6 ++++++ 5 files changed, 10 insertions(+) create mode 100755 docs/en/docs/img/sponsors/bump-sh-banner.png create mode 100755 docs/en/docs/img/sponsors/bump-sh.png diff --git a/README.md b/README.md index 266213426..7aa9d4be6 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ The key features are: + diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index 0d9597f07..504c373a2 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -11,6 +11,9 @@ gold: - url: https://www.porter.run title: Deploy FastAPI on AWS with a few clicks img: https://fastapi.tiangolo.com/img/sponsors/porter.png + - url: https://bump.sh/fastapi?utm_source=fastapi&utm_medium=referral&utm_campaign=sponsor + title: Automate FastAPI documentation generation with Bump.sh + img: https://fastapi.tiangolo.com/img/sponsors/bump-sh.png silver: - url: https://www.deta.sh/?ref=fastapi title: The launchpad for all your (team's) ideas diff --git a/docs/en/docs/img/sponsors/bump-sh-banner.png b/docs/en/docs/img/sponsors/bump-sh-banner.png new file mode 100755 index 0000000000000000000000000000000000000000..e75c0facd30acbee9121ae41cce236a99bea3646 GIT binary patch literal 10139 zcmV;MCuG=(P)u{4~2+= z2|+Nt)P?XAfy6;v3D1G>mV{XnB;l2iFbX?)Kyq(xPgS4Or>nZF&-dNT^!l?r;ZyP$jHS!*w`EaaGd{9bRSh45^L-19aY#)Qe|M9CHfrB^FJr zDeKgwoP)NGs}l2*dJv9W->lkIw)^sdic zte0}O#x9scV#>vYwi2{4gRYe;n;Ak_!JVNXrY zcHgJyosjDUvoya~fmMA(d5uCF98v)l35U)76-WhI%$uF|`2gf?jf9~oLS4g|Wlhs* zH0LBcj3g$^i-KB|=px_A(X;`wp$L7@qDoQ&EQCO8sb-}%uS@--W9o`1XNR5WF(>Ix zlu<=ASk8Fe)W=S|MOR|*q|~mJcV*`?0}C>;JY$R+DNbnxTE56fOei3+-@44QBQwwG zY|myh-2znU6I1kBCeD(Q7)P znCbFPa|s&_oX%zmvCzPsrm!##sEHCgfP)ayT;>B`l${N>HtA5RgBmn=*=SOgpjA}Y zPTU+p%#f1M>IBiT4um>`LNA*yq|+I6WZ6Af$-Xg2(O`fDScqN=XxcVF-u-Rk)uTl= zlV_}HrA#}7u5{6|FmG0JWsfYjl(i>Uw>a$$R|7dmTtGlIz)=?tZLXUhtN|*bM>VnZ zCVC4cD-av9^#~Z~?+4enNz+(^Uzh_NG|-oX+1)igXNlQmgXgBxi!PU8b%b?8X5!Sr zDpXjQDHh8DAj;8pp(m;=mqL!}AWV*_e(jKHx`3sV0%XY^RdckGZK>OF#X)$2>20bs zhY72WYh|L{*C2ob=C#p<+!Wj}olH_4z~qzVWq=o<8Z(8jQT1OX(|Mp`>VckKs@va0ajpTas8(-u=!#1dSrKv1u;DPU@dFC1tof=_*Xj=H zY?m&pU3N=Bj|QCphnypd>u~A{PMJhmChrcwC40NhPnQRg4;J{P-1VM?!XM}|r2|tN zzKXb1R0WXt;TEK{i53EYRi3jD3kJY;LXjb02|KHa1tQz=Q*hAZoB|CR3_AgxNn>1T ziJ0?BKnV@SLhtr;wPcRt;4%=6 z092!cQD@Su^YmIr7kOhlbxJ;mn&pkUm=u*{BvYFcyfXb0-mZ-&BtYkk=*jS0#oJD5 zm1fCLBDzASCtZ@XIn)WDmFQk#X(v>N^gP8*UEU5Dap94)6Yc69*w!YF(4uZao6>~; zXcER;IeOX#+X1OX7f@{4>!6*NW&|+nbR+8JE0$DJoHXR zURNi~^BVb1P+=n(Q#J}Da%jMUCh8)M4BMDs0aTn_-;ObD({A>*LE~Qvwq-DBa~0^& zPF^jDNLM;m2Tw#ujTQ5y9T?ZBIhgdxp0Sqd%sMLCYwq;Qllko186d=0X zRJF=lpw~*+eb`ucA&P2xq0*N(;Rn-d_L!0aU9;|d{>Tyl2I?XG$Hpo{N z99k_lBArWlW+-P2EkM!3&|aQ}I!?ex^h7ZODC#gR%0iMc{%n^2)dg6EyM=20;Mmbj2_D8 z4f!U@s%aEp^=`E_Z0_3#QB>@3G)>LOlh#r*FnzH$_nw)vd}@c1f2gL2gcFvtwZ4tXdHuFq46LQb<5Y^+j{Yo!-`0 z9!s>?!T51d*;%1z3KqyvjbqM2wnZ3im7_o)VUMXeJr-si$WxTEBU zpsurJG1@%R>BR1nVivSnNh(`CanY%guMi32Nax_?oY=Lh5U2(*eK)X%w#FYrB*v+U zu!aonh0Xo_pc{bZ-fwxek)BD_t%L>6?T+nQA{Na{=H+}{ZKQE6E$N-2<9(3$pl_TD zGAC-;ERRm%RB$5!6S{008o8}iL{ZF5y{ zp!aueH1L2mWa!WetP&07O)x7y(MYdL+@K6SSP!YoK< z*O)ExLtEu8XIzE2T15d`!cOJa#X^u!hhmLL4_Ljuy|D3v4~2rUN9D$Lgp+(Q(>wf8 zjjXqdC2cMmDe8uRh=`ge%{tZ9OFvbTAiQE1x+5WxPM=w`kY;d%c=yIB%80|k$X(WD z&g9;miRZc9*DL2kHlu~UiT#{!6WF?^xrT=rwUoCPXnHc5sGHV1lI6~?2P2E35PVS(l`S7&6tL_*Hai`Hu$=bE+{U;rc{%y8{-VJ|-HkeA2ylI=q zC$4xYGiB#xoD9&C%mUa+#1Ax0cNh9QFE!p} z$5!zKTLoxP$@yiqQRYsq{{VIAb^{k{G+HrgM{`sf)^Y^Bl3w_)D z7k}P2?62U>v0v&MtQwhCasW9zmq{uzW7r#5&*eI4Scpk_f0%<2TI$#NqehN^Pk-isMlEc3f|B3p0cigrCiOLKk1WT~4Jh;zd?Y`q z7bfLqA4cghnymiz-ghq;GiJ2tnK)GPllPk#8Ed8h!`4vCD3SIIY#u1{=_X?65^Fv& zZZ9uj^GCL?MAeVH+0*sc(8htjyM?Zz2m3wqaS>`tdG!X)TnJ>M*_@|bhMi{ zNRX~aCfr$+?!OZ)g-4AVVOP)sn&bg%`ZU;Mk6o11V~#lzesj;<`1#FTm~hA^6AoDT zRAoEvG^WvqwdK!_JC25Xh~KY%b(PYC<#B%aTyxE(up8;d>6)Ki>hl6$@ZDAUt84HJT_)s$o6u+X?h)+L(Zh0TM6_Jp#|h;f=B$2?UUjsMZZMKpFDVJ}p^FNMpu~q0ja{f9%J0 zhD)!w5WaTGae`j9SV$2J70*-vVltW~!lYUwhk(4=zj8*MG#&xa+r3s%+0mbQZiz1+ z_SVaOb`H-~4BPY`4DEXlx=9>~{oV0L`H1b1&wPT6Xd6{pH44;m_g!~}`|g}g^asS% zRi|W)rf6IG<`qs11oz#2EgXCFq}IC7p(tE_gn(uz;<3jL&@LK)7|@9Ez$A z^w+<*>2{bi`zCnp)fE~%5*a2?$$U&eDy@qKI3=`cms+v`pd)yNvP*tE11=$u;gMl(afASe^g< ztNr1qY#=Lq9beqQSzn71e?*Hw>FzNFXVA|9p;JO>`^ko;E zdkQ)9dbr}Mn_==16DSfp1g^XO4*1qLzv|16C0Yi$Ybo#8sS|zOb?er{E6Y|;of~28 zx(zVn+*4ugzpRHncHi00s0$xivG zH1RNB_lhfTfN4ihhH1x4@^lLyehRLeJI~V`LO^lS^ke+_%9U$c+dOaHeI7u3qOLvh zq-iAkv%bF>#4h~)Rle&_Bp{kN@v~mm&*$C(6ArGxX#DtnVCKwo;5*;B#5*a2)U#A? z_uY4)sA@7i{P2@X-u?Ig0iJvQCF-j&FmC+bRfJX;`rRnXIOFf9dzz(7mpA!L&a%!u z?;HNOiuNvVyX`)Bg!+Kf(bRtjPdLz*J^$QdSg_y^aKhJ)q2ojTbNcBgz^=QFB``S; zrcRjz2OoS8Jo4xwm^yV5Jo?B}@XkB$!W24x=bbfh=_RxL^$rB8-#h0tf8_ihTsqt5 zfBhTB6R(}&u}7bVDN`oGV~;I@*|Tnh)4qP3*JVGlsf#Z;3$C7Z1ALKWF)(o-K2CIZ z-?0F`a@*@SkXPpY` z*RO{WBSyf-_uC5|d|)Bma`WvfDB#2(J-{9QlPw@X5b9fX#dHi>ERXq*AX{eeVtu}O z<|$2`d+)v??8esPlLedT4dVfE^Fqcw=<9OG6($PuZ|vbSlf zV~>7ygtMtiwdGM2p-`nSSyG@b;?8F^8;u7e+sD8SMP)Q{X>6e>Du<_!r2N zQw&}GfAZ%K_Bq#Gf2ZHbXV9slEC=KF+mrTzcJ}2AYUB6c(=P{?E_>bQt)xz7aGFd& z#vrzeIyPKi@x!w`&3H17V~?KbdGk`?q_0i&r{kgioZ+73+BSdTpNq>qpo1X*WJYuByw0Oi|P1JFDI z59TqBz>Gnu5?&q7`1`N<ETdF&}ilj-G&-|VTt>>uiqhFRaJ&GzqMtaba`j`b@`(0cM=vLZAqFkFl^nogaMg0e+mPQUp7N zCXg4X?*0Te3}UR=tXUU&xw8n2`1n)0#+nz??N9c=^^YRZ;ytKWX^c4WB=_Qri+$hk zM&o+d9rHa9eU*S}2eP9-y;}7n=P^hyzkT=H)8`%j-w%QRecEw#*$RJs!9{2KFo;3w zZwbu8kbdc_Q{ZD%7scO6ryd9U?7Js{$P(Cv%KjUH$S%8%^?IK{;II$vAze>V&YIP0 z2t;Q1c8%TjqXZIr!ag703zkzG4*T2$IPCBWe3*v&g6oVWJ`7YWpFwXj>B4C_wP6hP zGXvLsch9G#-$$wJ2$J!x2P;mCU#x7I<%A_7^X6sZlaDVV@cL``-i2rSJuU`Yru!dX znCRQVI-hvz@r^CCtjY4^%+m_<_}Ig*plJx4$sw-v>~j67(hE=BMV~v>=S6?Kqxrm8 ze=a)zG!HBmpQx4d(a|J|oEU({k@2k}P+_ndUmK5=)~?+E_bpiH&mVr|Y5!r+SxW{*n1-XY$yA4rfk)8gyl>BdMNd2pFA=beA)qDFD;0zqs9l;Z-x|)cKMBt+aH!^77e@{#%kW@**8C3FTfHM_Mz^Jq>QX z{eHh>eX$7o1~ceS=^JljnxUveqTz6|Q-L`iI{>ACsX8P+&575SSNN)j#zjpG;+i?bw6pHQ+-1@ zw|hhlV)1TVl);hJAAa|6lg~@DgFbygRd@Xcr3)`TIV~YDTK?MW{+gGh!5=GngzYI} zy7)&k;3)#G+kSaF$v<8T4KagYzP=u}(Y1~NkR+&;BPhA|>r{@~3wCCetgtsc4v#eu z$>+cIt3)DLh5Rrt>er-n75u66iSZQv&(-x*pV7$#+GJ-H!<@$IuNr9GpkVCD2RaKU-sq_QJ@1ZK4xlWVC9J2o!?Lz+l( zPNL|Eqp?wSgylh(bmG0NnKMT=)^YO5=lE!i6Z7CZd%X8mN?6F`HZicVYzD2}L_0XD zwI6i=oKo*+arzWp^U^Kye%&9xOiMax3#`vQcg^?o zm;d+z&x`B*pPTQ98_g~UM)?>3M_$vuG6inE=SH8$K*0GMh!+FZVTVufU@^L$h#&gh zLa-9^eENw+1aMQ}K-za=P~v6aeRnN@)cZHAuk4hgvO{Sp$xA|B9&%#PoU=nZ2KeTX>@=^(X3@V)Q;P0Pk_N2I!XL$xHKHgu4 z_*f5r8e(w-GZWBl1__)z%sU*4xkX}Z>-`NzdQ?{xP4=5fU%`UVq~)pTFYuw_zFaA4Ysx=ICBJ9xkEfAGeP|iZ|9dpTXsM8YAAzT0(u! zyp}9p3JWMoVi4jo?ua*u51+H`{e{}W=kLAuzE_2Ti8s4>Q~eDZ^X<0V7G8dN8QgT^ zZ9c+b8eaA>osV|NVPB;4_lVD%Z?5$1c$4bgedhugHf$&iA2tl;-}|61`y(w8-zM3s zSO2rG`w}_!YSRC$H{XUo()rs&^LyG%=l#wN6v5m>01=jdY)h~F(^C2@^LGMSUtWrB zs3Rl(R2$&TZzbF{?>Byl`2KtUpTKIN2Ls+$`j6ZGm6vzhFYlzX_kABR{gOW}f&RXJ z=G@|~g!!~b#pSHa(tmn|KFgqw#)=~_ZtJ_mpJlC}{#*Xq z3RptzWyM$y*YDf3$@6A;9Fe_6eKCys=av7r46eEI=hQE2d`q}(yvfgf%JsQj+~@L_ zbIhZ&LCt~7u01b)=li&WCcFOrO_1&$R0jkZH(>xN|A;E6X)k~krxzFf8oEtQCo#|X58p=S&X=>;4-y3uStz4Ze>xC_< zknehjL2rdSJG0 z28pw2jV~huSnuH+oB_#{1CTRHoRTiVzAc%+hV@t*C=q!&D8eGP$if0myP6$Eq`>0} zU{xvNYCKJV3%dlciiyF?DO#w@1hr&Gu#$uWfFB=sxa%1@90vO8C`*o-inL^YLW5wGx8=2` zEq8Tma7$D!d-H+`lVKJpvei3$HhuRkYV61j{slc~oE7Pm(l!^HQ&OIVQJBtUc5O52 zL9L&~LOhfjPC9`ZqQPI~+Yweb`8KZ*R@t;rx=w5i2k$!NB!NN5F}`kG055|Nq*0YK z@}d;^24R=g$)l^LjDc&&P=L*w#1E6x>2fUrJXu7Q&MM*!4qF6PLJud5b&Jqj8evQwuu0TKnqQiAw?@fbB>cVHFCH2@X+P`gvctK~XjlM|$Fn4Swm50Q$+H;a70AahwY zyKWk3W#Hu!xf;Y|eOgfUOuiuB|Xb%gA3<0-!vmO}`=hh3o0F@$;&9*6RDaj`M zTT-ES)r99-K%h|H&Jac)2iNyj=hrqAdCC&?V=qBqIy7xbz9$SZ?u{XIp;ro1(Fxjd zr7R@nWdn~kCTy*rQslI4_0x025?7Z+b|{PL3~bS(t0VviH2}0|sc8pFrCfa5W9O8N7uw}g2o`DD<*AM3(j7hZ&~jah9ds5` z3u=Le4v-jzLF%Av@7NBhLH(SC6aU+JbjnYA$fgw_|KnKRjAji45WVp5e=i+V8GTao2Hy; z`_YVCR!4q)M-M@4SnepUa4aNslK^ZVa5*1T`sd}UF4{bDB6(e+(+Z#nN+q}^A%`tW zIcRLS1Dj1-YG_qB8aSq*h;}1Ylo*jc8CcQ<%LIH&wtz~9Xo(gr6!SaSS0G7m>vbTn z^otkSo}#iVkiVx2sT>Ig4exrgi!`b8It4m8*W}tI{%O%UF+5HKZVFz`*!z-EmaJ1f zebuYLs>)F;XmAHM5vmP`9=|k{tTE|fH3W^&YIYOra-#$5ANOd+d<)DaRoh@<7_=)h z$$VZH=an@r2;UX0MqrO$_1eUL|y zIW`yi-wZ#kV+((oFJ&!_3g+bCcq` zuz<--X4`r_|H<&LV82m7&B1A6vL8wK&oHM zh60gJ5m?%Qs~SZtvoY$V4p|x`17uV?F)K0y%HR!XYZPdjAH+?|!w_T8T(m-!B|IUu z>zJXM;nG$MhebgLX%E_MDO#4=EC;i`VwB{$5fTN!t=f0Tsd^=u&b6Ldnnw)jpN%?JWTX zYgm%mOj(aKJ!i9kEzzU1ZrW!NT3?`uZFu5x;@0wnsY}5C%VKI9&`yj6{R-fv&{}2( z`z*k0OZQae$z*5Lmp3EQyLJ(@^lwo*F#-Eh5gp05WX7iH$<6{Hb40L;p0JI^@-1ha zCZ?=lBlNv&2UwEgzq;k1>8yx#ky_-h?3PxK&l6#!J>hfkAl{!52f;MQg`)CM-2L+ z)Y~8v4%bN!8S%E!g$ECm3HgRl4t@M12%D3}xMr3? z&=a!^^BVFbIR%#gmLxhk9g${f>5Sfne8dJVw|(v_l0%dqXKV-U&r^Z4MNMNeZ&N|) zYoH)XfY2_U*Ee`T)HB^+mdjFkBStGw$U>=8UF2$5LdjdZjl5|XZMek9pekTSk+E}7EUluU!d*$xE(tan2d6c4Jwy4u#{irApr>ym zMD1LoixW!qTAmOFHGmdJTEx)ZR+{pE7jvYxyhggXAg*0!aziLQz?E!7|p#}%WMVjIZlXblqr zxwvRb9H^(ybMVORFL}5hY}}7gL-nH*>5$Y^PHHH-WT5VTx#bb9-=%|wayr@)8%(7n zDbdp?Qdds>lOtgRQOd&^yj?=*tQz6Kwm?fi{<;nH(LQ-!pW>^cszj$=$ujMJbf~{+ z?yC1@aoK>6y6S+%Ou+RSlt+ZP6`0RHb>Gi+1!IDZg`+AqajWN7wxJIpb;$S%C=%! z<_?!;2D?!-dMhUfKD3vKjr;_FLPNr@%rj6ml5{&FKbL{=zXGlM=hmBF!K?rP002ov JPDHLkV1j@T&F26B literal 0 HcmV?d00001 diff --git a/docs/en/docs/img/sponsors/bump-sh.png b/docs/en/docs/img/sponsors/bump-sh.png new file mode 100755 index 0000000000000000000000000000000000000000..61817e86fa1064e2ea239bb45a504998aa6274c2 GIT binary patch literal 18609 zcmV)7K*zs{P)19 z`soK%I~JXk$;mFqYK4!{PN?d~jLFSOsIC)jnamh3cI+!V=W+q&x-}2SWKGA3tks&} zd95G^+f-0dM zG&nYZTCp6a9`tBjf4xcx*-5q$L7`m-(p-QM^iC;BoYF|&v2=hUUv^I`+yA%J0LdWo zK@=Gbh)v$utX>9(#+p-dgPw}SJQ`A1ZDFgRCk4G3dpVyw90fNr>g8A${t~_dOIO?i z^q(%E0Nc58sCkJt$#QG%@SOw4-1ceHmpvJ!P2}zf4wra^%#~prtx?=baV_x zysB^Lk3Eo~^T;7eb}mznX3(YnwCQ&KasZ=W)jgUyFwK3RSNFB%hHW`Rk`!z2dkWO5TcM+me&lwOCg^5-vi9y!(z z(U(6QtyYIHdkE1&pEot3&@OI)xGHRoUq5h$BTyn#2M#ps`)x}N zXcRo_hyhiFb}ucfQb3)+-Wl{X#|}1de-55EM8$4hrYEXVVJb#Fc*n?h^GygGAjp6b z#)L0y6&otkO-Tu{IrvJs&BSm^oL=j3uUFQ2{rOy7QOl(|iY@%~HIv5CsUnP~(j_v? zSa(<(+|sl)Cq46N;cy5r^bu9~Zx{t^oFVp_9I^c9YPLABb40Vef;RRH1zSd#27@b! z>VQjpQ#cj$KN@1%j|$$mD;v!%cyy!RWp>>2Vm$$!O>d2E1Ek0tU-lyW9Ic*Z&jQRE zl?9`s*G=aJJ5Gg*j!OriCdsSwBRwA(2i)hd$&&?jQ?2IvXmvT3p~)mDt8-CaQL?A$ z6{iYH$s;KMxd9B&m`@8d6_k~+Xo1nDlQMEa3v8+$LIEPvLeWXKJCv_RbUmc}v9Kn7 zIaeqi*n3}>*Sx8@8o*9>vofDODNoQy{AJik;B0D%0%}?Sx&ib|>M|7Cl{DO>6uc@! z3(U;2qGqLyPK9cVwvmjg$>{UnBI|!cvigIA#*+(cR0l<>=<=Ll&8Oz5!HXuHR#_9c z8mtzbVua{=+AU?ZJWdch$MiOiZ;E0^+)J>|?@^76xnK%94@{wRzV3v|0%8ug@>Hoy zPL5Hxl7td3V4coHeF~t;GVoKLv|e0JgjQR9?QpiTte!3>yCt(gGPHh&O@{GG;abe+ zUPCKVn$vO>+Xk4KO!8ic)aOwqz2Rmy*J#JuFj9v#FR(Z>s&mI^#h+W=|4gf~Kaphio0VH^_7SjZ7tGk1|jHULXsNBpjNVzhT+CnEqm3*A| zF&M!x1FrMM<%F!E{}TxZ=k>&$Wk%4hJ52K?=B;_Ji__59;AHjtcAJI}Sr~8ZXOU>J zirnn_SQ(RIEzxL$>tPu8)V-{?k~-(aaLO?d(|Za2cF~tXpqPS&IBW1?om>bi=I= zX04khn)Q2(f`jbA^oQ@>Mn^V$HH;$c2U5rDO5q>~eTP@<>HPzy$})T=VfD$Plqh$`*b#XRC0 zZ@(M`DIru>&9L1a-kIS*%x)p%id^j~mvWcN!^vBltv1h7w4GixTXjk{F)e)|Evr|FHZWo17{K)YN=8kd%Ih*~ z%)zuXO|54`D*+A62!So5iDr!IryLHrvQHbiI&|zpOt(HBO&ioug$sS%LDm~7YX5wp z;lD};1aGjsvaDB*J355qh2;hoznLM^05b|Eh#7Aq1b3i%V>)$RPo@^kcpkD|l-_8w zg~nJ}<5o9_N*c=?lyO_nBy2O<5*gqTg%0}dMi!Va{A!vCkjaoW!CP*MDSJP$9BW?u z92;WVfa0<&9h+^Qz0X1`MDLz#97a?bA7$^&XT|KO66>$xI+gM<)+yLehgWc?r z4V`gkA=lRqCS60LBYqQW-OGi2_Q=(Ug7hp1P>ab1gI$Ey-ql|7MU&`C@13)zpV6_D z_#tb%UgvRIz1|v-m>8%FlBMpgT$?#9RPP--+7C}iVsMNRmk*KKnbntb~tGG3y7sax7*<&F=XB?}m3>~1K;N-Mg1h1tNH{B>JHlsf=u zDO**e7|2;OKL!sza0e{C{i}7w)DeK%!|O6`SetDczYYeu(W-06{urTjW5LbCfw5fCS(RlE?!&50=N;71CyL>+z_^hqnpl*ya1!O9qpcOI*r{ zWV9YkW7N`5hJT^E|EJdp>J|6@2Iil!7*?)&CeXjM>EFVCykj4DeuK^6HIF?2n?C-} zF#6(uGM*jN*Nxi_$HCVcwHGehxz>~=J~Fg6ax{__R+ugxdJ3+pz9t$zR2(y5p>*<@p1_bp(hoqYbs zrcZbNHtVh`^@sX?D+ zp4+GO<6!gTl|TYlA2u`Dl-VTMU?In;1zwSlXqN*hFCGO0tTv}=`ns$-9;OR%Qc~Dl z{fq30q;Q<{nQ2!9`lxO%6gxdSXjti%T67?$^wQtF8P>gSGl2j4Q?@ZLyk>lX5nuDN zo}YKoHQwt!kdqG$EFR2KYp46IL0pK6 zw`*Mk1QRt$-P{0372~8Pw6ComoYlQ;X<$!zOAe!286W7C3k(3}b8OY&d1`}69iVEf z0&OYE=Vs4Qx5Q-3>gFmN-qmG#qV8QtmyFVJROMVhLB{aF`hZC~WLmlE+3>r;(@y}t z_&o5tR5pC+KV@q%mf(9p1YQcolQI*aFFx z;_KmavkwV0haE9L@Lzc9QK8<7`+fyeChq_{P2N6~e|O1`;PNlu7|M>Aae&X?q2yn2 zJ*-^$bQr479sg<1cWYR&;+LN0t+3s8ZwS%C!w>%fzH!~HaO;vEfVdBt_syp%zRvsJ z_jkU|I4|!Sxa+Qe3A}XLbFY8!bngyyD_1@Vixyq$f1iS)#*PPC@u&yHJPj!BXj-TM z7S9c+-@-NkRB#lBst*lO_$HO!6w?5+yrKpt2fX;L04>|YhKyA{vjT}vIJE+&0tM5J zpaOF8*Q45clws6E0?_ELNUK*r4|7huD3p!7_$S!p=ih;Uf7?ex*@k}Y`!|o@U|W2{ z$|r9#WpWo<|Mro`!|#L%n}x7Ctg~4a0#*WO>LAfgn6P=yPigNx-yc9rWjdeo4x4dG zINxsK8v-4HmnQ=9!Ue~|Uwhii7hMM&PAScrc`(eHb&#lc@WCI3FMQz&xc1ub?+%gLLHcvxNTeP#e>G;}^{Hig3(Z6es? zqsMk~#H=-I{shmhE{`RS`^67nqu=}l#x{Hnj6VM#f>yE0>Ed(e!0I&}k4^mM)+U>5 z1oz(mYnZoiQKP1*og)2ln0a9Mz1Mrx6)S!rTlXUEwbuu_dLFEI-St!0+k4r`Q{Ec9 zuuhM7k4|2fjL`8X%nZLXXB`alPrVSTn@$GwcHq2Oh)ZjDL}7|BDnswR(6j9E(!F=z1?*4`8yZn)SKR+Axc9z?0$?e0ULB3hUCV#w!|R7UuzmjG8v-~fy~&5o zJpE|dPvN}tznse#c_61yc&90E1C<|QH7RVfR!3geB;JDK=-8^Z0v2DK+4sJ z)}v$`9)9F8(LDkvc|V=V8|$cqKu>AH_{|~v09UsB$@9+gUX<3r^hdlYZ?X5ZzBWcnm35(Lv;yi?$QA|{&0!*y!j-TC40}Q`!0>FiW1-Pohk#Ca;A(JaJ$h2?~QAEm?}e;8m?1VPplO z79!}^@uI;){58rd@M19xh^5_97u#*{zD7^2jg2-K0AQHT?sud;J({@5ZM{;u#3Y$X zhJWpMhD!)emybS_%l_!SciOz_%69Ac`i-uSAGo{Vi$*iOqW2U|nX&_X?4Z3e(X3eq z`n+yqD}~YrAG9~@wdX(NIHw z#6{!CKG$Wa$cpOnh|yNvQiTWPd9w&BwhGdUKA)ipxFnM$O;?8ba!^-4h0B=3Wyo|= z0syR~N&rV~;%s0881e#`2-c$*LuhAGI99}ksNbeAd`h9QOj7$!pEuL2+g)DzH9YLs z*+(4yOaSobW`8pLfyq_bt-PM%Nq3m%OUe_DKiqFx9T<*Nn0@Va-wgn!4XYDQ_;e?y z54R~qzw*kn1Bhu|OXrirnil*T`04fZqI{E12Utq}Vdh25DVk;Ngl1lnsJ5Cd4r&%G z1w~0Ie$mcDHZ~}$##*|SG=oyNrOy)s>fVTF+lPQ3u!sRZwBPNgl%_Soo*Kz zjonCN+_?$~hU<~K+bpkWmZ$5-x`p39@!Bpc$7sleMw1BEVWLmd z&C-?;|YGP7Aub?^04L&*ftbqexpnu3&g}j?MS3aPB(Gzy9rb8jHkihx$|`Q z=NHNwIXbD>aq6)cLX7(;_ORMXLSG8f$a7YyTpw<-$!o*ma|v*cce*3O_?w@cW4x>Q zQ>{V2a>W_pvJ!#ya{ot(*1jvNon)82^6KZ-WEm)2-fp`$hH|=8wBp`h=%FwYe~6!_ z^r)o0DTUC8!l5S}I!(}&8O+CPuBX6v(mOLl| z51cIXAdj)qcEdn45`kCnibuiYD(iZm53lbFYh)))t(o}U+WIxM0C|U70Rn|AAj7eo z+e$;IpmlktMxn{pv<2+Q#5~F#@@{AV@$$Y)LfD|yZTDVQYPG9=1;eZla^mx2^)8ia zh^K46AXHOQVtp&6Ok%MF75uQLPJ^H9Mq*;$0j!*%DrmHTVBV_6J=$Q1mPD7CsLmhR zWgf{l{p*B5kna;=sP1b#BTq1|>y%I;6LQk5P|jMH%6j%O49IUXR1^yM%4pi)wTuPy zdf8O14|K=~_4`YZ7R)u!pXeM)DAZ)uBX6KMgi`>wL{lcw5~>R#vHIQu1z7hx1Bvf~eNXs(q!= zY@P?KnIS!{$brr0L?xwL>0cfu=X}@cY0it`bpC}DVtWtlEJd^83vMEK<}2Nf&Z*}K z9CJL}0B*9Pt>W?-%l`J_E}`o)QI(5?SCWPTZ<5jV%$2I(OeSC(-(K))B`|hfnM~N1 zhh`Ni2G-)0R->`YAe8*|QlmO&zdEm*b1R?oXEGSFX=Un`Gvz!zE||h({)NgMHR*{n z@`%Jbg`;fvjo^r3}_Nq77d99l@S_ zQ5U7m;mnD8vR}+_a^4O=jA(WJxRt_}8Jv|LYr7%XSOI@cF^EHz=LS)9`Ki_Hm;k`u zaF?&r%e4H(cep-A2|>lof<)WpKE`={j%HyoH2e*k`51Z~z`E?u6+n+MF{OsQVh z77i;{w?(x_gGDbJ$IY_JwD$$(JqZxVM=u{z=p zxtyv_rZK>qyd6rAw*H`T%mI%~df9i@W>W#{)JHYK7talN_7?~xayD7Ts4oX|L2vh} zkQGG;?U*ziD4pJOTI}W9mO}Scs$rjr;v9xqXmGxw9qg-O6{30AqDjhT$xswlswxk0 zwc(H(N}4MJY~rCnrGK>y8Kikb3kn}Z(T)H#T0m#NxlgM7m{EoD$opK)8ekRMs6M$` zE6j02u&x6aweM7*yz;|l7thlxV6j}wZ0bleImOc1KWVcihvbNAQY`hu4Rmdu$rzdTRN5tH>}pW*=`NOW?#~X zk_C+_4Xp((GJHqHs-9zMY#PP_%Dk4o*XvpoGgznmeP4ju z|3srvX(Q&Z~ew%Wa#ZL}cEs5CIeIf^`a%)lygp!VQs z15}Nm?X6kcR*w^SWp7<&)U6S!%zYf}+LN}DoX1p;*Bt4zq9;_%=jA4eTF?k~ZDwM< zDjWswsjcL)#cL*4bI8YiXtP)jbd_mMi#_!kn(#_fXwV4nhvA*vis2dgxs{7m0ah&X zn<=2K!1F2)=6nXnnti5Emuz@>wb_J2fEOF}V%~_N7v*TlhIt&CZ{GlD8im%mF3g~h zDMyiv`)Dtd9Lu)s3)bh&I>}OL>!U)?>bpi3T?cUWO6F1~tOTP-(Wv7l#2Mjw%{AmE zZ5ZZyZRBB&p3Pp;Dqv_i`WFoRtll!I6bT2YmZ61OJTA8er=)R}3>=(XLKBD;ZK@&f zvNCU%1gJKZuqi_=pEz%A01Ocj1B+i_z!EOMADTN+)=qgIIIn?QfRzFi8mGiWgGLA2 zZLZ;WAV9iKn>eU4SpaR0mGRV%$ry43g0(0nNX`7jXc!PTlC2`;Fe$Z|nTP;AOEt=B z#ca|-;mOE*scm{3IxM!!9Ms04+l>}aKCG^nH}n9|&THVHjqLE_s*?3dZICf(oJE_=yA!8Y z_lK7Wt4jR+jB=!mBW&~Sb<9mqCD*m#5K+*gVKbUwRrJU~A44ikbeunbb&%j!uCMg9 z$$u*wCyskG!9gRMEXf(csGK*&7kM4m-3!gm>l-NY%DV+r{aeCd(V5CX$WXVkK+WVT zj$#&N0|pR7*5w`fJ|3J#-L6m zI!(cJmqsi?@nl49sg(AxFFCF}P2`LgH16vVrPWp}%XO+Dkuy3>!a;V!HR$yes&O-q zD*LWYdhWUF6WFTDL6zFY#!ztZBL870nS_HCjdse-5th&vE4jNX%kv01C=$gDqr^}D z@w$0E%=#q3j`@ao&^~~rx#G0j)x1MtGU6FGf_?X*E3}i-TL9aj(Liu?eaUO{Xk?qbWtiza$%oz zXq-0avA>tC^!zdgRzke}ioBB9xifD3nNq8Q(Ds`Y< zkL*}?%6_fOgiZjtp>2%76=i2lqgxN~4t&@hpt&`V1FC_-k82YfFUIIMo8VfI>&;C` z8V6-6MB6x+HR6EQ6b%aKN+m{P9O^?TH*WCDJrax@JXKu{ZcyMg`El?jPLv$9KS5f; zX-<&%I48?W@WpFO*@wGv1FD*)LNP4Wuaz}a%xMx~c47~z_lWeUs%v>}Mbt=oCl1!5 zZXZ);7_$MDpt=MF`gJ-0bLJcd3xxV zC*#^NtAiD58~7YZT4V|A`VvsfOJj>~qb_gD%yaTh8=OY=(MYkHtPMs+N$VGN)(zHc zU?HvocKJjaSRkG_@%8Y9GrOzooH)tN^fOskBRF!A5fN=s!(=) zYy=;!c)H6j?+PDMyPv=t0i1#tdc)=Ai_SDzU3B4T(n>1;A zm^<(2aD3B^x514!-p;BaoOAByV4H2W5;dQE@)@}4rrY7i_pAu#ms~m@w%l?v`0ek1?>24M4#3PJjN5plT%Ho0@3`Yz;Il_BfCnG^ zHH`P*-O}gLyqVxlU{Slr!doJk9-8ye-c+CGx?qF4HYhyLsdlB#%Xg{)t5v7;1;Oej zBPWPtq{nUv<_^u3qLS|<9sP~k>&e<~7ng!2-FOe&J$BnA9Isxp)=%ojFlom(!Tk^X z2DD9U*&7NoC-3-XgMYUH^tw$trJ_%(*dGAv5EVX2>UpJa7STt{_<2X_V%f$pVarX! z=S->G<@}9c@}%vdmB@3_mlz*;^s&le(*b#0l5zEOTuuaSo%vXtaKc&eX#55;r8RyW z(|8j<8lP29J{|b-n>DBp1SIm1-D`GTpq(^n2PIf{NVL0q0Ead_uZa`44zv$E@Q@_* z>5e;=!I@`V=Er^W;BjgEcbhgf09bU`0B@48@^)fGT%kc5fB+J_l@WkCU*uW&LiV!x z#M`XTx!iV0Yv=dWmg~W;)od{a-BOH=HE`c;mEw8T@|%G7$fHldBfonhfO*EJ_JLUs zoRMwVihH^*5-d1tF)X>`Zcj56PCI!9tn`XE^XRi+$u}>`&D?s^#UX#M{f_kj+YIKP zJX3&s$ACz- zZ&1GcJy4$1KdDdT-!iLdy| zHv zZEb$8v`Z2w=Y8SxaLaeDfbYaV3a!V*x;Nag1a7#^ z;p9_}4BnT*XG(W^uiWxVgTdR4eFiUr1h4u_d^ot#q8|dhXvx?~kdaLmHw^p6gi!QjD}S20_}_Na{ix+!_TA{1VFwjKo?$=Ohy>y=s!&a?& zCWq0qF23UGZ^59nDtO_Au;Rzph4bT2zAyl9*Y{2d9+*7sL5H3kyd-(ZE4;@gQ0q^M z&iw2`c-Vt&$<3Drz%5z298Np)GB|MmX&$_v44|ei5TAn%IU(>Sy5)Co1P6M>p1Sip z;rxrf8p7AbUs}-dJaT^M@Nu(06~IEEPrJ$VyQ}wTd+hPP2=dN59sKb*ejJ_(9{Jj@ zU(|sZRZOF_foL??uk_yZcJKYSo48d7i^+Qu&5o0I@IdXnHHE=sFqSW0lEdhQ3%>-n z+;Vv+Kk3Bt!ko~2PTPIgAQPnr;#^T*iTCvBd%#uRnDIpOI)41ra7=C7dFS%*r)96b zs{~+I2CNKa9@ySj*X3mZ61=%B2aTO)3xF(4Mz%~;JJH$kMIK@q3&W?@f>pY*5;{94 zpJ#W9k9wFv4ywGTSwF;I==a=hYUZ-&l6j(q1NYwzuD|IHSiSanB{Khw$EYYAWWzv; zmOd*=9yS>W?cpB|o~APh1k9CBMkQaXI7P}0Qd^7I_(Tw-&p~*Xzb>sxKb4eRYyY4b2ht-tE1rWKgcIATU zdaJMtr74Xc-+4*`G3|6GAg&2OmLTSX(;9PH{}Onw^SiHCTy+y{=P#KgkE>G}qqK3h z%wO7}b#Lkm$v_4rc{chTKVdTu=x4$)Z4%Y|Kx!g%9*%Fc(TC9pyp(^$5%a?O_HrMh zcbijP#S48HPwQdIJNDR5c@Oi+{gzQ zAVdCj3b-1kHH0!nEV6W}y6hu(N?PGmUHNs;{wu=7uT%oPR+d3OU2YT&p%$e4lINQg z!E@h(522#so&HH{M^mS$6lmvMr!pff0J5(s#|c=7&ELj4 z`8^+YFTMC;%t#Yuyt`gqTBwfoj5fMJ%1Ac=tjgCcEt-vjHz4q?VKj8Dnd(wl-FZ;G zj;NDdxih=yx@dz!-8HM%6j{`D0DTP^BTWKNx-P5J#BH|l8r2Qw-4JpI zkPk7A$2fI~sd&R}V~GA_kfnTXmHtU}*M>0v`KmVeX6!%#wQ+++Jh6a{s@yUF!=CFr z=*eq)U9F-#T(&%Lf{aWVPEMYKvDs8St1c@aFH3YKFY%)9|CZ%jdOVlc9UFa>2k^T+ zfdBkLr(W26lMw5@1!6IyP$wJAcCzj-HX?)IrUsfXcErB73q#av(K*JeSs&H0Ru)cE zl09;b+aVbnRJBnV0H|$6SAvX2vCeb~z;f*>TSlX9dHM+3;OsdsZOL)~n}*8+ik}<< zIXA83^f6Ptx~zD~cpA>eB0=%KE}mOJPJpigTk3T5>pXx*Qe}u0h3ODIajhw3;j;iA7ddLb@ue!44t)oh; zhIda&$tS3WW5o=xM^#)^(-;?l3SJkDun`-Ysk9s<5K$bUI26FZp0X&c1ti`^A4n^3 zLc11yu;RC_W7=@;w258Nvk*uDflm>a8 zZm50MaWs(P>>AkQ#PSC47(~}LLDqN_DtT$kGF(PN&P`Du*7ejlO9`;XuLduIsp!af zi0=+z4YrOdO03J-F9)p!UUL&l=PCZ026S2Yw+$eK!5}VxWHBF8QH!a(7F=G=uKz?k z?0|z`*BQ;!$PXe|g*R5^mCCU^r+}QiJjULjsjf|PAWfWWjC7C3OoQ#Xj`w)tEI`j> zBeQe7Z<<$SWY83{yx0u-=}QN|9>s>RMxF+Lt?UdC$Zj|6qfcIqmouPozM4-3Mk#Kg zNva77sAvNgg=W37R3cS1mP#F^$8|!b`0lZJgNFmtYZn@&_z2xwDf=Y{5RVT!1}{EC zg;-SvsDaJ%G0`JR{_leFZY(Y zZv4mcz(Q-)qlT7bqqg|$xAkQPwCeCiEFj1H+)?`yPgQN0b*o6x3+P<)OH!^r?cNyH z(}Y@IJ9C6eVgPKt9?Uq+U=W3RxXp?(j{~9RKcFsazS?!xJW*bN%rxp4Roy}01*1I) zUhQS2oTYxf!y?NEtH;pAPL(@(t7HMalT}=2DEQ_wFXweBjb?>)7Fc6GfGTsvrJHMF zAv#E#sPB|Qkbo$IT;F>=D7i*%+eELE7Y4NAqPIVDE-g zsv!3vbO@6MYxwag=utS4KvjQv6+NzO??S>+`0IvWMTI5Cbu?DXI5ZYh^kaV23 z;)QixOD53#jC_3!y8tx(p^!)OvPROHWeL>i136d$#KxA>;A^zQf$mgW0_cLb;^#Xy zYCCFXrg;x4^UXZ55l5zbtA!-IeKGj59BF1GZQE6 zChALllXYB`m!wS3E(_(Slfx4l_-VJyW}LTJxe zQh)xMJxkj&os;0MfX&q_R&qJNv61CeTb5CP&RCVl2`ep7`M^9f@*z;=F@Mk9b`8I` z-TotWGk<++AQ~+W3kk5qZOk58b$~U9jdE7c^~xd`4WN@`Q+M7eyxD`&hxeHk#tgOV zQm5t^n%9T>JUa(q?Jb3etBgQYBTGAc z?=bk&TKoQbp4zP>~tJX2Pv=( z|L(l?QqLhEjjll|eXq1pt`?%AwhTd+J2AYy^6+pz^T<;aqk0^uWV?x5!<=Kf2k>Vd zH7`)DeDc}&@J9D+F%Gy^Zc2(=X6|88)<+JQWXqvJBg@e$j~k$+)$;MM%4-`uG)?MJ zQo+>D>)*H)d~C+Y!`aya9zoy!tlS0mm`=|mlRLkE>V{K#Vf;&M|VN<8SPpd@#CY|u)Z zgtusJ(LI&4^66{}=q)0;&ZCe10Y31-PB|I;VqeM|DZ|rQSSu(^1Ib46tcq_gk&%c5 zAwAr`){o7awa-`OiDqNZkKX(I^pnqoH*s)J%t5!s7Mq6h=hv=e*0k>C#5yHVR|%j0a(8m+EZc<@5BD?p4UoL}TEf_O6cAOWz9X1MgU(=HNBj*5PsiwWgFp zB^vuy%3J|KUVAB)s>~uiTK5Jx|D4a~_d#8M;}Rv53yL-Wl+T7|Yg3}PLmqe1IYBWg zP4K{`b@SvM%UdIsEWHb^xavk&a`U2aKI5oU!$b2k51S4%{2v`t-k#Im7wWEzudqn~ zxz5W%54aO8B`TkJ)Z73llK-ITd%}!E%Ud6*{J0a(3eWLUf0lgv%ORhZ0(*Uwvaa}-?}i8eFTD6l-`8XEga4_{tRv?Jx#rLPY|g*;C%3?DzRqpFpNqcO zd6jqm{YL{3=xrXSo^muyV&if9(q(Y*C0B($(X-fJx^zK!H1qDee-6|4?_NqkZ`3&R z>@NixZ49>Ab}KmR%;N(vDcyYYQrLIjY2kRzv8RXE#ctujed38nz}w$Zz;esYcfg{{ zz7~La+~;P&KYnm3{Ni6dtp~^!k=J$C+zi)W(>>3%@f~xNn9TbB@UQ*DSpr?(ubh z{DZsU^XGQ2aXQ2UnG%8a@Y%Ecy14N9v8TQEife)qp|JbYo)>xT-~Qs)@Ln%R7&lL| z_`=J9r{W!vdC)%?$o8H7o54xw6 z@PFKA4-Mc=G^Eh<%f5AR$AABLV7r+I->*HWzV(fp19<8DLm%D^p7+M+-#m|$e&cEW?Xf37 z>%*qraGZ3`yZ~MTD*==ef%MaJW+~duV8{1N3B1C%c^dk2QMJYHv2)P>kOEWybP9!4 zzO#Xvjf7Hlo}Y@05IX&`Y&K~+c-wX7cjeIe;6sj$Z;jjm7JuoCOhd|;yw`#=FNI}y z|19KP_q7W{UE07|6<(XudGMKjUHGu~fCM5^-jpxo=`k(`9Wp!ATlW3#J=BZ8a)Srb z4RE|)cOE!>uV6SRB}231_8)dHX!mL5v<_^$GQd*0;GeGaUigP_{&^?+KL1@X5(N6y z;q8@Yb#mu5Fv7u~JT}Nrbo9m!YHN}omj!2B66!39<8#DOo%fieLTUmojS)o?M;tvr zL@ATsu{~V&#nW>cPlp_OLU@1GSFb)Fw%z9S{%@-)tlZIqlE6!J#~!l)p7a2ryo)ZL zpTveY`nrGr-kpN?{`%K$4%xSSYbhM>J@QBP*$uw)?WLJ70r%*cC&C;3+Vqme=Y+hi z{V^GV5CVH1OTK+)IRCi^^44#BJ-pKcX6Y?=WHlm?E;wOf2$d=8^WF<@?q%H6hrjoY zTM@uJ`Q20C8;h?AVerY(+n#;m>4C?a-})A&8V=s%&k301UC%!0^zci0pFj6hnDp+Q z;I$je_ovN0@}wZc#b3QN)T4H_2jxJs!D|CF^KpePmpNYUBn)}m(7!Go@NFcWSBKN- zg>B?{QdoN51Ha5(hnk+ktKwASlfgyB;f6AF(Byj1u82g{ce zwp5qro{V0Q-Y#M%m+PdLv=h_tg#|{FUJyv0^!$tDCuO$SQnvXA?-4QnavlU;N*j9~ zXPiGz)Y&}nD0)Pm`I)D>9rw+)*c|4ZdK_%!fo{oP8EwOc|5a?8fctFhWAeh3{`L)9 z33_VVL1#!d-J+8%gH?lx0Ws$r;1+-JkazR~NYm+SF_Hlr%d@w?IlUgAlfpS2J%Fd@EzXqTPK{sFxcntNf_W|en**Th%qY~#Ib`V`B1-@B8bO*Hg-{OkoG zL=NG%SMI0cCR0jlvq6+&@^st`A`d+8_yxgpQau9iv*{a6u}ZhG)umZ}{Y&7z^E*r7 zTYjxfyUugZJ9UT=k1QSJ&8olkbqSCJ(8=#EeSG5gkA*Os*RlBUUKQ}x3r=ya0iMpC zt7uAnSIbiIDe%_fjg$h1JJ&Z$Bg@T@}{0TMItq0n^(WDYOn>EZBDI z__>p(;nt-;?0_D<_Tn!s%rsYAbwk(`OMsel>|tRosoM}rJ z)W)9EKH#H-pZbvb%TNa_wN>EW{`_-K@taXU$mb*@-I#Ik^u1-BC!fk^=be2LEWP7L z@Z4(o-fY^GT6Jg#(#!l>m;k(O{954BJMM~~IVk{6*G9E`sgx3ZQQ=dE>>GBpFZN+G zt%LO!2!H>($HUr}*1!uE9uMFD-ZFUm+qVzimo~8yI9rKY`jy|@BF{=8^GOSihr7PN z9Cq+)(#<@Nt1pi{3{jIopfGmIdv@~Sb6KAUaIy&Mn0d$f4XovAciF)`-(LnF+0zSl*MNj0&;rUkZ|hQi{-VUy`8*mL&}1Rzr)`E~f-@@{0DlJ}-{ zGwsOJE;R9+Jn5}rm-=D0(bd&?I=pQ%)!EhqU0?lPy=IMH`_2n%a{?-@ducrGzUSwD z7y9a2ka$I))mQo>z=pN22S2Ta>F;Y_{boh|g>x^1pZwo@186_~$^GDM9?VbqDC!R$ z+?G*8qrJ1|pYMVEPza}|e|%rq-fw6<U?g-(XiRN*CIf%S7 zrOBS=Z#*!yPohgtXPvkphs1Q*X(NAG>dsr1!q>g=D1c)<;Nm1t>zn`}2Wz>(qToS* zne%YG4aSS{FQK)oc9}VlT~*>Q{cS&GcQ-Qj0?34oUrxfjb37*HEpK83lV{r4p|Co< zWzi*3im(LIa2=RvR+ek7aRkC&MUQ0Y3WsKe;fFL`S6 zwkh-$-B?j|{Cc?zLaFkq7YPX&%5YD&4^$j#E}&dCPBEiQQsp;IRv4_&HSP0DL9y z#F=EvUtr&5BF8EY*@%+A9HlpFqE!slEfXkf3$9EcS*ujykMNmvv5dYd_)q_IS~&mS zoy)?t*AIPYY6z(*Y@T!MX%e6nPpt2#LIq$}uMgaU9*z2?0|T^5oBxullexBK`6(~y zjtp%jz#XXY+SOq1j=7<5sy?*A%Zvf=JGHoxc-3p!CKi8Bv3~ZqsnM~e^uRheIr3r{ zQ4A7R+}P8<6?i2#ga(GR31;taauJ=mC(el=qKM8yf~u811gEz6`>$H`tmjyN=CTH? z>*oQ^y!A5oCl+-(%q{@=lZWh=>HgL4Zl8C~W%a$#shP~I(-TAu7HrJNK<(*Z5DLm` z35B5zO`@0^*A!AVq=5+mc?gF08JN(4B3kVV&>+E<^IVZxf=-tYsIS@-UoQj=UZ}vE z`!qHNxWl9mI&?3^EL6ynwp=D_%Crk;P3xetVUx3SgH0)5P6L;^lwRuI#{;;SP95i8 zth7=?uN0tRv^rXBj2HK^`5g4r$Xg`4DhQfmDTDBd6$+&(jHXSi-}yi7A@lp3ne4et z&=tOdrj?I|Tp&F+KiB=>=k1x3fXK%V%zUS4THq42EsqPX_Vi*Gx?amV1Eicj=dd#R z8{69}5fX5<$u?LjZ@qj$jsn{y~-FTCiX?Y^d{l`X`h8)U& zOtk3f0|9!J6 zHmEXvX~ztK0nBi__Og&YI4>*ZuJWK9=&4MH&2@bzbU9IsZ5Tpm1}brnGZ#e<6*kIQ zo&yjjIFLET2L|wNwc&b;P|=~T^GZ^m$T_=hoV0@GeB*(U3=1)4tX>W&khPnrIG^sf zu)&HA<^j_kln4lM52S0q^D&G!{z77%o-G}bjzPHCnc3M^_k=B&_Vlp zZ#j1=-zoVLa0M_$xvTgkFiV-7dCoJ-o=qtm1@C)7wc}2`RkopimMYhbTtcXj#bRdeOKjN(;4InJFLnDUl5Q_s8n)G`Z`~eI3}Ka zIKGI}^~Ld}fs=MyOnm|yN)YFAke{=yM9MuapvlM3pDbYJbDa+fbXbMYz~~FGjWx6e zujBRArWW*rx89kCP6ao3HQ-Axz~~5!`OI}7c5R&lJDk?TYn=m_%E5cbP;}mg9>aQ`@^A9XgVBZ&}IM@#^zc%=ef+EpOtUJi_Ch%^sA;7wB z-zor@lw)k918}7xO$WmjZvbEimj9*64LBdB&ibSRj1F9hr2`$gbfd&TDFDvKFO$?` ziYzih4JZXx(QrhTV-nZRYrrP&-4SqbbBVPb65wm$Oo=GHujG?ykQXbo@a`IZMS@j< zh0$1f-QieZ*m>R*=*#wM;FfkIKW76q(`YK5r}~5=A$+DZnkH(*uZ72aTs^YxMWJw* z8q~WGdh!Q&5t%GNbLM?%7@A?){7Vvld7ELZVpp(b$l`@ev|d|e8UeQBT3v@lWkmou zf+RahlfLrK-KWfR0+><-;$EIRZ++Ig5smjY_Uz&MQDO zfM_s^^W>Fn+H~CVrrL;9JJqfRtORsvl>Qxi^~QjIem-*?E%w$A{@9c*OBQFD(WsSPTl0Sx3FZE4CW zuUqq$VquY2Q6jSyPEZ20f-&Cynu_HGth?fG08y#O#@m3 zInlO&+$!g72!p@|um-PVP$R7QW$wC{fTvMy6*~}*_+2i4X^HNU7NXUjM9z>V>8oqe zzz{U{Pu>rRy+em3(XyYwXqIV1&Zv2;M9kG7p?mc&nh_%yQ8ihuzO)wrXvv^YsWKqcN3H zqo%J7FlYxPHw5_r@D7r!lu8EjQ(6{Xat~kvS(x!wCn9=uxLau-CW#=N^0bHwq-{ZLhK5GVUF9$UCH7>!xv>R2-sD%&Ws%y0IqtNwrRkZmVoI7vnqQ}S{(V-DpWR>WHa(9nMT7^wu=YCJ|;4M5uV$X`mw|4L8^YDGPZ0$ znLM3R%7-T~%pzxlxzDn;YNp4CAxJ!O+DbfwrXD4fU2R@SR{gCDYI~1hc*t_1L8XD4 z=gVn8#=~63%wJLRa9Ds>bgC4t>wZZ$=n@e