プライベートなpackageに依存するgithub のリポジトリをpipでインストールするには？

# 疑問

社内ツール提供の目的で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 も参照。

# 解決策

以下のような手順で進めていく：

1. ライブラリAを作成する
2. ライブラリAに`setup.py`を追加し、このライブラリをリモートレポジトリAにpushする

3. ライブラリAに依存するライブラリBを作成する。

4. ライブラリBに`setup.py`と`requirements.txt`を追加する
5. `pip install`する。

これで、プライベートなpackageに依存するgithub のリポジトリをpipでインストールする手筈は整った🙌

以下、詳細な説明。

---

+ gitで管理されている`abc-road`というライブラリを`pip install`で社内に配布したいのが目的。ここで、このライブラリは`abc-bicycle`というプライベートなpackageに依存する前提で話をすすめる。

2. ライブラリ直下に以下のsetup.pyを書く：

```python
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をおんなじように書く：

```python
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以外のライブラリをここに突っ込む)：

```txt
-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(`_`)に置換しておこう。

--- 

５. そして、以下のオプションをつけて、`pip install`する：

```bash
# インストールする
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
```

---

ここまでで、本題は終わり。以下、補足事項。

# 補足

## 仮想環境の準備

```bash
# 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の使用方法

以下の環境で試している：

```bash
$ pip list --format=columns
Package    Version
---------- -------
pip        9.0.1  
setuptools 28.8.0 
virtualenv 15.1.0 
```

最初はローカル環境で試す：

```bash
# ローカルにインストール
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
```

うまくいったら、リモートレポジトリに成功したやつをプッシュした上で以下のようにコマンドを叩く：

```bash
# 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より下のディレクトリをパッケージと認識させたい場合は、引数を以下のようにすると良い！

```python
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を をデフォルトで無視するようになっている[^ref_pip]。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`に追加しておくのがよい。

[^ref_pip]: https://pip.pypa.io/en/latest/news/#id34 を参照。

### それでも`dependency_links`を使ってみる

`abc-bicycle` (`private_repo_name_a`の方)は`dependency_links`と無縁なので変化なし。

リモートレポジトリが`git@github.com:org_name/private_repo_name_b.git`のライブラリの場合、setup.pyを以下のように変更する：

```python
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`のコマンドは以下のようになる：

```bash
# --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