本文 AI 產出,尚未審核

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 對外呈現文字。這時可以同時保留 intstr

class HttpStatus(int, Enum):
    OK = 200
    BAD_REQUEST = 400
    NOT_FOUND = 404
    INTERNAL_ERROR = 500

class ApiErrorResponse(BaseModel):
    code: HttpStatus
    message: str

FastAPI 會把 200400… 直接序列化成數字,且在 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 想要的字串或數字。
混用 EnumLiteral 有時會想限制字串但不想寫 Enum,使用 Literal 會失去 Enum 的自動文件化功能。 若需要文件化與集中管理,優先使用 Enum;若只需簡單限制,可考慮 Literal
自訂 json_encoders 時忘記在 Config 設定 會導致自訂格式無效,仍回傳原始 Enum。 確認 Config 中正確設定 json_encoders,且使用 BaseModel 的子類別。

最佳實踐

  1. 始終繼承基礎類型strint),確保序列化一致。
  2. 在 Enum 中加入說明方法description()),方便在文件或 UI 中使用。
  3. 利用 FastAPI 的自動文件生成:寫好 Enum 後,檢查 /docs 是否正確顯示選項。
  4. 將所有業務相關的常量集中管理:不在路由函式裡硬編碼字串,改用 Enum。
  5. 測試 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 提供了 類型安全、文件自動化、以及驗證友好 的解決方案。
  • 繼承 strint 是關鍵,讓 API 在 JSON 序列化時直接呈現期望的值。
  • 透過 json_encoders@validatorBaseSettings,我們可以靈活地自訂 Enum 的行為,滿足不同的業務需求。
  • 避免常見的陷阱(忘記繼承基礎類型、未設定預設值等),並遵循最佳實踐,能讓你的 FastAPI 專案在可維護性與開發效率上都有顯著提升。

掌握了 Enum 型別轉換 後,你的 API 不僅更安全,也更易於與前端、測試以及文件產生工具協作。快把這些技巧運用到實際專案中,讓開發流程更加順暢吧!