本文 AI 產出,尚未審核
FastAPI – 資料驗證與轉換(Validation & Serialization)
主題:Enum 型別自動轉換
簡介
在 Web API 開發中,列舉(Enum) 常被用來限制客戶端只能傳入特定的值,例如訂單狀態、使用者角色或產品類別。若每次都手動檢查字串是否合法,程式碼會變得冗長且易出錯。
FastAPI 內建對 Python Enum 的支援,能在 請求驗證 時自動把字串、整數或其他可比較的值轉換成對應的 Enum 成員,並在回傳時自動序列化為友好的字串(或值)。這不只提升開發效率,也讓 API 規格更清晰、錯誤回報更一致。
本文將說明 Enum 型別在 FastAPI 中的自動轉換機制,並提供實作範例、常見陷阱與最佳實踐,幫助你在實務專案中正確運用。
核心概念
1️⃣ Python Enum 基礎
from enum import Enum, IntEnum
class OrderStatus(str, Enum):
"""訂單狀態的列舉,繼承自 str 讓 JSON 序列化更直觀"""
PENDING = "pending"
PAID = "paid"
SHIPPED = "shipped"
CANCELED = "canceled"
class UserRole(IntEnum):
"""使用者角色的列舉,繼承自 IntEnum 讓值為整數"""
ADMIN = 1
MODERATOR = 2
USER = 3
- 以
str或int為基底的 Enum,在 JSON 序列化 時會直接輸出基底值,避免出現OrderStatus.PENDING這樣的物件表示。 Enum本身是不可變的、具備唯一性,適合作為 資料驗證 的「白名單」。
2️⃣ FastAPI 如何自動轉換
當你在 Pydantic 模型或 路由參數 中宣告 Enum 類別時,FastAPI 會:
- 解析請求:把傳入的字串(或整數)與 Enum 成員比對。
- 驗證成功:回傳對應的 Enum 成員實例。
- 驗證失敗:拋出 422 Unprocessable Entity,回應中會列出「值不在允許的列舉」訊息。
- 回傳序列化:Response 內的 Enum 會自動轉成基底值(
str/int),符合 OpenAPI 規範。
3️⃣ 路由參數的 Enum
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/orders/{status}")
async def list_orders(status: OrderStatus = Path(..., description="訂單狀態")):
"""
依照訂單狀態過濾清單。
- 若 `status` 為不合法值,FastAPI 會自動回傳 422。
"""
# `status` 已是 OrderStatus 成員,可直接使用其屬性
return {"filter": status, "message": f"取得 {status.value} 訂單"}
- URL 路徑、查詢參數、表單欄位 都可以直接使用 Enum。
Path(..., description=...)讓 OpenAPI 文件自動顯示可接受的列舉值。
4️⃣ 請求 Body 中的 Enum
from pydantic import BaseModel
class CreateUserRequest(BaseModel):
username: str
role: UserRole # 直接使用 Enum
@app.post("/users")
async def create_user(payload: CreateUserRequest):
"""
建立新使用者。`role` 若傳入非 1/2/3 會回傳 422。
"""
# payload.role 為 UserRole 成員,可直接比較
if payload.role == UserRole.ADMIN:
# 執行管理員專屬邏輯
pass
return {"username": payload.username, "role": payload.role}
- Pydantic 會在模型解析階段完成 Enum 轉換。
- 回傳時,
payload.role會序列化為整數1、2、3。
5️⃣ 自訂 Enum 的序列化方式
若想要在回傳 JSON 時使用 自訂字串(例如顯示中文),可以覆寫 __str__ 或使用 Pydantic 的 json_encoders:
class PaymentMethod(str, Enum):
CREDIT = "credit"
PAYPAL = "paypal"
ATM = "atm"
def __str__(self) -> str:
# 讓 API 回傳更友好的中文描述
mapping = {
"credit": "信用卡",
"paypal": "PayPal",
"atm": "ATM 轉帳"
}
return mapping[self.value]
from pydantic import BaseModel
class PaymentInfo(BaseModel):
method: PaymentMethod
class Config:
json_encoders = {
PaymentMethod: lambda v: str(v) # 使用上方 __str__ 的結果
}
- 只要在
Config.json_encoders中指定,就能讓 所有回傳 皆使用自訂的文字。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 忘記繼承基底類別 | class MyEnum(Enum): 只繼承 Enum,回傳時會是 MyEnum.VALUE,不符合 JSON 預期。 |
繼承 str 或 int(如 class Status(str, Enum):) |
| 大小寫不一致 | 客戶端傳入 Pending 而 Enum 為 pending,驗證失敗。 |
使用 case_insensitive=True(Pydantic 1.10+)或自行實作前置處理。 |
| 重複值 | 多個成員使用相同值會導致只保留第一個,其他成員不可直接比對。 | 確保每個成員的值唯一,或使用 @unique 裝飾器檢查。 |
| Enum 與資料庫不相容 | ORM(如 SQLAlchemy)需要把 Enum 轉成資料庫支援的類型。 | 在模型層使用 Enum,在 ORM 層使用 Enum 或 String 欄位,並在 CRUD 時自行轉換。 |
| 自訂序列化忘記註冊 | 覆寫 __str__ 後未在 json_encoders 設定,回傳仍是原始值。 |
設定 Config.json_encoders,或使用 FastAPI 的 response_model_exclude_unset。 |
最佳實踐
- 始終繼承基底類別(
str/int),確保 OpenAPI 文件正確顯示。 - 使用
Enum作為單一真相來源:所有驗證、業務邏輯與文件皆以同一個 Enum 定義為準。 - 在 Pydantic
Config中集中管理序列化,避免每個路由重複編寫。 - 加入
description、example,讓 Swagger UI 更友善。 - 測試列舉的邊界情況(大小寫、空字串、非法值),確保 422 回應符合預期。
實際應用場景
| 場景 | 為何使用 Enum | 範例程式碼 |
|---|---|---|
| 訂單系統:過濾、更新訂單狀態 | 防止非法狀態寫入資料庫 | GET /orders/{status}、PATCH /orders/{order_id} |
| 權限管理:角色授權檢查 | 角色固定且需對應數字 ID | POST /users 中的 role: UserRole |
| 支付平台:付款方式限定 | 不同方式需要不同的後端流程 | PaymentInfo 模型與自訂中文回傳 |
| 多語系 API:回傳語言代碼 | 語言代碼必須是已註冊的 ISO 639‑1 | GET /content?lang=LanguageCode |
| IoT 裝置:設備類型或指令代碼 | 裝置只能接受預定義指令 | PUT /devices/{device_id}/command |
示例:在訂單系統中,若前端傳入
status=invalid,FastAPI 會回傳:
{
"detail": [
{
"loc": ["path", "status"],
"msg": "value is not a valid enumeration member; permitted: 'pending', 'paid', 'shipped', 'canceled'",
"type": "type_error.enum"
}
]
}
這樣的錯誤訊息即時告知開發者哪裡出錯,降低除錯成本。
總結
- Enum 為 API 提供 明確、可驗證的值集合,FastAPI 透過 Pydantic 完成 自動轉換與序列化,讓開發者只需專注於業務邏輯。
- 正確的 繼承基底類別、統一定義、以及 在 Config 中設定序列化,是避免常見陷阱的關鍵。
- 在實務中,將 列舉作為單一真相來源,不僅提升程式碼可讀性,也讓 OpenAPI 文件自動保持同步,減少文件與實作不一致的風險。
掌握了 Enum 的自動轉換,你的 FastAPI 專案將更安全、更易維護,也能提供更友善的 API 使用者體驗。祝開發順利! 🚀