🐍

FastAPIでExceptionを使用せずにHttpStatus200以外を返却する

2024/08/08に公開

始めに

FastAPIではHTTPExceptionを使用すればHttpStatusを200以外でも返却可能です。しかし、エラー詳細は1つしか返却できません。ファイルのエラーハンドリングでは多数のエラーが発生した場合には、複数のエラーを返却したいユースケースがあります。

ExceptionHandlerを使用することでもHTTPStatusや複数エラーを返却するようにできますが、基本的にはファイルアップロード部分の処理でしか使用しないため、全体に適用するには範囲が大きすぎます。

今回の記事ではファイルのバリデーション結果を返却するために次の仕様を満たせるようにします。

  1. エラーのBodyには複数の理由を設定できる
  2. バリデーションに失敗したらHTTPStatus200以外を返却
  3. OpenAPIにもレスポンス構造を反映できる

環境

  • Python
    • 3.12.4
  • FastAPI
    • 0.112.0

実装

レスポンスの型を定義する

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で返却します。

return JSONResponse(
    status_code=422,
    content=ExceptionResponse(detail="File Invalid", error_lists=[]).model_dump(by_alias=True)
)

OpenAPIにもレスポンスの構造を伝える

APIRouterresponsesを定義するとOpenAPI側にレスポンスの型を伝えられます。そのため、先ほど定義したExceptionResponseを指定します。

@router.get("/files", responses={422: {"model": ExceptionResponse}})

結果

OpenAPI側に定義を反映できます。

ソースコード

終わりに

ファイルのバリデーションは雑にやるならHTTPExceptionを返却するだけで済むのですが、だいたいユーザビリティが低いのでエラーをまとめなければならず、毎回実装するたびに悩んでいる気がします。

ファイルバリデーションのあるべきハンドリングの記事があればぜひ参考にしたいです。

参考情報

Discussion