レスポンスモデル¶
path operations のいずれにおいても、response_model
パラメータを使用して、レスポンスのモデルを宣言することができます:
@app.get()
@app.post()
@app.put()
@app.delete()
- など。
from typing import Any, 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.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
return item
@app.get("/items/", response_model=List[Item])
async def read_items() -> Any:
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]
🤓 Other versions and variants
from typing import Any
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.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
return item
@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]
from typing import Any, 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.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
return item
@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]
備考
response_model
は「デコレータ」メソッド(get
、post
など)のパラメータであることに注意してください。すべてのパラメータやボディのように、path operation関数 のパラメータではありません。
Pydanticモデルの属性に対して宣言するのと同じ型を受け取るので、Pydanticモデルになることもできますが、例えば、List[Item]
のようなPydanticモデルのlist
になることもできます。
FastAPIはresponse_model
を使って以下のことをします:
- 出力データを型宣言に変換します。
- データを検証します。
- OpenAPIの path operation で、レスポンス用のJSON Schemaを追加します。
- 自動ドキュメントシステムで使用されます。
しかし、最も重要なのは:
- 出力データをモデルのデータに限定します。これがどのように重要なのか以下で見ていきましょう。
技術詳細
レスポンスモデルは、関数の戻り値のアノテーションではなく、このパラメータで宣言されています。なぜなら、パス関数は実際にはそのレスポンスモデルを返すのではなく、dict
やデータベースオブジェクト、あるいは他のモデルを返し、response_model
を使用してフィールドの制限やシリアライズを行うからです。
同じ入力データの返却¶
ここではUserIn
モデルを宣言しています。それには平文のパスワードが含まれています:
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
return user
🤓 Other versions and variants
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
return user
そして、このモデルを使用して入力を宣言し、同じモデルを使って出力を宣言しています:
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
return user
🤓 Other versions and variants
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
return user
これで、ブラウザがパスワードを使ってユーザーを作成する際に、APIがレスポンスで同じパスワードを返すようになりました。
この場合、ユーザー自身がパスワードを送信しているので問題ないかもしれません。
しかし、同じモデルを別のpath operationに使用すると、すべてのクライアントにユーザーのパスワードを送信してしまうことになります。
危険
ユーザーの平文のパスワードを保存したり、レスポンスで送信したりすることは絶対にしないでください。
出力モデルの追加¶
代わりに、平文のパスワードを持つ入力モデルと、パスワードを持たない出力モデルを作成することができます:
from typing import Any, Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
🤓 Other versions and variants
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
ここでは、path operation関数がパスワードを含む同じ入力ユーザーを返しているにもかかわらず:
from typing import Any, Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
🤓 Other versions and variants
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
...response_model
をUserOut
と宣言したことで、パスワードが含まれていません:
from typing import Any, Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
🤓 Other versions and variants
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
そのため、FastAPI は出力モデルで宣言されていない全てのデータをフィルタリングしてくれます(Pydanticを使用)。
ドキュメントを見る¶
自動ドキュメントを見ると、入力モデルと出力モデルがそれぞれ独自のJSON Schemaを持っていることが確認できます。
そして、両方のモデルは、対話型のAPIドキュメントに使用されます:
レスポンスモデルのエンコーディングパラメータ¶
レスポンスモデルにはデフォルト値を設定することができます:
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: float = 10.5
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
🤓 Other versions and variants
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
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: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
description: str = None
はNone
がデフォルト値です。tax: float = 10.5
は10.5
がデフォルト値です。tags: List[str] = []
は空のリスト([]
)がデフォルト値です。
しかし、実際に保存されていない場合には結果からそれらを省略した方が良いかもしれません。
例えば、NoSQLデータベースに多くのオプション属性を持つモデルがあるが、デフォルト値でいっぱいの非常に長いJSONレスポンスを送信したくない場合です。
response_model_exclude_unset
パラメータの使用¶
path operation デコレータにresponse_model_exclude_unset=True
パラメータを設定することができます:
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: float = 10.5
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
🤓 Other versions and variants
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
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: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
そして、これらのデフォルト値はレスポンスに含まれず、実際に設定された値のみが含まれます。
そのため、path operationにIDfoo
が設定されたitemのリクエストを送ると、レスポンスは以下のようになります(デフォルト値を含まない):
{
"name": "Foo",
"price": 50.2
}
情報
FastAPIはこれをするために、Pydanticモデルの.dict()
をそのexclude_unset
パラメータで使用しています。
情報
以下も使用することができます:
response_model_exclude_defaults=True
response_model_exclude_none=True
exclude_defaults
とexclude_none
については、Pydanticのドキュメントで説明されている通りです。
デフォルト値を持つフィールドの値を持つデータ¶
しかし、IDbar
のitemのように、デフォルト値が設定されているモデルのフィールドに値が設定されている場合:
{
"name": "Bar",
"description": "The bartenders",
"price": 62,
"tax": 20.2
}
それらはレスポンスに含まれます。
デフォルト値と同じ値を持つデータ¶
IDbaz
のitemのようにデフォルト値と同じ値を持つデータの場合:
{
"name": "Baz",
"description": None,
"price": 50.2,
"tax": 10.5,
"tags": []
}
FastAPIは十分に賢いので(実際には、Pydanticが十分に賢い)description
やtax
、tags
はデフォルト値と同じ値を持っているにもかかわらず、明示的に設定されていることを理解しています。(デフォルトから取得するのではなく)
そのため、それらはJSONレスポンスに含まれることになります。
豆知識
デフォルト値はNone
だけでなく、なんでも良いことに注意してください。
例えば、リスト([]
)や10.5
のfloat
などです。
response_model_include
とresponse_model_exclude
¶
path operationデコレータとしてresponse_model_include
とresponse_model_exclude
も使用することができます。
属性名を持つstr
のset
を受け取り、含める(残りを省略する)か、除外(残りを含む)します。
これは、Pydanticモデルが1つしかなく、出力からいくつかのデータを削除したい場合のクイックショートカットとして使用することができます。
豆知識
それでも、これらのパラメータではなく、複数のクラスを使用して、上記のようなアイデアを使うことをおすすめします。
これはresponse_model_include
やresponse_mode_exclude
を使用していくつかの属性を省略しても、アプリケーションのOpenAPI(とドキュメント)で生成されたJSON Schemaが完全なモデルになるからです。
同様に動作するresponse_model_by_alias
にも当てはまります。
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: float = 10.5
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
"baz": {
"name": "Baz",
"description": "There goes my baz",
"price": 50.2,
"tax": 10.5,
},
}
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
return items[item_id]
@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
return items[item_id]
🤓 Other versions and variants
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
"baz": {
"name": "Baz",
"description": "There goes my baz",
"price": 50.2,
"tax": 10.5,
},
}
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
return items[item_id]
@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
return items[item_id]
豆知識
{"name", "description"}
の構文はこれら2つの値をもつset
を作成します。
これはset(["name", "description"])
と同等です。
set
の代わりにlist
を使用する¶
もしset
を使用することを忘れて、代わりにlist
やtuple
を使用しても、FastAPIはそれをset
に変換して正しく動作します:
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: float = 10.5
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
"baz": {
"name": "Baz",
"description": "There goes my baz",
"price": 50.2,
"tax": 10.5,
},
}
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include=["name", "description"],
)
async def read_item_name(item_id: str):
return items[item_id]
@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"])
async def read_item_public_data(item_id: str):
return items[item_id]
🤓 Other versions and variants
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
"baz": {
"name": "Baz",
"description": "There goes my baz",
"price": 50.2,
"tax": 10.5,
},
}
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include=["name", "description"],
)
async def read_item_name(item_id: str):
return items[item_id]
@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"])
async def read_item_public_data(item_id: str):
return items[item_id]
まとめ¶
path operationデコレータのresponse_model
パラメータを使用して、レスポンスモデルを定義し、特にプライベートデータがフィルタリングされていることを保証します。
明示的に設定された値のみを返すには、response_model_exclude_unset
を使用します。