diff --git a/README.md b/README.md index 62eeda03b..7973f341b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,6 @@ The key features are: - diff --git a/docs/en/data/members.yml b/docs/en/data/members.yml index a3a6b912d..cf016eae1 100644 --- a/docs/en/data/members.yml +++ b/docs/en/data/members.yml @@ -14,9 +14,6 @@ members: - login: YuriiMotov avatar_url: https://avatars.githubusercontent.com/u/109919500 url: https://github.com/YuriiMotov -- login: estebanx64 - avatar_url: https://avatars.githubusercontent.com/u/10840422 - url: https://github.com/estebanx64 - login: patrick91 avatar_url: https://avatars.githubusercontent.com/u/667029 url: https://github.com/patrick91 diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index 1c83579e2..e66d592f5 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -45,9 +45,6 @@ silver: - url: https://www.svix.com/ title: Svix - Webhooks as a service img: https://fastapi.tiangolo.com/img/sponsors/svix.svg - - url: https://www.codacy.com/?utm_source=github&utm_medium=sponsors&utm_id=pioneers - title: Take code reviews from hours to minutes - img: https://fastapi.tiangolo.com/img/sponsors/codacy.png - url: https://www.stainlessapi.com/?utm_source=fastapi&utm_medium=referral title: Stainless | Generate best-in-class SDKs img: https://fastapi.tiangolo.com/img/sponsors/stainless.png diff --git a/docs/en/docs/fastapi-cli.md b/docs/en/docs/fastapi-cli.md index e27bebcb4..654d2fe91 100644 --- a/docs/en/docs/fastapi-cli.md +++ b/docs/en/docs/fastapi-cli.md @@ -9,47 +9,39 @@ To run your FastAPI app for development, you can use the `fastapi dev` command:
```console -$ fastapi dev main.py -INFO Using path main.py -INFO Resolved absolute path /home/user/code/awesomeapp/main.py -INFO Searching for package file structure from directories with __init__.py files -INFO Importing from /home/user/code/awesomeapp - - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ - -INFO Importing module main -INFO Found importable FastAPI app - - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ - -INFO Using import string main:app - - ╭────────── FastAPI CLI - Development mode ───────────╮ - │ │ - │ Serving at: http://127.0.0.1:8000 │ - │ │ - │ API docs: http://127.0.0.1:8000/docs │ - │ │ - │ Running in development mode, for production use: │ - │ │ - fastapi run - │ │ - ╰─────────────────────────────────────────────────────╯ - -INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [2265862] using WatchFiles -INFO: Started server process [2265873] -INFO: Waiting for application startup. -INFO: Application startup complete. +$ fastapi dev main.py + + FastAPI Starting development server 🚀 + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to + quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. ```
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e355ff14d..d57b411e5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,21 @@ hide: ## Latest Changes +### Docs + +* 📝 Update docs for `fastapi-cli`. PR [#13031](https://github.com/fastapi/fastapi/pull/13031) by [@tiangolo](https://github.com/tiangolo). + +### Internal + +* 🔧 Update team members. PR [#13033](https://github.com/fastapi/fastapi/pull/13033) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update sponsors: remove Codacy. PR [#13032](https://github.com/fastapi/fastapi/pull/13032) by [@tiangolo](https://github.com/tiangolo). + +## 0.115.6 + +### Fixes + +* 🐛 Preserve traceback when an exception is raised in sync dependency with `yield`. PR [#5823](https://github.com/fastapi/fastapi/pull/5823) by [@sombek](https://github.com/sombek). + ### Refactors * ♻️ Update tests and internals for compatibility with Pydantic >=2.10. PR [#12971](https://github.com/fastapi/fastapi/pull/12971) by [@tamird](https://github.com/tamird). @@ -19,6 +34,10 @@ hide: ### Translations +* 🌐 Add Traditional Chinese translation for `docs/zh-hant/docs/async.md`. PR [#12990](https://github.com/fastapi/fastapi/pull/12990) by [@ILoveSorasakiHina](https://github.com/ILoveSorasakiHina). +* 🌐 Add Traditional Chinese translation for `docs/zh-hant/docs/tutorial/query-param-models.md`. PR [#12932](https://github.com/fastapi/fastapi/pull/12932) by [@Vincy1230](https://github.com/Vincy1230). +* 🌐 Add Korean translation for `docs/ko/docs/advanced/testing-dependencies.md`. PR [#12992](https://github.com/fastapi/fastapi/pull/12992) by [@Limsunoh](https://github.com/Limsunoh). +* 🌐 Add Korean translation for `docs/ko/docs/advanced/websockets.md`. PR [#12991](https://github.com/fastapi/fastapi/pull/12991) by [@kwang1215](https://github.com/kwang1215). * 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/response-model.md`. PR [#12933](https://github.com/fastapi/fastapi/pull/12933) by [@AndreBBM](https://github.com/AndreBBM). * 🌐 Add Korean translation for `docs/ko/docs/advanced/middlewares.md`. PR [#12753](https://github.com/fastapi/fastapi/pull/12753) by [@nahyunkeem](https://github.com/nahyunkeem). * 🌐 Add Korean translation for `docs/ko/docs/advanced/openapi-webhooks.md`. PR [#12752](https://github.com/fastapi/fastapi/pull/12752) by [@saeye](https://github.com/saeye). diff --git a/docs/ko/docs/advanced/testing-dependencies.md b/docs/ko/docs/advanced/testing-dependencies.md new file mode 100644 index 000000000..780e19431 --- /dev/null +++ b/docs/ko/docs/advanced/testing-dependencies.md @@ -0,0 +1,53 @@ +# 테스트 의존성 오버라이드 + +## 테스트 중 의존성 오버라이드하기 + +테스트를 진행하다 보면 의존성을 오버라이드해야 하는 경우가 있습니다. + +원래 의존성을 실행하고 싶지 않을 수도 있습니다(또는 그 의존성이 가지고 있는 하위 의존성까지도 실행되지 않길 원할 수 있습니다). + +대신, 테스트 동안(특정 테스트에서만) 사용될 다른 의존성을 제공하고, 원래 의존성이 사용되던 곳에서 사용할 수 있는 값을 제공하기를 원할 수 있습니다. + +### 사용 사례: 외부 서비스 + +예를 들어, 외부 인증 제공자를 호출해야 하는 경우를 생각해봅시다. + +토큰을 보내면 인증된 사용자를 반환합니다. + +제공자는 요청당 요금을 부과할 수 있으며, 테스트를 위해 고정된 모의 사용자가 있는 경우보다 호출하는 데 시간이 더 걸릴 수 있습니다. + +외부 제공자를 한 번만 테스트하고 싶을 수도 있지만 테스트를 실행할 때마다 반드시 호출할 필요는 없습니다. + +이 경우 해당 공급자를 호출하는 종속성을 오버라이드하고 테스트에 대해서만 모의 사용자를 반환하는 사용자 지정 종속성을 사용할 수 있습니다. + +### `app.dependency_overrides` 속성 사용하기 + +이런 경우를 위해 **FastAPI** 응용 프로그램에는 `app.dependency_overrides`라는 속성이 있습니다. 이는 간단한 `dict`입니다. + +테스트를 위해 의존성을 오버라이드하려면, 원래 의존성(함수)을 키로 설정하고 오버라이드할 의존성(다른 함수)을 값으로 설정합니다. + +그럼 **FastAPI**는 원래 의존성 대신 오버라이드된 의존성을 호출합니다. + +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} + +/// tip | 팁 + +**FastAPI** 애플리케이션 어디에서든 사용된 의존성에 대해 오버라이드를 설정할 수 있습니다. + +원래 의존성은 *경로 동작 함수*, *경로 동작 데코레이터*(반환값을 사용하지 않는 경우), `.include_router()` 호출 등에서 사용될 수 있습니다. + +FastAPI는 여전히 이를 오버라이드할 수 있습니다. + +/// + +그런 다음, `app.dependency_overrides`를 빈 `dict`로 설정하여 오버라이드를 재설정(제거)할 수 있습니다: + +```python +app.dependency_overrides = {} +``` + +/// tip | 팁 + +특정 테스트에서만 의존성을 오버라이드하고 싶다면, 테스트 시작 시(테스트 함수 내부) 오버라이드를 설정하고 테스트 종료 시(테스트 함수 끝부분) 재설정하면 됩니다. + +/// diff --git a/docs/ko/docs/advanced/websockets.md b/docs/ko/docs/advanced/websockets.md new file mode 100644 index 000000000..fa60a428b --- /dev/null +++ b/docs/ko/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# WebSockets + +여러분은 **FastAPI**에서 WebSockets를 사용할 수 있습니다. + +## `WebSockets` 설치 + +[가상 환경](../virtual-environments.md){.internal-link target=_blank)를 생성하고 활성화한 다음, `websockets`를 설치하세요: + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## WebSockets 클라이언트 + +### 프로덕션 환경에서 + +여러분의 프로덕션 시스템에서는 React, Vue.js 또는 Angular와 같은 최신 프레임워크로 생성된 프런트엔드를 사용하고 있을 가능성이 높습니다. + +백엔드와 WebSockets을 사용해 통신하려면 아마도 프런트엔드의 유틸리티를 사용할 것입니다. + +또는 네이티브 코드로 WebSocket 백엔드와 직접 통신하는 네이티브 모바일 응용 프로그램을 가질 수도 있습니다. + +혹은 WebSocket 엔드포인트와 통신할 수 있는 다른 방법이 있을 수도 있습니다. + +--- + +하지만 이번 예제에서는 일부 자바스크립트를 포함한 간단한 HTML 문서를 사용하겠습니다. 모든 것을 긴 문자열 안에 넣습니다. + +물론, 이는 최적의 방법이 아니며 프로덕션 환경에서는 사용하지 않을 것입니다. + +프로덕션 환경에서는 위에서 설명한 옵션 중 하나를 사용하는 것이 좋습니다. + +그러나 이는 WebSockets의 서버 측에 집중하고 동작하는 예제를 제공하는 가장 간단한 방법입니다: + +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} + +## `websocket` 생성하기 + +**FastAPI** 응용 프로그램에서 `websocket`을 생성합니다: + +{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} + +/// note | 기술적 세부사항 + +`from starlette.websockets import WebSocket`을 사용할 수도 있습니다. + +**FastAPI**는 개발자를 위한 편의를 위해 동일한 `WebSocket`을 직접 제공합니다. 하지만 이는 Starlette에서 가져옵니다. + +/// + +## 메시지를 대기하고 전송하기 + +WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다. + +{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} + +여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다. + +## 시도해보기 + +파일 이름이 `main.py`라고 가정하고 응용 프로그램을 실행합니다: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +브라우저에서 http://127.0.0.1:8000을 열어보세요. + +간단한 페이지가 나타날 것입니다: + + + +입력창에 메시지를 입력하고 전송할 수 있습니다: + + + +**FastAPI** WebSocket 응용 프로그램이 응답을 돌려줄 것입니다: + + + +여러 메시지를 전송(그리고 수신)할 수 있습니다: + + + +모든 메시지는 동일한 WebSocket 연결을 사용합니다. + +## `Depends` 및 기타 사용하기 + +WebSocket 엔드포인트에서 `fastapi`에서 다음을 가져와 사용할 수 있습니다: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +이들은 다른 FastAPI 엔드포인트/*경로 작동*과 동일하게 동작합니다: + +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} + +/// info | 정보 + +WebSocket에서는 `HTTPException`을 발생시키는 것이 적합하지 않습니다. 대신 `WebSocketException`을 발생시킵니다. + +명세서에 정의된 유효한 코드를 사용하여 종료 코드를 설정할 수 있습니다. + +/// + +### 종속성을 가진 WebSockets 테스트 + +파일 이름이 `main.py`라고 가정하고 응용 프로그램을 실행합니다: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +브라우저에서 http://127.0.0.1:8000을 열어보세요. + +다음과 같은 값을 설정할 수 있습니다: + +* 경로에 사용된 "Item ID". +* 쿼리 매개변수로 사용된 "Token". + +/// tip | 팁 + +쿼리 `token`은 종속성에 의해 처리됩니다. + +/// + +이제 WebSocket에 연결하고 메시지를 전송 및 수신할 수 있습니다: + + + +## 연결 해제 및 다중 클라이언트 처리 + +WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDisconnect` 예외를 발생시킵니다. 이를 잡아 처리할 수 있습니다: + +{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} + +테스트해보기: + +* 여러 브라우저 탭에서 앱을 엽니다. +* 각 탭에서 메시지를 작성합니다. +* 한 탭을 닫아보세요. + +`WebSocketDisconnect` 예외가 발생하며, 다른 모든 클라이언트가 다음과 같은 메시지를 수신합니다: + +``` +Client #1596980209979 left the chat +``` + +/// tip | 팁 + +위 응용 프로그램은 여러 WebSocket 연결에 메시지를 브로드캐스트하는 방법을 보여주는 간단한 예제입니다. + +그러나 모든 것을 메모리의 단일 리스트로 처리하므로, 프로세스가 실행 중인 동안만 동작하며 단일 프로세스에서만 작동합니다. + +FastAPI와 쉽게 통합할 수 있으면서 더 견고하고 Redis, PostgreSQL 등을 지원하는 도구를 찾고 있다면, encode/broadcaster를 확인하세요. + +/// + +## 추가 정보 + +다음 옵션에 대한 자세한 내용을 보려면 Starlette의 문서를 확인하세요: + +* `WebSocket` 클래스. +* 클래스 기반 WebSocket 처리. diff --git a/docs/zh-hant/docs/async.md b/docs/zh-hant/docs/async.md new file mode 100644 index 000000000..6ab75d2ab --- /dev/null +++ b/docs/zh-hant/docs/async.md @@ -0,0 +1,442 @@ +# 並行與 async / await + +有關*路徑操作函式*的 `async def` 語法的細節與非同步 (asynchronous) 程式碼、並行 (concurrency) 與平行 (parallelism) 的一些背景知識。 + +## 趕時間嗎? + +TL;DR: + +如果你正在使用要求你以 `await` 語法呼叫的第三方函式庫,例如: + +```Python +results = await some_library() +``` + +然後,使用 `async def` 宣告你的*路徑操作函式*: + + +```Python hl_lines="2" +@app.get('/') +async def read_results(): + results = await some_library() + return results +``` + +/// note | 注意 + +你只能在 `async def` 建立的函式內使用 `await`。 + +/// + +--- + +如果你使用的是第三方函式庫並且它需要與某些外部資源(例如資料庫、API、檔案系統等)進行通訊,但不支援 `await`(目前大多數資料庫函式庫都是這樣),在這種情況下,你可以像平常一樣使用 `def` 宣告*路徑操作函式*,如下所示: + +```Python hl_lines="2" +@app.get('/') +def results(): + results = some_library() + return results +``` + +--- + +如果你的應用程式不需要與外部資源進行任何通訊並等待其回應,請使用 `async def`。 + +--- + +如果你不確定該用哪個,直接用 `def` 就好。 + +--- + +**注意**:你可以在*路徑操作函式*中混合使用 `def` 和 `async def` ,並使用最適合你需求的方式來定義每個函式。FastAPI 會幫你做正確的處理。 + +無論如何,在上述哪種情況下,FastAPI 仍將以非同步方式運行,並且速度非常快。 + +但透過遵循上述步驟,它將能進行一些效能最佳化。 + +## 技術細節 + +現代版本的 Python 支援使用 **「協程」** 的 **`async` 和 `await`** 語法來寫 **「非同步程式碼」**。 + +接下來我們逐一介紹: + +* **非同步程式碼** +* **`async` 和 `await`** +* **協程** + +## 非同步程式碼 + +非同步程式碼僅意味著程式語言 💬 有辦法告訴電腦/程式 🤖 在程式碼中的某個點,它 🤖 需要等待某些事情完成。讓我們假設這些事情被稱為「慢速檔案」📝。 + +因此,在等待「慢速檔案」📝 完成的這段時間,電腦可以去處理一些其他工作。 + +接著程式 🤖 會在有空檔時回來查看是否有等待的工作已經完成,並執行必要的後續操作。 + +接下來,它 🤖 完成第一個工作(例如我們的「慢速檔案」📝)並繼續執行相關的所有操作。 +這個「等待其他事情」通常指的是一些相對較慢的(與處理器和 RAM 記憶體的速度相比)的 I/O 操作,比如說: + +* 透過網路傳送來自用戶端的資料 +* 從網路接收來自用戶端的資料 +* 從磁碟讀取檔案內容 +* 將內容寫入磁碟 +* 遠端 API 操作 +* 資料庫操作 +* 資料庫查詢 +* 等等 + +由於大部分的執行時間都消耗在等待 I/O 操作上,因此這些操作被稱為 "I/O 密集型" 操作。 + +之所以稱為「非同步」,是因為電腦/程式不需要與那些耗時的任務「同步」,等待任務完成的精確時間,然後才能取得結果並繼續工作。 + +相反地,非同步系統在任務完成後,可以讓任務稍微等一下(幾微秒),等待電腦/程式完成手頭上的其他工作,然後再回來取得結果繼續進行。 + +相對於「非同步」(asynchronous),「同步」(synchronous)也常被稱作「順序性」(sequential),因為電腦/程式會依序執行所有步驟,即便這些步驟涉及等待,才會切換到其他任務。 + +### 並行與漢堡 + +上述非同步程式碼的概念有時也被稱為「並行」,它不同於「平行」。 + +並行和平行都與 "不同的事情或多或少同時發生" 有關。 + +但並行和平行之間的細節是完全不同的。 + +為了理解差異,請想像以下有關漢堡的故事: + +### 並行漢堡 + +你和你的戀人去速食店,排隊等候時,收銀員正在幫排在你前面的人點餐。😍 + + + +輪到你了,你給你與你的戀人點了兩個豪華漢堡。🍔🍔 + + + +收銀員通知廚房準備你的漢堡(儘管他們還在為前面其他顧客準備食物)。 + + + +之後你完成付款。💸 + +收銀員給你一個號碼牌。 + + + +在等待漢堡的同時,你可以與戀人選一張桌子,然後坐下來聊很長一段時間(因為漢堡十分豪華,準備特別費工。) + +這段時間,你還能欣賞你的戀人有多麼的可愛、聰明與迷人。✨😍✨ + + + +當你和戀人邊聊天邊等待時,你會不時地查看櫃檯上的顯示的號碼,確認是否已經輪到你了。 + +然後在某個時刻,終於輪到你了。你走到櫃檯,拿了漢堡,然後回到桌子上。 + + + +你和戀人享用這頓大餐,整個過程十分開心✨ + + + +/// info + +漂亮的插畫來自 Ketrina Thompson. 🎨 + +/// + +--- + +想像你是故事中的電腦或程式 🤖。 + +當你排隊時,你在放空😴,等待輪到你,沒有做任何「生產性」的事情。但這沒關係,因為收銀員只是接單(而不是準備食物),所以排隊速度很快。 + +然後,當輪到你時,你開始做真正「有生產力」的工作,處理菜單,決定你想要什麼,替戀人選擇餐點,付款,確認你給了正確的帳單或信用卡,檢查你是否被正確收費,確認訂單中的項目是否正確等等。 + +但是,即使你還沒有拿到漢堡,你與收銀員的工作已經「暫停」了 ⏸,因為你必須等待 🕙 漢堡準備好。 + +但當你離開櫃檯,坐到桌子旁,拿著屬於你的號碼等待時,你可以把注意力 🔀 轉移到戀人身上,並開始「工作」⏯ 🤓——也就是和戀人調情 😍。這時你又開始做一些非常「有生產力」的事情。 + +接著,收銀員 💁 將你的號碼顯示在櫃檯螢幕上,並告訴你「漢堡已經做好了」。但你不會瘋狂地立刻跳起來,因為顯示的號碼變成了你的。你知道沒有人會搶走你的漢堡,因為你有自己的號碼,他們也有他們的號碼。 + +所以你會等戀人講完故事(完成當前的工作 ⏯/正在進行的任務 🤓),然後微笑著溫柔地說你要去拿漢堡了 ⏸。 + +然後你走向櫃檯 🔀,回到已經完成的最初任務 ⏯,拿起漢堡,說聲謝謝,並帶回桌上。這就結束了與櫃檯的互動步驟/任務 ⏹,接下來會產生一個新的任務,「吃漢堡」 🔀 ⏯,而先前的「拿漢堡」任務已經完成了 ⏹。 + +### 平行漢堡 + +現在,讓我們來想像這裡不是「並行漢堡」,而是「平行漢堡」。 + +你和戀人一起去吃平行的速食餐。 + +你們站在隊伍中,前面有幾位(假設有 8 位)既是收銀員又是廚師的員工,他們同時接單並準備餐點。 + +所有排在你前面的人都在等著他們的漢堡準備好後才會離開櫃檯,因為每位收銀員在接完單後,馬上會去準備漢堡,然後才回來處理下一個訂單。 + + + +終於輪到你了,你為你和你的戀人點了兩個非常豪華的漢堡。 + +你付款了 💸。 + + + +收銀員走進廚房準備食物。 + +你站在櫃檯前等待 🕙,以免其他人先拿走你的漢堡,因為這裡沒有號碼牌系統。 + + + +由於你和戀人都忙著不讓別人搶走你的漢堡,等漢堡準備好時,你根本無法專心和戀人互動。😞 + +這是「同步」(synchronous)工作,你和收銀員/廚師 👨‍🍳 是「同步化」的。你必須等到 🕙 收銀員/廚師 👨‍🍳 完成漢堡並交給你的那一刻,否則別人可能會拿走你的餐點。 + + + +最終,經過長時間的等待 🕙,收銀員/廚師 👨‍🍳 拿著漢堡回來了。 + + + +你拿著漢堡,和你的戀人回到餐桌。 + +你們僅僅是吃完漢堡,然後就結束了。⏹ + + + +整個過程中沒有太多的談情說愛,因為大部分時間 🕙 都花在櫃檯前等待。😞 + +/// info + +漂亮的插畫來自 Ketrina Thompson. 🎨 + +/// + +--- + +在這個平行漢堡的情境下,你是一個程式 🤖 且有兩個處理器(你和戀人),兩者都在等待 🕙 並專注於等待櫃檯上的餐點 🕙,等待的時間非常長。 + +這家速食店有 8 個處理器(收銀員/廚師)。而並行漢堡店可能只有 2 個處理器(一位收銀員和一位廚師)。 + +儘管如此,最終的體驗並不是最理想的。😞 + +--- + +這是與漢堡類似的故事。🍔 + +一個更「現實」的例子,想像一間銀行。 + +直到最近,大多數銀行都有多位出納員 👨‍💼👨‍💼👨‍💼👨‍💼,以及一條長長的隊伍 🕙🕙🕙🕙🕙🕙🕙🕙。 + +所有的出納員都在一個接一個地滿足每位客戶的所有需求 👨‍💼⏯。 + +你必須長時間排隊 🕙,不然就會失去機會。 + +所以,你不會想帶你的戀人 😍 一起去銀行辦事 🏦。 + +### 漢堡結論 + +在「和戀人一起吃速食漢堡」的這個場景中,由於有大量的等待 🕙,使用並行系統 ⏸🔀⏯ 更有意義。 + +這也是大多數 Web 應用的情況。 + +許多用戶正在使用你的應用程式,而你的伺服器則在等待 🕙 這些用戶不那麼穩定的網路來傳送請求。 + +接著,再次等待 🕙 回應。 + +這種「等待」 🕙 通常以微秒來衡量,但累加起來,最終還是花費了很多等待時間。 + +這就是為什麼對於 Web API 來說,使用非同步程式碼 ⏸🔀⏯ 是非常有意義的。 + +這種類型的非同步性正是 NodeJS 成功的原因(儘管 NodeJS 不是平行的),這也是 Go 語言作為程式語言的一個強大優勢。 + +這與 **FastAPI** 所能提供的性能水平相同。 + +你可以同時利用並行性和平行性,進一步提升效能,這比大多數已測試的 NodeJS 框架都更快,並且與 Go 語言相當,而 Go 是一種更接近 C 的編譯語言(感謝 Starlette)。 + +### 並行比平行更好嗎? + +不是的!這不是故事的本意。 + +並行與平行不同。並行在某些 **特定** 的需要大量等待的情境下表現更好。正因如此,並行在 Web 應用程式開發中通常比平行更有優勢。但並不是所有情境都如此。 + +因此,為了平衡報導,想像下面這個短故事 + +> 你需要打掃一間又大又髒的房子。 + +*是的,這就是全部的故事。* + +--- + +這裡沒有任何需要等待 🕙 的地方,只需要在房子的多個地方進行大量的工作。 + +你可以像漢堡的例子那樣輪流進行,先打掃客廳,再打掃廚房,但由於你不需要等待 🕙 任何事情,只需要持續地打掃,輪流並不會影響任何結果。 + +無論輪流執行與否(並行),你都需要相同的工時完成任務,同時需要執行相同工作量。 + +但是,在這種情境下,如果你可以邀請8位前收銀員/廚師(現在是清潔工)來幫忙,每個人(加上你)負責房子的某個區域,這樣你就可以 **平行** 地更快完成工作。 + +在這個場景中,每個清潔工(包括你)都是一個處理器,完成工作的一部分。 + +由於大多數的執行時間都花在實際的工作上(而不是等待),而電腦中的工作由 CPU 完成,因此這些問題被稱為「CPU 密集型」。 + +--- + +常見的 CPU 密集型操作範例包括那些需要進行複雜數學計算的任務。 + +例如: + +* **音訊**或**圖像處理**; +* **電腦視覺**:一張圖片由數百萬個像素組成,每個像素有 3 個值/顏色,處理這些像素通常需要同時進行大量計算; +* **機器學習**: 通常需要大量的「矩陣」和「向量」運算。想像一個包含數字的巨大電子表格,並所有的數字同時相乘; +* **深度學習**: 這是機器學習的子領域,同樣適用。只不過這不僅僅是一張數字表格,而是大量的數據集合,並且在很多情況下,你會使用特殊的處理器來構建或使用這些模型。 + +### 並行 + 平行: Web + 機器學習 + +使用 **FastAPI**,你可以利用並行的優勢,這在 Web 開發中非常常見(這也是 NodeJS 的最大吸引力)。 + +但你也可以利用平行與多行程 (multiprocessing)(讓多個行程同時運行) 的優勢來處理機器學習系統中的 **CPU 密集型**工作。 + +這一點,再加上 Python 是 **資料科學**、機器學習,尤其是深度學習的主要語言,讓 **FastAPI** 成為資料科學/機器學習 Web API 和應用程式(以及許多其他應用程式)的絕佳選擇。 + +想了解如何在生產環境中實現這種平行性,請參見 [部屬](deployment/index.md){.internal-link target=_blank}。 + +## `async` 和 `await` + +現代 Python 版本提供一種非常直觀的方式定義非同步程式碼。這使得它看起來就像正常的「順序」程式碼,並在適當的時機「等待」。 + +當某個操作需要等待才能回傳結果,並且支援這些新的 Python 特性時,你可以像這樣編寫程式碼: + +```Python +burgers = await get_burgers(2) +``` + +這裡的關鍵是 `await`。它告訴 Python 必須等待 ⏸ `get_burgers(2)` 完成它的工作 🕙, 然後將結果儲存在 `burgers` 中。如此,Python 就可以在此期間去處理其他事情 🔀 ⏯ (例如接收另一個請求)。 + +要讓 `await` 運作,它必須位於支持非同步功能的函式內。為此,只需使用 `async def` 宣告函式: + +```Python hl_lines="1" +async def get_burgers(number: int): + # Do some asynchronous stuff to create the burgers + return burgers +``` + +...而不是 `def`: + +```Python hl_lines="2" +# This is not asynchronous +def get_sequential_burgers(number: int): + # Do some sequential stuff to create the burgers + return burgers +``` + +使用 `async def`,Python Python 知道在該函式內需要注意 `await`,並且它可以「暫停」 ⏸ 執行該函式,然後執行其他任務 🔀 後回來。 + +當你想要呼叫 `async def` 函式時,必須使用「await」。因此,這樣寫將無法運行: + +```Python +# This won't work, because get_burgers was defined with: async def +burgers = get_burgers(2) +``` + +--- + +如果你正在使用某個函式庫,它告訴你可以使用 `await` 呼叫它,那麼你需要用 `async def` 定義*路徑操作函式*,如: + +```Python hl_lines="2-3" +@app.get('/burgers') +async def read_burgers(): + burgers = await get_burgers(2) + return burgers +``` + +### 更多技術細節 + +你可能已經注意到,`await` 只能在 `async def` 定義的函式內使用。 + +但同時,使用 `async def` 定義的函式本身也必須被「等待」。所以,帶有 `async def` 函式只能在其他使用 `async def` 定義的函式內呼叫。 + +那麼,這就像「先有雞還是先有蛋」的問題,要如何呼叫第一個 `async` 函式呢? + +如果你使用 FastAPI,無需擔心這個問題,因為「第一個」函式將是你的*路徑操作函式*,FastAPI 會知道如何正確處理這個問題。 + +但如果你想在沒有 FastAPI 的情況下使用 `async` / `await`,你也可以這樣做。 + +### 編寫自己的非同步程式碼 + +Starlette (和 **FastAPI**) 是基於 AnyIO 實作的,這使得它們與 Python 標準函式庫相容 asyncioTrio。 + +特別是,你可以直接使用 AnyIO 來處理更複雜的並行使用案例,這些案例需要你在自己的程式碼中使用更高階的模式。 + +即使你不使用 **FastAPI**,你也可以使用 AnyIO 來撰寫自己的非同步應用程式,並獲得高相容性及一些好處(例如結構化並行)。 + +### 其他形式的非同步程式碼 + +使用 `async` 和 `await` 的風格在語言中相對較新。 + +但它使處理異步程式碼變得更加容易。 + +相同的語法(或幾乎相同的語法)最近也被包含在現代 JavaScript(無論是瀏覽器還是 NodeJS)中。 + +但在此之前,處理異步程式碼要更加複雜和困難。 + +在較舊的 Python 版本中,你可能會使用多執行緒或 Gevent。但這些程式碼要更難以理解、調試和思考。 + +在較舊的 NodeJS / 瀏覽器 JavaScript 中,你會使用「回呼」,這可能會導致回呼地獄。 + +## 協程 + +**協程** 只是 `async def` 函式所回傳的非常特殊的事物名稱。Python 知道它是一個類似函式的東西,可以啟動它,並且在某個時刻它會結束,但它也可能在內部暫停 ⏸,只要遇到 `await`。 + +這種使用 `async` 和 `await` 的非同步程式碼功能通常被概括為「協程」。這與 Go 語言的主要特性「Goroutines」相似。 + +## 結論 + +讓我們再次回顧之前的句子: + +> 現代版本的 Python 支持使用 **"協程"** 的 **`async` 和 `await`** 語法來寫 **"非同步程式碼"**。 + +現在應該能明白其含意了。✨ + +這些就是驅動 FastAPI(通過 Starlette)運作的原理,也讓它擁有如此驚人的效能。 + +## 非常技術性的細節 + +/// warning + +你大概可以跳過這段。 + +這裡是有關 FastAPI 內部技術細節。 + +如果你有相當多的技術背景(例如協程、執行緒、阻塞等),並且對 FastAPI 如何處理 `async def` 與常規 `def` 感到好奇,請繼續閱讀。 + +/// + +### 路徑操作函数 + +當你使用 `def` 而不是 `async def` 宣告*路徑操作函式*時,該函式會在外部的執行緒池(threadpool)中執行,然後等待結果,而不是直接呼叫(因為這樣會阻塞伺服器)。 + +如果你來自於其他不以這種方式運作的非同步框架,而且你習慣於使用普通的 `def` 定義僅進行簡單計算的*路徑操作函式*,目的是獲得微小的性能增益(大約 100 奈秒),請注意,在 FastAPI 中,效果會完全相反。在這些情況下,最好使用 `async def`除非你的*路徑操作函式*執行阻塞的 I/O 的程式碼。 + +不過,在這兩種情況下,**FastAPI** [仍然很快](index.md#_11){.internal-link target=_blank}至少與你之前的框架相當(或者更快)。 + +### 依賴項(Dependencies) + +同樣適用於[依賴項](tutorial/dependencies/index.md){.internal-link target=_blank}。如果依賴項是一個標準的 `def` 函式,而不是 `async def`,那麼它在外部的執行緒池被運行。 + +### 子依賴項 + +你可以擁有多個相互依賴的依賴項和[子依賴項](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} (作為函式定義的參數),其中一些可能是用 `async def` 宣告,也可能是用 `def` 宣告。它們仍然可以正常運作,用 `def` 定義的那些將會在外部的執行緒中呼叫(來自執行緒池),而不是被「等待」。 + +### 其他輔助函式 + +你可以直接呼叫任何使用 `def` 或 `async def` 建立的其他輔助函式,FastAPI 不會影響你呼叫它們的方式。 + +這與 FastAPI 為你呼叫*路徑操作函式*和依賴項的邏輯有所不同。 + +如果你的輔助函式是用 `def` 宣告的,它將會被直接呼叫(按照你在程式碼中撰寫的方式),而不是在執行緒池中。如果該函式是用 `async def` 宣告,那麼你在呼叫時應該使用 `await` 等待其結果。 + +--- + +再一次強調,這些都是非常技術性的細節,如果你特地在尋找這些資訊,這些內容可能會對你有幫助。 + +否則,只需遵循上面提到的指引即可:趕時間嗎?. diff --git a/docs/zh-hant/docs/tutorial/query-param-models.md b/docs/zh-hant/docs/tutorial/query-param-models.md new file mode 100644 index 000000000..76ee74016 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/query-param-models.md @@ -0,0 +1,68 @@ +# 查詢參數模型 + +如果你有一組具有相關性的**查詢參數**,你可以建立一個 **Pydantic 模型**來聲明它們。 + +這將允許你在**多個地方**去**重複使用模型**,並且一次性為所有參數聲明驗證和元資料 (metadata)。😎 + +/// note + +FastAPI 從 `0.115.0` 版本開始支援這個特性。🤓 + +/// + +## 使用 Pydantic 模型的查詢參數 + +在一個 **Pydantic 模型**中聲明你需要的**查詢參數**,然後將參數聲明為 `Query`: + +{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} + +**FastAPI** 將會從請求的**查詢參數**中**提取**出**每個欄位**的資料,並將其提供給你定義的 Pydantic 模型。 + +## 查看文件 + +你可以在 `/docs` 頁面的 UI 中查看查詢參數: + +
+ +
+ +## 禁止額外的查詢參數 + +在一些特殊的使用場景中(可能不是很常見),你可能希望**限制**你要收到的查詢參數。 + +你可以使用 Pydantic 的模型設定來 `forbid`(禁止)任何 `extra`(額外)欄位: + +{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} + +如果客戶端嘗試在**查詢參數**中發送一些**額外的**資料,他們將會收到一個**錯誤**回應。 + +例如,如果客戶端嘗試發送一個值為 `plumbus` 的 `tool` 查詢參數,如: + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +他們將收到一個**錯誤**回應,告訴他們查詢參數 `tool` 是不允許的: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## 總結 + +你可以使用 **Pydantic 模型**在 **FastAPI** 中聲明**查詢參數**。😎 + +/// tip + +劇透警告:你也可以使用 Pydantic 模型來聲明 cookie 和 headers,但你將在本教學的後面部分閱讀到這部分內容。🤫 + +/// diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 12faea987..823957822 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.115.5" +__version__ = "0.115.6" from starlette import status as status diff --git a/fastapi/concurrency.py b/fastapi/concurrency.py index 894bd3ed1..3202c7078 100644 --- a/fastapi/concurrency.py +++ b/fastapi/concurrency.py @@ -1,7 +1,7 @@ from contextlib import asynccontextmanager as asynccontextmanager from typing import AsyncGenerator, ContextManager, TypeVar -import anyio +import anyio.to_thread from anyio import CapacityLimiter from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa @@ -28,7 +28,7 @@ async def contextmanager_in_threadpool( except Exception as e: ok = bool( await anyio.to_thread.run_sync( - cm.__exit__, type(e), e, None, limiter=exit_limiter + cm.__exit__, type(e), e, e.__traceback__, limiter=exit_limiter ) ) if not ok: diff --git a/tests/test_exception_handlers.py b/tests/test_exception_handlers.py index 67a4becec..6a3cbd830 100644 --- a/tests/test_exception_handlers.py +++ b/tests/test_exception_handlers.py @@ -1,5 +1,5 @@ import pytest -from fastapi import FastAPI, HTTPException +from fastapi import Depends, FastAPI, HTTPException from fastapi.exceptions import RequestValidationError from fastapi.testclient import TestClient from starlette.responses import JSONResponse @@ -28,6 +28,18 @@ app = FastAPI( client = TestClient(app) +def raise_value_error(): + raise ValueError() + + +def dependency_with_yield(): + yield raise_value_error() + + +@app.get("/dependency-with-yield", dependencies=[Depends(dependency_with_yield)]) +def with_yield(): ... + + @app.get("/http-exception") def route_with_http_exception(): raise HTTPException(status_code=400) @@ -65,3 +77,12 @@ def test_override_server_error_exception_response(): response = client.get("/server-error") assert response.status_code == 500 assert response.json() == {"exception": "server-error"} + + +def test_traceback_for_dependency_with_yield(): + client = TestClient(app, raise_server_exceptions=True) + with pytest.raises(ValueError) as exc_info: + client.get("/dependency-with-yield") + last_frame = exc_info.traceback[-1] + assert str(last_frame.path) == __file__ + assert last_frame.lineno == raise_value_error.__code__.co_firstlineno diff --git a/tests/test_fastapi_cli.py b/tests/test_fastapi_cli.py index 20c928157..a5c10778a 100644 --- a/tests/test_fastapi_cli.py +++ b/tests/test_fastapi_cli.py @@ -22,7 +22,7 @@ def test_fastapi_cli(): encoding="utf-8", ) assert result.returncode == 1, result.stdout - assert "Using path non_existent_file.py" in result.stdout + assert "Path does not exist non_existent_file.py" in result.stdout def test_fastapi_cli_not_installed():