Contents
FastAPI バリデーション Pydantic 使い分けの実務的解説
FastAPIとPydanticを組み合わせた開発では、「どのバリデーション処理をPydanticで実装し、どの処理はFastAPIに任せるか」という判断が設計の肝になります。特に中級者向けには、FieldやValidatorといったPydantic特有のメカニズムの使い分け方が明確にならないと、冗長なコードや不完全な検証ルールにつながります。本記事では、データ検証の設計思想→実装例→応用テクニックの3段階で、PydanticとFastAPIの関係性と使い分け方を解説し、具体的な選定基準を提示します。
PydanticとFastAPIのバリデーション設計思想
mixiイベントや社内ツールなど、API開発において「リクエストパラメータの検証」は必須です。PydanticとFastAPIでは、それぞれ異なる役割を担いながら連携して動作します。
データ検証の役割分担
Pydanticはモデルベースのデータ検証を担当し、FastAPIはリクエストパラメータへの自動検証フローを提供します。具体的には、FastAPIが受け取ったリクエストをPydanticモデルに変換する際、以下のような処理が行われます。
| ステップ | 詳細 |
|---|---|
| 1. リクエストパラメータの解析 | JSONやQueryパラメータから値を取り出す |
| 2. Pydanticモデルへのマッピング | 型ヒントに基づいてデータを変換・検証 |
| 3. エラー処理の実行 | 検証失敗時、HTTP 422エラーとしてレスポンス返却 |
このフローにより、開発者は手でバリデーションロジックを記述する必要がなくなります。
自動検証フローの流れ
FastAPIはPydanticモデルを元に自動的に検証処理を生成します。たとえば以下のようなコードで、nameパラメータが文字列であることを検証できます。
|
1 2 3 4 5 6 7 8 |
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str |
この場合、FastAPIはnameに数値を渡したときに自動でエラーを返します。この自動検証フローにより、開発者は型ヒントだけで入力検証ルールを定義できるという利点があります。
Field/Annotated/Validatorの使い分け方
PydanticにはFieldやAnnotated、Validatorといったメカニズムが用意されており、用途に応じて使い分ける必要があります。
Fieldによる基本検証
Fieldは型チェックと単純なデフォルト値の設定に最適です。以下のように、必須項目や最小値の指定が可能です。
|
1 2 3 4 5 6 |
from pydantic import BaseModel, Field class User(BaseModel): name: str = Field(..., description="ユーザー名") age: int = Field(ge=18, description="年齢(18歳以上)") |
このケースではFieldが適している理由:
- 型チェックと基本的な制約(例:最小値、最大値)のみが必要
- ビジネスロジックの複雑な処理は不要
Annotatedでのメタデータ追加
Annotatedは型ヒントにメタデータを追加して、検証ルールやドキュメント説明を定義します。例えば、OpenAPIドキュメントでパラメータの説明を記述できます。
|
1 2 3 4 5 |
from pydantic import Annotated, StringConstraints class User(BaseModel): name: Annotated[str, StringConstraints(max_length=10)] |
このケースではAnnotatedが適している理由:
- 型チェックに加えて、メタデータの追加(例:OpenAPI用説明)が必要
- 複雑な検証ロジックは不要
Validatorとfield_validatorの使い分け (Pydantic v1 vs v2)
Pydantic v1における@validator
v1では、@validatorデコレータを使用してフィールドごとのカスタム検証を実装します。
|
1 2 3 4 5 6 7 8 9 10 11 |
from pydantic import BaseModel, validator class User(BaseModel): password: str @validator("password") def validate_password_strength(cls, v): if len(v) < 8: raise ValueError("パスワードは8文字以上で入力してください。") return v |
Pydantic v2におけるfield_validator
v2では、@field_validatorデコレータを使用し、mode="before"やmode="after"を指定して検証タイミングを調整できます。
|
1 2 3 4 5 6 7 8 9 10 11 |
from pydantic import BaseModel, field_validator class User(BaseModel): password: str @field_validator("password", mode="after") def validate_password_strength(cls, v): if len(v) < 8: raise ValueError("パスワードは8文字以上で入力してください。") return v |
v1とv2の主な差分:
@validator→@field_validator- 検証タイミング(before/after)を明示的に指定可能
型チェックとビジネスルール検証の境界線設定
型ヒントだけで完結する検証と、独自のビジネスロジックが混在するケースでは、境界線を明確に定義することが重要です。
型ヒントの限界
型ヒント(str, int, Enumなど)はデータの「型」のみをチェックします。たとえば、以下のような検証はできません。
- パスワードの強度
- 日付の順序(例:終了日が開始日より後であるか)
- 重複チェック(例:既存ユーザー名との比較)
Validatorでのカスタムルール定義
上記のような検証は、field_validatorで実装します。以下に代表的なケースと適用方法を示します。
| 検証内容 | 実装方法 |
|---|---|
| パスワード強度チェック | @field_validatorで正規表現や長さ制限を定義 |
| 日付の順序検証 | 2つのフィールド間でValidatorを実装し、比較ロジックを記述 |
| 唯一性チェック | データベースと通信して、既存データとの比較を行う |
境界線の設定基準:
- 型ヒント:基本的な型やEnumなどで表現可能なもの
- Validator:複数条件が絡む、または外部リソース(DB)との連携が必要な検証
HTTP 422エラー時のカスタムメッセージ実装例
Pydanticで発生するValidationErrorをFastAPIに接続し、親切なエラーメッセージをユーザーに返却することは、UX向上のポイントです。
ValidationErrorの拡張
以下のように、ExceptionHandlerを定義することでカスタムメッセージを作成できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from fastapi import FastAPI, HTTPException, status from pydantic import BaseModel, field_validator app = FastAPI() class User(BaseModel): password: str @field_validator("password") def validate_password_strength(cls, v): if len(v) < 8: raise ValueError("パスワードは8文字以上で入力してください。") return v @app.exception_handler(ValueError) async def validation_exception_handler(request, exc): return HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) |
OpenAPIドキュメントへの反映
カスタムメッセージがOpenAPIに反映されるには、descriptionやexampleを追加します。以下のコード例のように、フィールド定義時に説明を記述すると、Swagger UIなどにも表示されます。
|
1 2 3 4 5 6 7 8 9 |
from pydantic import Field class User(BaseModel): password: str = Field( ..., description="8文字以上のパスワード", example="StrongPass123" ) |
注意: Swagger UIに
descriptionが表示されるには、FastAPIのOpenAPI設定でshow_examples=Trueを指定する必要があります。
OpenAPI生成におけるバリデーション仕様の反映方法
FastAPIはPydanticモデルから自動でOpenAPIスキーマを生成しますが、カスタムルールやメッセージは手動で調整が必要な場合があります。
自動生成される仕様
以下のような情報が自動的に反映されます。
| 項目 | 内容 |
|---|---|
| パラメータの型 | str, intなどから自動推論される |
| 必須パラメータ | Field(...)で指定した場合、required: trueに設定される |
| エラーメッセージ | descriptionやexampleがSwagger UIに表示される |
手動調整が必要なケース
以下のようなケースでは、手動でOpenAPIスキーマを編集する必要があります。
- パスワード強度チェックなどのカスタムルールの説明
- エラーメッセージのカスタマイズ(例:DBエラー時のメッセージ)
- 外部リソースとの連携(例:API通信による唯一性検証)
手動編集方法の例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from fastapi import FastAPI, OpenAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id: int): return {"item_id": item_id} # OpenAPIスキーマにカスタム説明を追加 app.openapi_schema = app.get_openapi( title="Sample API", version="1.0.0", description="このAPIはPydanticによるバリデーションルールが反映されています。", ) |
Validatorによる唯一性チェックの実装例
唯一性検証(例:ユーザー名が既存かどうか)を行うには、DBとの通信が必要です。以下に簡単な実装例を示します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from fastapi import FastAPI, HTTPException, status from pydantic import BaseModel, field_validator app = FastAPI() class User(BaseModel): username: str @field_validator("username") def check_username_exists(cls, v): # 仮のDBチェック(実際にはDBクエリを使用) if v in ["existing_user1", "existing_user2"]: raise ValueError("ユーザー名はすでに存在します。") return v |
注意: 実際の唯一性チェックでは、データベースと通信して重複を検証する必要があります。
まとめ
- Pydanticはデータの検証をモデルベースで行い、FastAPIがリクエストパラメータに対して自動的に処理する
Fieldは型チェックと基本的な制約に、Validator(v1)またはfield_validator(v2)は複数条件や独自ルールに使用- 型ヒントではカバーできないビジネスロジックは必ず
Validatorで実装すること - HTTP 422エラー時のカスタムメッセージには
ExceptionHandlerを活用し、OpenAPIドキュメントにも反映させる - OpenAPI生成時には自動反映される項目と手動調整が必要な項目を明確に区別する
このように、PydanticとFastAPIのバリデーション機能は密接に関連しており、設計思想や実装方法によって柔軟に使い分けることが重要です。