はじめに

バックエンドのAPIサーバとして、Python + Flask + Blueprintの構成のサンプルプログラムを共有します。

Flaskというのは、Pythonで提供されるWebフレームワークです。
Blueprintは、Flaskの中で使われる部品で、アプリを分割することができ、プログラムの保守性を向上させることができるライブラリです。

本記事では、Python + Flask + Blueprintという構成で、テスト環境と本番環境の切り替えがしやすく、モジュールの分割もされ運用しやすい構成をするにはどうすれば良いか、サンプロプログラムを提供することで共有します。

環境

本記事では、以下の環境で動作確認を行いました。

  • Mac OSX Mojave(10.14.3)
  • Python 3.7.2
  • Flask==1.0.2
  • Flask-API==1.1
  • Flask-Cors==3.0.7
  • Flask-HTTPAuth==3.2.4
  • Flask-RESTful==0.3.7

Flaskについてのファイル構成について

APIサーバとして構築するので、URLによってプログラムを分割したり、共通のプログラムはモジュール化したり、規模がある程度大きくなることが予想されます。そのため、開発当初から、保守性を考慮したフォルダ/ファイルを構成することをおすすめします。

Flaskでは、以下のファイル構成を推奨しています。

config.py
requirements.txt
run.py
instance/
    config.py
yourapp/
    __init__.py
    views.py
    models.py
    forms.py
    static/
    templates/

○参考(Flaskのドキュメントより)
Organizing your project

今回は上記のフォルダ/ファイル構成を参考にしながら、不要なフォルダ/ファイルを除いた以下の構成にしました。

application.py
config.py
app/
    __init__.py
    create_response.py
    exceptions.py
    apiv1/
        __init__.py
        testapp.py
    templates/
        ok.json

それでは、ひとつひとつのプログラムについて見ていきます。

application.py

このプログラムは、Flaskやその他のWebサーバから呼ばれるプログラムです。
改めて説明する必要がないくらいシンプルです。

import os
import sys
from app import create_app

print(os.getenv('FLASK_CONFIG'))
app = create_app(os.getenv('FLASK_CONFIG') or 'default')

config.py

application.pyの中で、環境変数のFLASK_CONFIGを取得して、create_appモジュールに渡しています。
このように、config.pyの中に本番環境や開発環境などを定義しておけば、Flask内の環境を環境変数を変更することによって、簡単に切り替えられるようになります。
環境を変更する場合に、プログラム自体を変更するのではなく、環境変数等外部に変数を持たせ、それによってプログラムの呼び出しを変更するのはとても重要です。
(なぜなら、プログラム自体を変更すると、通常はプログラム自体のテストをし直すからです。)

import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config:
    API_KEY = os.getenv('environment variables') or 'Please put your parameter'

class ProductionConfig(Config):
    pass

class DevelopmentConfig(Config):
    SERVER_NAME = "127.0.0.1:5000"
    DEBUG = True

class TestingConfig(Config):
    TESTING = True
    WTF_CSRF_ENABLED = False

config = {
    'production': ProductionConfig,
    'development': DevelopmentConfig,
    'testing': TestingConfig,

    'default': DevelopmentConfig
    }

app/__init__.py

このプログラムはapplication.pyからcreate_appする際に呼ばれます。
この中でBlueprintモジュールを使ったAPIを作成するためのプログラムを呼び、そのAPIを登録しています。

from flask import Flask
from config import config
from flask_cors import CORS

def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    CORS(app)

    from .apiv1 import api as apiv1_blueprint
    app.register_blueprint(apiv1_blueprint, url_prefix='/v1')

    return app

app/create_response.py

APIサーバとして動作させるため、HTTPリクエストの処理はjsonで返却すべきです。
そのため、jsonとしてレスポンスを返却するための処理を、このモジュールでまとめています。
このモジュールはtestappの中で呼ばれています。

from flask import current_app

def create_response(content, status_code, mimetype):

    response = current_app.response_class(
        response=content,
        status=status_code,
        mimetype=mimetype
    )
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    response.headers['X-Frame-Options'] = 'deny'

    return response

app/exceptions.py

今後のために、exceptionクラスを作成しています。

class ValidationError(ValueError):
    pass

app/apiv1/__init__.py

このプログラムがBlurprintのAPIを作成しているプログラムです。
ただし、実際の処理は、後述するtestappモジュールに記載しています。

from flask import Blueprint

api = Blueprint('apiv1', __name__)

# This modules need to be imported after the above api is defined, because the "api" variable is imported in each file
from . import testapp

app/apiv1/testapp.py

このプログラムの中で実際のAPIサーバとして、URLを登録して、その処理を記載しています。
分かりやすく、すごくシンプルな処理にしています。
render_templateは、app/templatesフォルダからファイルを探すため、パスの指定に注意が必要です。

from . import api
from flask import render_template, request, current_app
from app.exceptions import ValidationError
import base64

@api.route('/test', methods=['GET'])
def test():

    from app.create_response import create_response
    content = render_template('ok.json')
    status_code = 200
    mimetype = 'application/json'
    response = create_response(content, status_code, mimetype)

    return response

app/templates/ok.json

これはステータスコードが200の場合に返却するレスポンス内容のtemplateです。
各APIの処理の中でこのテンプレートを読み込み、HTTPステータスコードとともに返却します。

{
  "message":"Success"
}

実行してみる

実行してみます。
まずは、ターミナルでサーバーとして起動します。

$ export FLASK_APP=application.py
$ flask run
 * Serving Flask app "application.py"
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
None
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

無事に立ち上がったので、別のターミナルからcurlしてみます。

$ curl http://127.0.0.1:5000/v1/test
{
  "message":"Success"
}
$ curl -LI http://127.0.0.1:5000/v1/test -o /dev/null -w '%{http_code}\n' -s
200

問題なく、動作しました!

おわり

今回はPython + Flask + Blueprintの構成で、枠組みとなるプログラムをどのように構成すればよいか共有しました。
次回は、例外をどのように処理するか、共有したいと思います。