FastAPI 教學:Pydantic 模型中的 Enum 型別轉換
簡介
在使用 FastAPI 建立 API 時,我們常會把資料驗證與序列化的工作交給 Pydantic 模型。
當 API 的請求或回應中需要使用一組固定的文字或數值(例如訂單狀態、使用者角色、付款方式…),Enum(列舉)是一個既直觀又安全的選擇。
然而,若直接把 Python 的 Enum 放進 Pydantic 模型,卻會遇到「字串/數值無法自動轉換」或「回傳的 JSON 仍是 Enum 的內部值」等問題。
本篇文章將深入探討 Enum 型別在 Pydantic 中的轉換機制,說明如何讓 FastAPI 能正確接受、驗證、以及回傳 Enum,從而提升 API 的可讀性與防呆能力。
核心概念
1. 為什麼要使用 Enum
- 限制輸入範圍:只允許預先定義好的值,避免資料錯誤。
- 自說自明的程式碼:
OrderStatus.PENDING比起純文字"pending"更具可讀性。 - 自動產生 OpenAPI 文件:FastAPI 會把 Enum 轉成
enum欄位,前端開發者一眼就能看出可接受的選項。
2. 基本的 Enum 定義
from enum import Enum
class OrderStatus(str, Enum):
"""訂單狀態的列舉,繼承自 str 讓 JSON 序列化更直觀"""
PENDING = "pending"
CONFIRMED = "confirmed"
SHIPPED = "shipped"
CANCELED = "canceled"
重點:
- 繼承
str(或int)可以讓 Enum 成員在序列化時直接以字串(或整數)呈現,而不是{ "value": "pending" }。- 使用大寫常數名稱,值則保持 API 想要的格式(小寫字串)。
3. 把 Enum 放入 Pydantic 模型
from pydantic import BaseModel
class OrderCreateRequest(BaseModel):
"""建立訂單的 Request Model"""
product_id: int
quantity: int
status: OrderStatus = OrderStatus.PENDING # 預設值
class OrderResponse(BaseModel):
"""回傳給前端的 Response Model"""
order_id: int
product_id: int
quantity: int
status: OrderStatus
- 當 FastAPI 收到
POST /orders時,Pydantic 會自動把傳入的字串(如"shipped")轉成OrderStatus.SHIPPED。 - 回傳時,
OrderResponse會把 Enum 成員序列化為其字串值("shipped"),符合大多數前端的期待。
4. Enum 與驗證:自訂錯誤訊息
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.post("/orders", response_model=OrderResponse)
def create_order(req: OrderCreateRequest):
# 假設這裡有商業邏輯,直接回傳示範資料
return {
"order_id": 123,
"product_id": req.product_id,
"quantity": req.quantity,
"status": req.status, # 直接使用 Enum 成員
}
若請求的 status 不是 Enum 中的合法值,FastAPI 會回傳:
{
"detail": [
{
"loc": ["body", "status"],
"msg": "value is not a valid enumeration member; permitted: 'pending', 'confirmed', 'shipped', 'canceled'",
"type": "type_error.enum"
}
]
}
這樣的錯誤訊息已經相當友好,若想自訂可在 Pydantic 中使用 @validator。
5. Enum 與 int 類型的混合使用
有時候系統內部會以數字代碼存儲狀態,卻希望 API 對外呈現文字。這時可以同時保留 int 與 str:
class HttpStatus(int, Enum):
OK = 200
BAD_REQUEST = 400
NOT_FOUND = 404
INTERNAL_ERROR = 500
class ApiErrorResponse(BaseModel):
code: HttpStatus
message: str
FastAPI 會把 200、400… 直接序列化成數字,且在 OpenAPI 中產生 enum: [200, 400, 404, 500]。
6. Enum 與自訂序列化(json_encoders)
若想讓 Enum 在回傳時使用 自訂格式(例如同時回傳代碼與說明),可以在 Config 中設定 json_encoders:
class OrderStatus(str, Enum):
PENDING = "pending"
CONFIRMED = "confirmed"
SHIPPED = "shipped"
CANCELED = "canceled"
def description(self) -> str:
return {
"pending": "等待處理",
"confirmed": "已確認",
"shipped": "已出貨",
"canceled": "已取消",
}[self.value]
class OrderResponse(BaseModel):
order_id: int
status: OrderStatus
class Config:
json_encoders = {
OrderStatus: lambda v: {"code": v.value, "desc": v.description()}
}
回傳結果:
{
"order_id": 101,
"status": {
"code": "shipped",
"desc": "已出貨"
}
}
這在需要同時提供機器可讀與人類可讀資訊時非常有用。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記繼承 str(或 int) |
Enum 會被序列化為物件,前端會收到 {"value":"pending"} 而非 "pending"。 |
繼承 str(或 int)讓 Pydantic 直接使用基礎類型值。 |
在 Request Model 中使用 Enum 但未設定預設值 |
客戶端若未傳入該欄位,會得到 None,而非預期的預設 Enum。 |
為欄位提供 default= 或在路由函式內自行填補。 |
| Enum 成員名稱與值不一致 | 若名稱與值不同,OpenAPI 會顯示名稱而非值,可能造成前端誤解。 | 保持名稱語意清晰,值則為 API 想要的字串或數字。 |
混用 Enum 與 Literal |
有時會想限制字串但不想寫 Enum,使用 Literal 會失去 Enum 的自動文件化功能。 |
若需要文件化與集中管理,優先使用 Enum;若只需簡單限制,可考慮 Literal。 |
自訂 json_encoders 時忘記在 Config 設定 |
會導致自訂格式無效,仍回傳原始 Enum。 | 確認 Config 中正確設定 json_encoders,且使用 BaseModel 的子類別。 |
最佳實踐:
- 始終繼承基礎類型(
str、int),確保序列化一致。 - 在 Enum 中加入說明方法(
description()),方便在文件或 UI 中使用。 - 利用 FastAPI 的自動文件生成:寫好 Enum 後,檢查
/docs是否正確顯示選項。 - 將所有業務相關的常量集中管理:不在路由函式裡硬編碼字串,改用 Enum。
- 測試 Enum 的驗證邏輯:使用
TestClient撰寫單元測試,確保非法值會得到預期的 422 回應。
實際應用場景
1. 電子商務:訂單狀態管理
在電商平台,訂單會經歷 待付款 → 已付款 → 出貨 → 完成 等階段。使用 Enum 可以:
- 防止前端傳入非法狀態(例如
"delivered")。 - 在 OpenAPI 中自動顯示可選狀態,減少前端開發溝通成本。
- 於後端服務中直接以
OrderStatus進行條件判斷,提升程式可讀性。
2. 多語系系統的錯誤代碼
後端回傳錯誤時,常用代碼與訊息同時呈現。利用 json_encoders 把 Enum 包裝成 {code, message},前端只要根據 code 取對應的多語系文字即可。
class ErrorCode(int, Enum):
USER_NOT_FOUND = 1001
INVALID_PASSWORD = 1002
TOKEN_EXPIRED = 1003
3. 設定檔與環境變數的類型安全
在大型微服務中,設定值往往是字串。把設定項目寫成 Enum,配合 Pydantic 的 BaseSettings,即可在啟動時即時驗證設定是否正確。
class LogLevel(str, Enum):
DEBUG = "debug"
INFO = "info"
WARNING = "warning"
ERROR = "error"
class Settings(BaseSettings):
log_level: LogLevel = LogLevel.INFO
總結
- Enum 為 FastAPI 與 Pydantic 提供了 類型安全、文件自動化、以及驗證友好 的解決方案。
- 繼承
str或int是關鍵,讓 API 在 JSON 序列化時直接呈現期望的值。 - 透過
json_encoders、@validator或BaseSettings,我們可以靈活地自訂 Enum 的行為,滿足不同的業務需求。 - 避免常見的陷阱(忘記繼承基礎類型、未設定預設值等),並遵循最佳實踐,能讓你的 FastAPI 專案在可維護性與開發效率上都有顯著提升。
掌握了 Enum 型別轉換 後,你的 API 不僅更安全,也更易於與前端、測試以及文件產生工具協作。快把這些技巧運用到實際專案中,讓開發流程更加順暢吧!