FastAPI – 測試與除錯
主題:uvicorn --reload 的 hot‑reload 機制
簡介
在開發 FastAPI 應用時,我們常會使用 uvicorn 作為 ASGI 伺服器。若直接執行 uvicorn main:app,每當程式碼變動後都必須手動停止再重新啟動,這不僅浪費時間,也容易在除錯過程中遺漏變更。
uvicorn --reload 提供的 hot‑reload 機制,會在偵測到檔案變化時自動重新載入應用,讓開發者即時看到修改結果,極大提升開發效率與除錯流暢度。本文將深入說明其運作原理、使用方式與實務上的注意事項,協助你在 FastAPI 專案中安全、有效地運用 hot‑reload。
核心概念
1. --reload 旗標的本質
--reload 其實是 uvicorn 內建的開發模式,背後使用 watchdog(或 stat)監控檔案系統變化。當偵測到 Python 檔案被修改、建立或刪除時,uvicorn 會:
- 終止 目前的工作執行緒與事件迴圈
- 重新載入 目標模組(如
main:app) - 啟動 新的事件迴圈與工作者
這個過程對使用者是透明的,唯一的副作用是 第一次載入後的記憶體狀態會被清空,因此不會保留先前的全域變數或快取。
2. 監控範圍與排除機制
預設情況下,uvicorn 只會監控 Python 檔案(.py) 與 .pyc,以及 .env、.ini 等設定檔。若專案中有大量非 Python 檔案(如前端資源)會不必要地觸發重載,可透過 --reload-dir 或 --reload-exclude 參數自訂監控目錄與排除規則。
uvicorn main:app --reload \
--reload-dir ./app \
--reload-dir ./config \
--reload-exclude "./static/*"
3. 與 --workers 的相容性
在開發階段,我們通常只使用單一工作者(worker),但若同時使用 --workers > 1,--reload 會失效,因為多工作者模式下每個 worker 都會自行監控檔案,容易造成競爭與不必要的重啟。建議在開發時僅使用 --reload,正式上線再改為 --workers。
程式碼範例
以下示範在 FastAPI 專案中使用 uvicorn --reload 的實務寫法,並說明常見的設定技巧。
範例 1:最簡單的啟動指令
# 直接在終端機執行
uvicorn main:app --reload
說明:
main:app表示從main.py匯入名為app的 FastAPI 實例。加上--reload後,任何.py檔案的變動都會觸發自動重新載入。
範例 2:自訂監控目錄與排除靜態檔案
uvicorn main:app \
--reload \
--reload-dir ./app \
--reload-dir ./settings \
--reload-exclude "./static/*"
說明:
--reload-dir讓 uvicorn 只監控./app與./settings兩個資料夾,減少不必要的檔案掃描。--reload-exclude排除static目錄,避免前端資源變動時觸發後端重載。
範例 3:結合環境變數與自動載入
# main.py
import os
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
# 透過環境變數顯示當前模式
mode = os.getenv("APP_MODE", "development")
return {"message": f"Hello FastAPI! Mode: {mode}"}
# 使用 .env 檔案,並在啟動時自動載入
export APP_MODE=debug
uvicorn main:app --reload
說明:當程式碼變更後,
--reload會重新載入main.py,同時重新讀取環境變數,確保開發者可以即時看到設定的變化。
範例 4:自訂 watchdog 事件處理(進階)
# reload_handler.py
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
import subprocess
import time
import os
class ReloadHandler(FileSystemEventHandler):
def __init__(self, command: str):
self.command = command
self.process = None
self.start_server()
def start_server(self):
self.process = subprocess.Popen(self.command, shell=True)
def on_any_event(self, event):
# 停止舊的服務並重新啟動
self.process.terminate()
time.sleep(0.5) # 等待資源釋放
self.start_server()
print(f"[Reload] {event.src_path} 變更,已重新啟動伺服器")
if __name__ == "__main__":
path = os.getcwd()
observer = Observer()
handler = ReloadHandler("uvicorn main:app")
observer.schedule(handler, path=path, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
說明:這段程式展示如何自行實作類似
--reload的機制,適合需要自訂重載行為(例如在重載前執行測試或清理暫存)的情境。在日常開發中仍建議直接使用uvicorn --reload,除非有特殊需求。
範例 5:在 Docker 中使用 hot‑reload
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
# 建立並執行容器
docker build -t fastapi-dev .
docker run -p 8000:8000 -v $(pwd):/app fastapi-dev
說明:
-v $(pwd):/app把本機程式碼掛載到容器內,讓容器內的uvicorn --reload能偵測到變更。- 這是開發環境常見的做法,讓後端程式碼的修改即時反映在容器中。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 記憶體洩漏 | --reload 會在每次重載時重新啟動 Python 解譯器,若有全域資源(如資料庫連線)未正確關閉,可能導致資源浪費。 |
使用 依賴注入(Depends)或在 startup / shutdown 事件中管理資源。 |
| 多工作者失效 | 同時使用 --workers > 1 時,--reload 失效。 |
開發階段僅使用單一 worker;正式部署時改為 uvicorn main:app --workers N(不加 --reload)。 |
| 過度監控 | 監控過多目錄會導致重載頻繁,影響效能。 | 透過 --reload-dir 限制監控範圍,或使用 --reload-exclude 排除不必要的檔案。 |
| 環境變數未更新 | 某些情況下環境變數在重載後仍保持舊值。 | 在 --reload 時確保 .env 檔案被重新載入,或在程式碼中使用 os.getenv 每次呼叫。 |
| Docker 中的檔案同步延遲 | Volume 掛載在 Windows/macOS 上可能出現同步延遲,導致重載不即時。 | 使用 delegated 或 cached 選項(如 -v $(pwd):/app:delegated),或在 Linux 主機上開發。 |
最佳實踐:
- 僅在開發環境開啟
--reload,避免在測試或正式環境誤用。 - 將
uvicorn的啟動指令寫入scripts/start-dev.sh,統一管理參數。 - 利用
startup/shutdown事件 清理全域資源,確保每次重載都能乾淨啟動。 - 結合
pytest:在每次重載前自動跑測試,可使用pre-commit或watchdog自訂腳本。
實際應用場景
API 原型開發
開發者在設計新路由或調整請求驗證時,只需保存檔案,即可立刻在瀏覽器或 Postman 中看到變更,省去手動重啟的時間。熱更新資料模型
當使用 Pydantic 定義資料模型,調整欄位或驗證規則後,--reload會自動載入新模型,讓測試 API 時不必重新啟動伺服器。與前端協作
前端開發者常需要配合後端調整回傳格式。使用uvicorn --reload,後端改動立即反映,前端可即時測試 API 呼叫,提升跨團隊協作效率。Docker 開發環境
在容器化的開發流程中,將程式碼掛載至容器內,配合--reload,讓容器內的 FastAPI 與本機編輯器同步,實現「一次編寫、隨處執行」的開發體驗。
總結
uvicorn --reload 的 hot‑reload 機制是 FastAPI 開發過程中不可或缺的加速器。它透過檔案監控自動重新載入應用,讓開發者能即時看到程式碼變更的結果,顯著提升除錯與迭代速度。
在使用時,務必注意 監控範圍、全域資源的釋放、以及開發與正式環境的差異,並遵循最佳實踐(如單一 worker、適當排除目錄、善用 startup/shutdown 事件)。結合 Docker、環境變數與測試框架,hot‑reload 能在多種實務情境中發揮最大效益,讓你的 FastAPI 專案開發更加順暢、可靠。祝開發愉快!