はじめに
Blueprintを使うほどでもない規模やAWS Lambda用のプロジェクトで、共通処理のみを別モジュールに切り出したいことがあるかとおもいます。
そんなときに使える手法まとめます。
検証環境
- CentOS7
- Python3.6.6
- Flask1.0.2
実装例
各手法を適用した例です。
Flaskメインモジュール
Flaskのメイン処理を記述するモジュールです。
from flask import Flask, request, jsonify, make_response
import logging
import api_filter
app = Flask(__name__)
logger = app.logger
logger.setLevel(logging.INFO)
'''''''''''''''''
GET request
'''''''''''''''''
@app.route('/flask-example', methods=['GET'])
def get():
result = api_filter.make_result(request.method)
return make_response(jsonify(result))
'''''''''''''''''
POST request
'''''''''''''''''
@app.route('/flask-example', methods=['POST'])
def post():
result = api_filter.make_result(request.method)
return make_response(jsonify(result))
'''''''''''''''''
Exception
'''''''''''''''''
@app.route('/flask-exception', methods=['GET'])
def flask_exception():
raise Exception
'''''''''''''''''
共通処理の設定
'''''''''''''''''
# request前後に実行する処理
app.before_request(api_filter.before_request)
app.after_request(api_filter.after_request)
# エラー発生時
app.register_error_handler(Exception, api_filter.exception_handler)
app.register_error_handler(404, api_filter.not_found_handler)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
共通モジュール
共通処理を記述するモジュールです。
from flask import request, jsonify, current_app, logging
import traceback
from http import HTTPStatus
def make_result(method):
result = {
"result": "hello! Flask"
, "method": method
, "app_name": current_app.name
}
return result
def before_request():
'''
リクエストの前処理
'''
logger = logging.create_logger(current_app)
logger.info('start ' + current_app.name + ' :: ' + str(request.json))
def after_request(response):
'''
リクエストの後処理
'''
logger = logging.create_logger(current_app)
logger.info('end ' + current_app.name + ' :: httpStatusCode=' +
str(response._status_code) + ', response=' + str(response.response))
return response
def exception_handler(ex):
'''
共通例外処理
'''
logger = logging.create_logger(current_app)
# 予期せぬ例外発生時
logger.error(traceback.format_exc())
response = {"message":"undefined error occured."}
return (jsonify(response), HTTPStatus.INTERNAL_SERVER_ERROR)
def not_found_handler(ex):
'''
存在しないURLへのアクセス
'''
logger = logging.create_logger(current_app)
logger.info('not found ' + request.url)
response = {"message":"resource not found."}
return (jsonify(response), HTTPStatus.NOT_FOUND)
手法
current_app
app = Flask(__name__)
はメインモジュールに記述するので共通モジュールでは使えませんが、appの要素を使いたいときがあるかとおもいます。
そんなときには current_app
を使います。
@app.route('/flask-example', methods=['GET'])
def get():
result = api_filter.make_result(request.method)
return make_response(jsonify(result))
def make_result(method):
result = {
"result": "hello! Flask"
, "method": method
, "app_name": current_app.name # アプリケーション名。特に指定がなければ Flask(__name__) を実行したモジュール名が使われる
}
return result
$ curl http://192.168.33.10:5000/flask-example | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 72 100 72 0 0 21667 0 --:--:-- --:--:-- --:--:-- 24000
{
"app_name": "api_flask_example",
"method": "GET",
"result": "hello! Flask"
}
current_app
はApplicationContextがpushされている間のみ使えます。
before_request, after_request
リクエストの前後に実施したい処理がメインモジュールにあれば@before_request
, @after_request
デコレータを使いますが、別モジュールに分かれているときは文で記述し、引数に実行したいメソッドを指定します。
app.before_request(api_filter.before_request)
app.after_request(api_filter.after_request)
def before_request():
logger = logging.create_logger(current_app)
logger.info('start ' + current_app.name + ' :: ' + str(request.json))
def after_request(response):
logger = logging.create_logger(current_app)
logger.info('end ' + current_app.name + ' :: httpStatusCode=' +
str(response._status_code) + ', response=' + str(response.response))
return response
$ curl -X POST -H "Content-Type: application/json" -d '{"request":"flask"}' http://192.168.33.10:5000/flask-example | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 92 100 73 100 19 17124 4456 --:--:-- --:--:-- --:--:-- 18250
{
"app_name": "api_flask_example",
"method": "POST",
"result": "hello! Flask"
}
[2018-12-15 05:38:34,172] INFO in api_filter: start api_flask_example :: {'request': 'flask'}
[2018-12-15 05:38:34,173] INFO in api_filter: end api_flask_example :: httpStatusCode=200, response=[b'{"app_name":"api_flask_example","method":"POST","result":"hello! Flask"}\n']
192.168.33.10 - - [15/Dec/2018 05:38:34] "POST /flask-example HTTP/1.1" 200 -
ログについては後述します。
ログ
メインモジュール内であれば app.logger
でロガーを使用できますが、別モジュールではloggerとcurrent_appで改めてロガーを取得する必要があります。
def before_request():
logger = logging.create_logger(current_app)
logger.info('start ' + current_app.name + ' :: ' + str(request.json))
例外処理
例外発生時に実行したい処理がメインモジュールにあれば@exception_handler
デコレータで登録しますが、別モジュールに分かれているときはregister_error_handler
で登録します。
@app.route('/flask-exception', methods=['GET'])
def flask_exception():
raise Exception
app.register_error_handler(Exception, api_filter.exception_handler) # 何らかのExceptionが発生した
app.register_error_handler(404, api_filter.not_found_handler) # 404 not found
def exception_handler(ex):
logger = logging.create_logger(current_app)
# 予期せぬ例外発生時
logger.error(traceback.format_exc())
response = {"message":"undefined error occured."}
return (jsonify(response), HTTPStatus.INTERNAL_SERVER_ERROR)
def not_found_handler(ex):
logger = logging.create_logger(current_app)
logger.info('not found ' + request.url)
response = {"message":"resource not found."}
return (jsonify(response), HTTPStatus.NOT_FOUND)
curl http://192.168.33.10:5000/flask-exception | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 39 100 39 0 0 4703 0 --:--:-- --:--:-- --:--:-- 4875
{
"message": "undefined error occured."
}
[2018-12-15 05:45:50,188] INFO in api_filter: start api_flask_example :: None
[2018-12-15 05:45:50,192] ERROR in api_filter: Traceback (most recent call last):
File "/home/vagrant/venv36/lib64/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/home/vagrant/venv36/lib64/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/vagrant/workspaces/py-example/src/functions/api_flask_example.py", line 30, in flask_exception
raise Exception
Exception
[2018-12-15 05:45:50,193] INFO in api_filter: end api_flask_example :: httpStatusCode=HTTPStatus.INTERNAL_SERVER_ERROR, response=[b'{"message":"undefined error occured."}\n']
192.168.33.10 - - [15/Dec/2018 05:45:50] "GET /flask-exception HTTP/1.1" 500 -