見出し画像

.envに環境変数名を書きたかった話

python-dotenv

Pythonには外部定義された環境変数をプロセスの実行時に設定するパッケージ「python-dotenv」があります。一般的にはPythonのソース・コードと同じディレクトリに.envファイルを用意し、そこへ環境変数の定義を列挙していくことができる便利なパッケージです。

たとえば次のように環境変数を.envファイルへ定義できます。

HOGE=C:\temp\hoge
PIYO=C:\temp\hoge\piyo

.envファイルをPython側のソース・コードからload_dotenvメソッドを使って読み込むと、プロセス内のローカル環境変数として設定されます。設定された環境変数の値はos.environを通じて取得できるようになります。

from dotenv import load_dotenv
import os

if __name__ == '__main__':
    load_dotenv()

    print(os.environ["HOGE"])
    print(os.environ["PIYO"])

実行結果

C:\temp\hoge
C:\temp\hoge\piyo

コマンド・プロンプトの挙動

Windowsのコマンド・プロンプトではsetコマンドを使って環境変数を設定します。

C:>set HOGE=C:\temp\hoge

C:>set PIYO=C:\temp\hoge\piyo

setコマンドを使って設定した環境変数は、%で環境変数名をくくると値を参照することができるようになります。

C:>echo %HOGE% %PIYO%
C:\temp\hoge C:\temp\hoge\piyo

setコマンドの面白いところは、setコマンドを利用する際に環境変数名を指定することができる点で、setコマンド内部で環境変数名を展開して値を設定してくれます。

C:>set HOGE=C:\temp\hoge

C:>set PIYO=%HOGE%\piyo

C:>echo %HOGE% %PIYO%
C:\temp\hoge C:\temp\hoge\piyo

環境変数名を自動的に展開してくれるため、同じ文字列を管理する必要がなくなります。バッチ・ファイル内では他の環境変数名を指定して派生する環境変数を設定していけば、値を多重管理することを避けられます。

python-dotenvの挙動

python-dotenvでは、環境変数名を指定することができません。たとえば、.envファイルを次のように書き直し、Python側から読み込ませてみます。

HOGE=C:\temp\hoge
PIYO=%HOGE%\piyo

os.envrironを利用してprintすると、環境変数名がそのまま文字列として取得されて表示されることがわかります。

実行結果

C:\temp\hoge
%HOGE%\piyo

Pythonには環境変数を展開するための仕組みとしてos.path.expandvarsが用意されています。冒頭に挙げたPythonのソース・コードを修正し、os.path.expandvarsを呼び出すようにします。

from dotenv import load_dotenv
import os

if __name__ == '__main__':
    load_dotenv()

    print(os.environ["HOGE"])
    print(os.path.expandvars(os.environ["PIYO"]))

実行結果

C:\temp\hoge
C:\temp\hoge\piyo

入れ子にできない

os.path.expandvarsは一見便利なのですが、入れ子にすることができません。.envに環境変数FUGAを追加してみます。環境変数FUGAは環境変数PIYOを参照します。

HOGE=C:\temp\hoge
PIYO=%HOGE%\piyo
FUGA=%PIYO%\fuga

そしてPython側からもos.path.expandvarsを介して環境変数FUGAを参照させます。

from dotenv import load_dotenv
import os

if __name__ == '__main__':
    load_dotenv()

    print(os.environ["HOGE"])
    print(os.path.expandvars(os.environ["PIYO"]))
    print(os.path.expandvars(os.environ["FUGA"]))

実行結果

C:\temp\hoge
C:\temp\hoge\piyo
%HOGE%\piyo\fuga

os.path.expandvarsは1回だけしか環境変数名を展開くれないのです。

入れ子の環境変数を展開する

そこで環境変数名を再帰的に展開するメソッド、expandvarsRecursiveを書く必要があります。

from dotenv import load_dotenv
import os
import re

def expandvarsRecursive(value):
    expanded = os.path.expandvars(value)
    return value if expanded == value else expandvarsRecursive(expanded)

if __name__ == '__main__':
    load_dotenv()

    print(os.environ["HOGE"])
    print(os.path.expandvars(os.environ["PIYO"]))
    print(expandvarsRecursive(os.environ["FUGA"]))

実行結果

C:\temp\hoge
C:\temp\hoge\piyo
C:\temp\hoge\piyo\fuga

さらにすべての環境変数について値を展開したいので、expandvarsAllのようなものが必要になります。

def expandvarsAll():
    for key in os.environ:
        os.environ[key] = expandvarsRecursive(os.environ[key])

これを組み込んむと次のように使うことができます。

from dotenv import load_dotenv
import os
import re

def expandvarsAll():
    for key in os.environ:
        os.environ[key] = expandvarsRecursive(os.environ[key])

def expandvarsRecursive(value):
    expanded = os.path.expandvars(value)
    return value if expanded == value else expandvarsRecursive(expanded)

if __name__ == '__main__':
    load_dotenv()
    expandvarsAll()

    print(os.environ["HOGE"])
    print(os.environ["PIYO"])
    print(os.environ["FUGA"])

ただ、短いコードとは言え、これを毎回処理するのもそれなりに手間なので公式githubにIssueを書いてみました(forkしてpull requestを出すほどの修正量でもないため)。やはりパッケージで処理してくれたほうが確実に処理される分、安心感がありますからね。

以上、.envに環境変数名を書きたかった話でした。

この記事が気に入ったらサポートをしてみませんか?