疑問
社内ツール提供の目的でgitなどのVCS管理下のpackageを直接pipでインストール出来るようにしたいです。ここで、プライベートなpackageに依存する場合、どうやってpip installで社内に配布可能なのか、よくわかりません。
動機
- 普通に
setup.pyを書いて試したけど、この挙動が自身の想定と異なっていて、setuptools.setup関数の引数の意味(特にinstall_requiresとdependency_links)をもしかすると勘違いしているのかなぁ、と思ったのがきっかけ。
https://stackoverflow.com/questions/12518499/pip-ignores-dependency-links-in-setup-py も参照。
解決策
以下のような手順で進めていく:
-
ライブラリAを作成する
-
ライブラリAに
setup.pyを追加し、このライブラリをリモートレポジトリAにpushする -
ライブラリAに依存するライブラリBを作成する。
-
ライブラリBに
setup.pyとrequirements.txtを追加する -
pip installする。
これで、プライベートなpackageに依存するgithub のリポジトリをpipでインストールする手筈は整った🙌
以下、詳細な説明。
- gitで管理されている
abc-roadというライブラリをpip installで社内に配布したいのが目的。ここで、このライブラリはabc-bicycleというプライベートなpackageに依存する前提で話をすすめる。
- ライブラリ直下に以下のsetup.pyを書く:
from setuptools import setup, find_packages
project_name = 'abc-bicycle'
setup(name=project_name,
version='0.0.1',
description='description',
long_description='long description',
packages=find_packages(),
include_package_data=True, # 気にせずTrue
zip_safe=False,
install_requires=[
#library in PyPi only
"pandas==0.20.1",
],
# 書くべきでない(詳細を参照)
# dependency_links = [],
entry_points="""
""")
これを含んだライブラリをリモートにpushする。この時、git@github.com:org_name/private_repo_name_a.gitをリモートレポジトリと設定する。ここで、上記のproject_name変数とprivate_repository_name_aは違う名前であっても良い。
3&4. このrepositoryに依存するライブラリを新規作成し、pip でinstallできるようにしよう。
ここで、git@github.com:org_name/private_repo_name_b.gitをリモートレポジトリとする。
setup.pyをおんなじように書く:
from setuptools import setup, find_packages
project_name = 'abc-road'
setup(name=project_name,
version='0.0.1',
description='description',
long_description='long description',
packages=find_packages(),
include_package_data=True, # 気にせずTrue
zip_safe=False,
entry_points="""
""")
また、ライブラリ直下にrequirements.txtを以下のように書く(PyPi以外のライブラリをここに突っ込む):
-e git+ssh://git@github.com/org_name/private_repo_name_a.git@master#egg=abc_bicycle
いくつか留意点を。
-
#egg以下の部分は依存するライブラリのsetup関数のname引数であることに留意すること。この部分は必須。 - ブランチ指定もできて、
@branch_nameみたいな記法となる。ただし、マスターブランチの場合は指定しなくても良い。 - バージョン指定するときは、
#egg=abc_bicycle-0.1.1みたいな感じで書く。
ここで、<project_name>-<version>のように、hyphen(-)でレポジトリ名とバージョン名が識別されるため、レポジトリ名にhyphenを使用している場合は、underbar(_)に置換しておこう。
5. そして、以下のオプションをつけて、pip installする:
# インストールする
pip install -r requirements.txt
pip install -e git+ssh://git@github.com/org_name/private_repo_name_b@master#abc_road
# 再インストールの場合、ローカルにライブラリのキャッシュが残っている可能性があるので、いくつかオプションをつける(--no-cache-dirと-I)と確実
pip --no-cache-dir install -I -r requirements.txt
pip --no-cache-dir install -I -e git+ssh://git@github.com/org_name/private_repo_name_b@master#egg=abc_road
ここまでで、本題は終わり。以下、補足事項。
補足
仮想環境の準備
# pyenvでpython3.6.1をインストール
brew install pyenv # pythonのバージョン切り替えを管理
pyenv install 3.6.1
# インストールされてるpythonのバージョン(複数)を確認
pyenv versions
pyenv global 3.6.1 # defaultのpython versionを3.6.1.に変更
pyenv rehash # 諸々更新する必要がある
# virtualenvをインストールする (pipは元から入っている)
pip install --user virtualenv # sudoの問題のため、--userをつけてインストールした
# virtualenvはPython の isolated な仮想環境を便利に作成するコマンド(pオプション無しの場合、pyenv globalで指定したPythonのversionが入る
virtualenv test01 # 標準で setuptools, pip, wheelがインストールされる
# 仮想環境を起動する
source ./test01/bin/activate
# この後、それぞれの仮想環境に対して、pipして、ライブラリをinstall, uninstallする
pipの使用方法
以下の環境で試している:
$ pip list --format=columns
Package Version
---------- -------
pip 9.0.1
setuptools 28.8.0
virtualenv 15.1.0
最初はローカル環境で試す:
# ローカルにインストール
pip install -e . # --editableでもよい
## ローカルに(強制)再インストール
pip --no-cache-dir install -I -e .
## pip内のpackageを全て削除( grep -v "^-e" でvcsのものも削除できる)
pip freeze | grep -v "^-e" | xargs pip uninstall -y
## vcs管理下のもののpackageを削除する
pip freeze | sed "s/.*#egg=//g" | xargs pip uninstall -y
# requirements.txtに記載のpackagesを全てinstall
pip install -r requirements.txt
うまくいったら、リモートレポジトリに成功したやつをプッシュした上で以下のようにコマンドを叩く:
# project_nameはsetup関数のname引数に記載のやつ
pip install -e git+ssh://git@github.com/org_name/private_repo_name.git@master#egg=project_name
用語
http://www.yunabe.jp/docs/python_package_management.html のキーワードの節を見ましょう。
一般的と思われるsetuptoolsモジュールを用いてライブラリのインストール環境を整える。
setuptoolsの引数について
https://setuptools.readthedocs.io/en/latest/ の"New and Changed setup() Keywords" を主に参照する。ここでは、上記で用いられている引数について、簡単に解説、補足を行う:
MetaData
https://setuptools.readthedocs.io/en/latest/setuptools.html#metadata も見よう。nameとversion以外は適当で良い。
-
name .. projectの名前(
str型)。これは、repository_nameと異なっていても構わない。
PyPiに登録したときは、インストール時にpip install project_nameとすれば良い。gitなどvcsに管理されたものをインストールする時には、pip install -e vcs+proto://host/path@revision#egg=[project]-[version]みたいな感じでeggの引数部分にproject_nameが指定される。 -
version .. version名(str型) 文字通り、version指定。
-
description, long_despription .. ライブラリの概要とか書けばいいと思うよ。
Option
https://setuptools.readthedocs.io/en/latest/setuptools.html#options も見よう。
- packages .. package名(
str型)のlistを突っ込む。とくに何もなければ、、find_packages()を引数に渡す。こうすれば、このライブラリのパッケージ名を取得してくれる。
ちなみに、ライブラリ直下にsrcディレクトリがあり、import srcみたいにせずに、srcより下のディレクトリをパッケージと認識させたい場合は、引数を以下のようにすると良い!
packages=find_packages("src"),
package_dir={'':'src'},
-
include_package_data .. ライブラリ内にあるdata形式をstrのlistで指定できる。
Trueならば、全てのdataをパッケージとして包むことになる。 -
zip_safe .. ライブラリが安定版なら、
True, 開発中ならFalseにする。(とりあえずはFalse指定で良い) -
install_requires .. 依存するライブラリ(
str型)のリストを渡す。解決策では、プライベートレポジトリのライブラリはここに含めていないが、理由は次節で解説する。 -
dependency_links .. PyPi以外のレポジトリを参照する際、どこから入手したらいいかを書く。例えば、
["git+ssh://git@github.com/usr_name/private_repo_name.git@master#egg=abc_bicycle-0.0.1"]みたいに記述する。本記事では、dependency_linksは使わないようにしているが、次節で解説する。
dependency_links を用いるべきでない理由
簡単にいうと、以下の2点に集約される:
-
pip1.5の時点で、
pipはdependency linksを をデフォルトで無視するようになっている1。dependency linksの引数を有効にするには、--process-dependency-linksが必要である。
しかし、一つでも依存ライブラリのsetup関数にdependency links引数を指定していたら、pip install時に--process-dependency-linksオプションをつける必要があり(つまり、--process-dependency-linksが遺伝する)、使う側に不便を強いる。 -
dependency linksをつけた依存ライブラリは
pip freeze > requirements.txtする時、vcsから入手したということにならない(install requiresの情報のみ書き込まれる。)というわけで、freezeした後のpip isntall -r requirements.txtは失敗してしまい、もはや、pip freezeは使い物にならない。
なので、install_requiresには、PyPiにあるライブラリのみを記述し、その他は、requirements.txtに追加しておくのがよい。
それでもdependency_linksを使ってみる
abc-bicycle (private_repo_name_aの方)はdependency_linksと無縁なので変化なし。
リモートレポジトリがgit@github.com:org_name/private_repo_name_b.gitのライブラリの場合、setup.pyを以下のように変更する:
from setuptools import setup, find_packages
setup(name='abc-road',
version='0.0.1',
description='description',
long_description='long description',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires= [
"pandas",
'abc-bicycle==0.0.1' # バージョン名を明記
],
# vcs(gitなど)の場合は、必ず、eggを追記する必要がある
dependency_links = [
"git+ssh://git@github.com/org_name/abc-bicycle.git@master#egg=abc_bicycle-0.0.1"],
entry_points="""
""")
dependency_linksのeggの書き方は「解決策」の章の3&4.の詳細(下の方)と全く同じ。ただし、バージョン名は必須なところが相違点。
ポイントは、install_requiresとdependency_linksの両方にPyPi以外のライブラリの情報を付記することだ。pip installで dependency_linksを有効にする(つまり、--process-dependency-linksオプションを追加する)と、install_requiresの各ライブラリについて、dependency_linksのリンク先と合致するかを確認した上で、PyPi内で符合するライブラリを探す。
もし、所要のPyPi以外の依存ライブラリをdependency_linksのみに書くと、install_requiresに記載されていないため、無視される。
上記でprivateなpackageをdependency_linksに渡したので、requirements.txtは不要。
そして、pip installのコマンドは以下のようになる:
# --process-dependency-linksをつける必要がある
pip install -e git+ssh://git@github.com/org_name/private_repo_name_b.git@master#egg=abc_road --process-dependency-links
これでOK!!
参考url
- https://setuptools.readthedocs.io/en/latest/ setuptoolsのdocument
- https://pip.pypa.io/en/latest/reference/pip_install/#git pipのdocument