11 KiB
エラーハンドリング
APIを使用しているクライアントにエラーを通知する必要がある状況はたくさんあります。
このクライアントは、フロントエンドを持つブラウザ、誰かのコード、IoTデバイスなどが考えられます。
クライアントに以下のようなことを伝える必要があるかもしれません:
- クライアントにはその操作のための十分な権限がありません。
- クライアントはそのリソースにアクセスできません。
- クライアントがアクセスしようとしていた項目が存在しません。
- など
これらの場合、通常は 400(400から499)の範囲内の HTTPステータスコード を返すことになります。
これは200のHTTPステータスコード(200から299)に似ています。これらの「200」ステータスコードは、何らかの形でリクエスト「成功」であったことを意味します。
400の範囲にあるステータスコードは、クライアントからのエラーがあったことを意味します。
"404 Not Found" のエラー(およびジョーク)を覚えていますか?
HTTPExceptionの使用
HTTPレスポンスをエラーでクライアントに返すには、HTTPExceptionを使用します。
HTTPExceptionのインポート
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *}
コード内でのHTTPExceptionの発生
HTTPExceptionは通常のPythonの例外であり、APIに関連するデータを追加したものです。
Pythonの例外なので、returnではなく、raiseです。
これはまた、path operation関数の内部で呼び出しているユーティリティ関数の内部からHTTPExceptionを発生させた場合、path operation関数の残りのコードは実行されず、そのリクエストを直ちに終了させ、HTTPExceptionからのHTTPエラーをクライアントに送信することを意味します。
値を返すreturnよりも例外を発生させることの利点は、「依存関係とセキュリティ」のセクションでより明確になります。
この例では、クライアントが存在しないIDでアイテムを要求した場合、404のステータスコードを持つ例外を発生させます:
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *}
レスポンス結果
クライアントがhttp://example.com/items/foo(item_id "foo")をリクエストすると、HTTPステータスコードが200で、以下のJSONレスポンスが返されます:
{
"item": "The Foo Wrestlers"
}
しかし、クライアントがhttp://example.com/items/bar(存在しないitem_id "bar")をリクエストした場合、HTTPステータスコード404("not found"エラー)と以下のJSONレスポンスが返されます:
{
"detail": "Item not found"
}
/// tip | 豆知識
HTTPExceptionを発生させる際には、strだけでなく、JSONに変換できる任意の値をdetailパラメータとして渡すことができます。
dictやlistなどを渡すことができます。
これらは FastAPI によって自動的に処理され、JSONに変換されます。
///
カスタムヘッダーの追加
例えば、いくつかのタイプのセキュリティのために、HTTPエラーにカスタムヘッダを追加できると便利な状況がいくつかあります。
おそらくコードの中で直接使用する必要はないでしょう。
しかし、高度なシナリオのために必要な場合には、カスタムヘッダーを追加することができます:
{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *}
カスタム例外ハンドラのインストール
カスタム例外ハンドラはStarletteと同じ例外ユーティリティを使用して追加することができます。
あなた(または使用しているライブラリ)がraiseするかもしれないカスタム例外UnicornExceptionがあるとしましょう。
そして、この例外をFastAPIでグローバルに処理したいと思います。
カスタム例外ハンドラを@app.exception_handler()で追加することができます:
{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *}
ここで、/unicorns/yoloをリクエストすると、path operationはUnicornExceptionをraiseします。
しかし、これはunicorn_exception_handlerで処理されます。
そのため、HTTPステータスコードが418で、JSONの内容が以下のような明確なエラーを受け取ることになります:
{"message": "Oops! yolo did something. There goes a rainbow..."}
/// note | 技術詳細
また、from starlette.requests import Requestとfrom starlette.responses import JSONResponseを使用することもできます。
FastAPI は開発者の利便性を考慮して、fastapi.responsesと同じstarlette.responsesを提供しています。しかし、利用可能なレスポンスのほとんどはStarletteから直接提供されます。これはRequestと同じです。
///
デフォルトの例外ハンドラのオーバーライド
FastAPI にはいくつかのデフォルトの例外ハンドラがあります。
これらのハンドラは、HTTPExceptionをraiseさせた場合や、リクエストに無効なデータが含まれている場合にデフォルトのJSONレスポンスを返す役割を担っています。
これらの例外ハンドラを独自のものでオーバーライドすることができます。
リクエスト検証の例外のオーバーライド
リクエストに無効なデータが含まれている場合、FastAPI は内部的にRequestValidationErrorを発生させます。
また、そのためのデフォルトの例外ハンドラも含まれています。
これをオーバーライドするにはRequestValidationErrorをインポートして@app.exception_handler(RequestValidationError)と一緒に使用して例外ハンドラをデコレートします。
この例外ハンドラはRequestと例外を受け取ります。
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *}
これで、/items/fooにアクセスすると、以下のデフォルトのJSONエラーの代わりに:
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
以下のテキスト版を取得します:
Validation errors:
Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer
HTTPExceptionエラーハンドラのオーバーライド
同様に、HTTPExceptionハンドラをオーバーライドすることもできます。
例えば、これらのエラーに対しては、JSONではなくプレーンテキストを返すようにすることができます:
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *}
/// note | 技術詳細
また、from starlette.responses import PlainTextResponseを使用することもできます。
FastAPI は開発者の利便性を考慮して、fastapi.responsesと同じstarlette.responsesを提供しています。しかし、利用可能なレスポンスのほとんどはStarletteから直接提供されます。
///
/// warning | 注意
RequestValidationErrorには、検証エラーが発生したファイル名と行番号の情報が含まれているため、必要であれば関連情報と一緒にログに表示できます。
しかし、そのまま文字列に変換して直接その情報を返すと、システムに関する情報が多少漏えいする可能性があります。そのため、ここではコードが各エラーを個別に抽出して表示します。
///
RequestValidationErrorのボディの使用
RequestValidationErrorには無効なデータを含むbodyが含まれています。
アプリ開発中にボディのログを取ってデバッグしたり、ユーザーに返したりなどに使用することができます。
{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *}
ここで、以下のような無効な項目を送信してみてください:
{
"title": "towel",
"size": "XL"
}
受信したボディを含むデータが無効であることを示すレスポンスが表示されます:
{
"detail": [
{
"loc": [
"body",
"size"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
],
"body": {
"title": "towel",
"size": "XL"
}
}
FastAPIのHTTPExceptionとStarletteのHTTPException
FastAPIは独自のHTTPExceptionを持っています。
また、 FastAPIのHTTPExceptionエラークラスはStarletteのHTTPExceptionエラークラスを継承しています。
唯一の違いは、FastAPI のHTTPExceptionはdetailフィールドにJSONに変換可能な任意のデータを受け付けるのに対し、StarletteのHTTPExceptionは文字列のみを受け付けることです。
そのため、コード内では通常通り FastAPI のHTTPExceptionを発生させ続けることができます。
しかし、例外ハンドラを登録する際には、StarletteのHTTPExceptionに対して登録しておく必要があります。
これにより、Starletteの内部コードやStarletteの拡張機能やプラグインの一部がStarletteのHTTPExceptionを発生させた場合、ハンドラがそれをキャッチして処理できるようになります。
この例では、同じコード内で両方のHTTPExceptionを使用できるようにするために、Starletteの例外をStarletteHTTPExceptionにリネームしています:
from starlette.exceptions import HTTPException as StarletteHTTPException
FastAPI の例外ハンドラの再利用
FastAPI から同じデフォルトの例外ハンドラと一緒に例外を使用したい場合は、fastapi.exception_handlersからデフォルトの例外ハンドラをインポートして再利用できます:
{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *}
この例では、非常に表現力のあるメッセージでエラーをprintしているだけですが、要点は理解できるはずです。例外を使用し、その後デフォルトの例外ハンドラを再利用できます。