始めに
FastAPIではHTTPException
を使用すればHttpStatusを200以外でも返却可能です。しかし、エラー詳細は1つしか返却できません。ファイルのエラーハンドリングでは多数のエラーが発生した場合には、複数のエラーを返却したいユースケースがあります。
ExceptionHandler
を使用することでもHTTPStatusや複数エラーを返却するようにできますが、基本的にはファイルバリデーション処理でしか使用しないため、全体に適用するには範囲が大きすぎます。
今回の記事ではファイルのバリデーション結果を返却するために次の仕様を満たせるようにします。
- バリデーションに失敗したらHTTPStatus200以外を返却
- エラーのBodyには複数の理由を設定できる
- ExceptionHandlerを使用しない
- OpenAPIにもレスポンス構造を反映できる
環境
- Python
- 3.12.4
- FastAPI
- 0.112.0
実装
レスポンスの型を定義する
FastAPIのデフォルト機能として、HTTPException
をraiseすると次のレスポンス型で返却されます。
{ "detail": "FILE_INVALID" }
今回は他でも流用しやすくするため、このレスポンスをベースに型を拡張します。
{ "detail": "FILE_INVALID", // ここから下を追加する形のレスポンスにする "errorLists": { "indexes": [ 1, 2, 3 ], "reason": "Not Found" } }
具体的には次のような型定義をします。後でOpenAPI側でもデータ構造をわかるようにしたいので、examples
まで定義しておきます。
from typing import Any, List, Annotated from pydantic import BaseModel, Field class ErrorMessage(BaseModel): reason: Annotated[str, Field(description="")] indexes: Annotated[List[int], Field(description="エラーが発生したインデックスのリスト")] class ExceptionResponse(BaseModel): detail: Annotated[str, Field(description="Exception detail", examples=["File Invalid"])] error_lists: Annotated[List[ErrorMessage], Field(description="Exception details", examples=[{"reason": "Not Found", "indexes": [1, 2, 3]}])]
HttpStatus 200以外で返却する
JSONResponse
を返却することで任意のHTTPStatusコードや任意の型定義を返却できます。
今回はファイルの内容が誤っていたことを伝えたいので、HTTPStatus=422で返却します。また今回の記事では省略していますが、Pythonではスネークケース、Responseではキャメルケースにしたいのでby_alias=True
も付与しておきます。
return JSONResponse( status_code=422, content=ExceptionResponse(detail="File Invalid", error_lists=[]).model_dump(by_alias=True) )
OpenAPIにもレスポンスの構造を伝える
APIRouter
のresponses
を定義するとOpenAPI側にレスポンスの型を伝えられます。そのため、先ほど定義したExceptionResponse
を指定します。
@router.get("/files", responses={422: {"model": ExceptionResponse}})
結果
OpenAPI側に定義を反映できます。
ソースコード
終わりに
ファイルのバリデーションは雑にやるならHTTPException
を返却するだけで済むのですが、だいたいユーザビリティが低いのでエラーをまとめなければならず、毎回実装するたびに悩んでいる気がします。
ファイルバリデーションのあるべきハンドリングの記事があればぜひ参考にしたいです。