コンテンツへスキップ

Body - Nested Models

FastAPIでは、任意に深くネストされたモデルを定義、検証、ドキュメント化、使用できます (Pydanticのおかげです)。

リストフィールド

属性をサブタイプとして定義できます。例えば、Pythonのlistなどです。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


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

これにより、tagsはリストになりますが、リストの要素の型は宣言されません。

型パラメーター付きのリストフィールド

しかし、Pythonには内部型、または「型パラメーター」を持つリストを宣言するための特定の方法があります。

typingのListをインポートする

Python 3.9以降では、後述のように標準のlistを使ってこれらの型アノテーションを宣言できます。💡

しかし、3.9より前のPythonバージョン (3.6以降) では、まず標準PythonのtypingモジュールからListをインポートする必要があります。

from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
🤓 その他のバージョンとバリアント
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


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

型パラメーター付きのlistを宣言する

listdicttupleのような型パラメーター (内部型) を持つ型を宣言するには

  • Pythonのバージョンが3.9より低い場合は、typingモジュールから同等のバージョンをインポートします。
  • 角括弧[]を使って内部型を「型パラメーター」として渡します。

Python 3.9では次のようになります。

my_list: list[str]

Python 3.9より前のバージョンでは次のようになります。

from typing import List

my_list: List[str]

これらはすべて、型宣言のための標準的なPythonの構文です。

内部型を持つモデル属性にも、同じ標準構文を使用します。

したがって、私たちの例では、tagsを具体的に「文字列のリスト」にすることができます。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


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

セット型

しかし、よく考えると、タグは重複すべきではなく、おそらく一意の文字列であるべきだと気づきます。

そしてPythonには、一意なアイテムのセットのための特別なデータ型、setがあります。

それから、tagsを文字列のセットとして宣言できます。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
from typing import Set, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


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

これにより、重複するデータを含むリクエストを受信した場合でも、一意なアイテムのセットに変換されます。

そして、そのデータを出力する際には、たとえソースに重複があったとしても、一意なアイテムのセットとして出力されます。

そして、それに応じて注釈付け/ドキュメント化もされます。

ネストされたモデル

Pydanticモデルの各属性には型があります。

しかし、その型自体が別のPydanticモデルであることもあります。

したがって、特定の属性名、型、およびバリデーションを持つ、深くネストされたJSON「オブジェクト」を宣言できます。

それらすべてが、任意にネストされます。

サブモデルを定義する

例えば、Imageモデルを定義できます。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    image: Image | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
from typing import Set, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    image: Union[Image, None] = None


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

サブモデルを型として使用する

そして、それを属性の型として使用できます。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    image: Image | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
from typing import Set, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    image: Union[Image, None] = None


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

これは、FastAPIが次のようなボディを期待することを意味します。

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2,
    "tags": ["rock", "metal", "bar"],
    "image": {
        "url": "http://example.com/baz.jpg",
        "name": "The Foo live"
    }
}

繰り返しますが、その宣言だけで、FastAPIでは次のものが得られます。

  • ネストされたモデルでもエディターサポート (補完など)
  • データ変換
  • データ検証
  • 自動ドキュメント

特殊な型とバリデーション

strintfloatなどの通常の単一型に加えて、strを継承するより複雑な単一型を使用できます。

利用可能なすべてのオプションについては、Pydanticの型概要をご覧ください。次の章でいくつかの例を紹介します。

例えば、Imageモデルにurlフィールドがある場合、それをstrの代わりにPydanticのHttpUrlのインスタンスとして宣言できます。

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    image: Image | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
from typing import Set, Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    image: Union[Image, None] = None


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

文字列は有効なURLであるかどうかがチェックされ、JSONスキーマ/OpenAPIでそのようにドキュメント化されます。

サブモデルのリストを持つ属性

Pydanticモデルをlistsetなどのサブタイプとして使用することもできます。

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    images: list[Image] | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: set[str] = set()
    images: Union[list[Image], None] = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
from typing import List, Set, Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    images: Union[List[Image], None] = None


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

これにより、次のようなJSONボディが期待されます (変換、検証、ドキュメント化など)。

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2,
    "tags": [
        "rock",
        "metal",
        "bar"
    ],
    "images": [
        {
            "url": "http://example.com/baz.jpg",
            "name": "The Foo live"
        },
        {
            "url": "http://example.com/dave.jpg",
            "name": "The Baz"
        }
    ]
}

情報

imagesキーに画像のオブジェクトのリストが含まれていることに注目してください。

深くネストされたモデル

任意に深くネストされたモデルを定義できます。

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    images: list[Image] | None = None


class Offer(BaseModel):
    name: str
    description: str | None = None
    price: float
    items: list[Item]


@app.post("/offers/")
async def create_offer(offer: Offer):
    return offer
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: set[str] = set()
    images: Union[list[Image], None] = None


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


@app.post("/offers/")
async def create_offer(offer: Offer):
    return offer
from typing import List, Set, Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    images: Union[List[Image], None] = None


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


@app.post("/offers/")
async def create_offer(offer: Offer):
    return offer

情報

OfferItemのリストを持ち、ItemがさらにオプションのImageのリストを持つことに注目してください。

純粋なリストのボディ

期待するJSONボディのトップレベルの値がJSON array (Pythonのlist) の場合、Pydanticモデルと同じように関数のパラメーターで型を宣言できます。

images: List[Image]

またはPython 3.9以降では

images: list[Image]

例:

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
    return images
🤓 その他のバージョンとバリアント
from typing import List

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


@app.post("/images/multiple/")
async def create_multiple_images(images: List[Image]):
    return images

どこでもエディターサポート

そして、どこでもエディターサポートが受けられます。

リスト内のアイテムでも

Pydanticモデルの代わりにdictを直接扱っていたら、このようなエディターサポートは得られなかったでしょう。

しかし、それらを心配する必要もありません。受信した辞書は自動的に変換され、出力も自動的にJSONに変換されます。

任意のdictのボディ

また、ボディを、ある型のキーと別の型の値を持つdictとして宣言することもできます。

これにより、有効なフィールド/属性名が何であるかを事前に知る必要がありません (Pydanticモデルの場合とは異なります)。

これは、まだ知らないキーを受信したい場合に役立ちます。


もう1つの有用なケースは、別の型 (例: int) のキーを持ちたい場合です。

ここではそれを見ていきます。

この場合、intキーとfloat値を持つdictであれば何でも受け入れます。

from fastapi import FastAPI

app = FastAPI()


@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
    return weights
🤓 その他のバージョンとバリアント
from typing import Dict

from fastapi import FastAPI

app = FastAPI()


@app.post("/index-weights/")
async def create_index_weights(weights: Dict[int, float]):
    return weights

ヒント

JSONはキーとしてstrのみをサポートしていることに注意してください。

しかし、Pydanticには自動データ変換機能があります。

これは、APIクライアントがキーとして文字列しか送信できない場合でも、それらの文字列が純粋な整数を含んでいる限り、Pydanticがそれらを変換して検証することを意味します。

そして、weightsとして受け取るdictは、実際にintキーとfloat値を持つことになります。

まとめ

FastAPIを使用すると、Pydanticモデルが提供する最大限の柔軟性を享受しながら、コードをシンプル、短く、エレガントに保つことができます。

しかし、すべての利点も享受できます。

  • エディターサポート (どこでも補完!)
  • データ変換 (別名パース/シリアライズ)
  • データ検証
  • スキーマドキュメント
  • 自動ドキュメント