きり丸の技術日記

技術検証したり、資格等をここに残していきます。

FastAPIのpydanticの422UnprocessableEntityはExceptionのExceptionHandlerではキャッチできない

FastAPIで意図しないエラーが発生したときにExceptionでハンドリングしていましたが、それだけではpydanticで発生するエラーがキャッチできなかったのでメモします。

なお、pydanticはAPIのRequestResponseのモデルで使用していますので、フロントのバリデーションエラーと考えてください。

環境

  • Python
    • 3.11
  • FastAPI
    • 0.105.0
  • pydantic
    • 2.5.3

ゴール

pydanticで発生していた次の詳細すぎるメッセージが返却されないこと。

{
  "detail": [
    {
      "type": "greater_than_equal",
      "loc": [
        "body",
        "age"
      ],
      "msg": "Input should be greater than or equal to 18",
      "input": 0,
      "ctx": {
        "ge": 18
      },
      "url": "https://errors.pydantic.dev/2.5/v/greater_than_equal"
    }
  ]
}

実装

Exceptionだけでなく、FastAPIRequestValidaionErrorもException_handlerに追加する必要があります。

# 全てのエラーをキャッチするハンドラ
# ただし、これだけではキャッチできないエラーも存在する
@app.exception_handler(Exception)
async def unhandled_exception_handler(request: Request, exc: Exception):
    return JSONResponse(
        status_code=500,
        content={"detail": f"SystemError"},
    )

# 追記したハンドラ
# pydanticのエラーをFastAPIでラップしたRequestValidationErrorを使用します。
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=400,
        content={"detail": f"BadRequest"},
    )

原因

FastAPI側でハンドリングしていたから。

次の3つのエラーに関してはFastAPI側で定義しています。

  • HTTPException
  • RequestValidationError
  • WebSocketRequestValidationError

継承元のExceptionよりも継承先の特化したExceptionを定義しているので、特化したエラーが発生したときは特化したハンドリングが優先されます。FastAPIを経由したテストを書かないと、エラーにひっかかるので気づくのに遅れました。

ソースコード

exception_handlerと対応したテストを書いてます。ただ、通常のExceptionが発生した場合は、Pytest上だとうまく確認できませんでした。

終わりに

最悪、urlをレスポンスしなければそのまま使用していいんですけどね。完全にpydanticを使ってます!ってメッセージはちょっといただけないですね。

Exceptionでハンドリングしているから大丈夫なはず、というので油断しました。今後も新しいフレームワークを使うときは、既存のexception_handlersを確認したほうが良いですね。

参考文献