本文 AI 產出,尚未審核

FastAPI 依賴注入系統:參數依賴(傳入 query / path / body)

簡介

FastAPI 中,依賴注入(Dependency Injection,簡稱 DI)是協助開發者把「取得資料」或「前置處理」的邏輯抽離出來、重複使用的核心機制。透過 DI,我們可以把 query stringpath parameterrequest body 等不同來源的參數,統一交給同一套函式或類別處理,讓路由函式只專注於業務邏輯本身。

對於 初學者 來說,直接在路由函式裡寫 request.query_paramsrequest.path_params 或手動解析 JSON,往往會導致程式碼冗長且難以測試。
中級開發者,則可以利用 DI 的「可呼叫物件」特性,將驗證、授權、快取等跨路由需求以 可組合可替換 的方式注入,提升程式的可維護性與可測試性。

本篇將深入探討 參數依賴 的寫法與最佳實踐,從最基礎的 query、path、body 注入,到多層依賴與自訂類別的應用,並提供完整範例與常見陷阱的說明,讓你在實務開發中能即時上手。


核心概念

1. 為什麼使用參數依賴?

  • 統一驗證:所有路由共用同一套驗證規則,避免重複程式。
  • 自動文件化:FastAPI 會根據依賴的型別自動產生 OpenAPI 文件。
  • 測試便利:在測試時只要替換依賴函式,即可模擬不同情境。

FastAPI 的依賴是透過 Depends 這個特殊物件實現的,只要把 Depends(some_callable) 作為路由函式的參數,即可將 some_callable 的返回值注入進來。


2. 基本寫法

from fastapi import FastAPI, Depends

app = FastAPI()

def common_query(q: str = None):
    """取得共用的 query 參數,若未提供則回傳 None。"""
    return q

@app.get("/items/")
def read_items(q: str = Depends(common_query)):
    # q 已經是 common_query 的返回值
    return {"query": q}

重點Depends 會自動解析 common_query 的參數(此例為 q: str = None),並把結果傳給路由函式。


3. Query 參數依賴

3.1 單一參數的依賴

from fastapi import Query

def pagination(
    skip: int = Query(0, ge=0, description="跳過多少筆"),
    limit: int = Query(10, le=100, description="一次回傳上限")
):
    """回傳分頁資訊的字典。"""
    return {"skip": skip, "limit": limit}

@app.get("/users/")
def list_users(pagination: dict = Depends(pagination)):
    # 直接取得已驗證的分頁資訊
    return {"msg": "取得使用者列表", "pagination": pagination}
  • Query 內建的驗證(ge, le)會在依賴階段完成,若不符合會直接回傳 422 Unprocessable Entity

3.2 多個 query 依賴的組合

def search_params(
    q: str = Query(..., min_length=3, description="搜尋關鍵字"),
    tags: list[str] = Query([], description="標籤清單")
):
    return {"q": q, "tags": tags}

@app.get("/search/")
def search(
    pagination: dict = Depends(pagination),
    params: dict = Depends(search_params)
):
    # 結合兩個依賴的結果
    return {"search": params, "page": pagination}

技巧:把 共用的 query 參數 抽成獨立函式,可在多個路由間重複使用。


4. Path 參數依賴

Path 參數本身就是路由的一部份,使用 Depends 可以讓我們在取得路徑變數的同時,執行額外的驗證或查詢。

from fastapi import Path, HTTPException, status

def verify_item_id(item_id: int = Path(..., gt=0)):
    # 假設要檢查資料庫是否存在此 ID
    if item_id > 1000:          # 假設 1000 之後的 ID 都不存在
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Item {item_id} not found"
        )
    return item_id

@app.get("/items/{item_id}")
def read_item(item_id: int = Depends(verify_item_id)):
    # 此時 item_id 已經過驗證
    return {"item_id": item_id, "msg": "取得單一商品"}
  • Path 的驗證(如 gt=0)與自訂邏輯(如資料庫查詢)可以同時寫在同一個依賴函式中,保持路由函式的簡潔。

5. Body 參數依賴

在需要對 request body 進行前置處理(例如統一的資料模型驗證、欄位過濾)時,亦可使用依賴。

from pydantic import BaseModel, Field

class ItemCreate(BaseModel):
    name: str = Field(..., min_length=1)
    price: float = Field(..., gt=0)
    tags: list[str] = []

def parse_item(body: ItemCreate):
    # 可以在此做額外的商業規則檢查
    if "forbidden" in body.tags:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="包含禁用標籤"
        )
    return body

@app.post("/items/")
def create_item(item: ItemCreate = Depends(parse_item)):
    # item 已經是經過 parse_item 處理過的模型實例
    return {"msg": "建立成功", "item": item}
  • 關鍵:即使 parse_item 只接受一個參數 body: ItemCreate,FastAPI 仍會自動把請求的 JSON 轉成 ItemCreate,然後再把結果傳給 parse_item

6. 多層依賴與自訂類別

依賴可以相互嵌套,形成 多層依賴,這讓我們可以把「資料庫連線」或「服務物件」抽成類別,然後在路由中一次注入。

from typing import Generator
from sqlalchemy.orm import Session
from database import SessionLocal

def get_db() -> Generator[Session, None, None]:
    """取得 SQLAlchemy Session,使用 yield 讓 FastAPI 自動關閉。"""
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

class ItemService:
    def __init__(self, db: Session):
        self.db = db

    def get_item(self, item_id: int):
        # 這裡簡化為直接回傳 dict
        return {"id": item_id, "name": "Demo Item"}

def get_item_service(db: Session = Depends(get_db)):
    return ItemService(db)

@app.get("/items/{item_id}")
def read_item(
    item_id: int = Path(..., gt=0),
    service: ItemService = Depends(get_item_service)
):
    item = service.get_item(item_id)
    return {"item": item}
  • get_db 使用 Generatoryield)的方式,FastAPI 會在請求結束時自動執行 finally 區塊,確保資源釋放。
  • ItemService 被包裝在 get_item_service 之中,路由只需要 Depends(get_item_service) 即可取得完整的服務物件。

常見陷阱與最佳實踐

陷阱 說明 解決方式
依賴函式內部寫死資料 把資料庫查詢結果硬編碼在依賴裡,導致測試無法 mock。 使用 參數注入(例如 db: Session = Depends(get_db))或 抽象介面,在測試時替換。
過度巢狀依賴 多層依賴過深會讓除錯變得困難。 保持每個依賴的職責單一,必要時使用 組合物件(如 Service 類別)封裝。
忘記 yield 關閉資源 使用資料庫連線卻忘記在依賴結束時關閉,會造成連線泄漏。 Generatoryield)或 contextmanager,讓 FastAPI 自動清理。
依賴參數與路由參數同名衝突 若路由與依賴同時使用相同名稱,FastAPI 可能會把路由參數傳給依賴,導致不預期結果。 為依賴函式使用 不同的參數名,或在路由中明確使用 Path(..., alias="...")
未使用型別提示 沒寫 int, str, BaseModel 等型別,FastAPI 無法產生正確的 OpenAPI 文件。 必寫 型別提示,並盡量使用 Pydantic 模型描述 Body。

最佳實踐

  1. 把驗證邏輯放在依賴:讓路由只負責回傳結果。
  2. 使用 Depends 串接服務層:保持路由與業務邏輯的分離。
  3. 在測試時利用 override_dependency:快速替換資料庫或外部 API。
  4. 適度使用 Dependsuse_cache=False:對於每次請求都要重新計算的依賴(例如隨機 token)可關閉快取。
@app.get("/rand/")
def random_number(num: int = Depends(lambda: random.randint(1, 100), use_cache=False)):
    return {"num": num}

實際應用場景

場景 依賴的角色 具體實作
分頁與排序 paginationsorting 兩個獨立依賴 def pagination(skip: int = Query(...), limit: int = Query(...))
使用者認證 取得 JWT、驗證、載入使用者模型 def get_current_user(token: str = Depends(oauth2_scheme))
多租戶 (Tenant) 系統 依據 X-Tenant-ID Header 注入不同的資料庫 Session def get_tenant_db(tenant_id: str = Header(...))
外部 API 快取 先檢查 Redis 快取,若無則呼叫外部服務 def get_weather(city: str = Query(...), cache: Redis = Depends(get_redis))
檔案上傳前的掃描 依賴病毒掃描服務,檢查 UploadFile def scan_file(file: UploadFile = File(...), scanner: VirusScanner = Depends(get_scanner))

以上範例展示了 參數依賴 如何在不同層面(驗證、快取、服務抽象)提供統一入口,讓程式碼保持乾淨且易於維護。


總結

  • 參數依賴 是 FastAPI 依賴注入系統中最常見、最實用的功能,涵蓋 querypathbody 三大來源。
  • 透過 Depends,我們可以把 驗證、授權、資料轉換 等前置工作抽離成獨立函式或類別,讓路由函式只負責核心業務。
  • 實作時要注意 型別提示資源釋放yield)以及 依賴層級的清晰,避免常見的資源泄漏與測試困難。
  • 結合 服務層(Service Class)與 資料庫 Session,可以打造出 可測試、可擴充 的後端架構。

掌握了參數依賴的寫法與最佳實踐,你就能在 FastAPI 專案中快速建立一致、可維護且具備自動文件化功能的 API,從而提升開發效率與程式品質。祝你寫程式開心,API 更順手!