更大的應用程式 - 多個檔案¶
如果您正在建構應用程式或 Web API,很少會將所有程式碼都放在單一檔案中。
FastAPI 提供了一個便利的工具來組織您的應用程式,同時保持所有彈性。
資訊
如果您來自 Flask,這相當於 Flask 的藍圖(Blueprints)。
檔案結構範例¶
假設您的檔案結構如下:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
提示
有幾個 __init__.py
檔案:每個目錄或子目錄中都有一個。
這就是允許將程式碼從一個檔案導入到另一個檔案的原因。
例如,在 app/main.py
中,您可以有一行程式碼,例如:
from app.routers import items
app
目錄包含所有內容。它有一個空的檔案app/__init__.py
,所以它是一個「Python 套件」(「Python 模組」的集合):app
。- 它包含一個
app/main.py
檔案。由於它位於 Python 套件內(帶有__init__.py
檔案的目錄),因此它是該套件的「模組」:app.main
。 - 還有一個
app/dependencies.py
檔案,就像app/main.py
一樣,它是一個「模組」:app.dependencies
。 - 有一個子目錄
app/routers/
,其中包含另一個檔案__init__.py
,因此它是一個「Python 子套件」:app.routers
。 - 檔案
app/routers/items.py
位於套件app/routers/
內,因此它是一個子模組:app.routers.items
。 app/routers/users.py
也一樣,它是另一個子模組:app.routers.users
。- 還有一個子目錄
app/internal/
,其中包含另一個檔案__init__.py
,因此它是另一個「Python 子套件」:app.internal
。 - 檔案
app/internal/admin.py
是另一個子模組:app.internal.admin
。
帶有註釋的相同檔案結構
.
├── app # "app" is a Python package
│ ├── __init__.py # this file makes "app" a "Python package"
│ ├── main.py # "main" module, e.g. import app.main
│ ├── dependencies.py # "dependencies" module, e.g. import app.dependencies
│ └── routers # "routers" is a "Python subpackage"
│ │ ├── __init__.py # makes "routers" a "Python subpackage"
│ │ ├── items.py # "items" submodule, e.g. import app.routers.items
│ │ └── users.py # "users" submodule, e.g. import app.routers.users
│ └── internal # "internal" is a "Python subpackage"
│ ├── __init__.py # makes "internal" a "Python subpackage"
│ └── admin.py # "admin" submodule, e.g. import app.internal.admin
APIRouter
¶
假設專用於處理使用者檔案的子模組位於 /app/routers/users.py
。
您希望將與使用者相關的*路徑操作*與程式碼的其餘部分分開,以保持程式碼井然有序。
但它仍然是同一個 FastAPI 應用程式/Web API 的一部分(它是同一個「Python 套件」的一部分)。
您可以使用 APIRouter
為該模組建立*路徑操作*。
導入 APIRouter
¶
您可以導入它並建立一個「實例」,就像使用 FastAPI
類別一樣。
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
使用 APIRouter
的*路徑操作*¶
然後您可以使用它來宣告您的*路徑操作*。
使用它的方式與使用 FastAPI
類別相同。
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
您可以將 APIRouter
視為一個「迷你 FastAPI
」類別。
所有相同的選項皆可使用。
所有相同的 參數
、回應
、依賴項
、標籤
等等。
提示
在此範例中,變數名稱為 router
,但您可以任意命名。
我們將把這個 APIRouter
包含在主要的 FastAPI
應用程式中,但在這之前,讓我們先檢查依賴項和另一個 APIRouter
。
依賴項¶
我們看到在應用程式的多個地方會用到一些依賴項。
因此,我們將它們放在自己的 dependencies
模組中 (app/dependencies.py
)。
我們現在將使用一個簡單的依賴項來讀取自訂的 X-Token
標頭
from typing import Annotated
from fastapi import Header, HTTPException
async def get_token_header(x_token: Annotated[str, Header()]):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def get_query_token(token: str):
if token != "jessica":
raise HTTPException(status_code=400, detail="No Jessica token provided")
from fastapi import Header, HTTPException
from typing_extensions import Annotated
async def get_token_header(x_token: Annotated[str, Header()]):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def get_query_token(token: str):
if token != "jessica":
raise HTTPException(status_code=400, detail="No Jessica token provided")
提示
如果可以,建議使用 Annotated
版本。
from fastapi import Header, HTTPException
async def get_token_header(x_token: str = Header()):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def get_query_token(token: str):
if token != "jessica":
raise HTTPException(status_code=400, detail="No Jessica token provided")
另一個包含 APIRouter
的模組¶
假設您在 app/routers/items.py
模組中也有專門處理應用程式「項目」的端點。
您有以下路徑操作:
/items/
/items/{item_id}
這與 app/routers/users.py
的結構完全相同。
但我們想要更聰明一點,並簡化程式碼。
我們知道此模組中的所有路徑操作都具有相同的
- 路徑
前綴
:/items
。 標籤
:(只有一個標籤:items
)。- 額外的
回應
。 依賴項
:它們都需要我們建立的X-Token
依賴項。
因此,我們可以將所有這些添加到 APIRouter
中,而不是添加到每個路徑操作中。
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
由於每個路徑操作的路徑都必須以 /
開頭,例如
@router.get("/{item_id}")
async def read_item(item_id: str):
...
...前綴不能包含結尾的 /
。
因此,此案例中的前綴是 /items
。
我們還可以添加一個 標籤
列表和額外的 回應
,這些將套用於此路由器中包含的所有路徑操作。
我們還可以添加一個 依賴項
列表,這些依賴項將添加到路由器中的所有路徑操作中,並將針對每個發送到它們的請求執行/解析。
提示
請注意,就像 路徑操作裝飾器中的依賴項 一樣,不會將任何值傳遞到您的路徑操作函式。
最終結果是項目路徑現在是
/items/
/items/{item_id}
...正如我們預期的那樣。
- 它們將被標記為包含單個字串
"items"
的標籤列表。- 這些「標籤」對於自動互動式文件系統(使用 OpenAPI)特別有用。
- 它們都將包含預定義的
回應
。 - 所有這些路徑操作都將在它們之前評估/執行
依賴項
列表。
提示
在 APIRouter
中設定 dependencies
可以用來,例如,對一整組的 *路徑操作* 要求驗證。即使沒有個別地將 dependencies 加入到每個路徑操作中。
檢查
prefix
、tags
、responses
和 dependencies
參數(如同許多其他情況)只是 FastAPI 的一個功能,可以幫助您避免程式碼重複。
匯入 dependencies¶
這段程式碼位於模組 app.routers.items
,也就是檔案 app/routers/items.py
。
我們需要從模組 app.dependencies
,也就是檔案 app/dependencies.py
取得 dependency 函式。
因此我們使用 ..
進行相對匯入 dependencies
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
相對匯入的運作方式¶
提示
如果您完全了解匯入的運作方式,請繼續閱讀下一節。
單點 .
,例如:
from .dependencies import get_token_header
意思是
- 從這個模組(檔案
app/routers/items.py
)所在的套件(目錄app/routers/
)開始… - 找到模組
dependencies
(一個假想的檔案,位於app/routers/dependencies.py
)… - 並從中匯入函式
get_token_header
。
但該檔案並不存在,我們的 dependencies 位於 app/dependencies.py
檔案中。
回想一下我們的 app/檔案結構
兩個點 ..
,例如:
from ..dependencies import get_token_header
意思是
- 從這個模組(檔案
app/routers/items.py
)所在的套件(目錄app/routers/
)開始… - 前往父套件(目錄
app/
)… - 並在其中找到模組
dependencies
(檔案位於app/dependencies.py
)… - 並從中匯入函式
get_token_header
。
這樣就能正常運作了!🎉
同樣地,如果我們使用三個點 ...
,例如:
from ...dependencies import get_token_header
那意思是
- 從這個模組(檔案
app/routers/items.py
)所在的套件(目錄app/routers/
)開始… - 前往父套件(目錄
app/
)… - 前往該套件的父套件(沒有父套件,
app
是最上層 😱)… - 並在其中找到模組
dependencies
(檔案位於app/dependencies.py
)… - 並從中匯入函式
get_token_header
。
這將會參考 app/
上方的某個套件,它有自己的檔案 __init__.py
等等。但在我們的例子中並沒有這樣的套件。所以,這會拋出一個錯誤。🚨
但現在您知道它的運作方式了,所以您可以在自己的應用程式中使用相對匯入,無論應用程式有多複雜。🤓
新增一些自訂的 tags
、responses
和 dependencies
¶
我們沒有將 prefix /items
和 tags=["items"]
加入到每個 *路徑操作*,因為我們已經將它們加入到 APIRouter
中了。
但我們仍然可以新增 *更多* 的 tags
,這些 tags 將會套用到特定的 *路徑操作*,也可以新增一些特定於該 *路徑操作* 的額外 responses
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
提示
最後一個路徑操作將會結合這些 tags:["items", "custom"]
。
而且在文件中也會同時顯示兩個 responses,一個是 404
,另一個是 403
。
主要的 FastAPI
¶
現在,讓我們看看 app/main.py
模組。
這裡是您匯入和使用 FastAPI
類別的地方。
這將會是您應用程式中的主要檔案,將所有東西連結在一起。
由於您的大部分邏輯現在都將位於其特定的模組中,因此主要檔案會相當簡潔。
匯入 FastAPI
¶
您可以像平常一樣匯入和建立 FastAPI
類別。
我們甚至可以宣告全域 dependencies,這些 dependencies 將會與每個 APIRouter
的 dependencies 結合。
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
匯入 APIRouter
¶
現在我們匯入其他具有 APIRouter
的子模組
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
由於檔案 app/routers/users.py
和 app/routers/items.py
是同一個 Python 套件 app
的子模組,我們可以使用單個點 .
來使用「相對導入」的方式導入它們。
導入機制¶
程式碼段落
from .routers import items, users
的含義是
- 從這個模組(檔案
app/main.py
)所在的套件(目錄app/
)開始… - 尋找子套件
routers
(位於app/routers/
的目錄)… - 並從中導入子模組
items
(檔案位於app/routers/items.py
)和users
(檔案位於app/routers/users.py
)…
模組 items
將有一個變數 router
(items.router
)。這與我們在檔案 app/routers/items.py
中建立的相同,它是一個 APIRouter
物件。
然後我們對模組 users
執行相同的操作。
我們也可以像這樣導入它們
from app.routers import items, users
資訊
第一個版本是「相對導入」
from .routers import items, users
第二個版本是「絕對導入」
from app.routers import items, users
要深入了解 Python 套件和模組,請閱讀關於模組的官方 Python 文件。
避免命名衝突¶
我們直接導入子模組 items
,而不是僅導入其變數 router
。
這是因為我們在子模組 users
中也有另一個名為 router
的變數。
如果我們像這樣一個接一個地導入它們
from .routers.items import router
from .routers.users import router
來自 users
的 router
將覆蓋來自 items
的 router
,我們將無法同時使用它們。
因此,為了能夠在同一個檔案中使用它們,我們直接導入子模組
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
包含 users
和 items
的 APIRouter
¶
現在,讓我們包含來自子模組 users
和 items
的 router
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
資訊
users.router
包含檔案 app/routers/users.py
中的 APIRouter
。
而 items.router
包含檔案 app/routers/items.py
中的 APIRouter
。
使用 app.include_router()
,我們可以將每個 APIRouter
添加到主要的 FastAPI
應用程式中。
它會將該路由器中的所有路由包含在內。
「技術細節」
它實際上會在內部為 APIRouter
中宣告的每個*路徑操作*建立一個*路徑操作*。
因此,在幕後,它的實際工作方式就像所有東西都在同一個應用程式中一樣。
檢查
包含路由器時,您不必擔心效能問題。
這只需要幾微秒,並且只會在啟動時發生。
因此它不會影響效能。⚡
包含具有自訂 prefix
、tags
、responses
和 dependencies
的 APIRouter
¶
現在,讓我們想像一下您的組織給您了 app/internal/admin.py
檔案。
它包含一個 APIRouter
,其中包含一些您的組織在多個專案之間共用的管理員*路徑操作*。
在本例中,它會非常簡單。但假設因為它與組織中的其他專案共用,我們無法修改它並直接向 APIRouter
添加 prefix
、dependencies
、tags
等。
from fastapi import APIRouter
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "Admin getting schwifty"}
但我們仍然希望在包含 APIRouter
時設定自訂 prefix
,以便其所有*路徑操作*都以 /admin
開頭,我們希望使用我們已經為這個專案準備好的 dependencies
來保護它,並且我們希望包含 tags
和 responses
。
我們可以透過將這些參數傳遞給 app.include_router()
來宣告所有這些內容,而無需修改原始的 APIRouter
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
這樣一來,原來的 APIRouter
將保持不變,因此我們仍然可以與組織中的其他專案共享同一個 app/internal/admin.py
檔案。
結果是在我們的應用程式中,來自 admin
模組的每個*路徑操作*都將具有
- 前綴
/admin
。 - 標籤
admin
。 - 依賴項
get_token_header
。 - 回應
418
。 🍵
但这只會影響我們應用程式中的 APIRouter
,而不會影響使用它的任何其他程式碼。
因此,例如,其他專案可以使用相同的 APIRouter
搭配不同的驗證方法。
包含*路徑操作*¶
我們也可以直接將*路徑操作*新增到 FastAPI
應用程式中。
我們在這裡這樣做...只是為了表明我們可以 🤷
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
它將與使用 app.include_router()
新增的所有其他*路徑操作*一起正常運作。
「非常技術性的細節」
注意:這是一個非常技術性的細節,您可能可以直接跳過。
APIRouter
並未「掛載」,它們與應用程式的其餘部分並未隔離。
這是因為我們希望將它們的*路徑操作*包含在 OpenAPI 綱要和使用者介面中。
由於我們不能僅將它們隔離並獨立於其餘部分「掛載」它們,因此*路徑操作*是被「複製」(重新建立)的,而不是直接包含的。
檢查自動 API 文件¶
現在,執行您的應用程式
$ fastapi dev app/main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
並在 http://127.0.0.1:8000/docs 開啟文件。
您將看到自動 API 文件,其中包含來自所有子模組的路徑,使用正確的路徑(和前綴)以及正確的標籤。
使用不同的 prefix
多次包含相同的路由器¶
您也可以使用不同的前綴多次使用 .include_router()
包含*相同的*路由器。
例如,這對於在不同的前綴(例如 /api/v1
和 /api/latest
)下公開相同的 API 很有用。
這是一種您可能並不需要的高級用法,但如果您需要,它就在這裡。
在另一個 APIRouter
中包含一個 APIRouter
¶
與您可以在 FastAPI
應用程式中包含 APIRouter
的方式相同,您可以使用以下方法在另一個 APIRouter
中包含一個 APIRouter
:
router.include_router(other_router)
確保您在將 router
包含在 FastAPI
應用程式中之前執行此操作,以便也包含來自 other_router
的*路徑操作*。