FastAPI 帶有 Yield 的依賴項 (Dependencies with yield)
FastAPI 支援使用 yield 關鍵字來定義依賴項。這讓你的依賴項不僅能提供一個值,還能在使用完畢後執行額外的「清理 (Cleanup)」邏輯。
這在需要開啟與關閉資源的情境中非常強大,例如:
- 資料庫連線 (Database Connections)。
- 網路會話 (Network Sessions)。
- 暫存檔案的建立與刪除。
基礎語法與運作原理
當你在依賴函數中使用 yield 而不是 return 時,FastAPI 會將其視為一個兩個階段的操作:
- 請求開始前:執行
yield之前的代碼(設定階段)。 - 注入值:傳遞
yield出去的值給路徑操作函數。 - 回應結束後:當回應處理完畢(不論成功或失敗)後,執行
yield之後的代碼(清理階段)。
實務範例:資料庫連線
async def get_db():
db = MyDatabaseSession()
try:
# 第一階段:開啟資源並傳遞出去
yield db
finally:
# 第二階段:請求結束後,不論成敗都會執行這裡
db.close()
在路由中使用:
@app.get("/items/{item_id}")
async def read_item(item_id: str, db: Annotated[MyDatabaseSession, Depends(get_db)]):
return db.get(item_id)
使用 try...finally 確保安全性
在帶有 yield 的依賴項中,強烈建議使用 try...finally 區塊。
這可以確保即使你的路徑操作函數中途拋出異常,或者後續的依賴項出錯,你的清理邏輯(如關閉連線)依然會被執行,避免資源洩漏。
async def get_resource():
resource = acquire_resource()
try:
yield resource
finally:
release_resource(resource)
捕捉路徑操作中的異常 (except)
有時候你希望在依賴項中捕捉並處理路由函數產生的 Exception(例如:發生錯誤時回滾資料庫交易)。
FastAPI 會將路由函數中的異常向外傳遞到 yield 所在的位置。因此,你可以像處理一般 Python 代碼一樣使用 except:
async def get_db_with_transaction():
db = MyDatabaseSession()
try:
yield db
# 如果路由函數順利完成,這裡可以執行 commit
db.commit()
except Exception:
# 如果路由函數拋出異常,這裡執行 rollback
db.rollback()
raise # 記得重新拋出異常,讓 FastAPI 的處理機制接手
finally:
db.close()
如果你在
except 中捕捉了異常,除非你真的想要「吞掉」這個錯誤(通常不建議),否則請務必再次 raise,讓 FastAPI 能夠產生正確的錯誤回應。子依賴項與 Yield
帶有 yield 的依賴項也可以擁有子依賴項。FastAPI 會確保整個「依賴鏈」的順序:
- 設定階段:由底層往頂層執行(從子依賴到主依賴)。
- 執行路由函數。
- 清理階段:由頂層往底層執行(先清理主依賴,再清理子依賴)。
這就像一個「先進後出 (LIFO)」的堆疊結構。
使用限制
- 不能在
dependencies=中使用需要回傳值的 yield 依賴項:雖然路徑裝飾器的dependencies=[Depends(...)]參數可以使用yield依賴項來執行副作用,但由於該方式會丟棄回傳值,因此如果你需要yield出來的物件,請務必將其注入到函數參數中。 - 背景任務 (Background Tasks):如果你使用了
yield依賴項,請注意該清理邏輯會在「回應已送出」後才執行。但如果在依賴項中關閉了資料庫連線,你的背景任務可能就無法再存取該連線。
總結
yield讓依賴項具備 Setup 與 Cleanup 的能力。- 使用
try...finally是最安全的資源釋放方式。 - 你可以透過
except捕捉路由層級的異常。 - 這是管理資料庫連線、檔案控制等資源生命週期的標準做法。