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 會在 模型實例化 時依序執行:
- 型別轉換:將原始資料轉為欄位聲明的型別(例如
str→int)。 - 驗證:根據欄位的 constraint(限制)執行檢查。
- 錯誤拋出:若任一限制失敗,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) |
以下範例會同時示範 regex、gt、lt,以及如何在 FastAPI 路由中使用。
程式碼範例
註:所有範例採用 Python 3.11+、FastAPI 0.110 與 Pydantic 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 數值限制:gt 與 lt 確保價格範圍
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=0、lt=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=17、lt=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 |
單一欄位限制無法檢查「起始 < 結束」等邏輯。 | 使用 @validator 或 root_validator(Pydantic v2 改為 model_validator)。 |
| 錯誤訊息不友善 | Pydantic 預設錯誤訊息較技術性,直接給前端不友好。 | 在 @validator 中自訂錯誤訊息,或在 FastAPI 中使用 exception_handler 包裝回傳。 |
最佳實踐
- 先聲明限制,再寫自訂驗證:盡量讓 Pydantic 自動完成大部分檢查,僅在必要時才加入
@validator。 - 保持限制與說明同步:使用
description參數說明限制,讓自動產生的 API 文檔更易讀。 - 測試驗證邏輯:使用
pytest搭配TestClient撰寫單元測試,確保各種非法輸入都能得到正確的 422 回應。 - 統一錯誤格式:如果需要自訂錯誤結構,可在 FastAPI 中設定全域的
validation_exception_handler。
實際應用場景
1. 電子商務平台的商品上架
- 價格 必須大於 0 且不超過平台設定的上限(例如 100,000)。
- 庫存 必須在合理範圍(1~10,000)內,避免負數或過大。
- SKU 必須符合特定格式(例如
^[A-Z]{3}-\d{4}$),使用regex防止錯誤代碼。
2. 金融系統的交易金額驗證
- 交易金額 需要
gt=0,且根據用戶等級設定lt上限。 - 日期 必須是 ISO 8601 格式,可使用
regex或datetime型別。 - 帳號號碼 常用
regex驗證長度與檢查碼。
3. 社交平台的使用者註冊
- 電子郵件 必須符合公司允許的域名(
.com、.org),使用regex限制。 - 年齡 必須
gt=12(未滿 13 歲不允許註冊),lt=120防止異常輸入。 - 手機號碼 依國家格式使用
regex,避免錯誤或惡意測試。
4. 內部 API 的資料清理管線
在批次匯入 CSV 時,先以 Pydantic 模型驗證每筆資料,利用 gt/lt/regex 濾除不合格資料,減少後端資料庫的錯誤寫入。
總結
- Pydantic 的欄位限制(
regex、gt、lt)是 FastAPI 中最直接、最有效的資料驗證手段。 - 透過 宣告式 的限制,我們能在模型層即完成大部分檢查,讓 API 自動產生的文件同步呈現限制資訊。
- 正確使用
Field(..., ...)、description、以及必要的@validator,可以避免常見的驗證盲點與錯誤訊息不友善的問題。 - 在實務專案中,將 業務規則 轉化為 Pydantic 限制,不僅提升程式碼可讀性,也降低後端錯誤率,讓開發與維護更輕鬆。
實踐建議:在新建每個 API 時,先思考「哪些欄位需要什麼限制?」再以 Pydantic 完成模型宣告。隨著需求變更,只要調整模型即可,無需逐一搜尋散落在服務層的驗證程式碼。
祝你在 FastAPI 的開發旅程中,善用 Pydantic 的強大驗證功能,寫出更安全、更可靠的 API! 🚀