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

# はじめに

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

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

# ファイル構成

```bash: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`という設定値のみ記述してあります。

```py:production.py
ENV = 'production'
```

```py:development.py
ENV = 'development'
```

```py:test.py
ENV = 'test'
```

## 設定ファイルの振り分け

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

```py:__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`値を呼び出すだけのクラスと、そのクラスを使うだけのスクリプトを作ります。

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

class Hoge():

    def print_env(self):
        print(ENV)

    def env(self):
        return ENV
```

```py:main.py
""" Hoge#print_envを呼ぶだけのスクリプト """
from hoge import Hoge

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

# テストサンプル

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

```py: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`環境の設定ファイルが使用されます。

```bash
> python main.py
development
```

## 開発環境を明示して実行

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

```bash
> python main.py -d
development
```

## 本番環境で実行

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

```bash
> python main.py -p
production
```

## テスト環境で実行

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

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

OK
```

# イケてないところ

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

# さいごに

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