Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

Python moduleがない場合に自動でpip installする

- はじめに -

この記事は、Xonsh Advent Calendar 2018 - Qiita 10日目の記事です。

複数のサーバを業務で利用おり、それぞれのサーバ環境設定のためpythonパッケージもインストールする作業が必要だが、別サーバからpip freezeするほどでもない(ちょっとした作業サーバなど)という時にjupyter_configや.xonshrc等の設定ファイルに自動インストールするように書いておけば良いのではと思って書いたメモです。

端的にはimportが失敗したらpip moduleを使ってインストールしてやる仕組みを作ります。
また加えて「PyPiに登録されているパッケージのリストを取得し、その中になければ諦めるという仕組み」「xontribの自動インストール」も示します。

 

- 動的なpip install -

python3にはpipモジュールが含まれているため、以下のような形で動的なインストールを実装します。
as構文を利用したい場合もあるかと思いますので、globalsに入れるように書いておきます。

from pip._internal import main as _main
import importlib

def _import(name, module, ver=None):
    try:
        globals()[name] = importlib.import_module(module)
    except ImportError:
        try:
            if ver is None:
                _main(['install', module])
            else:
                _main(['install', '{}=={}'.format(module, ver)])
            globals()[name] = importlib.import_module(module)
        except:
            print("can't import: {}".format(module))

_import('pd','pandas', '0.22.0')
print(pd)

pandasをアンインストールした状態でもスクリプト実行時にインストールが行われます。


試しに、上記スクリプトpythonが動くshellであるxonshの設定ファイルに書き込んでみます。
pandas 消してもxonsh起動時になければ新しくインストールされています。
f:id:vaaaaaanquish:20181202210419p:plain

たぶんjupyterもconfigに書けば同じことができると思います。


参考:pip - Installing python module within code - Stack Overflow

 

- PyPiに登録されているパッケージのリストを取得する -

上記に記載の仕組みに加え「自身が作成しているオリジナルのモジュールもpipで入れたい」などの要望と入り混じった時、PyPiに登録されているライブラリかどうかを判定する必要が稀に出てきます。
PyPiに登録されているライブラリ一覧を取得するスクリプトを加えたものを以下に示します。

if not os.path.exists('~/.pypilist'):
    import xmlrpc.client as xmlrpclib
    client = xmlrpclib.ServerProxy('https://pypi.python.org/pypi')
    packages = client.list_packages()
    with open('~/.pypilist', 'w') as f:
        f.write('\n'.join(packages))
pypilist = open('~/.pypilist', 'r').read().split('\n')
if module in pypilist:
     print('not found pypi')

また、月更新くらいでpypiにあるパッケージやバージョンを見るようにしておけば、rcファイル等に書く時にも便利になるでしょう。

参考:JSON API for PyPi - how to list packages? - Stack Overflow


 

- xonshrcに書くために -

xonshアドベントカレンダーの記事ですので、xonshに書くためのtipsも示します。

xontribをインストールする

xontribも先程と同様にインストールできますが、xontribの場合、xontrib loadなるコマンドが用意されています。

実装を見ると、xontribs_load関数で読み込めなかった場合、prompt_xontrib_install関数で生成した以下のような文字列を返す実装になっています。

The following xontribs are enabled but not installed:
   z
To install them run
    xpip install xontrib-z

参考:xonsh/xontribs.py at master · xonsh/xonsh · GitHub

文字列返しても仕方ないので、ロード部分となるupdate_contextをoverrideして自動でpipするようにしてみます。

from pip._internal import main as _main
import xonsh.xontribs
from xonsh.xontribs import xontrib_context, update_context

def _update_context(name, ctx=None):
    if ctx is None:
        ctx = builtins.__xonsh__.ctx
    if not hasattr(update_context, "bad_imports"):
        _update_context.bad_imports = []
    modctx = xontrib_context(name)
    if modctx is None:
        _main(['install', 'xontrib-{}'.format(name)])
        remodctx = xontrib_context(name)
        if remodctx is None:
            _update_context.bad_imports.append(name)
            return ctx
        return ctx.update(remodctx)
    return ctx.update(modctx)

xonsh.xontribs.update_context = _update_context

xonshにはxpipなるpip wrapperがあり、xonsh環境配下にpip packageをinstallし、他env環境などから切り離すことが出来ます。
そちらを利用する事でxonshのアップグレードにも対応できます。
そちらはpip._internal.mainからは利用できないため、その場合は以下のように_mainでなくsubprocessなどでインストールしてやれば良いでしょう。

import subprocess

cmd = "xonsh -c 'xpip install xontrib-{}'".format(name)
subprocess.call(cmd, shell=True)

 
これらを組み合わせ、xonshrcに書いた上でxontrib loadコマンドを実行すれば、勝手にpipでパッケージを取ってくることが出来ます。
f:id:vaaaaaanquish:20181202210543p:plain

xontrib-readable-tracebackは以前XonshのException発生時のtracebackを見やすくする - Stimulatorなる記事で私が作成したライブラリです。結果として1/0のException messageが短くキレイに色付けされ表示されています。



 

コマンドをxonshrc内でインストールする

コマンドの有無も確認し、なければインストールするようにしておくと便利です。
pythonではshutil.whichメソッドがそれらをサポートしてくれているので利用すると良いでしょう。

「コマンドが無ければ〜」というサンプルを以下に示します。

import shutil

if shutil.which( COMMAND_NAME ) is None:
    # install script


xonshではpython以外にshell scriptもほぼ同等に動くわけなので、何も考えず最新版をインストールシェルスクリプトを書くだけです。

例えば私がよく使っているpecoなら以下をそのまま書くだけ。
Linux に最新版の peco をインストールするシェルスクリプト - Qiita


 

- おわりに -

これでxonsh周りの実行環境作成は、xonshrcだけで完結すると思います。

個人的には以下のlazy load等も組み合わせながら、xonshの起動を高速化しつつ、最低限のラインとして使っています。
vaaaaaanquish.hatenablog.com


xonshアドベントカレンダーのほうはまだ空きがあるみたいなので、よければ是非。
qiita.com