CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: PyPI にないパッケージを依存パッケージにするには

PyPI に無いパッケージを自作パッケージの依存ライブラリにしようとしたら色々と苦労したので、そこで得られた知見を共有しておく。 どうやら現状では setuptools と pip で対応方法が異なっているために、それぞれで微妙に異なるやり方を取る必要があるようだ。

今回使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.11.3
BuildVersion:   15D21
$ python --version
Python 3.5.1
$ pip list
pip (8.0.2)
setuptools (20.1.1)
wheel (0.24.0)

自作パッケージを用意する

まずは動作確認に使う自作パッケージを用意する。 名前は mypackage にしよう。 パッケージは枠だけ用意して中身は空っぽにする。 今回、パッケージ自体は関心の外にあるため。

$ mkdir mypackage
$ touch mypackage/__init__.py

次にパッケージに対してセットアップスクリプトを書く。 これでパッケージ mypackage がインストール可能になる。

$ cat << 'EOF' > setup.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from setuptools import setup


def main():
    setup(
        name='mypackage',
        version='0.0.1',
        zip_safe=False,
        packages=['mypackage'],
    )


if __name__ == '__main__':
    main()
EOF

早速セットアップスクリプトを使って今の環境に mypackage をインストールしよう。

$ python setup.py install

パッケージをインストールすると pip list サブコマンドで mypackage が見えるようになる。

$ pip list | grep mypackage
mypackage (0.0.1)

もちろん Python でインポートして使うこともできる。 まあ、中身は空っぽだけど。

$ python -c "import mypackage"

PyPI にない依存パッケージを追加する

さて、ここまででやっとスタートラインに立てた。 作成した mypackage に PyPI には置かれていない依存パッケージを追加してみよう。 追加するのは自分で作った以下のジョークパッケージにする。

github.com

PyPI にないパッケージを依存パッケージに追加するには次のドキュメントが参考になる。

Specifying Dependencies — Python Packaging Tutorial

通常であれば依存パッケージは setuptools.setup() 関数の install_requires 引数に名前を追加して終わりだ。 しかし、追加したいパッケージが PyPI に無いときは dependency_links の指定も必要になる。 ここにインストールに使うリンクを記述する。

$ cat << 'EOF' > setup.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from setuptools import setup


def main():
   setup(
       name='mypackage',
       version='0.0.1',
       zip_safe=False,
       packages=['mypackage'],
       install_requires=[
           'wehatemtg==0.0.1',
       ],
       dependency_links=[
           'git+https://github.com/momijiame/wehatemtg.git#egg=wehatemtg-0.0.1',
       ]
   )


if __name__ == '__main__':
   main()

EOF

セットアップスクリプトを修正したら、もう一度 mypackage をインストールし直そう。

$ python setup.py install

今度は依存パッケージの wehatemtg が一緒にインストールされるようになった。

$ pip list | grep wehatemtg
wehatemtg (0.0.1)

これで、セットアップスクリプトを使ったインストールで PyPI にないパッケージを依存パッケージに追加できるようになった。

pip で PyPI にない依存パッケージを扱う

めでたしめでたし、と行きたいところだけど残念ながらそうはいかない。 実は問題は pip を使ったときに発生する。

まずは一旦依存パッケージの wehatemtg をアンインストールしておこう。

$ pip uninstall -y wehatemtg

次に mypackage をソース配布物としてビルドする。

$ python setup.py sdist

できあがったソース配布物を pip コマンドでインストールしてみよう。

$ pip install dist/mypackage-0.0.1.tar.gz
Processing ./dist/mypackage-0.0.1.tar.gz
  Requirement already satisfied (use --upgrade to upgrade): mypackage==0.0.1 from file:///Users/amedama/Documents/temporary/package-not-on-pypi in /Users/amedama/.virtualenvs/temporary/lib/python3.5/site-packages
Collecting wehatemtg==0.0.1 (from mypackage==0.0.1)
  Could not find a version that satisfies the requirement wehatemtg==0.0.1 (from mypackage==0.0.1) (from versions: )
No matching distribution found for wehatemtg==0.0.1 (from mypackage==0.0.1)

見事にエラーになった。 wehatemtg をインストールしようとしたけど見つからないらしい。

何故エラーになるのだろうか? pip のソースコードを調べたところ原因が判明した。 次を見てもらいたい。

github.com

ここには次のようなコメントがある。

# We trust every url that the user has given us whether it was given
#   via --index-url or --find-links
# We explicitly do not trust links that came from dependency_links
# We want to filter out any thing which does not have a secure origin.

つまり pip はセットアップスクリプトの dependency_links を信用しないらしい。 PyPI にないパッケージをインストールするときは --index-url か --find-links オプションを明示的に指定する必要があるようだ。

ではそのコメントにしたがってオプションをつけて実行してみよう。

$ pip install dist/mypackage-0.0.1.tar.gz --find-links=git+https://github.com/momijiame/wehatemtg.git#egg=wehatemtg-0.0.1
...(省略)...
Successfully installed wehatemtg-0.0.1

今度は上手くいった。

$ pip list | grep wehatemtg
wehatemtg (0.0.1)

まとめ

今回は PyPI にないパッケージを依存パッケージにする方法について書いた。 基本的には setuptools.setup() 関数の dependency_links 引数にパッケージのインストール元 URL を記述すれば良い。 ただし、どうやら pip はそこに書いてある内容を信用しないのでコマンド実行時にオプションで渡してやる必要がある点に注意が必要だ。