ボディ - ネストされたモデル¶
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
を使用してこれらの型アノテーションを宣言できます。💡
しかし、Python 3.9より前のバージョン (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
を宣言する¶
list
、dict
、tuple
のような型パラメータ(内部の型)を持つ型を宣言するには
- 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でその宣言を行うだけで、次のものが得られます。
- ネストされたモデルであっても、エディターサポート(補完など)
- データ変換
- データ検証
- 自動ドキュメント
特殊な型と検証¶
str
、int
、float
などの通常の単一型とは別に、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モデルをlist
、set
などのサブタイプとして使用することもできます。
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
情報
Offer
がItem
のリストを持ち、さらに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モデルによって提供される最大限の柔軟性を持ちながら、コードをシンプル、短く、エレガントに保つことができます。
しかし、すべての利点もあります。
- エディターサポート(どこでも補完!)
- データ変換(別名:パース/シリアライズ)
- データ検証
- スキーマ文書
- 自動ドキュメント