本文 AI 產出,尚未審核
FastAPI 教學:依賴注入系統(Dependency Injection)— Depends() 基本概念
簡介
在 Web 框架中,依賴注入(Dependency Injection, DI) 是一種把「外部資源」或「共用邏輯」以函式參數的形式傳入路由處理函式的設計手法。FastAPI 以 Depends() 為核心,讓開發者可以:
- 保持路由函式簡潔:把驗證、資料庫連線、設定讀取等重複程式碼抽離成獨立的依賴。
- 自動產生 OpenAPI 文件:FastAPI 會根據依賴的型別提示自動寫入 API 規格,提升文件品質。
- 支援測試與替換:在單元測試時,只要提供另一個依賴即可輕鬆 mock,避免硬耦合。
本篇文章將從最基本的 Depends() 用法說起,搭配實作範例,說明它在日常開發中的價值與注意事項,讓你從「新手」快速晉升為「中級」的 FastAPI 使用者。
核心概念
1. Depends() 是什麼?
Depends 本身是一個 可呼叫的類別(callable class),在 FastAPI 中它的作用是 告訴框架「這個參數需要先執行某個函式」。當請求抵達時,FastAPI 會先執行依賴函式,取得返回值,再把該值注入到路由函式的對應參數。
from fastapi import FastAPI, Depends
app = FastAPI()
def get_token_header(x_token: str = Header(...)):
"""檢查自訂的 X-Token 標頭是否正確"""
if x_token != "secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
return x_token
@app.get("/items/")
def read_items(token: str = Depends(get_token_header)):
return {"msg": f"Valid token: {token}"}
關鍵點
Depends()不需要傳入實例,只要傳入 可呼叫物件(函式、類別、lambda)即可。- 依賴函式的參數本身也可以再使用
Depends(),形成 依賴樹(dependency tree)。
2. 基本依賴的寫法
範例 1:共用的資料庫連線
# db.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
"""依賴:提供一個資料庫 session,使用完自動關閉"""
db = SessionLocal()
try:
yield db # 使用 yield 讓 FastAPI 知道這是「產生器」型別
finally:
db.close()
# main.py
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from . import db, models
app = FastAPI()
@app.get("/users/{user_id}")
def read_user(user_id: int, db: Session = Depends(db.get_db)):
user = db.query(models.User).filter(models.User.id == user_id).first()
return {"user": user}
說明
get_db使用yield,FastAPI 會在請求結束後自動執行finally區塊,確保連線釋放。- 只要在任何路由加上
db: Session = Depends(db.get_db),就能取得同樣的 session。
3. 依賴的參數驗證與 Pydantic
範例 2:把驗證邏輯抽成依賴
from fastapi import Depends, HTTPException, Query
def validate_page(page: int = Query(1, ge=1), size: int = Query(10, ge=1, le=100)):
"""檢查分頁參數是否合法,返回 (skip, limit)"""
skip = (page - 1) * size
limit = size
return skip, limit
@app.get("/products/")
def list_products(paging: tuple = Depends(validate_page)):
skip, limit = paging
# 假設有一個 get_products(skip, limit) 的服務
products = get_products(skip, limit)
return {"items": products, "skip": skip, "limit": limit}
重點
validate_page直接使用 FastAPI 的Query參數,讓驗證與預設值在依賴內完成。- 路由只需要
paging: tuple = Depends(validate_page),保持簡潔。
4. 依賴的層級(Nested Depends)
範例 3:多層依賴 – 認證 → 取得使用者 → 授權檢查
from fastapi import Header, Security, HTTPException, status
def get_current_user(token: str = Header(...)):
"""根據 token 取得使用者物件,若失敗拋出例外"""
user = decode_jwt(token) # 假設有此函式
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials")
return user
def verify_admin(user: dict = Depends(get_current_user)):
"""只允許 admin 權限的使用者存取"""
if user.get("role") != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
detail="Insufficient privileges")
return user
@app.delete("/admin/users/{user_id}")
def delete_user(user_id: int, admin: dict = Depends(verify_admin)):
# 只有 admin 通過 verify_admin 後才會執行
perform_deletion(user_id)
return {"msg": f"User {user_id} deleted by {admin['username']}"}
說明
verify_admin依賴get_current_user,形成 依賴樹。- 若
get_current_user拋出例外,後面的verify_admin與路由函式都不會被呼叫。
5. 使用類別作為依賴(依賴注入的 OOP 風格)
範例 4:把服務封裝成類別
class EmailService:
def __init__(self, smtp_server: str = "smtp.example.com"):
self.smtp_server = smtp_server
def send(self, to: str, subject: str, body: str):
# 這裡寫實際的寄信程式
print(f"Sending email to {to} via {self.smtp_server}")
def get_email_service() -> EmailService:
return EmailService()
@app.post("/notify/")
def notify_user(email: str, service: EmailService = Depends(get_email_service)):
service.send(to=email, subject="Welcome", body="Thanks for joining!")
return {"msg": "Notification sent"}
優點
- 依賴類別讓 測試 更方便:只要在測試時提供一個 mock 版本的
EmailService即可。
6. Depends 與 BackgroundTasks、Request 的結合
範例 5:在依賴中使用 Request 取得客戶端資訊
from fastapi import Request, BackgroundTasks
def log_request(request: Request, background_tasks: BackgroundTasks):
client_ip = request.client.host
background_tasks.add_task(save_log, client_ip) # 非同步寫入
return client_ip
@app.get("/ping")
def ping(ip: str = Depends(log_request)):
return {"msg": f"Pong from {ip}"}
說明
log_request接收 FastAPI 已內建的Request與BackgroundTasks,利用Depends把結果(client_ip)回傳給路由。- 這樣的寫法把 日誌、統計 等橫切面需求抽離出去,保持主業務程式碼乾淨。
常見陷阱與最佳實踐
| 陷阱 | 為什麼會發生 | 解決方案 |
|---|---|---|
依賴函式忘記加 yield |
若依賴需要在請求結束後釋放資源(如 DB 連線),卻寫成普通函式,資源不會自動關閉。 | 使用 yield 並在 finally 區塊釋放,或改用 async with。 |
依賴返回 None 卻未處理 |
FastAPI 不會自動檢查返回值,路由收到 None 後可能產生 500 錯誤。 |
在依賴內部自行拋出 HTTPException,或在路由中加入檢查。 |
| 依賴參數與路由參數同名衝突 | 若依賴與路由都使用相同名稱的參數,FastAPI 會把依賴的值覆寫。 | 使用不同名稱或在依賴中使用 *、** 參數避免衝突。 |
| 過度依賴樹層級過深 | 多層 Depends 會讓除錯變得困難,且每層都會執行一次,影響效能。 |
把相關邏輯合併到同一個依賴,或使用 singleton 方式緩存結果。 |
| 在依賴中使用全域變數 | 會產生競爭條件(race condition),尤其在 async 環境下。 | 盡量使用 Depends 內部的局部變數或 ContextVar。 |
最佳實踐
- 保持依賴單一職責:每個依賴應只負責一件事(驗證、取得資源、授權),讓測試與重用更簡單。
- 利用
Depends的快取機制:在同一請求內,多個路由使用相同依賴時,FastAPI 只會執行一次(除非設定use_cache=False)。def get_current_user(..., use_cache: bool = True): ... - 在測試時使用
dependency_overrides:可以在測試環境把真實依賴換成 mock,避免外部資源干擾。app.dependency_overrides[get_db] = lambda: FakeSession() - 適度使用型別提示:讓 IDE 與 FastAPI 都能自動補全,提升開發效率。
- 針對 async 依賴使用
async def:若依賴內部有 I/O(DB、HTTP),使用 async 可避免阻塞。
實際應用場景
| 場景 | 為什麼適合使用 Depends() |
範例簡述 |
|---|---|---|
| 使用者認證與授權 | 需要在多條路由上統一檢查 token、取得使用者資訊、驗證權限。 | get_current_user → verify_admin → 路由 |
| 資料庫或外部服務的連線池 | 每個請求只需要一次連線,且請求結束後要釋放。 | get_db、get_redis_client |
| 分頁、排序、過濾等共用查詢參數 | 把 Query 參數抽成依賴,可在多個列表 API 重複使用。 | validate_page、parse_sort |
| 跨域請求的 CORS 設定或自訂 Header | 只要在需要的路由加上依賴,即可自動檢查 Header。 | validate_api_key |
| 背景任務或日誌 | 把背景寫入或統計邏輯抽成依賴,保持主業務乾淨。 | log_request、track_metrics |
| 多語系、國際化 | 依賴可根據 Accept-Language Header 產生翻譯器物件。 |
get_locale |
總結
Depends()是 FastAPI 依賴注入 的核心工具,讓開發者可以把驗證、資源取得、授權等橫切面邏輯抽離成 可重用、可測試 的函式或類別。- 透過 yield、型別提示、以及 多層依賴,可以建立清晰的依賴樹,讓每個請求只執行一次必要的程式碼,並在結束時自動清理資源。
- 常見的陷阱包括忘記釋放資源、依賴返回
None、以及過深的依賴樹。遵循「單一職責」與「快取」的最佳實踐,可讓程式碼更健全、效能更佳。 - 在實務上,從 認證授權、資料庫連線、分頁參數 到 背景任務,幾乎所有需要跨多條路由共享的功能,都可以透過
Depends()來優雅實現。
掌握 Depends() 後,你的 FastAPI 專案將會變得 模組化、可維護且易於測試,也能更快速地產出符合 OpenAPI 標準的文件。祝你開發順利,寫出更乾淨、更強大的 API! 🚀