本文 AI 產出,尚未審核

FastAPI 課程 – 資料驗證與轉換(Validation & Serialization)

主題:Pydantic 欄位限制(regex、gt、lt)


簡介

在使用 FastAPI 建立 API 時,資料驗證是最關鍵的第一道防線。
FastAPI 之所以能夠快速且安全,核心就在於 Pydantic —— 一套以型別註解為基礎的資料模型系統。透過 Pydantic,我們不只可以自動產生 OpenAPI 文件,還能在請求進來前即時檢查資料的格式、範圍與條件,將錯誤回饋給前端,避免不合法的資料進入商業邏輯。

本單元聚焦於 欄位限制(field constraints),特別是三個常用的限制:

  • regex – 正則表達式匹配
  • gt – 大於(greater than)
  • lt – 小於(less than)

掌握這些限制,能讓你在 資料層面 完成大部分驗證工作,減少手動檢查的程式碼,提升開發效率與系統安全性。


核心概念

1. Pydantic 欄位限制的運作原理

Pydantic 會在 模型實例化 時依序執行:

  1. 型別轉換:將原始資料轉為欄位聲明的型別(例如 strint)。
  2. 驗證:根據欄位的 constraint(限制)執行檢查。
  3. 錯誤拋出:若任一限制失敗,Pydantic 會產生 ValidationError,FastAPI 會自動把錯誤轉成 422 Unprocessable Entity 回應。

重點:限制是宣告在欄位的型別上,而非在程式碼的流程裡寫 if 判斷。這樣做的好處是驗證邏輯集中、易於維護,且自動產生的 OpenAPI 會顯示相同的限制說明。


2. 常見限制說明

限制 參數 說明
regex regex="^\\d{4}-\\d{2}-\\d{2}$" 必須符合指定的正則表達式(常用於日期、電話、郵箱等格式)
gt gt=0 必須 大於 給定的值(不含等於)
lt lt=100 必須 小於 給定的值(不含等於)
ge ge=0 大於或等於(greater or equal)
le le=100 小於或等於(less or equal)

以下範例會同時示範 regexgtlt,以及如何在 FastAPI 路由中使用。


程式碼範例

:所有範例採用 Python 3.11+FastAPI 0.110Pydantic v2(語法相容於 v1),請自行安裝 fastapi[all]

2.1 基本模型:使用 regex 驗證郵箱

from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr

app = FastAPI()

class UserCreate(BaseModel):
    # EmailStr 內建 email 格式驗證,若要自訂 regex 可直接使用 Field
    email: str = Field(
        ...,
        regex=r"^[\w\.-]+@[\w\.-]+\.(com|net|org)$",
        description="必須是 .com、.net 或 .org 結尾的電子郵件"
    )
    name: str = Field(..., min_length=2, max_length=30)

@app.post("/users/")
async def create_user(payload: UserCreate):
    # 若驗證失敗,FastAPI 會自動回傳 422 錯誤
    return {"msg": "使用者建立成功", "data": payload}

說明

  • Field(..., regex=...)email 必須符合正則表達式。
  • 若傳入 test@example.xyz,FastAPI 會回傳:
{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "string does not match regex \"^[\\w\\.-]+@[\\w\\.-]+\\.(com|net|org)$\"",
      "type": "value_error.str.regex"
    }
  ]
}

2.2 數值限制:gtlt 確保價格範圍

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(..., min_length=1)
    price: float = Field(
        ...,
        gt=0,               # 必須大於 0
        lt=10000,           # 必須小於 10000
        description="商品價格,範圍 0 < price < 10,000"
    )
    quantity: int = Field(..., ge=1, le=1000, description="庫存數量,1~1000 件")

@app.post("/products/")
async def add_product(item: Product):
    return {"msg": "商品已加入", "product": item}

說明

  • gt=0lt=10000 直接限制 price 必須在 (0, 10000) 之間。
  • 若傳入 price=-5,回傳錯誤訊息會指出「ensure this value is greater than 0」。

2.3 結合多個限制:電話號碼 + 年齡驗證

class Profile(BaseModel):
    # 台灣手機號碼格式:09xx-xxxxxx 或 09xxxxxxxx
    phone: str = Field(
        ...,
        regex=r"^09\d{2}-?\d{6}$",
        description="台灣手機號碼,例:0912-345678 或 0912345678"
    )
    # 年齡必須在 18 歲以上且小於 120 歲
    age: int = Field(..., gt=17, lt=120, description="年齡,必須 18~119 歲")

@app.post("/profiles/")
async def create_profile(data: Profile):
    return {"msg": "個人資料建立成功", "profile": data}

說明

  • 正則表達式 ^09\d{2}-?\d{6}$ 同時接受有無 - 的寫法。
  • gt=17lt=120 組合保證 成年合理的最大年齡

2.4 自訂驗證函式:使用 @validator 搭配限制

有時候單純的 gt/lt/regex 無法滿足複雜需求,例如 起始日期必須早於結束日期。下面示範如何結合欄位限制與自訂驗證。

from datetime import date
from pydantic import BaseModel, Field, validator

class Event(BaseModel):
    name: str = Field(..., min_length=1)
    start_date: date = Field(..., description="活動開始日期")
    end_date: date = Field(..., description="活動結束日期")
    participants: int = Field(..., gt=0, description="參與人數,必須大於 0")

    @validator("end_date")
    def end_must_be_after_start(cls, v, values):
        start = values.get("start_date")
        if start and v <= start:
            raise ValueError("結束日期必須晚於開始日期")
        return v

說明

  • participants 使用 gt=0 限制正整數。
  • @validator 允許在欄位間進行跨欄位驗證,補足單一欄位限制的不足。

2.5 產生 OpenAPI 文件:限制自動顯示

FastAPI 會根據 Pydantic 的限制自動產生 Swagger/OpenAPI 說明。以下截圖示意(此處僅文字描述):

POST /products/
Request Body:
{
  "name": "string",
  "price": {
    "type": "number",
    "format": "float",
    "minimum": 0,
    "exclusiveMaximum": 10000
  },
  "quantity": {
    "type": "integer",
    "minimum": 1,
    "maximum": 1000
  }
}
  • minimum / exclusiveMaximum 正是由 gt / lt 產生的。
  • pattern(正則)則對應 regex

常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記加 ...(必填) Field() 預設是可選的,若未加 ...,即使限制不符合也不會觸發錯誤。 使用 Field(..., ...) 明確標示必填,或在模型上使用 typing.Annotated
正則表達式寫錯 複雜的 regex 容易漏掉轉義字元,導致驗證結果不如預期。 先在線上 regex 測試工具(regex101)驗證,再貼到程式碼。
使用 gt/lt 時混淆「含等」 gt / lt嚴格 大於/小於,若需要「≥」或「≤」應使用 ge / le 根據需求選擇正確的限制參數。
跨欄位驗證未使用 @validator 單一欄位限制無法檢查「起始 < 結束」等邏輯。 使用 @validatorroot_validator(Pydantic v2 改為 model_validator)。
錯誤訊息不友善 Pydantic 預設錯誤訊息較技術性,直接給前端不友好。 @validator 中自訂錯誤訊息,或在 FastAPI 中使用 exception_handler 包裝回傳。

最佳實踐

  1. 先聲明限制,再寫自訂驗證:盡量讓 Pydantic 自動完成大部分檢查,僅在必要時才加入 @validator
  2. 保持限制與說明同步:使用 description 參數說明限制,讓自動產生的 API 文檔更易讀。
  3. 測試驗證邏輯:使用 pytest 搭配 TestClient 撰寫單元測試,確保各種非法輸入都能得到正確的 422 回應。
  4. 統一錯誤格式:如果需要自訂錯誤結構,可在 FastAPI 中設定全域的 validation_exception_handler

實際應用場景

1. 電子商務平台的商品上架

  • 價格 必須大於 0 且不超過平台設定的上限(例如 100,000)。
  • 庫存 必須在合理範圍(1~10,000)內,避免負數或過大。
  • SKU 必須符合特定格式(例如 ^[A-Z]{3}-\d{4}$),使用 regex 防止錯誤代碼。

2. 金融系統的交易金額驗證

  • 交易金額 需要 gt=0,且根據用戶等級設定 lt 上限。
  • 日期 必須是 ISO 8601 格式,可使用 regexdatetime 型別。
  • 帳號號碼 常用 regex 驗證長度與檢查碼。

3. 社交平台的使用者註冊

  • 電子郵件 必須符合公司允許的域名(.com.org),使用 regex 限制。
  • 年齡 必須 gt=12(未滿 13 歲不允許註冊),lt=120 防止異常輸入。
  • 手機號碼 依國家格式使用 regex,避免錯誤或惡意測試。

4. 內部 API 的資料清理管線

在批次匯入 CSV 時,先以 Pydantic 模型驗證每筆資料,利用 gt/lt/regex 濾除不合格資料,減少後端資料庫的錯誤寫入。


總結

  • Pydantic 的欄位限制(regexgtlt)是 FastAPI 中最直接、最有效的資料驗證手段。
  • 透過 宣告式 的限制,我們能在模型層即完成大部分檢查,讓 API 自動產生的文件同步呈現限制資訊。
  • 正確使用 Field(..., ...)description、以及必要的 @validator,可以避免常見的驗證盲點與錯誤訊息不友善的問題。
  • 在實務專案中,將 業務規則 轉化為 Pydantic 限制,不僅提升程式碼可讀性,也降低後端錯誤率,讓開發與維護更輕鬆。

實踐建議:在新建每個 API 時,先思考「哪些欄位需要什麼限制?」再以 Pydantic 完成模型宣告。隨著需求變更,只要調整模型即可,無需逐一搜尋散落在服務層的驗證程式碼。

祝你在 FastAPI 的開發旅程中,善用 Pydantic 的強大驗證功能,寫出更安全、更可靠的 API! 🚀