コンテンツにスキップ

リクエストボディ

クライアント (ブラウザなど) からAPIにデータを送信する必要があるとき、データを リクエストボディ (request body) として送ります。

リクエスト ボディはクライアントによってAPIへ送られます。レスポンス ボディはAPIがクライアントに送るデータです。

APIはほとんどの場合 レスポンス ボディを送らなければなりません。しかし、クライアントは必ずしも リクエスト ボディを送らなければいけないわけではありません。

リクエスト ボディを宣言するために Pydantic モデルを使用します。そして、その全てのパワーとメリットを利用します。

情報

データを送るには、POST (もっともよく使われる)、PUTDELETE または PATCH を使うべきです。

GET リクエストでボディを送信することは、仕様では未定義の動作ですが、FastAPI でサポートされており、非常に複雑な(極端な)ユースケースにのみ対応しています。

非推奨なので、Swagger UIを使った対話型のドキュメントにはGETのボディ情報は表示されません。さらに、中継するプロキシが対応していない可能性があります。

Pydanticの BaseModel をインポート

ます初めに、 pydantic から BaseModel をインポートする必要があります:

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

データモデルの作成

そして、BaseModel を継承したクラスとしてデータモデルを宣言します。

すべての属性にpython標準の型を使用します:

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

クエリパラメータの宣言と同様に、モデル属性がデフォルト値をもつとき、必須な属性ではなくなります。それ以外は必須になります。オプショナルな属性にしたい場合は None を使用してください。

例えば、上記のモデルは以下の様なJSON「オブジェクト」(もしくはPythonの dict ) を宣言しています:

{
    "name": "Foo",
    "description": "An optional description",
    "price": 45.2,
    "tax": 3.5
}

...descriptiontax はオプショナル (デフォルト値は None) なので、以下のJSON「オブジェクト」も有効です:

{
    "name": "Foo",
    "price": 45.2
}

パラメータとして宣言

パスオペレーション に加えるために、パスパラメータやクエリパラメータと同じ様に宣言します:

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

...そして、作成したモデル Item で型を宣言します。

結果

そのPythonの型宣言だけで FastAPI は以下のことを行います:

  • リクエストボディをJSONとして読み取ります。
  • 適当な型に変換します(必要な場合)。
  • データを検証します。
    • データが無効な場合は、明確なエラーが返され、どこが不正なデータであったかを示します。
  • 受け取ったデータをパラメータ item に変換します。
    • 関数内で Item 型であると宣言したので、すべての属性とその型に対するエディタサポート(補完など)をすべて使用できます。
  • モデルのJSONスキーマ定義を生成し、好きな場所で使用することができます。
  • これらのスキーマは、生成されたOpenAPIスキーマの一部となり、自動ドキュメントのUIに使用されます。

自動ドキュメント生成

モデルのJSONスキーマはOpenAPIで生成されたスキーマの一部になり、対話的なAPIドキュメントに表示されます:

そして、それらが使われる パスオペレーション のそれぞれのAPIドキュメントにも表示されます:

エディターサポート

エディターによる型ヒントと補完が関数内で利用できます (Pydanticモデルではなく dict を受け取ると、同じサポートは受けられません):

型によるエラーチェックも可能です:

これは偶然ではなく、このデザインに基づいてフレームワークが作られています。

全てのエディターで機能することを確認するために、実装前の設計時に徹底的にテストしました。

これをサポートするためにPydantic自体にもいくつかの変更がありました。

上記のスクリーンショットはVisual Studio Codeを撮ったものです。

しかし、PyCharmやほとんどのPythonエディタでも同様なエディターサポートを受けられます:

豆知識

PyCharmエディタを使用している場合は、Pydantic PyCharm Pluginが使用可能です。

以下のエディターサポートが強化されます:

  • 自動補完
  • 型チェック
  • リファクタリング
  • 検索
  • インスペクション

モデルの使用

関数内部で、モデルの全ての属性に直接アクセスできます:

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    item_dict = item.dict()
    if item.tax:
        price_with_tax = item.price + item.tax
        item_dict.update({"price_with_tax": price_with_tax})
    return item_dict

リクエストボディ + パスパラメータ

パスパラメータとリクエストボディを同時に宣言できます。

FastAPI はパスパラメータである関数パラメータはパスから受け取り、Pydanticモデルによって宣言された関数パラメータはリクエストボディから受け取るということを認識します。

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item):
    return {"item_id": item_id, **item.dict()}

リクエストボディ + パスパラメータ + クエリパラメータ

また、ボディパスクエリのパラメータも同時に宣言できます。

FastAPI はそれぞれを認識し、適切な場所からデータを取得します。

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item, q: Union[str, None] = None):
    result = {"item_id": item_id, **item.dict()}
    if q:
        result.update({"q": q})
    return result

関数パラメータは以下の様に認識されます:

  • パラメータがパスで宣言されている場合は、優先的にパスパラメータとして扱われます。
  • パラメータが単数型 (intfloatstrbool など)の場合はクエリパラメータとして解釈されます。
  • パラメータが Pydantic モデル型で宣言された場合、リクエストボディとして解釈されます。

備考

FastAPIは、= Noneがあるおかげで、qがオプショナルだとわかります。

Optional[str]Optional はFastAPIでは使用されていません(FastAPIはstrの部分のみ使用します)。しかし、Optional[str] はエディタがコードのエラーを見つけるのを助けてくれます。

Pydanticを使わない方法

もしPydanticモデルを使用したくない場合は、Bodyパラメータが利用できます。Body - Multiple Parameters: Singular values in bodyを確認してください。