当サイトは、アフィリエイト広告を利用しています
PythonのWEBフレームワークであるFlaskで実装したREST APIで
独自のカスタム例外クラスを作り、エラーハンドラー関数を使って
エラーハンドリングする方法をまとめておく
Pythonの例外処理の基本的なことについては下記記事でまとめています。
flaskでエラーハンドリングをする場合は基本的に下記の
いずれかを使って行う。
この中でカスタム例外クラスを組合わせて使用できるのが
「errorhandlerデコレーター」と「register_error_handler()メソッド」になる。
当記事では「register_error_handler()メソッド」を使う。
※「errorhandlerデコレーター」でも同じことはできる
この二つを組み合わせることで効率的にエラーハンドリングを
行うことができる。
エラーハンドラー関数を使うことで、エラーハンドリングのロジックをコード内で
一つに集約することができるので、コードの可読性や保守性(変更や拡張)が上がる。
カスタム例外クラスを使用することで仕様に合わせたエラー情報を選択できるので
柔軟なエラーハンドリングが可能。
実際にflaskで簡易なREST APIを実装して動作を確認する。
開発はdockerコンテナ上で行う。
プロジェクトの構成は下記のようにする
.|-- Dockerfile|-- api| |-- __init__.py| |-- api.py| |-- error_handling.py| |-- exceptions.py| |-- routes.py| `-- wsgi.py|-- docker-compose.yml|-- .env`-- requirements.txt
それぞれの役割について説明する
コンテナのimageをつくるためのファイル
FROM python:3.12# workspaceディレクトリ作成、移動WORKDIR /workspace# プロジェクトディレクトリにコピーCOPY requirements.txt /workspace# 必要モジュールのインストールRUN pip install --upgrade pipRUN pip install -r requirements.txt
python:3.12を元にしてrequirements.txtにある
ライブラリをpipでインストールしたimageを作る
コンテナにインストールするライブラリを記載
Flask==3.0.2
# flask-REST APIコンテナ用# Flaskがどのアプリケーションを実行するか指定(pyファイル:インスタンス)FLASK_APP=api.wsgi:api# Flaskがどの環境で動作するか指定FLASK_ENV=development#デバッグモードの指定FLASK_DEBUG=true
flaskのビルトインサーバーでREST APIを起動させるための設定を行う
ここで設定した値はコンテナの環境変数に設定される
コンテナをdocker-composeで作成する
version: "3"services:flask-api-error:container_name: "flask-api-error"build:context: .dockerfile: Dockerfileports:- "5000:5000"volumes:# バインドマウント- .:/workspace# 環境変数読み込みenv_file:- .envtty: true
カスタム例外クラスを独自で定義する
class CustomException(Exception):status_code: strerror_message: strdef __init__(self, error_message: str):self.error_message = error_messageclass CustomBadRequestException(CustomException):status_code = 400 # override fieldclass CustomNotFoundException(CustomException):status_code = 404 # override fieldclass CustomInternalServerErrorException(CustomException):status_code = 500 # override field
例外の種類ごとにカスタム例外クラスを継承したサブクラスを定義している
エラーハンドラー関数を定義する
アプリケーション内で発生させたカスタム例外はすべてこの関数で処理する
from flask import jsonifyfrom api.exceptions import CustomExceptiondef handle_custom_exception(error: CustomException):response = {"status_code": error.status_code,"error_message": error.error_message}return jsonify(response)
カスタム例外クラスのオブジェクト(例外オブジェクト)を受け取り
レスポンスを編集して返却する
アプリケーションファクトリパターンを使って
Flaskアプリケーションの初期設定を行う
from flask import Flaskfrom api.routes import configure_user_routesfrom api.exceptions import CustomExceptionfrom api.error_handling import handle_custom_exceptiondef create_app() -> Flask:# アプリケーションインスタンスの作成app = Flask(__name__)# 日本語文字化け対応app.json.ensure_ascii = False# エラーハンドラ関数登録app.register_error_handler(CustomException, handle_custom_exception)# ルーティング設定configure_user_routes(app)return app
register_error_handlerメソッドにて
を登録している。
これでアプリケーション内でCustomExceptionや
CustomExceptionを継承したサブクラスの例外をraiseした際は
handle_custom_exception関数が実行されるようになる
※try~exceptで捕まえた場合を除く。
REST APIのルーティングを設定するモジュール
from api.exceptions import CustomBadRequestExceptionfrom api.exceptions import CustomInternalServerErrorExceptionfrom api.exceptions import CustomNotFoundExceptionfrom flask import Flaskclass Routes:def __init__(self, app: Flask):self.app = appself.configure_routes()def configure_routes(self):@self.app.route('/<code>', methods=['GET'])def get_users(code):if code == '400':raise CustomBadRequestException(error_message="不正なリクエストです")elif code == '404':raise CustomNotFoundException(error_message="リソースが見つかりません")elif code == '500':raise CustomInternalServerErrorException(error_message="内部サーバーエラーが発生しました")else:return "Code not recognized", 200def configure_user_routes(app: Flask):Routes(app)
GETリクエスト時、パスパラメータにて指定したコードの例外を発生させるようにする。
flaskのアプリケーションのエントリーポイントとなるファイル。
from api.api import create_appapi = create_app()
flaskのビルトインサーバーはこのファイルを参照して
flaskアプリケーションインスタンスを取得し起動する
カスタム例外を発生させた場合の処理の流れについてまとめる。
下記のようなフローになる
具体的にその部分のコードを抜粋する
from api.exceptions import CustomBadRequestExceptionfrom api.exceptions import CustomInternalServerErrorExceptionfrom api.exceptions import CustomNotFoundExceptionfrom flask import Flaskclass Routes:def __init__(self, app: Flask):self.app = appself.configure_routes()def configure_routes(self):@self.app.route('/<code>', methods=['GET'])def get_users(code):if code == '400':raise CustomBadRequestException(error_message="不正なリクエストです")elif code == '404':raise CustomNotFoundException(error_message="リソースが見つかりません")elif code == '500':raise CustomInternalServerErrorException(error_message="内部サーバーエラーが発生しました")else:return "Code not recognized", 200def configure_user_routes(app: Flask):Routes(app)
GETリクエストのパスパラメータで設定されたコードに
したがってカスタム例外を発生させる
上記でraise時に指定した例外クラスのオブジェクトが作成される
class CustomException(Exception):status_code: strerror_message: strdef __init__(self, error_message: str):self.error_message = error_messageclass CustomBadRequestException(CustomException):status_code = 400 # override fieldclass CustomNotFoundException(CustomException):status_code = 404 # override fieldclass CustomInternalServerErrorException(CustomException):status_code = 500 # override field
カスタム例外が発生した場合はこの関数が検知し
上記で指定した例外クラスのオブジェクトを受け取り処理を行う
from flask import jsonifyfrom api.exceptions import CustomExceptiondef handle_custom_exception(error: CustomException):response = {"status_code": error.status_code,"error_message": error.error_message}return jsonify(response)
実際にcurlでGEリクエストを送信して動作を確認する。
コンテナで下記コマンドで起動できる。
flask run --host=0.0.0.0
コンテナ起動していて、ホストとコンテナのポートは5000でバインドしているので
ホスト側からGETリクエストを送信する
$ curl localhost:5000/400{"error_message": "不正なリクエストです","status_code": 400}
$ curl localhost:5000/404{"error_message": "リソースが見つかりません","status_code": 404}
$ curl localhost:5000/500{"error_message": "内部サーバーエラーが発生しました","status_code": 500}
エラーハンドリングできているのが確認できる
上記までは「register_error_handler()メソッド」を使って
実装したが「errorhandlerデコレーター」を使いたい場合は
下記のように書き換えることで同様のことができる。
エラーハンドラ関数登録を削除
from flask import Flaskfrom api.routes import configure_user_routesfrom api.exceptions import CustomException# from api.error_handling import handle_custom_exceptiondef create_app() -> Flask:# アプリケーションインスタンスの作成app = Flask(__name__)# 日本語文字化け対応app.json.ensure_ascii = False# # エラーハンドラ関数登録# app.register_error_handler(CustomException, handle_custom_exception)# ルーティング設定configure_user_routes(app)return app
ここではエラーハンドラー関数を登録しないため
デコレーターを追加する
from flask import Flask,jsonifyfrom api.exceptions import CustomExceptionfrom api.exceptions import CustomBadRequestExceptionfrom api.exceptions import CustomInternalServerErrorExceptionfrom api.exceptions import CustomNotFoundExceptionclass Routes:def __init__(self, app: Flask):self.app = appself.configure_routes()def configure_routes(self):@self.app.route('/<code>', methods=['GET'])def get_users(code):if code == '400':raise CustomBadRequestException(error_message="不正なリクエストです")elif code == '404':raise CustomNotFoundException(error_message="リソースが見つかりません")elif code == '500':raise CustomInternalServerErrorException(error_message="内部サーバーエラーが発生しました")else:return "Code not recognized", 200@self.app.errorhandler(CustomException)def handle_custom_exception(error: CustomException):response = {"status_code": error.status_code,"error_message": error.error_message}return jsonify(response)def configure_user_routes(app: Flask):Routes(app)
errorhandlerデコレーターを使ってエラーハンドラー関数を設定する
errorhandlerデコレーターでは検知するカスタム例外クラスを設定しておく。
結果的には同じことができるので、プロジェクトの方針や個人の好みによるかもしれない。
個人的にはルーティング処理とエラー処理で役割ごとにモジュールを分けておきたかったので
「register_error_handler()メソッド」を使うようにした。
もう一つのパターンであるabort関数に関しては下記でまとめています
flaskのREST APIでカスタム例外をハンドリングする方法を
まとめてみた。
register_error_handler()メソッドを使うことで
try~catchを書くことなく、一元的に例外を管理できるので
開発効率と保守性は高くなると感じた。
当記事ではエラーハンドリングをメインにまとめたが
flaskのREST APIの実装、コンテナの作成、サーバー起動について詳しくは
下記でまとめています。
またより実践的なREST APIの実装については下記記事でまとめています。
※カスタムエラー例外クラスでのエラーハンドリングも行っている