FastAPI Request 混合參數處理 (Body, Path, Query)

在之前的章節中,我們學會了如何分別使用 Path, QueryBody。這一章我們將學習如何將它們混合在一起使用,以及如何處理更複雜的情況。

混合使用 Path, Query 和 Body

FastAPI 能夠聰明地分辨參數的來源:

  • Path: 在路徑 {} 中宣告的參數。
  • Body: 型別為 Pydantic Model 的參數。
  • Query: 其他預設型別參數 (int, str, etc.)。
from typing import Optional
from fastapi import FastAPI, Path
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

@app.put("/items/{item_id}")
async def update_item(
    item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
    q: Annotated[str | None, "搜尋字串"] = None,
    item: Annotated[Item | None, "項目資料"] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    if item:
        results.update({"item": item})
    return results

在這個例子中,FastAPI 會期待:

  1. URL 路徑中有 item_id
  2. URL 查詢字串中有選填的 q
  3. Request Body 中有一個符合 Item 模型的 JSON 物件。

多個 Body 參數

如果你想要在一個請求中接收多個 Pydantic 模型,該怎麼辦?例如,除了 Item 資訊,還想接收 User 資訊。

class User(BaseModel):
    username: str
    full_name: Optional[str] = None

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
    results = {"item_id": item_id, "item": item, "user": user}
    return results

在這種情況下,FastAPI 會預期 Request Body 是一個 JSON 物件,並且其中的 鍵 (Key) 對應到參數名稱 (itemuser):

Request Body 格式:

{
  "item": {
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2
  },
  "user": {
    "username": "dave",
    "full_name": "Dave Grohl"
  }
}
注意:原本單一 Body 參數時,Body 直接就是 Pydantic 模型的內容。但在多個 Body 參數時,FastAPI 會自動將它們包在一層以參數名稱為 Key 的物件中。

單一值的 Body 參數

有時候你不需要一個完整的 Pydantic Model,只想接收一個簡單的整數或字串作為 Body,而不是 Query 參數。

這時如果你直接宣告 importance: int,FastAPI 會預設把它當作 Query 參數。要解決這個問題,你需要使用 Body 類別。

from fastapi import Body

@app.put("/items/{item_id}")
async def update_item(
    item_id: int,
    item: Item,
    user: User,
    importance: Annotated[int, Body()]
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results

Request Body 格式:

{
  "item": {
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2
  },
  "user": {
    "username": "dave",
    "full_name": "Dave Grohl"
  },
  "importance": 5
}

嵌入單一 Body 參數 (Embed)

如果你只有一個 Pydantic 模型參數 item: Item,FastAPI 預設會把 Model 的欄位直接展開在 Body 的根層級。

但如果你希望它像多個參數時一樣,被包在一個 {"item": {...}} 的結構中,可以使用 embed=True

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
    results = {"item_id": item_id, "item": item}
    return results

Request Body 格式:

{
  "item": {
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2
  }
}

這在某些需要統一 JSON 結構的場景下非常有用。

總結

  • FastAPI 可以同時處理 Body, Path, Query 參數。
  • 多個 Pydantic 模型參數會被自動合併並以參數名稱作為 Key。
  • 使用 Body(...) 可以指示 FastAPI 將基本型別 (int, str) 作為 Body 而非 Query。
  • 使用 embed=True 可以強制將單一 Pydantic 模型包裝在物件中。