本文 AI 產出,尚未審核

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 會:

  1. 終止 目前的工作執行緒與事件迴圈
  2. 重新載入 目標模組(如 main:app
  3. 啟動 新的事件迴圈與工作者

這個過程對使用者是透明的,唯一的副作用是 第一次載入後的記憶體狀態會被清空,因此不會保留先前的全域變數或快取。

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 上可能出現同步延遲,導致重載不即時。 使用 delegatedcached 選項(如 -v $(pwd):/app:delegated),或在 Linux 主機上開發。

最佳實踐

  1. 僅在開發環境開啟 --reload,避免在測試或正式環境誤用。
  2. uvicorn 的啟動指令寫入 scripts/start-dev.sh,統一管理參數。
  3. 利用 startup / shutdown 事件 清理全域資源,確保每次重載都能乾淨啟動。
  4. 結合 pytest:在每次重載前自動跑測試,可使用 pre-commitwatchdog 自訂腳本。

實際應用場景

  1. API 原型開發
    開發者在設計新路由或調整請求驗證時,只需保存檔案,即可立刻在瀏覽器或 Postman 中看到變更,省去手動重啟的時間。

  2. 熱更新資料模型
    當使用 Pydantic 定義資料模型,調整欄位或驗證規則後,--reload 會自動載入新模型,讓測試 API 時不必重新啟動伺服器。

  3. 與前端協作
    前端開發者常需要配合後端調整回傳格式。使用 uvicorn --reload,後端改動立即反映,前端可即時測試 API 呼叫,提升跨團隊協作效率。

  4. Docker 開發環境
    在容器化的開發流程中,將程式碼掛載至容器內,配合 --reload,讓容器內的 FastAPI 與本機編輯器同步,實現「一次編寫、隨處執行」的開發體驗。


總結

uvicorn --reloadhot‑reload 機制是 FastAPI 開發過程中不可或缺的加速器。它透過檔案監控自動重新載入應用,讓開發者能即時看到程式碼變更的結果,顯著提升除錯與迭代速度。

在使用時,務必注意 監控範圍、全域資源的釋放、以及開發與正式環境的差異,並遵循最佳實踐(如單一 worker、適當排除目錄、善用 startup/shutdown 事件)。結合 Docker、環境變數與測試框架,hot‑reload 能在多種實務情境中發揮最大效益,讓你的 FastAPI 專案開發更加順暢、可靠。祝開發愉快!