Open1

FastAPIのBody(embed=True)でハマった話:/docsが利口すぎて気づきにくい罠

tech_mwtech_mw

FastAPIの Body(embed=True) は「受け取るリクエストJSONの形」を縛るだけ
/​docsでは最初から「⚪︎⚪︎キーでラップした正しい形」が提示されるので、間違った例を試さないとエラーを確認できずハマった体験談

embed=Trueは「⚪︎⚪︎キーでラップしたJSONを要求する」という設計になる
/​docsはその「正しい形」を最初から自動生成してくれる
逆に言うと「⚪︎⚪︎キーを外した間違ったリクエストを試さないと気づけない」

/docs(Swagger UI)って何?

FastAPIではサーバーを起動すると、標準で自動生成ドキュメントが使えるようになる

uvicorn main:app --reload

を実行してサーバーを立ち上げたら、

http://127.0.0.1:8000/docs

にブラウザでアクセスするだけで、Swagger UI形式のドキュメントが表示される

特徴

  • 自動でOpenAPIスキーマを解析して、APIエンドポイントやリクエスト例を表示
  • その場でリクエストを試せる
  • 返却されるレスポンスを確認できる

(↓docs画面)

どんな詰まり方をしたか

FastAPIで以下のようにエンドポイントを定義。

from typing import Union

from fastapi import Body, FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = Field(
        default=None, title="The description of the item", max_length=300
    )
    price: float = Field(gt=0, description="The price must be greater than zero")
    tax: Union[float, None] = None


@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

/docs(Swagger UI)では、リクエストボディ例が自動でこう出る。

{
  "item": {
    "name": "string",
    "description": "string",
    "price": 1,
    "tax": 0
  }
}

これをそのまま送ればもちろん成功するので、何も気づかず通ってしまう。

でも本質は、「itemというキーでラップした形を必ず送らないといけない」というAPI設計になる。

例えば itemキーを外して↓のように送ると

{
  "name": "string",
  "description": "string",
  "price": 1,
  "tax": 0
}

エラーになる。

{
  "detail": [
    {
      "loc": ["body", "item"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

embed=True / Falseの比較

embed指定 期待するリクエストボディ
なし { "name": "...", "price": ... }
あり { "item": { "name": "...", "price": ... } }

embed=Trueを使うとどうなるか

  • クライアントに「⚪︎⚪︎キーでラップしたJSONを送ってね」と強制する
  • その形じゃないとバリデーションエラー

なぜ気づきにくかったか

  • /docsはFastAPIの型定義を解析して「期待する正しいJSON例」を自動生成する
  • embed=Trueを指定すると最初からitemキー付きの例しか出ない
  • だから「itemキーを外したらどうなるか」を試さないとエラーを体験できない

解決策

  • /docsでも意図的にフィールドを消してエラーを確認する
  • Postmanやcurlなどを使って自由にリクエストボディを編集し、間違ったケースを試す
  • embed=Trueを使うと「APIの受け取る形がこうなる」という設計をきちんと意識する

学び

  • FastAPIのembed=Trueは「受け取るJSONを1階層包む」ための指定
  • /docsは正しい形を出してくれるけど、逆に「間違った形を送るとどうなるか」を試すには1手間必要
  • Postmanなどを併用して失敗ケースを体験するのが大事