😊
FastAPI の OpenAPI定義で oneOf の値を定義する方法
こんにちは。hayata-yamamoto です。
本記事では、FastAPI を用いて OpenAPI 定義を作成する際に oneOf な値をエクスポートする方法を共有します。「OpenAPIの定義なんて、ちゃんと書かなくてもチーム内で分かればいいやろ」と思う人もいらっしゃるでしょうが、弊社では OpenAPI の定義を用いて TypeScript の型が生成されてしまうため、意図した通りにOpenAPIの定義を記述する必要がありました。
なお、本記事の執筆に際しては FastAPI の以下の Issue を参考にしています。原文で知りたい人はそちらをご確認ください
OpenAPI における oneOf, anyOf, allOf の違い
以下のページが詳しいです。
どのように書き分けるか
以下のコードを手元で実行してみていただくとわかりやすいです。例として、丸と四角のアイテム情報をPOSTするAPIを用意しました。違いは Annotated を使って、Python の型ヒントにメタデータを付与するかどうかです。Annotated については以下の記事も合わせてご覧ください。
from typing import Annotated, Literal, Union
from fastapi import FastAPI
from pydantic import BaseModel, Field
class Round(BaseModel):
kind: Literal['round']
label: str
border_radius: float
class Rectangle(BaseModel):
kind: Literal['rectangle']
label: str
x: int
y: int
height: int
width: int
# ここが重要
Item = Annotated[Union[Round, Rectangle], Field(discriminator="kind")]
class ItemAnyOfRequest(BaseModel):
item: Union[Round, Rectangle]
class ItemOneOfRequest(BaseModel):
item: Item
app = FastAPI()
@app.post('/item-one-of')
def post_item(request: ItemOneOfRequest):
...
@app.post('/item-any-of')
def post_any_of_item(request: ItemAnyOfRequest):
...
たったこれだけですが、OpenAPIで出力される結果が変わります。型ヒントのヒントを Annotated で定義するイメージですね。「kind を先に見て、Union[Round, Rectangle]
を区別しますよ」と言っています。
Discussion