LoginSignup
44
31

More than 5 years have passed since last update.

小規模なFlaskのプロジェクトでモジュールを分割する

Last updated at Posted at 2018-12-19

はじめに

Blueprintを使うほどでもない規模やAWS Lambda用のプロジェクトで、共通処理のみを別モジュールに切り出したいことがあるかとおもいます。
そんなときに使える手法まとめます。

検証環境

  • CentOS7
  • Python3.6.6
  • Flask1.0.2

実装例

各手法を適用した例です。

Flaskメインモジュール

Flaskのメイン処理を記述するモジュールです。

api_flask_example.py
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)

共通モジュール

共通処理を記述するモジュールです。

api_filter.py
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を使います。

api_flask_example.py
@app.route('/flask-example', methods=['GET'])
def get():
    result = api_filter.make_result(request.method)
    return make_response(jsonify(result))
api_filter.py
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デコレータを使いますが、別モジュールに分かれているときは文で記述し、引数に実行したいメソッドを指定します。

api_flask_example.py
app.before_request(api_filter.before_request)
app.after_request(api_filter.after_request)
api_filter.py
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で改めてロガーを取得する必要があります。

api_filter.py
def before_request():
    logger = logging.create_logger(current_app)
    logger.info('start ' + current_app.name + ' :: ' + str(request.json))

例外処理

例外発生時に実行したい処理がメインモジュールにあれば@exception_handlerデコレータで登録しますが、別モジュールに分かれているときはregister_error_handlerで登録します。

api_flask_example.py
@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
api_filter.py
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 -
44
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
31