FastAPIで404エラーを自在にカスタムする
エラーハンドリングしたい
FastAPIは存在しないURLにリクエストすると
{"detail":"Not Found"}
がデフォルトで返却される。
これを好きなレスポンスに整形して返却してみる。
実装方法
実装前
とりあえず検証コードを作成。
ここからハンドリングを実装していく。
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
def hello_world() -> dict[str, str]:
return {"hello": "world"}
def main() -> None:
uvicorn.run("main:app", port=8000, host="0.0.0.0", reload=True)
if __name__ == "__main__":
main()
python {モジュール名}
でAPIサーバを起動して動作確認。
# /hello GET
$ curl 127.0.0.1:8000/hello
{"hello":"world"}
# /user GET (存在しない)
$ curl 127.0.0.1:8000/user
{"detail":"Not Found"}
/hello
は定義したレスポンス
/user
は定義していないため404のデフォルトレスポンスになっている。
この状態からエラーハンドリングを実装する。
404エラー実装
公式ドキュメントを参考に実装。
FastAPIは公式ドキュメントが読みやすくて好き☺
公式は自作のハンドラーを使用する方法が記載されている。
今回はHTTPステータス404
自体を拾いたいため、以下のように実装してみた。
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/hello")
def hello_world() -> dict[str, str]:
return {"hello": "world"}
# 404エラーを拾う
@app.exception_handler(404)
def not_found(req: Request, exc: HTTPException) -> JSONResponse:
return JSONResponse(content={"notFound": str(req.url)}, status_code=404)
def main() -> None:
uvicorn.run("main:app", port=8000, host="0.0.0.0", reload=True)
if __name__ == "__main__":
main()
@app.exception_handler
のデコレートを使う。
引数は任意のエラー型かintを指定できる。
公式はカスタムハンドラーの型を指定しているが、今回はステータスコードの404を指定
fastapi.responses.JSONResponse
にcontentとstatus_codeを指定する。
再度リクエストを送信。
$ curl 127.0.0.1:8000/hello
{"hello":"world"}
$ curl 127.0.0.1:8000/user
{"notFound":"http://127.0.0.1:8000/user"}
404
を拾ってくれるのが確認できた。
JSONResponse
content
JSONResponseはcontest
に指定したものをjson形式に変換してくれる。
サンプルではdict
を指定したがjson形式にできるなら何でも良い。
list
は正常に返却してくれる。
# contentは変換できれば何でもOK
@app.exception_handler(404)
def not_found(req: Request, exc: HTTPException) -> JSONResponse:
return JSONResponse(content=["abc", "def"], status_code=404)
# $ curl 127.0.0.1:8000/user
# ["abc","def"]
使う機会は限られそうだが、str
だけも大丈夫。
# contentは変換できれば何でもOK
@app.exception_handler(404)
def not_found(req: Request, exc: HTTPException) -> JSONResponse:
return JSONResponse(content="sample", status_code=404)
# $ curl 127.0.0.1:8000/user
# "sample"
pydantic.BaseModel
でスキーマ定義しておくことも可能。
ただし、スキーマをそのまま返却することはできない点に注意。
事前にdict()
メソッドで変換してあげる必要がある。
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
app = FastAPI()
# スキーマ定義
class NotFound(BaseModel):
msg: str = Field(...)
# NG
@app.exception_handler(404)
def not_found_ng(req: Request, exc: HTTPException) -> JSONResponse:
return JSONResponse(content=NotFound(msg=str(req.url)), status_code=404)
# OK
@app.exception_handler(404)
def not_found_ok(req: Request, exc: HTTPException) -> JSONResponse:
return JSONResponse(content=NotFound(msg=str(req.url)).dict(), status_code=404)
# $ curl 127.0.0.1:8000/user
# {"msg":"http://127.0.0.1:8000/user"}
status_code
status_code
にはHTTPステータスを指定できる。
APIでは404エラーを拾うが
実際に返却するときのステータスは400で返却…みたいなことが可能。
# ステータスコード変更
@app.exception_handler(404)
def not_found(req: Request, exc: HTTPException) -> JSONResponse:
return JSONResponse(content={"notFound": str(req.url)}, status_code=400)
ステータス変更されたか確認してみる。
vscode
を使っているなら拡張機能REST Client
を入れておくと良い。
拡張子.http
でリクエストを書ける。
GET http://localhost:8000/user
リクエストを送信すると、HTTPステータスが400 Bad Request
に変更されている。
まとめ
記事中にもリンクを貼っているがFastAPIはドキュメントが優秀で大抵のことは書いてある。
ある特定の処理を書きたい…みたいなことでない限りは
最初に公式を読んでおくと良いと思う!
以上、お疲れ様でした!
Discussion