Python アプリ依存パッケージ管理 ベストプラクティス集

# はじめに

Python のパッケージ管理は複雑です。

ある程度 Python に詳しい方でも、そのすべてを把握しきれていない方は多いのではないでしょうか。

私もその一人です。

そこで、本記事では Python アプリケーションにおける依存パッケージ管理方法のベストプラクティスをまとめます。

なお、ライブラリにおける依存パッケージ管理は扱わないものとしますが、参考になる点はあるかもしれません。

## ベストプラクティス**集**?

本記事のタイトルには、「ベストプラクティス**集**」とあります。また、「ベストプラクティスを**まとめます**」と前述しました。

これはどういうことでしょうか。

答えは簡単です。Python の依存パッケージ管理方法には、唯一のベストプラクティスがないのです。

よって、本記事ではベストプラクティスを一つに決めることをせず、様々な方法を紹介することにしました。

また、新しい方法を発見した場合は随時追記し、本記事が常にベストプラクティス集であることを目指します。

# ベストプラクティス集

## Traditional requirements.txt

```sh
$ pip install flake8
$ pip freeze > requirements.txt
$ cat requirements.txt
flake8==2.5.4
mccabe==0.4.0
pep8==1.7.0
pyflakes==1.0.0
$  pip install -r requieremnts.txt
```

必要なパッケージをインストールした後、インストールされているすべてのパッケージとそのバージョンを出力します。

あまり便利ではなく、ベストプラクティスとは言えないかもしれません。ですが、最もよく見かける方法であるため、ここに挙げました。

## requirements.txt

```
$ echo "python-dateutil" > requirements.txt
$ echo "flake8" > testRequirements.txt
$ pip install -r requirements.txt -r testRequirements.txt
$ pip freeze > constraints.txt
$ cat constraints.txt
flake8==2.5.4
mccabe==0.4.0
pep8==1.7.0
pyflakes==1.0.0
python-dateutil==2.5.3
six==1.10.0
$ pip install -r requirements.txt -r testRequirements.txt -c constraints.txt
```

シンプルな requirements.txt のみの方法を constraints.txt で補強した、より実践的な方法です。

Constraints File に書かれた情報は、インストール対象には追加されずバージョン制約のみを適用されます。詳しくは[解説記事](http://qiita.com/zakuro9715/items/13ab727475920f9eccef) を参照してください。

pip freeze の結果を書き込むのではなく、手動で requirements.txt を記述します。記述するのは、直接依存するパッケージのみで、バージョンの指定も必要最低限のみにします。多くの場合は、`pip install` する代わりに、requirements.txt に記述する程度で十分です。

requirements.txt に詳細な情報を記述しない代わりに、pip freeze の出力を constraints.txt に書き込むことで、バージョンを固定します。

また、必要に応じて複数ファイルに分割することで、グルーピングを行うことが出来ます。

なお、constraints.txt に書かれた内容がインストールされることはないため、requirements.txt をグループ分けした場合にも、constraints.txt は単一ファイルのままで問題ありません。

## setup.py + constraints.txt

```sh
from setuptools import setup

requires = ['python-dateutil']
extras = {'test': ['flake8']}

setup(
    name='app',
    install_requires=requires,
    test_requires=extras['test'],
    extras_require=extras,
)
$ pip install -e .[test]
$ pip freeze > constraints.txt
$ cat constraints.txt
app==0.0.0
flake8==2.5.4
mccabe==0.4.0
pep8==1.7.0
pyflakes==1.0.0
python-dateutil==2.5.3
six==1.10.0
$ pip install -e .[test] -c constraints.txt
```

後述する setup.py + requirements.txt を、Constraints File を用いて改善したパターンです。

setup.py の install_require, extra_requires を用いて依存パッケージを記述します。 

なお、extra_requires を `extra_requires={'test': ['flake8']}` のように指定した場合、`pip install -e .[test]` のようにして 指定したパッケージをインストールできます。

Constraints File は比較的新しい機能であるため、この方法を採用しているプロジェクトはあまりありませんが、いま採用するなら 次のパターンよりも、このパターンのほうが良いのではないでしょうか。


## setup.py + requirements.txt

```sh
from setuptools import setup

requires = ['python-dateutil']
extras = {'test': ['flake8']}

setup(
    name='app',
    install_requires=requires,
    test_requires=extras['test'],
    extras_require=extras,
)
$ pip install -e .[test]
$ pip freeze > requirements.txt
$ cat requirements.txt
app==0.0.0
flake8==2.5.4
mccabe==0.4.0
pep8==1.7.0
pyflakes==1.0.0
python-dateutil==2.5.3
six==1.10.0
$ pip install -r requirements.txt
```

pip にまだ Constraints File がなかった頃によく見られた手法で、著名なパッケージ等でも使用されています。

しかし、この場合は test の対象も requirements.txt に書き込まれるため、本番環境でもテスト用のパッケージがインストールされてしまいます。

requirements.txt 分割することで対応はできますが、Constraints File を使用したほうが簡潔です。

## pip-tools

```sh
$ pip install pip-tools
$ echo "python-dateutil" > requirements.in
$ echo "flake8" > testRequirements.in
$ pip-compile --output-file requirements.txt requirements.in
#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile --output-file requirements.txt requirements.in
#

python-dateutil==2.5.3
six==1.10.0               # via python-dateutil
$ pip-compile --output-file testRequirements.txt testRequirements.in
#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile --output-file testRequirements.txt testRequirements.in
#  

flake8==2.5.4
mccabe==0.4.0             # via flake8
pep8==1.7.0               # via flake8
pyflakes==1.0.0           # via flake8
$ pip-sync requirements.txt testRequirements.txt
$ pip freeze
click==6.6
first==2.0.1
flake8==2.5.4
mccabe==0.4.0
pep8==1.7.0
pip-tools==1.6.5
pyflakes==1.0.0
python-dateutil==2.5.3
six==1.10.0
```

pip-tools というサードパーティのツールを使用するパターンです。

pip-compile は、requirements.in から requirements.txt を作成します。

pip-sync は 必要なパッケージをインストールするだけでなく、不要なパッケージを自動で削除します。 もちろん、requirements.in に pip-tools を記述していなくても、pip-sync によって pip-tools が削除されることはありません。

また、ファイルを分割することによってグルーピングすることができます。

# まとめ

Python アプリで依存パッケージを管理するときの、幾つかのベストプラクティスを紹介しました。

現状では、どれがベストということはできないため、プロジェクトの性質に合わせて、最適なものを選ぶしかありません。

できるだけ近いうちに、いずれか方法がデファクトスタンダードとなることを願います。