flaskでconfigファイルの指定に物理的なパスを指定できるようにしたくなったという話

はじめに

flaskを使い始めたのだけれど。flaskのconfigファイルの指定が対象のflaskアプリの存在するモジュールの位置からの相対パスを指定するらしく使いづらい。

例えば、fooパッケージの中で、以下の様なコードがあった場合には、fooパッケージからの相対位置を指定する必要がある。

from flask import Flask
app = Flask(__name__)
app.config.from_python("<config path>")
app.run()

この __name__ を使っているパッケージの位置からの相対位置を指定する必要がある。だるい。これを止めて直接特定のファイルの位置を指定できるようにしようという話。

Appのconfig_classを変更すれば良い

はじめに手抜きをするために以下のmagicalimportというパッケージをインストールしておく*1

$ pip install magicalimport

configオブジェクトを作るファクトリーの設定を変更する必要がある。以下の様な形で作れば良い。

from flask import Flask, Config
import logging
import magicalimport
logger = logging.getLogger(__name__)


class CustomConfig(Config):
    def from_pyfile(self, path):
        return self.from_object(magicalimport.import_from_physical_path(path))


class App(Flask):
    config_class = CustomConfig

# or monkey patching
# Flask.config_class = CustomConfig


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-c", "--config", default=None, required=True)
    args = parser.parse_args()

    app = App(__name__)
    if args.config:
        app.config.from_pyfile(args.config)

    logging.basicConfig(
        level=app.config["LOGGING_LEVEL"].upper(),
        format=app.config["LOGGING_FORMAT"]
    )
    logger.info("running.. (port=%s)", app.config["PORT"])
    app.run(port=app.config["PORT"])

あとは以下のように好きな位置のファイルを指定してアプリを起動する事ができる。

$ tree config
config
├── base.py
└── local.py

1 directory, 2 files

$ python foo/app.py --config=./config/local.py
asctime2016-10-02 00:52:40,828  loglevel:INFO   message:running.. (port=8888)
asctime2016-10-02 00:52:40,847  loglevel:INFO   message: * Running on http://127.0.0.1:8888/ (Press CTRL+C to quit)

やりましたね。

設定ファイルの内容

設定ファイルの内容は以下

config/base.py

PLAIN_LOGGING_FORMAT = "%(asctime)s %(levelname)s %(message)s"
LTSV_LOGGING_FORMAT = "asctime%(asctime)s\tloglevel:%(levelname)s\tmessage:%(message)s"

config/local.py

import magicalimport
import os.path
here = os.path.dirname(os.path.abspath(__file__))
base = magicalimport.import_from_physical_path(os.path.join(here, "./base.py"), as_="base")

LOGGING_LEVEL = "info"
LOGGING_FORMAT = base.LTSV_LOGGING_FORMAT


PORT = 8888 

*1:実はこのためについ先程作りました