FastAPI請求體詳解:用Pydantic定義複雜數據結構

1. 什麼是請求體?

在Web開發中,我們經常需要處理客戶端發送的數據。FastAPI中,當客戶端通過POST、PUT等方法發送複雜數據(比如JSON格式)時,這些數據會被放在請求體(Request Body)中。和通過URL傳遞的查詢參數(Query Parameters)不同,請求體更適合傳遞結構化數據(如用戶信息、表單等)。

舉個例子:用戶註冊時,我們需要收集姓名、年齡、地址等信息,這些數據如果用查詢參數會顯得冗長且不安全,因此通常放在請求體中。

2. Pydantic是什麼?

Pydantic是FastAPI推薦使用的數據驗證和解析庫。它允許我們定義數據結構(如用戶信息、商品信息),並自動進行數據類型檢查、格式驗證,還能將請求體數據轉換爲Python對象。

簡單來說,Pydantic就像一個“數據保鏢”,確保你收到的數據符合預期格式,避免因數據錯誤導致的API異常。

3. 定義第一個Pydantic模型

首先,我們需要安裝Pydantic(FastAPI已內置,若未安裝可通過pip install pydantic安裝)。然後,通過繼承pydantic.BaseModel來定義數據模型。

示例1:簡單模型(無嵌套)

from pydantic import BaseModel

# 定義一個User模型,包含name和age字段
class User(BaseModel):
    name: str  # 字符串類型,必填
    age: int   # 整數類型,必填

# FastAPI接口:接收User模型作爲請求體
from fastapi import FastAPI

app = FastAPI()

@app.post("/users/")
def create_user(user: User):
    # FastAPI會自動解析請求體爲User對象
    return {"message": f"用戶 {user.name} 創建成功!年齡:{user.age}"}

測試這個接口
用Postman或curl發送POST請求到http://localhost:8000/users/,請求體爲JSON:

{
    "name": "小明",
    "age": 20
}

接口會返回成功信息:{"message": "用戶 小明 創建成功!年齡:20"}

4. 複雜數據結構:嵌套模型

當數據包含子結構時(比如用戶有地址信息),我們可以在模型中嵌套其他Pydantic模型。

示例2:嵌套模型(用戶+地址)

from pydantic import BaseModel
from typing import Optional  # 用於可選字段

# 先定義子模型:地址
class Address(BaseModel):
    street: str  # 街道
    city: str    # 城市
    zipcode: str = "100000"  # 可選,默認值

# 定義主模型:用戶(嵌套Address)
class User(BaseModel):
    name: str
    age: int
    address: Optional[Address] = None  # 地址是可選的(Optional+默認None)

@app.post("/users/with-address/")
def create_user_with_address(user: User):
    # 訪問嵌套字段:user.address.street
    return {
        "name": user.name,
        "address": user.address.dict() if user.address else "無地址"
    }

測試
請求體中加入地址信息:

{
    "name": "小紅",
    "age": 22,
    "address": {
        "street": "科技路100號",
        "city": "北京"
    }
}

接口返回:{"name": "小紅", "address": {"street": "科技路100號", "city": "北京", "zipcode": "100000"}}

5. 複雜數據結構:列表類型

如果需要接收多個同類數據(如用戶的興趣愛好列表),可以用List類型(需導入typing.List)。

示例3:列表類型(興趣愛好列表)

from pydantic import BaseModel
from typing import List, Optional

class User(BaseModel):
    name: str
    age: int
    hobbies: List[str] = []  # 字符串列表,默認空列表
    extra_info: Optional[dict] = None  # 可選字典(比如額外信息)

@app.post("/users/with-hobbies/")
def create_user_with_hobbies(user: User):
    return {
        "name": user.name,
        "hobbies": user.hobbies,
        "extra_info": user.extra_info if user.extra_info else "無額外信息"
    }

測試
請求體包含列表:

{
    "name": "小剛",
    "age": 25,
    "hobbies": ["籃球", "編程"],
    "extra_info": {"height": 180, "weight": 70}
}

接口返回:{"name": "小剛", "hobbies": ["籃球", "編程"], "extra_info": {"height": 180, "weight": 70}}

6. 複雜數據結構:嵌套列表

如果列表中包含其他模型(比如多個商品),可以用List[模型名]

示例4:嵌套列表(商品列表)

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float

class Order(BaseModel):
    order_id: int
    items: List[Item]  # 列表中的每個元素是Item模型

@app.post("/orders/")
def create_order(order: Order):
    total = sum(item.price for item in order.items)
    return {
        "order_id": order.order_id,
        "items": [item.dict() for item in order.items],
        "total_price": total
    }

測試
請求體包含嵌套列表:

{
    "order_id": 1001,
    "items": [
        {"name": "蘋果", "price": 5.0},
        {"name": "香蕉", "price": 3.0}
    ]
}

接口返回總價格:{"order_id": 1001, "items": [{"name": "蘋果", "price": 5.0}, {"name": "香蕉", "price": 3.0}], "total_price": 8.0}

7. 數據驗證:自動攔截錯誤數據

Pydantic會自動檢查數據是否符合模型定義。如果數據類型錯誤(比如age寫成字符串),FastAPI會返回422 Validation Error

示例5:驗證失敗測試
發送錯誤請求體(age是字符串):

{
    "name": "小麗",
    "age": "20",  # 錯誤類型:字符串而非整數
    "hobbies": ["畫畫"]
}

接口會返回錯誤:

{
    "detail": [
        {
            "loc": ["body", "age"],
            "msg": "輸入值不是整數",
            "type": "type_error.integer"
        }
    ]
}

8. 總結

  • 請求體:適合傳遞複雜數據(如JSON),通過POST/PUT方法發送。
  • Pydantic:定義數據結構、自動驗證、減少手動解析代碼。
  • 嵌套模型:用模型名作爲字段類型實現子結構(如用戶+地址)。
  • 列表/字典:支持Listdict類型,滿足多數據項需求。
  • 自動驗證:不符合模型的請求會被FastAPI自動攔截,返回清晰錯誤信息。

掌握Pydantic是FastAPI處理複雜請求體的核心技能,能讓你的API開發更規範、更健壯。

(注:所有代碼可直接在FastAPI項目中運行,需安裝fastapiuvicornpip install fastapi uvicorn,運行命令:uvicorn main:app --reload

小夜