[Flask] Application Factory とは?

Python3Flask

Flask チュートリアルを見てアプリを作っていたとき、日本語版の通りアプリを作ると、英語版にあった pytest を使った方法でテストを書けなかった。英語版にあった Application Factory を使わないとダメなようだ。そこで、Application Factory について調べたのでメモ。

アプリケーションファクトリーとは

まず、日本語版の Flask チュートリアルでは __init__.py にこう書いている。

# __init__.py
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_object('flaskr.config')

db = SQLAlchemy(app)

import flaskr.views

英語版では、The Application Factory ( アプリケーションファクトリー ) という方法で書かれている。

# __init__.py

def create_app(test_config=None):
    # create and configure the app
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY='dev',
        DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
    )
    # ...中略...

    # a simple page that says hello
    @app.route('/hello')
    def hello():
        return 'Hello, World!'

    return app

create_app 関数を使って、  app = Flask() して、最後に appreturn するというのがアプリケーションファクトリーの方法。

他のチュートリアルみても、どちらかの書き方もあったので、どちらが良くて、何が違うのかわからなかった。

しかし、英語版チュートリアルのテストの章では pytest を使っていて、 create_app() を呼び出す記述があったので、アプリケーションファクトリーを使わない方法ではテストが書けなかった。

アプリケーションファクトリーのメリット

  • テストにするのにいい
     → 公式では、インスタンスを複数作れるから、異なるテストケースを設定できると書いてある。
  • 循環 import ( Circular Imports ) を避けるため
     → MVT ( Model / View / Template ) モデルでファイルを分け、機能ごとにフォルダも分けるといったように、ファイルを分割していくと循環 import が起こりやすくなる。

チュートリアルとか Hello, World! のような、短いコード、少ない画面数で 1 ファイルで済むようなアプリではない限り、アプリケーションファクトリーで書くのが良さそうだ。

アプリケーションファクトリーを使うにあたって

Blueprint を使う

他のファイルから from flask import current_app as app と書いて app オブジェクトを呼び出すことはできるが、そのような方法を使っているのは見かけなかった。

例えば、日本語版チュートリアルのやり方だと、 @app.route('/') の記述があるが、MTV モデルで書くと、処理やページの呼び出しは、 view.py に記述されるためview.py にこれを記述することになる。しかし、 view.py でこの記述は使わず、 Blueprint を使う。

プラグインのオブジェクト生成場所

SQLAlchemy とか Flask-login といったプラグイン(公式では Extensions と書かれている)のオブジェクトを作る場所もいくつかの方法で書かれていた。
自分は「方法 2 」を使うことにした。

オブジェクトを作る、とはコレのこと。
例) db = SQLAlchemy()

方法 1

公式のチュートリアルでは、このオブジェクトを使うファイル内に記述するといいと書かれている。

例えば、 SQLAlchemy なら model.py とか DB 操作をする用のファイルでオブジェクトを作成し、 __init__.py などで初期化する。

# models.py
db = SQLAlchemy()
# __init__.py
def create_app():
    …...
    from yourapplication.model import db
    db.init_app(app)

しかし、これで書いていたら、 pytest のときにインポートの循環が起きた。

方法 2

 __init__.py でオブジェクトを作成し、グローバルとして扱う。 create_app() 内で初期化する。

# __init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# オブジェクト作成
db = SQLAlchemy()

def create_app():
    …….
    from yourapplication.model import db
    db.init_app(app)

このパターン。

ファイル構成

アプリケーションファクトリー使用時の、ファイル構成はこのような形になっている。

app 名のフォルダの上にもう一つフォルダ ( 以下の例では xxx にあたる ) が存在している。( このプロジェクト以外に何もなければルートからでもいい。 )

xxx
├── app名/
│   ├── __init__.py
│   ├── hoge.py
│   ├── templates/
│   │   ├── base.html
│   │   ├── auth/
│   │   │   ├── login.html
│   │   │   └── register.html
│   │   └── hoge/
│   │       ├── create.html
│   │       ├── index.html
│   │       └── update.html
│   └── static/
│       └── style.css
├── tests/
│   ├── conftest.py
│   └── test_hoge.py
├── setup.py
└── requirements.txt

起動方法

create_app() を書いたときの起動方法は、コンソールでプロジェクトフォルダ( app 名のフォルダの上のフォルダ)に移動し、以下のコマンドで実行。(ソースファイルは app 名のフォルダの中に作っていく。)

Linux の場合

$ export FLASK_APP=app名
$ flask run

developing mode…うんちゃらという警告メッセージが出るが、消したい場合は、flask run の前に以下を打つ。

$ export FLASK_ENV=development

Windows cmd の場合

$ set FLASK_APP=flaskr
$ set FLASK_ENV=development
$ flask run

Posted by Agopeanuts