【FastAPI 教學】#04 Pydantic 資料驗證

測驗:Pydantic 資料驗證

共 5 題,點選答案後會立即顯示結果

1. 在 Pydantic 中,如何定義一個具有自動資料驗證功能的資料結構?

  • A. 繼承 DataClass
  • B. 繼承 BaseModel
  • C. 使用 @dataclass 裝飾器
  • D. 繼承 Validator

2. 以下哪個欄位定義表示「可以不填,預設為 None」?

class User(BaseModel): name: str nickname: ???
  • A. nickname: str
  • B. nickname: str = ""
  • C. nickname: Optional[str] = None
  • D. nickname: str = Required

3. 當 FastAPI 收到的 JSON 資料不符合 Pydantic Model 定義時,會回傳什麼 HTTP 狀態碼?

  • A. 400 Bad Request
  • B. 404 Not Found
  • C. 500 Internal Server Error
  • D. 422 Unprocessable Entity

4. 在 Field() 中,ge=0le=150 分別代表什麼意思?

age: int = Field(ge=0, le=150)
  • A. 大於 0,小於 150
  • B. 大於等於 0,小於等於 150
  • C. 等於 0,等於 150
  • D. 最小長度 0,最大長度 150

5. 使用 response_model 的主要目的是什麼?

@app.get(“/users/{id}”, response_model=UserResponse) async def get_user(id: int): return {“id”: id, “name”: “Alice”, “password”: “secret”}
  • A. 驗證輸入的請求資料格式
  • B. 自動產生資料庫結構
  • C. 控制 API 回傳格式,自動過濾不該回傳的欄位
  • D. 設定 API 的路由路徑

一句話說明

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 自動:

  1. 把 JSON 解析成 User 物件
  2. 檢查 name 是不是文字
  3. 檢查 age 是不是整數
  4. 如果格式錯誤,自動回傳 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 題,包含情境題與錯誤診斷題。

1. 你正在開發一個用戶註冊 API,需要接收使用者名稱(3-20字)、密碼(至少8字)和年齡(可選,預設18)。應該如何定義這個 Model? 情境題

  • A. 使用 strint 基本型別,不加任何驗證
  • B. 使用 Field() 設定 min_lengthmax_lengthdefault
  • C. 在 API 函式內手動檢查字串長度
  • D. 使用 Optional 包裝所有欄位

2. 小明的 API 收到以下錯誤回應,問題出在哪裡? 錯誤診斷

{ “detail”: [ { “loc”: [“body”, “age”], “msg”: “value is not a valid integer”, “type”: “type_error.integer” } ] }
  • A. API 路由設定錯誤
  • B. 缺少 age 欄位
  • C. 傳入的 age 值不是整數(可能是字串或其他型別)
  • D. Pydantic Model 沒有定義 age 欄位

3. 你需要設計一個訂單 API,每個用戶可以有多筆訂單,每筆訂單包含商品名稱和數量。應該如何設計資料結構? 情境題

  • A. 把所有資料放在一個 Model,用字串存訂單資訊
  • B. 使用 dict 型別存放訂單
  • C. 只定義 User Model,訂單資料不做驗證
  • D. 定義 Order Model,在 User Model 中使用 orders: list[Order]

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 物件
  • A. 沒有處理用戶不存在的情況
  • B. 會把密碼等敏感資料一起回傳給前端
  • C. 沒有使用 async/await
  • D. id 參數應該用 str 型別

5. 你要建立一個商品 CRUD API,建立時不需要 id(由資料庫產生),但回傳時要包含 id。最佳做法是? 情境題

  • A. 在 Model 中把 id 設為 Optional[int] = None
  • B. 建立和回傳都用同一個 Model,手動處理 id
  • C. 分開定義 ItemCreate(無 id)和 ItemResponse(有 id)兩個 Model
  • D. 不用 Model,直接回傳 dict

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *