FastAPI 類別作為依賴項 (Class Dependencies)
FastAPI 允許你使用類別來定義依賴項,這在很多情況下比函數更方便。
使用類別定義依賴
讓我們把之前的分頁參數範例改用類別來寫:
from fastapi import FastAPI, Depends
app = FastAPI()
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
在路由中使用類別依賴
使用方式幾乎一樣,只是把函數換成類別:
from typing import Annotated
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
注意到了嗎?這裡有兩個 CommonQueryParams:
commons: CommonQueryParams:這是 型別提示 (Type Hint)。告訴編輯器commons是這個類別的實例,這樣你打commons.時就會有自動補全 (如.q,.skip)。Depends(CommonQueryParams):這是告訴 FastAPI 使用這個類別來產生依賴值。
簡化寫法 (Shortcut)
FastAPI 提供了一個簡化寫法。如果型別提示的類別和 Depends 裡的類別是一樣的,你可以讓 Depends 留空:
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
# FastAPI 會自動推斷:啊,你需要 CommonQueryParams,那我去找它的 __init__
return commons
這讓程式碼更加簡潔,同時保有完整的型別檢查功能。
運作原理
- FastAPI 分析
CommonQueryParams的__init__方法。 - 發現它需要
q,skip,limit參數。 - 從 HTTP 請求 (Query Parameters) 中擷取這些值。
- 建立
CommonQueryParams的實例 (Instance)。 - 將這個實例傳入
read_items函數。
類別依賴的優勢
相較於函數回傳 dict,使用類別的優勢在於:
- 型別安全:不再是操作寬鬆的字典,而是操作有明確屬性的物件。
- 重用性:這個類別可以在多個地方被重用,甚至可以用繼承來擴展功能。
可呼叫實例 (Callable Instance)
有時候你希望依賴項能夠根據不同的場景進行「參數設定」。在 Python 中,你可以透過實作 __call__ 魔術方法,讓一個類別的「實例 (Instance)」變得像函數一樣可以被呼叫。
這在需要動態設定驗證邏輯時非常有用,例如:針對不同的 API 路徑檢查不同的使用者角色。
實務範例:權限角色檢查器
from fastapi import Depends, HTTPException, status
class RoleChecker:
def __init__(self, allowed_roles: list[str]):
# 在初始化時設定允許的角色
self.allowed_roles = allowed_roles
def __call__(self, user: Annotated[User, Depends(get_current_user)]):
# 當依賴被執行時,會呼叫這裡
if user.role not in self.allowed_roles:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="權限不足"
)
return user
# 建立不同權限的依賴項實例
allow_admin = RoleChecker(["admin"])
allow_editor = RoleChecker(["admin", "editor"])
@app.get("/admin-only")
async def admin_portal(user: Annotated[User, Depends(allow_admin)]):
return {"message": "歡迎管理員"}
@app.get("/editor-only")
async def editor_portal(user: Annotated[User, Depends(allow_editor)]):
return {"message": "歡迎編輯者或管理員"}
為什麼要這樣做?
- 參數化:你不需要為每個角色都寫一個獨立的依賴函數,只要建立不同的實例即可。
- 封裝性:驗證邏輯被封裝在類別中,程式碼更加結構化。
- 靈活性:這在開發大型系統(具備複雜 RBAC 權限控管)時是非常強大的技巧。
總結
- 可以定義
Class作為依賴項,參數定義在__init__中。 - 使用
Depends(MyClass)來注入。 - 若型別提示與依賴相同,可簡寫為
Depends()。 - 獲得更好的編輯器自動補全支援。