FastAPI 整合
FastAPI 是建立在 Pydantic 之上的 Python Web 框架:在路由函式的參數寫上 Pydantic 模型,FastAPI 就會自動解析請求內容、驗證資料、在失敗時回傳 HTTP 422,並把模型寫進 OpenAPI 文件(/docs)。本頁說明請求模型、回應模型、建立/更新/輸出模型分離的寫法,以及 PATCH 部分更新。
怎麼用 Pydantic 模型接收請求內容?
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class ItemCreate(BaseModel):
name: str = Field(min_length=1, max_length=50)
price: float = Field(gt=0)
tags: list[str] = []
@app.post("/items")
def create_item(item: ItemCreate):
# 進到這裡時,item 已經是驗證過的 ItemCreate 物件
return {"name": item.name, "price_with_tax": round(item.price * 1.05)}
用 uv run fastapi dev main.py 啟動後,打開 http://127.0.0.1:8000/docs 就能看到依模型自動產生的互動式文件。送出不合法的資料(例如 price: -1)時,FastAPI 會自動回傳 422,內容就是 Pydantic 的錯誤清單:
{
"detail": [
{
"type": "greater_than",
"loc": ["body", "price"],
"msg": "Input should be greater than 0",
"input": -1,
"ctx": {"gt": 0}
}
]
}
為什麼要另外定義回應模型?
response_model 決定 API 回傳什麼欄位。它會過濾掉模型沒有宣告的欄位,避免把密碼雜湊之類的內部資料不小心傳出去:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserCreate(BaseModel):
email: EmailStr
password: str
class UserOut(BaseModel):
id: int
email: EmailStr
@app.post("/users", response_model=UserOut, status_code=201)
def create_user(user: UserCreate):
record = {"id": 1, "email": user.email, "password_hash": "hashed..."}
return record # password_hash 會被 UserOut 過濾掉
建立、更新、輸出要用同一個模型嗎?
不建議。三種情境需要的欄位不同,常見做法是用繼承拆成多個模型:
| 模型 | 用途 | 特點 |
|---|---|---|
UserBase |
共用欄位 | 被其他模型繼承 |
UserCreate |
建立時的請求 | 包含密碼,id 由伺服器產生所以沒有 |
UserUpdate |
更新時的請求 | 所有欄位都是選填 |
UserOut |
回應 | 包含 id,不包含密碼 |
from pydantic import BaseModel, EmailStr
class UserBase(BaseModel):
email: EmailStr
full_name: str | None = None
class UserCreate(UserBase):
password: str
class UserUpdate(BaseModel):
email: EmailStr | None = None
full_name: str | None = None
class UserOut(UserBase):
id: int
PATCH 部分更新怎麼只改有傳的欄位?
用 model_dump(exclude_unset=True) 取得「使用者真的有傳」的欄位,再套用到既有資料:
from pydantic import BaseModel
class UserUpdate(BaseModel):
email: str | None = None
full_name: str | None = None
stored = {"email": "old@example.com", "full_name": "Alice"}
patch = UserUpdate.model_validate({"full_name": "Alice Chen"})
stored.update(patch.model_dump(exclude_unset=True))
print(stored) # {'email': 'old@example.com', 'full_name': 'Alice Chen'}
如果改用 model_dump(),沒傳的 email 會變成 None,把原本的資料覆蓋掉。
推薦影音
Corey Schafer:FastAPI 教學第 4 集:Pydantic Schemas
簡述:在 FastAPI 專案中建立請求與回應模型、處理驗證錯誤,示範本頁的模型分離做法,是 Corey Schafer FastAPI 系列的一集。
BugBytes:FastAPI 與 Pydantic 模型、巢狀模型
簡述:示範在 FastAPI 中使用模型類別與巢狀模型,適合搭配本頁與 巢狀模型 一起看。