既存プロジェクトに pipenv を導入した方法

みなさん pipenv を使ってますか？ まだ pip ですか？

最近、pipenv を導入した際に必要だった作業を紹介します[^1]。

[^1]: なお、導入したのは内製のレポート生成ツールでした。顧客へのレポートも生成しているので、一応トラブルがあると困るものです。

# pipenv とは何か？なぜ pipenv なのか？

`pipenv` は `pip` のラッパーで、RubyでいうBundlerです。pip単体でもライブラリの管理はできるのですが、`pipenv` の方が色々便利です。

`pip`と同様に [Python Packaging Authority が管理しています](https://github.com/pypa/pipenv)。

詳しくは[ドキュメント](http://pipenv-ja.readthedocs.io/ja/translate-ja/)や[Qiitaの記事](https://qiita.com/tags/pipenv/likes) をご覧ください。

# setup.py や requirements.txt を削除

今回 pipenv 化したのは **アプリケーション** です。

PyPIに公開したりする必要はないので、`setup.py` の中身は最小限を残して削除します。

```diff
 setup(
     name='hoge',
     version='1.4.5',
     packages=find_packages(exclude=['test']),
-    python_requires='==3.6.3',
-    install_requires=[
-        'cx_Oracle==5.3',
-        'pandas==0.23.0',
-        'PyYAML==3.12'
-    ],
-    extras_require={
-        'dev': [
-            'yapf==0.20.0',
-            'mypy==0.560'
-        ]
     package_data={'hoge': ['res/*']},
     entry_points={
         'console_scripts': [
             'hoge = hoge.cli:main',
         ],
     },
```

# Pipfile の追加

`Pipfile` を追加し、`setup.py` から削除した内容を代わりに書き込みます。

```ini
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
hoge = {editable = true, path = "."} # setup.py を読み込むため
cx_Oracle = "==5.3"
pandas = "==0.23.0"
PyYAML = "==3.12" # 本来は "*" を指定するべき（後述）

[dev-packages]
yapf = "==0.20.0"
mypy = "==0.560"

[requires]
python_full_version = "3.6.3" # バージョンを厳密に指定
```

ちなみに、`[requires]` で `python_version = "3.6"` ではなく `python_full_version = "3.6.3"`を指定しています。パッチバージョンの違いにより不具合が生じるリスクはないとも言えませんし、バージョンごとにPythonインタープリタを用意するのは難しくないので、`python_full_version` を指定するのをオススメします。

# デプロイスクリプトの変更

今までは、こんなシェルスクリプトでデプロイしていました。

```bash
# pipenv以前

mkdir "$INSTALL_DIR" # 所定のディレクトリを作成
pushd "$INSTALL_DIR"
  # ソースコードをコピー
  git -C "$REPO_DIR" archive --format=tar "$VERSION" | tar -xf -

  # 仮想環境を作り、そこにインストール
  "$PYTHON" -m venv .venv
  .venv/bin/pip install .
popd > /dev/null
```

`pip install` を `pipenv install` に差し替えます。

```bash
# pipenv化

mkdir "$INSTALL_DIR" # 所定のディレクトリを作成
pushd "$INSTALL_DIR"
  # ソースコードをコピー
  git -C "$REPO_DIR" archive --format=tar "$VERSION" | tar -xf -

  # pipenv で 仮想環境を作り、そこにインストール
  export PIPENV_VENV_IN_PROJECT=1 
  "$PYTHON" -m pipenv install \
    --python="$PYTHON" \
    --deploy       
popd > /dev/null
```

なお、`pipenv install` にはいくつかオプションを指定しています。
本番環境へのデプロイではどれも必須でしょう。

## --deploy をつける

`--deploy` を付けると、`Pipfile`と`Pipfile.lock` にズレがあるとき（`pipenv sync`し忘れた時）に、エラーにします。

本番環境へのデプロイでは必ず`--deploy`をつけましょう。

## 環境変数 `PIPENV_VENV_IN_PROJECT` を定義する

`pipenv` はデフォルト設定では所定のディレクトリにハッシュ値名の仮想環境を作ります（`~/.local/share/virtualenvs/hoge-okggs9cf`）。

しかしこれだと、**どの仮想環境がどのデプロイに対応するのか判りにくい**ため、例えば、ディスク容量が足りなくなって不要な仮想環境を削除したいときに困りそうです。

`PIPENV_VENV_IN_PROJECT` が定義されていると、仮想環境がカレントディレクトリ下の`./.venv`に作られるようになります。

## `--python` でインタープリタを指定する（`pyenv` を使わない）

`pipenv` は `pyenv` を使っていると、自動的にインタープリタを選択してくれます。

しかし、うちの本番環境では `pyenv` を使っていないので、`--python` でインタープリタを直接指定しています。

# ライブラリのアップデート

今回 pipenv を導入したツールでは、`PyYAML==3.12`のようにライブラリのバージョンを厳密に指定していました。**しかし、単に `PyYAML==3.12` のように書くのは問題があります。**

というのも:

a. 単に `3.12`でしか動作確認していないだけで、`3.13` でも動くかもしれない（ので、できればアップデートするのが望ましい）
b. （バグや仕様変更により） `3.12` でしか動作しない。アップデートしてはいけない。

のどちらなのか判りません。

今回 pipenv を導入したツールの `PyYAML` は前者だったので、`Pipfile` を書き換えて動作確認しました。

```diff
  # Pipfile
  [packages]
  cx_Oracle = "==5.3"
  pandas = "==0.23.0"
- PyYAML = "==3.12'
+ PyYAML = "*"
```

一方、cx_Oracle や pandas は、このバージョンしか使えない事情があったので、バージョンを厳密に指定しています。

# 感想

実際の作業としては、特にハマり所はありませんでした
（`Pipfile` の文法を覚えるのや、チームメンバーに説明するのは面倒でしたが）。

バージョンを厳密に管理できるメリットは大きいです。ぜひ pipenv の導入を。