測驗:Pydantic 資料驗證
共 5 題,點選答案後會立即顯示結果
1. 在 Pydantic 中,如何定義一個具有自動資料驗證功能的資料結構?
2. 以下哪個欄位定義表示「可以不填,預設為 None」?
class User(BaseModel):
name: str
nickname: ???
3. 當 FastAPI 收到的 JSON 資料不符合 Pydantic Model 定義時,會回傳什麼 HTTP 狀態碼?
4. 在 Field() 中,ge=0 和 le=150 分別代表什麼意思?
age: int = Field(ge=0, le=150)
5. 使用 response_model 的主要目的是什麼?
@app.get(“/users/{id}”, response_model=UserResponse)
async def get_user(id: int):
return {“id”: id, “name”: “Alice”, “password”: “secret”}
一句話說明
Pydantic 讓你定義「資料長什麼樣」,FastAPI 自動幫你檢查格式。
為什麼要學這個?
當你用 AI 寫 FastAPI,它一定會用 Pydantic 來處理請求資料。你會看到一堆 class User(BaseModel): 這種寫法。讀懂 Pydantic,你就能理解 AI 怎麼定義 API 的輸入輸出。
最小範例:BaseModel 基礎
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
# 使用
user = User(name="Alice", age=25)
print(user.name) # Alice
逐行翻譯
from pydantic import BaseModel # 匯入 Pydantic 的基礎類別
class User(BaseModel): # User 繼承 BaseModel,自動有資料驗證功能
name: str # name 欄位,必須是文字
age: int # age 欄位,必須是整數
白話翻譯:「定義一個 User 資料結構,必須有 name(文字)和 age(整數)」
Request Body 自動解析
FastAPI 搭配 Pydantic,自動把 JSON 變成 Python 物件:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
age: int
@app.post("/users")
async def create_user(user: User): # FastAPI 自動從 JSON 解析成 User
return {"message": f"建立了 {user.name}"}
這在幹嘛?
當有人送這個 JSON 到 /users:
{"name": "Alice", "age": 25}
Code language: JSON / JSON with Comments (json)FastAPI 自動:
- 把 JSON 解析成
User物件 - 檢查
name是不是文字 - 檢查
age是不是整數 - 如果格式錯誤,自動回傳 422 錯誤
欄位型別速查
AI 寫的代碼會用到這些型別:
| 你會看到 | 意思 | 範例 |
|---|---|---|
str |
文字 | "hello" |
int |
整數 | 42 |
float |
小數 | 3.14 |
bool |
布林值 | True / False |
list[str] |
文字列表 | ["a", "b"] |
dict[str, int] |
字典 | {"a": 1} |
class Product(BaseModel):
name: str # 文字
price: float # 小數
in_stock: bool # 布林值
tags: list[str] # 文字列表
Code language: CSS (css)Optional 欄位與預設值
不是每個欄位都必填。AI 會這樣處理可選欄位:
常見寫法 1:給預設值
class User(BaseModel):
name: str
age: int = 18 # 有預設值,可以不填
翻譯:「age 如果沒給,預設是 18」
常見寫法 2:用 Optional
from typing import Optional
class User(BaseModel):
name: str
nickname: Optional[str] = None # 可以是文字,也可以是 None
翻譯:「nickname 可填可不填,不填就是 None」
常見寫法 3:Python 3.10+ 的寫法
class User(BaseModel):
name: str
nickname: str | None = None # 同上,更簡潔的寫法
翻譯:「nickname 可以是 str 或 None」
Field() 進階驗證
當 AI 需要更細緻的驗證規則,會用 Field():
from pydantic import BaseModel, Field
class User(BaseModel):
name: str = Field(min_length=1, max_length=50)
age: int = Field(ge=0, le=150)
email: str = Field(pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")
Field 常用參數翻譯
| 你會看到 | 意思 |
|---|---|
min_length=1 |
至少 1 個字 |
max_length=50 |
最多 50 個字 |
ge=0 |
大於等於 0(greater or equal) |
le=150 |
小於等於 150(less or equal) |
gt=0 |
大於 0(greater than) |
lt=100 |
小於 100(less than) |
pattern=r"..." |
符合這個正則表達式 |
default="hello" |
預設值 |
example="Alice" |
API 文件範例值 |
完整範例
from pydantic import BaseModel, Field
class CreateUserRequest(BaseModel):
username: str = Field(
min_length=3,
max_length=20,
example="alice123"
)
password: str = Field(
min_length=8,
example="secretpass"
)
age: int = Field(
default=18,
ge=0,
le=150
)
翻譯:
- username:3-20 字,範例是 “alice123”
- password:至少 8 字
- age:預設 18,必須在 0-150 之間
巢狀模型(Nested Model)
AI 處理複雜資料結構時,會把 Model 放進 Model 裡:
class Address(BaseModel):
city: str
street: str
class User(BaseModel):
name: str
address: Address # address 是另一個 Model
對應的 JSON:
{
"name": "Alice",
"address": {
"city": "Taipei",
"street": "忠孝東路"
}
}
Code language: JSON / JSON with Comments (json)巢狀列表
class Order(BaseModel):
product: str
quantity: int
class User(BaseModel):
name: str
orders: list[Order] # 多個訂單
Code language: CSS (css)對應的 JSON:
{
"name": "Alice",
"orders": [
{"product": "書", "quantity": 2},
{"product": "筆", "quantity": 5}
]
}
Code language: JSON / JSON with Comments (json)Response Model 設定
AI 會用 response_model 告訴 FastAPI 回傳什麼格式:
class UserResponse(BaseModel):
id: int
name: str
# 注意:沒有 password!
@app.get("/users/{id}", response_model=UserResponse)
async def get_user(id: int):
# 即使你回傳的物件有 password,FastAPI 也會過濾掉
return {"id": id, "name": "Alice", "password": "secret"}
這在幹嘛:
- 自動過濾掉不該回傳的欄位(如密碼)
- 自動產生 API 文件
- 自動驗證回傳格式
常見模式:分開定義 Request 和 Response
# 建立用戶時的輸入
class UserCreate(BaseModel):
name: str
password: str
# 回傳用戶時的輸出(不包含密碼)
class UserResponse(BaseModel):
id: int
name: str
@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate):
# 存到資料庫,拿到 id
return {"id": 1, "name": user.name}
Code language: CSS (css)AI 最常這樣用
用法 1:CRUD API 的資料模型
class ItemBase(BaseModel):
name: str
price: float
class ItemCreate(ItemBase): # 繼承 ItemBase
pass
class ItemResponse(ItemBase):
id: int
翻譯:建立時用 ItemCreate,回傳時用 ItemResponse(多一個 id)
用法 2:分頁回應
class PaginatedResponse(BaseModel):
items: list[User]
total: int
page: int
size: int
Code language: CSS (css)翻譯:回傳一頁的資料,包含項目列表、總數、頁碼、每頁幾筆
用法 3:API 錯誤回應
class ErrorResponse(BaseModel):
detail: str
code: str | None = None
翻譯:錯誤回應包含錯誤訊息,可選的錯誤代碼
Vibe Coder 檢查點
看到 Pydantic Model 時確認:
- [ ] 哪些欄位是必填?(沒有預設值的就是必填)
- [ ] 哪些欄位可選?(有
= None或= 預設值) - [ ] 有沒有用 Field() 做驗證?(看驗證規則合不合理)
- [ ] Request 和 Response 是分開定義嗎?(好習慣)
- [ ] Response 有沒有不小心包含敏感資料?(如密碼)
常見錯誤翻譯
當驗證失敗,FastAPI 會回傳 422 錯誤:
{
"detail": [
{
"loc": ["body", "age"],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
Code language: JSON / JSON with Comments (json)翻譯:
loc: ["body", "age"]→ 錯誤發生在 request body 的 age 欄位msg→ 「值不是有效的整數」type→ 錯誤類型是「型別錯誤」
延伸:知道就好
這些進階功能遇到再查:
- validator / field_validator:自定義驗證邏輯
- model_validator:跨欄位驗證
- Config:設定 Model 的行為(如 orm_mode)
- computed_field:動態計算的欄位
小結
| 概念 | 一句話 |
|---|---|
| BaseModel | 定義資料結構,自動驗證 |
| 必填欄位 | 沒預設值就是必填 |
| Optional | 可以是 None |
| Field() | 進階驗證規則 |
| 巢狀模型 | Model 裡放 Model |
| response_model | 控制 API 回傳格式 |
下一篇,我們會學習錯誤處理與例外管理。
進階測驗:Pydantic 資料驗證
測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。
共 5 題,包含情境題與錯誤診斷題。
1. 你正在開發一個用戶註冊 API,需要接收使用者名稱(3-20字)、密碼(至少8字)和年齡(可選,預設18)。應該如何定義這個 Model? 情境題
2. 小明的 API 收到以下錯誤回應,問題出在哪裡? 錯誤診斷
{
“detail”: [
{
“loc”: [“body”, “age”],
“msg”: “value is not a valid integer”,
“type”: “type_error.integer”
}
]
}
3. 你需要設計一個訂單 API,每個用戶可以有多筆訂單,每筆訂單包含商品名稱和數量。應該如何設計資料結構? 情境題
4. 以下程式碼有什麼安全問題? 錯誤診斷
class User(BaseModel):
id: int
name: str
password: str
@app.get(“/users/{id}”)
async def get_user(id: int):
user = get_user_from_db(id)
return user # 直接回傳完整 user 物件