FastAPI のクエリパラメータの宣言に Pydantic モデルが正式に使えるようになった | TrustHub テックブログ
トラストハブ AI チームの中村です。
つい先日、FastAPI から v0.115.0 がリリースされました。
これにより、FastAPI のクエリパラメータの宣言に Pydantic モデルが使えるようになりました。
本記事では、今回リリースされた新機能の具体的な使用方法を、それが特に威力を発揮できそうなユースケースに沿って解説していこうと思います。
新機能の概要
今回のリリースで、FastAPI のクエリパラメータの指定に Pydantic モデルが使えるようになりました。Pydantic モデルは強力な型ヒントとバリデーション機能を提供するので、今後はクエリパラメータの型チェック、制約条件の設定、そしてデータの正規化などが容易に実装できるようになります。また、クエリだけではなくヘッダーや Cookie の宣言も同じく Pydantic モデルが適用できるようになったので、これらも同様にバリデーションや制限を比較的簡単に実装できるようになりました。
その他のアップデート内容については、公式ドキュメントのリリースノートを参照してください。
Pydantic モデルを使用したクエリパラメータの宣言
まずは簡単な例を見ていきましょう。ここでは、とある EC サイトの商品検索 API の検索機能を FastAPI で実装するケースを考えます。
検索機能の仕様
エンドポイント
- GET
/search
クエリパラメータ
-
query *
: 検索クエリの文字列(最大50文字) -
category *
: 検索する商品のカテゴリーで、以下のどれか- 本 (
books
) - 衣服 (
clothing
) - 電化製品 (
electronics
)
- 本 (
-
page
:検索結果のページ番号(1以上)、デフォルトは 1 -
per_page
:1ページあたりのアイテム数(1〜100)、デフォルトは 20
ここで *
が付いているパラメータは入力必須とします。
実装例
まずは、クエリパラメータで使用する Pydantic モデル SearchParams
を定義します。
from typing import Literal
from pydantic import BaseModel, Field
class SearchParams(BaseModel):
model_config = {"extra": "forbid"}
query: str = Field(..., min_length=1, max_length=50)
category: Literal["books", "clothing", "electronics"]
page: int = Field(1, gt=0)
per_page: int = Field(20, gt=0, le=100)
ここでは、Pydantic の Field
を使って、デフォルト値の設定やクエリの条件を設定しています。また、上記以外の余分なクエリパラメータの入力を許可しないために model_config
に {"extra": "forbid"}
を追加しています。
あとは、通常通り FastAPI のエンドポイントを定義するだけですが、以下のように引数の type hint をAnnotated[SearchParams, Query()]
としているところが新しい部分です。先ほど書いた Pydantic モデルと合わせた実装例が次のようになります。
from typing import Annotated, Literal
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
app = FastAPI()
class SearchParams(BaseModel):
model_config = {"extra": "forbid"}
query: str = Field(..., min_length=1, max_length=50)
category: Literal["books", "clothing", "electronics"]
page: int = Field(1, gt=0)
per_page: int = Field(20, gt=0, le=100)
@app.get("/search")
async def search_products(
search_params: Annotated[SearchParams, Query()], # 新しい部分
):
# ここに検索ロジックを書く(今回は省略)
return {"params": search_params}
使用例
早速、今回実装したエンドポイントにリクエストを送信してみましょう。今回は以下の実行環境を用いて、ローカルホストで開発サーバーを起動して試してみます。
実行環境
- Python 3.12.6
- FastAPI 0.115.0
参考:pyproject.toml
[tool.poetry]
package-mode = false
[tool.poetry.dependencies]
python = "^3.12.6"
fastapi = {extras = ["standard"], version = "^0.115.0"}
開発サーバー起動
fastapi dev main.py
リクエスト成功例
次のようなクエリを考えてみましょう:
- 検索クエリ
query
: python - 商品カテゴリー
category
: books
curl -X 'GET' \
"http://localhost:8000/search?query=python&category=books"
この例では、クエリパラメータの型バリデーションも制限もクリアしているので、クエリは無事に通過して正常に処理されます。
また、クエリパラメータとして page
と per_page
が何も指定されていないので、この場合は Pydantic モデルの Field
で定義した通りデフォルト値が適用されます。
- Response
200
{
"params": {
"query": "python",
"category": "books",
"page": 1,
"per_page": 20
}
}
型のバリデーションが通らない例
先ほどの成功例を少し変えてみます:
- 検索クエリ
query
: water - 商品カテゴリー
category
: drinks
curl -X 'GET' \
"http://localhost:8000/search?query=water&category=drinks"
この例では、商品カテゴリーに drinks が入力されており、これは先ほどの SearchParams
において Literal
を用いて定義した通り、想定していた入力の範囲外になっています。したがって、Pydantic モデルの型バリデーションを通過しません。
試しにリクエストを送信すると、無事にステータスコード 422 を返してくれます。
- Response
422
{
"detail": [
{
"type": "literal_error",
"loc": [
"query",
"category"
],
"msg": "Input should be 'books', 'clothing' or 'electronics'",
"input": "drinks",
"ctx": {
"expected": "'books', 'clothing' or 'electronics'"
}
}
]
}
クエリ制限に引っかかる例
余分なクエリパラメータを追加した場合はどうなるか試してみましょう:
- 検索クエリ
query
: python - 商品カテゴリー
category
: books - 価格の最大値
max_price
: 3000
curl -X 'GET' \
"http://localhost:8000/search?query=python&category=books&max_price=3000"
今回の例では、想定していなかったクエリパラメータ「価格の最大値 max_price
」を入力しています。確かに、他のパラメータについては型のバリデーションをクリアしているはずですが、先ほどの SearchParams
で model_config = {"extra": "forbid"}
と設定したおかげで、余分なクエリパラメータの入力を受け付けないようになっています。
試しにリクエストを送信すると、無事にステータスコード 422 を返してくれます。
- Response
422
{
"detail": [
{
"type": "extra_forbidden",
"loc": [
"query",
"max_price"
],
"msg": "Extra inputs are not permitted",
"input": "3000"
}
]
}
コラム:旧機能のみで実装した場合
FastAPI は何といってもやはり Pydantic の強力なサポートがあってこそ、そのおかげで型安全な API 開発ができるのが強みの一つです。なので、今回のリリース以前からクエリパラメータの宣言に Pydantic モデルを使いたいユーザーは多かったのではないでしょうか。実際、自分もそのユーザーの一人でした。
次の GitHub の Discussion における実装例では、 FastAPI の Depends
を用いてクエリパラメータの宣言を試しています。一見上手くいっているようには見えるのですが、Pydantic モデルの Field
で定義したはずの description
が OpenAPI 文書上では正常に反映されていない、という Issue が元になった Discussion です。
今回、クエリパラメータでも Pydantic モデルが公式にサポートされるようになりました。上でも分かるとおり以前から GitHub の Discussion でも活発に議論されていたようなので、これは多くのユーザーにとって待望のアップデートだったのではないでしょうか。
ヘッダーや Cookie の場合
本稿では主にクエリパラメータの例で説明しましたが、リリースノートにもあるとおり今回のリリースにはヘッダーや Cookie に対するサポートも含まれています。以下、公式のリリースノートにあるコードをそのまま掲載しただけなのですが、ヘッダーと Cookie の場合の例も紹介しようと思います。主な変更点は、単に type hint で Query()
としていた部分を Header()
や Cookie()
に変更しただけです。
ヘッダーの例
from typing import Annotated
from fastapi import FastAPI, Header
from pydantic import BaseModel
app = FastAPI()
class CommonHeaders(BaseModel):
host: str
save_data: bool
if_modified_since: str | None = None
traceparent: str | None = None
x_tag: list[str] = []
@app.get("/items/")
async def read_items(headers: Annotated[CommonHeaders, Header()]): # ヘッダーの場合
return headers
Cookie の例
from typing import Annotated
from fastapi import Cookie, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Cookies(BaseModel):
session_id: str
fatebook_tracker: str | None = None
googall_tracker: str | None = None
@app.get("/items/")
async def read_items(cookies: Annotated[Cookies, Cookie()]): # Cookie の場合
return cookies
まとめ
今回の FastAPI のリリース v0.115.0 によって、クエリ・ヘッダー・Cookie パラメータの宣言に Pydantic モデルを正式に使用できるようになりました。このアップデートは、特に複雑なパラメータを扱う API や、厳密なバリデーションが必要なケースで真価を発揮すると思われます。是非、みなさんのプロジェクトでも試してみてください。
Discussion