本文 AI 產出,尚未審核

FastAPI 教學:依賴注入系統(Dependency Injection)— Depends() 基本概念


簡介

在 Web 框架中,依賴注入(Dependency Injection, DI) 是一種把「外部資源」或「共用邏輯」以函式參數的形式傳入路由處理函式的設計手法。FastAPI 以 Depends() 為核心,讓開發者可以:

  1. 保持路由函式簡潔:把驗證、資料庫連線、設定讀取等重複程式碼抽離成獨立的依賴。
  2. 自動產生 OpenAPI 文件:FastAPI 會根據依賴的型別提示自動寫入 API 規格,提升文件品質。
  3. 支援測試與替換:在單元測試時,只要提供另一個依賴即可輕鬆 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. DependsBackgroundTasksRequest 的結合

範例 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 已內建的 RequestBackgroundTasks,利用 Depends 把結果(client_ip)回傳給路由。
  • 這樣的寫法把 日誌、統計 等橫切面需求抽離出去,保持主業務程式碼乾淨。

常見陷阱與最佳實踐

陷阱 為什麼會發生 解決方案
依賴函式忘記加 yield 若依賴需要在請求結束後釋放資源(如 DB 連線),卻寫成普通函式,資源不會自動關閉。 使用 yield 並在 finally 區塊釋放,或改用 async with
依賴返回 None 卻未處理 FastAPI 不會自動檢查返回值,路由收到 None 後可能產生 500 錯誤。 在依賴內部自行拋出 HTTPException,或在路由中加入檢查。
依賴參數與路由參數同名衝突 若依賴與路由都使用相同名稱的參數,FastAPI 會把依賴的值覆寫。 使用不同名稱或在依賴中使用 *** 參數避免衝突。
過度依賴樹層級過深 多層 Depends 會讓除錯變得困難,且每層都會執行一次,影響效能。 把相關邏輯合併到同一個依賴,或使用 singleton 方式緩存結果。
在依賴中使用全域變數 會產生競爭條件(race condition),尤其在 async 環境下。 盡量使用 Depends 內部的局部變數或 ContextVar

最佳實踐

  1. 保持依賴單一職責:每個依賴應只負責一件事(驗證、取得資源、授權),讓測試與重用更簡單。
  2. 利用 Depends 的快取機制:在同一請求內,多個路由使用相同依賴時,FastAPI 只會執行一次(除非設定 use_cache=False)。
    def get_current_user(..., use_cache: bool = True):
        ...
    
  3. 在測試時使用 dependency_overrides:可以在測試環境把真實依賴換成 mock,避免外部資源干擾。
    app.dependency_overrides[get_db] = lambda: FakeSession()
    
  4. 適度使用型別提示:讓 IDE 與 FastAPI 都能自動補全,提升開發效率。
  5. 針對 async 依賴使用 async def:若依賴內部有 I/O(DB、HTTP),使用 async 可避免阻塞。

實際應用場景

場景 為什麼適合使用 Depends() 範例簡述
使用者認證與授權 需要在多條路由上統一檢查 token、取得使用者資訊、驗證權限。 get_current_userverify_admin → 路由
資料庫或外部服務的連線池 每個請求只需要一次連線,且請求結束後要釋放。 get_dbget_redis_client
分頁、排序、過濾等共用查詢參數 把 Query 參數抽成依賴,可在多個列表 API 重複使用。 validate_pageparse_sort
跨域請求的 CORS 設定或自訂 Header 只要在需要的路由加上依賴,即可自動檢查 Header。 validate_api_key
背景任務或日誌 把背景寫入或統計邏輯抽成依賴,保持主業務乾淨。 log_requesttrack_metrics
多語系、國際化 依賴可根據 Accept-Language Header 產生翻譯器物件。 get_locale

總結

  • Depends() 是 FastAPI 依賴注入 的核心工具,讓開發者可以把驗證、資源取得、授權等橫切面邏輯抽離成 可重用、可測試 的函式或類別。
  • 透過 yield型別提示、以及 多層依賴,可以建立清晰的依賴樹,讓每個請求只執行一次必要的程式碼,並在結束時自動清理資源。
  • 常見的陷阱包括忘記釋放資源、依賴返回 None、以及過深的依賴樹。遵循「單一職責」與「快取」的最佳實踐,可讓程式碼更健全、效能更佳。
  • 在實務上,從 認證授權資料庫連線分頁參數背景任務,幾乎所有需要跨多條路由共享的功能,都可以透過 Depends() 來優雅實現。

掌握 Depends() 後,你的 FastAPI 專案將會變得 模組化、可維護且易於測試,也能更快速地產出符合 OpenAPI 標準的文件。祝你開發順利,寫出更乾淨、更強大的 API! 🚀