最近は開発手順やデプロイ手順の自動化、ドキュメント化にも積極的に取り組んでいます。
Dockerとmakeをよく利用しているのですが、社内メイン言語はPython。
そこでmakeの代わりにFabricやAnsibleを使うのもいいかなと思ったところAnsibleの実行でこけました。
原因がPYTHONPATHだったので、2系と3系でPYTHONPATHを使い分けたいなと思った話です。
Ansible実行時のエラー内容と原因
まずはインストール。 Macの場合はpipでインストールできます。(Latest Releases on Mac OSX)
[alpaca]~/github/fab % python3 -m venv venv
[alpaca]~/github/fab % . venv/bin/activate
(venv) [alpaca]~/github/fab % pip install ansible
すんなり入りました。 ここでドキュメントにあるコマンドを実行したところエラーに。
(venv) [alpaca]~/github/fab % ansible localhost -m ping
ERROR! Unexpected Exception: No module named 'error'
the full traceback was:
Traceback (most recent call last):
File "/Users/rhoboro/github/fab/venv/bin/ansible", line 88, in <module>
mycli = getattr(__import__("ansible.cli.%s" % sub, fromlist=[myclass]), myclass)
File "/Users/rhoboro/github/fab/venv/lib/python3.6/site-packages/ansible/cli/__init__.py", line 28, in <module>
import yaml
File "/Users/rhoboro/google-cloud-sdk/platform/google_appengine/lib/yaml/lib/yaml/__init__.py", line 2, in <module>
from error import *
ModuleNotFoundError: No module named 'error'
エラーメッセージをよく見ると、ここでは全く関係なさそうなgoogle_appengine
という文字列。
ということで$PYTHONPATHを確認。
(venv) [alpaca]~/github/fab % echo $PYTHONPATH
/Users/rhoboro/google-cloud-sdk/platform/google_appengine/lib/yaml/lib:/Users/rhoboro/google-cloud-sdk/platform/google_appengine
(venv) [alpaca]~/github/fab % cat ~/.zshrc | grep "PYTHONPATH"
export PYTHONPATH=$PYTHONPATH:${CLOUDSDK_ROOT}/platform/google_appengine/lib/yaml/lib:$GAE_PYTHON_ROOT
はい。やっぱりPYTHONPATHが原因っぽい。
ということで$PYTHONPATHを消してあげると無事動きました。
(venv) [alpaca]~/github/fab % env PYTHONPATH="" ansible localhost -m ping
[WARNING]: Host file not found: /etc/ansible/hosts
[WARNING]: provided hosts list is empty, only localhost is available
localhost | SUCCESS => {
"changed": false,
"ping": "pong"
}
めでたしめでたし。
PYTHONPATHを2/3で分けたい
これ、根本の原因はおそらくPYTHONPATHで読み込んでるgoogle-cloud-sdkのpythonが2系、ansibleようにvenvで作ったpythonの環境は3系というところなんですよね。
PYTHONPATHをPython2とPython3でいい感じに分ける方法はないんでしょうか。
ただの環境変数だしやるとしたらこんな流れかな。
- PYTHONPATH2, PYTHONPATH3をそれぞれ定義しておく
- シェルコマンド実行をフック
- そのときのPythonのバージョンを判定
- PYTHONPATHを置き換える
ちょっと試しにそれっぽいのを書いてみた(.zshrc)。
export PYTHONPATH2="/python2"
export PYTHONPATH3="/python3"
function preexec_func() {
a=`readlink \`which python\``
if [ $(echo $a | grep -e 'python3') ] ; then
export PYTHONPATH=$PYTHONPATH3 > /dev/null
else
export PYTHONPATH=$PYTHONPATH2 > /dev/null
fi
}
autoload -Uz add-zsh-hook
add-zsh-hook preexec preexec_func
環境作って動作確認。
# python2の環境で確認
[alpaca]~/github/pythonpath % virtualenv venv2
[alpaca]~/github/pythonpath % . venv2/bin/activate
(venv2) [alpaca]~/github/pythonpath % python -c "import sys; print sys.path"
['', '/python2', '/Users/rhoboro/github/pythonpath/venv2/lib/python27.zip', '/Users/rhoboro/github/pythonpath/venv2/lib/python2.7',...]
(venv2) [alpaca]~/github/pythonpath % deactivate
# python3の環境で確認
[alpaca]~/github/pythonpath % python3 -m venv venv3
[alpaca]~/github/pythonpath % . venv3/bin/activate
(venv3) [alpaca]~/github/pythonpath % python -c "import sys; print(sys.path)"
['', '/python3', '/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python36.zip',...]
(venv3) [alpaca]~/github/pythonpath % deactivate
パッとみた感じは動いてそうです。
ただ、export
を使うとコマンド実行後に元に戻しておかないと気持ち悪いですね。
export
の代わりにenv
で楽できないかなと思ったけどコマンド実行時には消えてるから使えませんでした。
判定適当でこんな動きしてるので実用的ではないかなー。
[alpaca]~/github/pythonpath % python3
Python 3.6.1 (default, May 13 2017, 14:08:52)
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/python2', '/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/usr/local/lib/python3.6/site-packages']
ここまでやりましたが、そもそも論としてグローバルなPYTHONPATHを編集しないで必要なところでのみdirenvで設定するほうがいい気がしますね。
もし何かいい方法知ってる方はTwitterとかで教えてくれると嬉しいです。