サイトアイコン IT & ライフハックブログ|学びと実践のためのアイデア集

FastAPIとPydantic v2で学ぶリクエストとレスポンスの定義

green snake

Photo by Pixabay on Pexels.com

FastAPIでは、Pydanticモデルをエンドポイントに指定すると、受け取るJSONの検証と返すJSONの形をコード上で定義できます。

この記事では、Pydantic v2の書き方を使い、リクエストモデルとレスポンスモデルを分けるところから、制約、エイリアス、ネスト、独自検証までを順に説明します。

Pythonの型注釈そのものが入力の正しさを保証するのではありません。Pydanticが受け取ったデータを実行時に検証し、FastAPIがその定義をOpenAPIにも反映します。

Pydanticモデルが担う役割

Pydanticモデルは、Pythonの型注釈を使ってデータの形と検証条件を表すクラスです。

バリデーションは、受け取った値が型や制約に合うかを確認する処理です。

シリアライズは、PythonオブジェクトをJSONとして返せる形へ変換する処理です。

FastAPIでPydanticモデルを使う主な場面
場面 モデルの役割 設計時に確認すること
リクエスト JSONを読み取り、型と制約を検証する 必須項目、任意項目、許可する値
レスポンス 返却値を検証し、公開する項目に絞る 外部へ返してよい項目、nullの扱い
API仕様 JSON SchemaをOpenAPIへ反映する 説明、例、入力名と出力名

BaseModelで入力スキーマを定義する

BaseModelを継承し、各フィールドへ型と必要な制約を指定します。

from pydantic import BaseModel, Field

class ItemIn(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    price: float = Field(gt=0)
    is_offer: bool = False
    description: str | None = None

「省略できること」と「nullを受け取れること」は別の条件です。

たとえばstr | Noneと書くだけではなく、= Noneまで指定すると、省略時の値が明確になります。

型注釈の基礎から確認したい場合は、FastAPIの型指定とPydanticによる入力検証も参照してください。

リクエストボディを検証する

エンドポイント関数の引数にPydanticモデルを指定すると、FastAPIはその引数をリクエストボディとして扱います。

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class ItemIn(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    price: float = Field(gt=0)
    is_offer: bool = False

class ItemOut(BaseModel):
    name: str
    price_with_tax: float
    is_offer: bool

@app.post("/items/", response_model=ItemOut)
async def create_item(item: ItemIn) -> ItemOut:
    return ItemOut(
        name=item.name,
        price_with_tax=item.price * 1.1,
        is_offer=item.is_offer,
    )

この例では、次のJSONをリクエストとして送れます。

{
  "name": "Book",
  "price": 1200,
  "is_offer": true
}

nameが空文字列だった場合や、priceが0以下だった場合、既定のFastAPIは検証エラーとしてHTTP 422を返します。

エラー情報には問題のある場所と理由が含まれるため、クライアント側はどの入力を直すべきか判断できます。

例の1.1はレスポンス変換を示すための固定値であり、実際の税計算を表すものではありません。

レスポンスモデルで公開範囲を決める

response_model=ItemOutは、レスポンスをItemOutの形で検証し、定義されていない項目を出力から除外します。

入力用のItemInと出力用のItemOutを分けると、内部処理で使う項目をそのまま外部へ返す事故を避けやすくなります。

ただし、レスポンスモデルは認可の代わりにはなりません。

公開してよい項目をモデルで限定したうえで、誰がそのエンドポイントを呼べるかも別に設計します。

Fieldで制約とエイリアスを設定する

Fieldでは、数値の範囲、文字列長、パターン、説明、別名などを指定できます。

from pydantic import BaseModel, Field

class Product(BaseModel):
    product_id: int = Field(alias="id", ge=1)
    name: str = Field(min_length=1, max_length=100)
    sku: str = Field(pattern=r"^[A-Z0-9-]+$")

このモデルは、入力JSONのidをPython側のproduct_idへ対応づけます。

aliasは検証とシリアライズの両方に関係しますが、model_dump()で別名を出力するにはby_alias=Trueを指定します。

入力名と出力名を変えたい場合は、validation_aliasとserialization_aliasを分けて指定できます。

ネストとリストでJSONの構造を表す

JSONの中にオブジェクトや配列がある場合は、モデルを組み合わせて構造を表します。

from pydantic import BaseModel, Field

class Address(BaseModel):
    street: str
    city: str

class Tag(BaseModel):
    name: str

class ArticleIn(BaseModel):
    title: str = Field(min_length=1)
    author_address: Address
    tags: list[Tag] = Field(default_factory=list)

author_addressではAddressの各項目が検証され、tagsでは配列の各要素がTagとして検証されます。

default_factory=listを使うと、tagsを省略したときに空のリストを作れます。

field_validatorで独自の検証を加える

Fieldで表せないルールには、Pydantic v2の@field_validatorを使えます。

from pydantic import BaseModel, field_validator

class Order(BaseModel):
    quantity: int

    @field_validator("quantity")
    @classmethod
    def must_be_positive(cls, value: int) -> int:
        if value <= 0:
            raise ValueError("quantity must be positive")
        return value

検証に成功した場合は、検証後の値を必ず返します。

この例の「0より大きい」という条件だけなら、quantity: int = Field(gt=0)のほうが短く、スキーマにも制約を反映しやすくなります。

複数の手順が必要な整形や、単純なFieldでは表せない条件にだけ独自検証を使うと、モデルを読みやすく保てます。

Pydantic v1のコードを読み替える

古い記事や既存コードでは、Pydantic v1の構文が使われている場合があります。

Pydantic v1からv2への主な読み替え
目的 Pydantic v1 Pydantic v2
文字列パターン Field(regex=...) Field(pattern=...)
フィールド検証 @validator @field_validator
モデルの辞書化 .dict() .model_dump()

既存プロジェクトを変更するときは、インストール済みのPydanticのメジャーバージョンを確認してから書き換えます。

v1とv2では検証や変換の細部も異なるため、構文だけを機械的に置換せず、正常値、異常値、境界値のテストを実行します。

実装時の確認項目

  1. 入力用モデルと出力用モデルの役割を分ける。
  2. 各フィールドについて、必須、省略可能、null許容を区別する。
  3. 文字列長や数値範囲など、モデルで表せる制約をFieldへ置く。
  4. エイリアスを入力だけに使うのか、出力にも使うのかを決める。
  5. レスポンスに内部項目や機密情報が含まれないことを確認する。
  6. 正常値だけでなく、欠落、型違い、境界値のテストを書く。
  7. OpenAPI上のスキーマがクライアントの期待と一致するか確認する。

公式資料と次のステップ

モデル定義ができたら、入力検証、出力制御、認可、テストを一つのAPI契約として揃えていきます。

設計を本番運用へつなげる方法は、FastAPIで型定義とテストを一貫させる実践ガイドで確認できます。

この記事に関連する株式会社greedenの取り組み

APIの型定義を実装前に整えると、入力と出力の認識差を減らせます。株式会社greedenは、要件定義からAPI開発、テスト、保守運用まで一貫して支援し、業務に合うWebシステムづくりを進めます。

モバイルバージョンを終了