Pythonの設定ファイルを環境別に切り替える

はじめに

今まで開発環境、テスト環境、本番環境全てで同じ設定ファイルを使いまわしていて、都度パラメータを書き換えていました。
しかし2018年にもなってその運用はナンセンスと思い直しまして、設定ファイルの自動切換えを検討しました。

モジュールや事例を探しましたがDjangoの記事が数件引っかかるのみで良いものも無かった為、自力で作ってみました。

ファイル構成

tree
.
├── config
│   ├── development.py
│   ├── __init__.py
│   ├── production.py
│   └── test.py
├── hoge.py
├── main.py
└── tests
    └── test_hoge.py

2 directories, 7 files

configディレクトリ

設定ファイルをまとめて入れておくディレクトリです。
今回設定ファイルとしてpythonスクリプトをそのまま使いましたが、
好みに合わせてiniなりjsonなりyamlなり置き換えて貰えればいいと思います。

設定ファイル

各環境の設定ファイルです。
とりあえずENVという設定値のみ記述してあります。

production.py
ENV = 'production'
development.py
ENV = 'development'
test.py
ENV = 'test'

設定ファイルの振り分け

configモジュールの__init__.pyに振り分け処理を書いていきます。
ちょっとごちゃごちゃしてますが、引数に応じてimportする設定ファイルを変えているだけです。

__init__.py
import sys
import argparse

if 'unittest' in sys.argv[0]:
    from config.test import *
else:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-p",
        "--production",
        action="store_true",
        help="use production config")
    parser.add_argument(
        "-d",
        "--development",
        action="store_true",
        help="use development config")
    parser.add_argument(
        "-t", "--test", action="store_true", help="use test config")
    args = parser.parse_args()
    if args.production:
        from config.production import *
    elif args.development:
        from config.development import *
    elif args.test:
        from config.test import *
    else:
        from config.development import *

__init__.pyにこんな処理を書くのってそもそもどうなんだろう…?
__init__.pyって大体空ファイルか、import文をまとめて書いているような使い方しか見たことが無い。
このような処理、ましてはargparseを書くのはすごく作法に反している気がする…!

実装サンプル

設定ファイルのENV値を呼び出すだけのクラスと、そのクラスを使うだけのスクリプトを作ります。

hoge.py
""" ENVを呼び出すだけのクラス """
from config import *

class Hoge():

    def print_env(self):
        print(ENV)

    def env(self):
        return ENV
main.py
""" Hoge#print_envを呼ぶだけのスクリプト """
from hoge import Hoge

if __name__ == '__main__':
    hoge = Hoge()
    hoge.print_env()

テストサンプル

unittest用のテストスクリプトです。
Hoge#env'test'を返すかどうかをテストします。

test_hoge.py
import unittest
from hoge import Hoge

class TestHoge(unittest.TestCase):
    """ test class """

    def test_env(self):
        """ hoge test """
        hoge = Hoge()
        env = hoge.env()
        self.assertEqual(env, 'test')

if __name__ == "__main__":
    unittest.main()

実行例

普通に実行

なにも指定しないで実行するとdevelopment環境の設定ファイルが使用されます。

> python main.py
development

開発環境を明示して実行

オプション-d(もしくは--development)を指定してもdevelopment環境の設定ファイルが使用されます。
省略しても同じなのであんまり意味無いです、コレ。

> python main.py -d
development

本番環境で実行

オプション-p(もしくは--production)を指定するとproduction環境の設定ファイルが使用されます。
本番環境はうっかり実行はしたくないので引数での明示が必須です。

> python main.py -p
production

テスト環境で実行

unittestで実行するとtest環境の設定ファイルが使用されます。

> python -m unittest tests.test_hoge
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

イケてないところ

他にいい方法が思いつかなかったとはいえ、configモジュールでargparseを使って分岐している点がイケてないです。
main.pyなどで引数を定義することができなくなってしまっています。
個人的にはargparse自体あまり使う機会がないので困らないんですが…この設計はイケてない…

さいごに

もっと簡単に実現できるモジュールとか無いかな?