コンテンツへスキップ

入力と出力にOpenAPIスキーマを分けるかどうか

Pydantic v2を使用する場合、生成されるOpenAPIは以前よりも少し正確で正しいものです。😎

実際、場合によっては、同じPydanticモデルに対して、入力用と出力用で、デフォルト値があるかどうかによって、OpenAPI内に2つのJSONスキーマを持つことがあります。

その仕組みと、必要に応じて変更する方法を見てみましょう。

入力と出力のためのPydanticモデル

以下のように、デフォルト値を持つPydanticモデルがあるとしましょう。

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None

# Code below omitted 👇
👀 ファイル全体のプレビュー
from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI()


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
🤓 その他のバージョンとバリアント
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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


@app.get("/items/")
def read_items() -> List[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

入力のためのモデル

このモデルを以下のように入力として使用する場合

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI()


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

# Code below omitted 👇
👀 ファイル全体のプレビュー
from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI()


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
🤓 その他のバージョンとバリアント
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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


@app.get("/items/")
def read_items() -> List[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

...この場合、descriptionフィールドは必須ではありません。デフォルト値がNoneだからです。

ドキュメント内の入力モデル

ドキュメントで確認すると、descriptionフィールドには赤いアスタリスクがなく、必須としてマークされていないことがわかります。

出力のためのモデル

しかし、同じモデルを以下のように出力として使用する場合

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI()


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
🤓 その他のバージョンとバリアント
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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


@app.get("/items/")
def read_items() -> List[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

...この場合、descriptionにデフォルト値があるため、そのフィールドに何も返さない場合でも、デフォルト値が設定されます。

出力応答データのためのモデル

ドキュメントを操作して応答を確認すると、コードはdescriptionフィールドの1つに何も追加していませんが、JSON応答にはデフォルト値(null)が含まれています。

これは、常に値を持つことを意味し、その値がNone(またはJSONではnull)になる場合があるということです。

つまり、APIを使用するクライアントは、値が存在するかどうかをチェックする必要はなく、フィールドは常に存在すると仮定できますが、場合によってはNoneというデフォルト値を持つということです。

これをOpenAPIで記述する方法は、そのフィールドを必須としてマークすることです。なぜなら、それは常に存在するからです。

そのため、モデルのJSONスキーマは、入力用か出力用かによって異なる場合があります。

  • 入力用の場合、description必須ではありません
  • 出力用の場合、それは必須です(そして、おそらくNone、またはJSON用語ではnull)。

ドキュメント内の出力モデル

ドキュメント内の出力モデルも確認できます。namedescription両方赤いアスタリスク必須としてマークされています。

ドキュメント内の入力と出力のモデル

OpenAPIで利用可能なすべてのスキーマ (JSONスキーマ) を確認すると、Item-InputItem-Outputの2つがあることがわかります。

Item-Inputの場合、description必須ではありません。赤いアスタリスクはありません。

しかし、Item-Outputの場合、description必須です。赤いアスタリスクがあります。

Pydantic v2のこの機能により、APIドキュメントはより正確になり、自動生成されたクライアントやSDKがある場合も、より正確になり、より良い開発者体験と一貫性が得られます。🎉

スキーマを分離しない

さて、入力と出力で同じスキーマを使用したい場合もあります。

おそらく主なユースケースは、すでに自動生成されたクライアントコード/SDKがあり、まだすべての自動生成されたクライアントコード/SDKを更新したくない場合です。いずれは更新したいと思うかもしれませんが、今すぐではないかもしれません。

その場合、FastAPIでこの機能を無効にすることができます。パラメーターseparate_input_output_schemas=Falseを使用します。

情報

separate_input_output_schemasのサポートはFastAPI 0.102.0で追加されました。🤓

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI(separate_input_output_schemas=False)


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
🤓 その他のバージョンとバリアント
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI(separate_input_output_schemas=False)


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI(separate_input_output_schemas=False)


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


@app.get("/items/")
def read_items() -> List[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

ドキュメント内の入力と出力モデルのスキーマを同じにする

そして、モデルの入力と出力には、単一のスキーマであるItemのみが存在し、description必須ではありません

これはPydantic v1と同じ動作です。🤓