概要
Pythonでコマンドライン引数とはどんなものなのか、djangoでどこで使われているか、また、Djangoのsettings.py
で、コマンドライン引数を使ってテスト用のDATABASE
設定を行う方法も紹介します。
サンプルコード
実際に動きを見てからの方が早いと思うので、まずは以下のサンプルコード。
import sys
if __name__ == "__main__":
try:
print(f"sys.argv: {sys.argv}")
if len(sys.argv) >= 3:
arg1 = sys.argv[1]
arg2 = sys.argv[2]
print(f"引数1: {arg1}")
print(f"引数2: {arg2}")
else:
print("引数が少なくとも2つ必要です。")
except Exception as ex:
print(ex)
実行結果は以下の通り。
sample-test.py abc def
↓
sys.argv: ['sample-test.py', 'abc', 'def']
引数1: abc
引数2: def
上記から分かる通り、コマンドライン引数に期待通りの数指定されていれば、正常に出力されます。
sys.argv
とは?
sys.argv
とは、標準ライブラリsys
モジュールに含まれる変数で、コマンドライン引数を取得するためのリストです。
上述の通り、Pythonスクリプトを実行するときに、python script.py arg1 arg2
のように、スクリプト名の後にスペースで区切られた引数を追加された際、この引数のリストをsys.argv
に格納することができます。
引数を一つにすると...?
では引数を一つにしたらどうなるでしょうか。
sample-test.py abc
↓
sys.argv: ['sample-test.py', 'abc']
引数が少なくとも2つ必要です。
一つしか連携されなかった場合は、if len(sys.argv) >= 3:
がFalse
になるので、else
で指定したプリント文が出力されます。
なぜ3
以上にしているか、というと、上記デバッグ結果を見て分かる通り、sys.argv
リストにはスクリプト名も格納されているためです。スクリプト名、引数1、引数2が入っているかどうか、を確認するために、3
以上かどうかを判定しています。
if sys.argv[1] and sys.argv[2]:
でも良いのか?
ちなみに、私は最初以下でも可能と思っていました。
一見、以下のIF
文でも「引数が2つあるかどうか」をチェックしているので問題なさそうに見えます。
import sys
if __name__ == "__main__":
try:
print(f"sys.argv: {sys.argv}")
if sys.argv[1] and sys.argv[2]: # ここを変えています
arg1 = sys.argv[1]
arg2 = sys.argv[2]
print(f"引数1: {arg1}")
print(f"引数2: {arg2}")
else:
print("引数が少なくとも2つ必要です。")
except Exception as ex:
print(ex)
しかし、これでは、たとえ引数が一つでも"引数が少なくとも2つ必要です。"
が表示されず、代わりにException
が拾われてlist index out of range
と表示されます。インデックスエラーですね。
なぜそうなるか、というと、if sys.argv[1] and sys.argv[2]:
の場合は、sys.argv[1]
とsys.argv[2]
が存在するかつ空でない場合にTrue
になるからです。しかし、そもそもこの条件式で使う引数が足りていないので、その時点でプログラムの実行が中断され、try
ブロックの例外処理が行われます。その結果、list index out of range
エラーになってしまいます。
端的に言えば「条件式が評価される前にエラーが発生している」ということですね...。
Djangoのmanage.py
でも使われている
例えばどこで使われているか?というと、Djangoで一番身近なところで言えばmanage.py
です。
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
python manage.py runserver
を実行すると、sys.argv
は['manage.py', 'runserver']
のようなリストになります。そしてこの引数に応じて、異なる処理を実行するのです。runserver
コマンドは開発サーバーの起動、makemigrations
コマンドはデータベースのマイグレーションファイルの作成、といった感じです。上述のtest
もですね。
関数が具体的にどんなことをしているのか?という詳細は以下にコードがあります。
Code source de django.core.management
Djangoのsettings.py
でテスト用Databaseを指定する
最後に。sys.argv
を使うことで、Djangoでテスト実行時のみ、テスト用のDatabaseを指定することができます。
Djangoでは通常、テストファイルを実行するときにpython manage.py test xxx
(xxx
は実行したいアプリケーション)といった形でtests.py
を実行させます。つまりテスト実行時のコマンドライン引数には必ずtest
が入っていることになるので、test
を指定した際にはテスト用のデータベース設定を見るように指定すれば良い、というわけです。
import sys
if 'test' in sys.argv:
DATABASES['default'] = DATABASES['test'];
DATABASES = {
# 本番用
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': DATABASE,
'USER': USER,
'PASSWORD': PASSWORD,
'HOST': HOST,
'PORT': '3306',
'OPTIONS': {
'init_command':
'SET character_set_connection=utf8,collation_connection=utf8_unicode_ci'
}
},
# テスト用
'test': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'test_db',
'USER': 'root',
'PASSWORD': 'root_password',
'HOST': 'mysql',
'PORT': '3306',
'OPTIONS': {
'connect_timeout': 500000,
'init_command':
'SET character_set_connection=utf8,collation_connection=utf8_unicode_ci'
}
}
}
上記により、DATABASES['test']
に対応する値がDATABASES['default']
にコピーされます。
これはあくまでテスト実施時にテスト用のDBを設ける場合、という想定です(本番とローカルのDBを分ける、という意味ではありません)。
ローカル用と本番用、という意味では、基本的にはsettings.py
をsettings_local.py
としてローカル環境で別々に設定し、環境変数や環境ごとの設定ファイルを読み込む方法などが推奨されると思います。