Body - ネストされたモデル¶
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
型パラメータ付きの list
を宣言する¶
list
、dict
、tuple
のように、型パラメータ(内部型)を持つ型を宣言するには
- Python 3.9 より前のバージョンの場合は、
typing
モジュールから対応するバージョンをインポートします。 - 角かっこ:
[
および]
を使用して、内部型を「型パラメータ」として渡します。
Python 3.9 では、次のようになります。
my_list: list[str]
3.9 より前のバージョンの Python では、次のようになります。
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 Schema/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
を直接操作している場合、このようなエディターサポートは得られません。
しかし、それらを心配する必要もありません。受信した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モデルによって提供される最大限の柔軟性を得ることができます。
しかし、すべての利点
- エディターサポート(あらゆる場所で補完!)
- データ変換(別名:解析/シリアライズ)
- データ検証
- スキーマドキュメント
- 自動ドキュメント