ボディ - ネストされたモデル¶
FastAPI を使用すると、深くネストされた任意のモデルを定義、検証、文書化、使用することができます(Pydanticのおかげです)。
リストのフィールド¶
属性をサブタイプとして定義することができます。例えば、Pythonのlist
は以下のように定義できます:
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の標準の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
のようなタイプパラメータ(内部の型)を持つ型を宣言するには:
typing
モジュールからそれらをインストールします。- 角括弧(
[
と]
)を使って「タイプパラメータ」として内部の型を渡します:
from typing import List
my_list: List[str]
型宣言の標準的なPythonの構文はこれだけです。
内部の型を持つモデルの属性にも同じ標準の構文を使用してください。
そのため、以下の例ではtags
を具体的な「文字列のリスト」にすることができます:
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
があります。
そのため、以下のように、Set
をインポートしてstr
のset
としてtags
を宣言することができます:
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モデルである可能性があります。
そのため、特定の属性名、型、バリデーションを指定して、深くネストしたJSONobject
を宣言することができます。
すべては、任意のネストにされています。
サブモデルの定義¶
例えば、Image
モデルを定義することができます:
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 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 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 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 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
のリストであり、オプションのImage
のリストを持っていることに注目してください。
純粋なリストのボディ¶
期待するJSONボディのトップレベルの値がJSONarray
(Pythonのlist
)であれば、Pydanticモデルと同じように、関数のパラメータで型を宣言することができます:
images: List[Image]
以下のように:
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モデルの場合のように)。
これは、まだ知らないキーを受け取りたいときに便利だと思います。
他にも、int
のように他の型のキーを持ちたい場合などに便利です。
それをここで見ていきましょう。
この場合、int
のキーとfloat
の値を持つものであれば、どんなdict
でも受け入れることができます:
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モデルが提供する最大限の柔軟性を持ちながら、コードをシンプルに短く、エレガントに保つことができます。
以下のような利点があります:
- エディタのサポート(どこでも補完!)
- データ変換(別名:構文解析・シリアライズ)
- データの検証
- スキーマ文書
- 自動文書化