StackTraces
FastAPI已驗證

FastAPI Depends 依賴注入的三個常見坑

@debug_master32 天前更新閱讀 6 min00

一句話先說結論:FastAPI 的依賴注入靠的是“檢查函數簽名”——參數必須用 Depends() 包起來,才會被當成依賴去解析,否則就退化成普通 query 參數。三個最常見的坑:漏寫 Depends() 直接 422、yield 依賴的清理沒寫在 finally 裡(例外時洩漏)、以及預設的同請求緩存讓帶副作用的依賴被“複用”出詭異結果。

背景

FastAPI 的 DI 不是 Spring 那種“裝配整個物件圖”的容器,而是一套很輕量的機制:你聲明端點需要什麼(數據庫會話、當前用戶、設定),框架在請求時自動調用對應的函數,把返回值注入進來。理解這套“簽名驅動”的解析方式,是避開大部分坑的前提。

現象

幾種典型報錯。

漏寫 Depends() 時,依賴參數被降級成普通 query 參數,FastAPI 返回 422,響應體:

{
  "detail": [
    {
      "type": "missing",
      "loc": ["query", "current_user"],
      "msg": "Field required"
    }
  ]
}

async 依賴配了 def(或依賴寫成循環),會直接拋運行時例外。報錯原文:

RuntimeError: Dependency 'get_current_user' is a coroutine function but it is not wrapped with async def in the function signature.

完整堆疊:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 428, in run_asgi
    result = await self.app(scope, receive, send)
  File "/usr/local/lib/python3.10/site-packages/starlette/routing.py", line 680, in __call__
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.10/site-packages/fastapi/routing.py", line 227, in app
    solved_result = await solve_dependencies(request=request, dependant=dependant, body=body)
  File "/usr/local/lib/python3.10/site-packages/fastapi/dependencies/utils.py", line 606, in solve_dependencies
    raise RuntimeError(
RuntimeError: Dependency 'get_current_user' is a coroutine function but it is not wrapped with async def in the function signature.

還有一種最隱蔽的:依賴偷偷返回了 None,路由裡接著調 user.id,報的卻是看起來跟依賴無關的 AttributeError: 'NoneType' object has no attribute 'id'。

根因分析

坑 1:漏寫 Depends()

FastAPI 判斷一個參數是不是依賴,全靠 Depends(...) 這個標記。不包,它就是個普通請求參數:

# 错:缺少 Depends,get_user 被当成 query 参数,触发 422
@app.get("/items")
def read_items(user=get_user):
    return user

# 对
@app.get("/items")
def read_items(user=Depends(get_user)):
    return user

坑 2:yield 依賴清理沒進 finally,或 except 忘了 raise

yield 依賴是“上下文管理器”,yield 之後的程式碼在響應返回後跑。要保證例外時也能清理,必須用 finally:

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

還有兩個容易被忽略的細節:

  • 在 yield 依賴裡用 except 捕獲例外後,必須再 raise,否則例外被你吞掉,接口會返回一個“看似成功”的結果而不是正確的錯誤。
  • async 生成器依賴要用 async def + yield,不能寫成 def + yield。

坑 3:預設緩存讓副作用依賴“複用”

FastAPI 預設在同一次請求內緩存依賴結果:同一個依賴被多個地方 Depends 時,只會執行一次、共用同一實例。

@app.get("/profile")
async def profile(
    user1=Depends(get_current_user),
    user2=Depends(get_current_user),
):
    assert user1 is user2   # True,同一实例

如果某個依賴帶副作用(比如生成隨機數、讀取實時狀態),這種複用會帶來意外。要每次執行,用 use_cache=False:

@app.get("/data")
async def data(fresh=Depends(get_fresh_data, use_cache=False)):
    return fresh

坑 4:測試 override 用的 key 必須是同一個函數物件

依賴替換 app.dependency_overrides[get_db] = lambda: mock_session,key 必須是你模塊裡導入的那個確切函數物件,不是重新 import 或拷貝的。否則就悄悄不生效。測完記得 app.dependency_overrides.clear(),避免汙染其他用例。

解決方案

標準的數據庫依賴(yield + finally + 提交/回滾)

from typing import Generator

def get_db() -> Generator:
    db = SessionLocal()
    try:
        yield db
        db.commit()          # 成功才提交
    except Exception:
        db.rollback()        # 异常回滚
        raise                # 一定要重新抛出
    finally:
        db.close()           # 一定关闭

子依賴鏈(認證 → 角色 → 數據)

def get_current_user(token: str = Depends(oauth2_scheme), db=Depends(get_db)):
    user = db.query(User).filter(User.token == token).first()
    if not user:
        raise HTTPException(status_code=401, detail="Invalid token")
    return user

def get_admin_user(user=Depends(get_current_user)):
    if not user.is_admin:
        raise HTTPException(status_code=403, detail="Not authorized")
    return user

依賴可以任意嵌套成樹,每個 yield 的退出程式碼會按正確順序執行。

別讓依賴返回 None

依賴裡凡是可能走到“不返回”的分支,都要顯式處理,否則下一步的 AttributeError 會把你引到錯誤的方向:

def get_user(user_id: int, db=Depends(get_db)):
    user = db.query(User).get(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")  # 显式抛出,别返回 None
    return user

小結

  • 依賴參數一定包 Depends(),否則變 query 參數報 422。
  • yield 依賴:清理放 finally,except 後記得 raise。
  • 預設同請求緩存依賴結果,帶副作用的依賴用 use_cache=False。
  • 測試 override 用同一個函數物件做 key,測完 clear()。

References(參考來源)

最後更新於 2026-09-09

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法