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

  1. commons: CommonQueryParams:這是 型別提示 (Type Hint)。告訴編輯器 commons 是這個類別的實例,這樣你打 commons. 時就會有自動補全 (如 .q, .skip)。
  2. Depends(CommonQueryParams):這是告訴 FastAPI 使用這個類別來產生依賴值。

簡化寫法 (Shortcut)

FastAPI 提供了一個簡化寫法。如果型別提示的類別和 Depends 裡的類別是一樣的,你可以讓 Depends 留空:

@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
    # FastAPI 會自動推斷:啊,你需要 CommonQueryParams,那我去找它的 __init__
    return commons

這讓程式碼更加簡潔,同時保有完整的型別檢查功能。

運作原理

  1. FastAPI 分析 CommonQueryParams__init__ 方法。
  2. 發現它需要 q, skip, limit 參數。
  3. 從 HTTP 請求 (Query Parameters) 中擷取這些值。
  4. 建立 CommonQueryParams 的實例 (Instance)。
  5. 將這個實例傳入 read_items 函數。

類別依賴的優勢

相較於函數回傳 dict,使用類別的優勢在於:

  1. 型別安全:不再是操作寬鬆的字典,而是操作有明確屬性的物件。
  2. 重用性:這個類別可以在多個地方被重用,甚至可以用繼承來擴展功能。

可呼叫實例 (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": "歡迎編輯者或管理員"}

為什麼要這樣做?

  1. 參數化:你不需要為每個角色都寫一個獨立的依賴函數,只要建立不同的實例即可。
  2. 封裝性:驗證邏輯被封裝在類別中,程式碼更加結構化。
  3. 靈活性:這在開發大型系統(具備複雜 RBAC 權限控管)時是非常強大的技巧。

總結

  • 可以定義 Class 作為依賴項,參數定義在 __init__ 中。
  • 使用 Depends(MyClass) 來注入。
  • 若型別提示與依賴相同,可簡寫為 Depends()
  • 獲得更好的編輯器自動補全支援。