Pipenvとは
Pythonのパッケージ管理ツール(Rubyでいうところのbundlerのようなもの)としてpipenvが標準的になってきている。
https://pipenv-ja.readthedocs.io/ja/translate-ja/
Pipfileで必要なパッケージを指定し、pipenv install
するとパッケージがインストールされた仮想環境が構築され、以後pipenv shell
を実行するとその仮想環境上のshellで作業することができる。
また特定のコマンドを実行したい場合は、pipenv run [実行したいコマンド]
とすれば実行できる。
Pipfileのパスを指定する
さて、デフォルトでは実行時のカレントディレクトリにある"Pipfile"という名前のファイルを参照して仮想環境を構築する。
しかし、バッチ処理などをしたい場合に任意のディレクトリからアプリケーションを実行したい。つまりカレントではない場所にあるPipfileを指定して処理を行いたい。
カレントディレクトリにはないPipfileを使うには環境変数PIPENV_PIPFILE
でパスを指定すればよい。
export PIPENV_PIPFILE=/path/to/Pipfile
pipenv run python my_script.py
こうすれば事前にこのパスでインストールした仮想環境を使ってpython my_script.py
を実行することができる。
PIPENV_PIPFILEに相対パスを使うとバグる
ただし、相対パスを使うとバグる場合がある。例えば以下のようなシェルスクリプトを使って起動することを考える。
script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd) # 実行中のシェルスクリプトがあるパスを取得
export PIPENV_PIPFILE=$script_dir/../Pipfile # そのシェルスクリプトの親ディレクトリにあるPipfileを参照
pipenv run python $script_dir/my_script.py # pythonを起動
するとpythonを実行しようとしても必要なモジュールがないというエラーが出ることになる。
なぜか?
こちらのページにヒントがあった。以下、引用する。
pipenvで任意のディレクトリからアプリケーションを実行したい
"こうなるのは仮想環境が(カレントディレクトリに対してではなく)Pipfileのパスに対して作られるためのようです。残念ながら、この振る舞いについて明記されたドキュメントは見つけられませんでした。以下にソースコードの該当箇所を抜粋します。"
@property
def name(self):
if self._name is None:
self._name = self.pipfile_location.split(os.sep)[-2]
return self._name
となっている。ここでpipfile_location
にはPIPENV_PIPFILE
で指定したファイルパスが入っていると思われる。
先ほどはパスを相対パスで指定してしまったので、self.name = ".."
となってしまったものと思われる。
解決法
よって先ほどの挙動を修正するにはシェルスクリプトを以下のように書き換える必要がある。
script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd)
PIPFILE=$script_dir/../Pipfile
export PIPENV_PIPFILE=$(cd $(dirname $PIPFILE) && pwd)/$(basename $PIPFILE) # 正規化された絶対パスにする
pipenv run python $script_dir/my_script.py