FastAPI 依賴注入系統:參數依賴(傳入 query / path / body)
簡介
在 FastAPI 中,依賴注入(Dependency Injection,簡稱 DI)是協助開發者把「取得資料」或「前置處理」的邏輯抽離出來、重複使用的核心機制。透過 DI,我們可以把 query string、path parameter、request body 等不同來源的參數,統一交給同一套函式或類別處理,讓路由函式只專注於業務邏輯本身。
對於 初學者 來說,直接在路由函式裡寫 request.query_params、request.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使用 Generator(yield)的方式,FastAPI 會在請求結束時自動執行finally區塊,確保資源釋放。ItemService被包裝在get_item_service之中,路由只需要Depends(get_item_service)即可取得完整的服務物件。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 依賴函式內部寫死資料 | 把資料庫查詢結果硬編碼在依賴裡,導致測試無法 mock。 | 使用 參數注入(例如 db: Session = Depends(get_db))或 抽象介面,在測試時替換。 |
| 過度巢狀依賴 | 多層依賴過深會讓除錯變得困難。 | 保持每個依賴的職責單一,必要時使用 組合物件(如 Service 類別)封裝。 |
忘記 yield 關閉資源 |
使用資料庫連線卻忘記在依賴結束時關閉,會造成連線泄漏。 | 用 Generator(yield)或 contextmanager,讓 FastAPI 自動清理。 |
| 依賴參數與路由參數同名衝突 | 若路由與依賴同時使用相同名稱,FastAPI 可能會把路由參數傳給依賴,導致不預期結果。 | 為依賴函式使用 不同的參數名,或在路由中明確使用 Path(..., alias="...")。 |
| 未使用型別提示 | 沒寫 int, str, BaseModel 等型別,FastAPI 無法產生正確的 OpenAPI 文件。 |
必寫 型別提示,並盡量使用 Pydantic 模型描述 Body。 |
最佳實踐
- 把驗證邏輯放在依賴:讓路由只負責回傳結果。
- 使用
Depends串接服務層:保持路由與業務邏輯的分離。 - 在測試時利用
override_dependency:快速替換資料庫或外部 API。 - 適度使用
Depends的use_cache=False:對於每次請求都要重新計算的依賴(例如隨機 token)可關閉快取。
@app.get("/rand/")
def random_number(num: int = Depends(lambda: random.randint(1, 100), use_cache=False)):
return {"num": num}
實際應用場景
| 場景 | 依賴的角色 | 具體實作 |
|---|---|---|
| 分頁與排序 | pagination、sorting 兩個獨立依賴 |
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 依賴注入系統中最常見、最實用的功能,涵蓋 query、path、body 三大來源。
- 透過
Depends,我們可以把 驗證、授權、資料轉換 等前置工作抽離成獨立函式或類別,讓路由函式只負責核心業務。 - 實作時要注意 型別提示、資源釋放(
yield)以及 依賴層級的清晰,避免常見的資源泄漏與測試困難。 - 結合 服務層(Service Class)與 資料庫 Session,可以打造出 可測試、可擴充 的後端架構。
掌握了參數依賴的寫法與最佳實踐,你就能在 FastAPI 專案中快速建立一致、可維護且具備自動文件化功能的 API,從而提升開發效率與程式品質。祝你寫程式開心,API 更順手!