はじめに
Python のパッケージ管理は複雑です。
ある程度 Python に詳しい方でも、そのすべてを把握しきれていない方は多いのではないでしょうか。
私もその一人です。
そこで、本記事では Python アプリケーションにおける依存パッケージ管理方法のベストプラクティスをまとめます。
なお、ライブラリにおける依存パッケージ管理は扱わないものとしますが、参考になる点はあるかもしれません。
ベストプラクティス集?
本記事のタイトルには、「ベストプラクティス集」とあります。また、「ベストプラクティスをまとめます」と前述しました。
これはどういうことでしょうか。
答えは簡単です。Python の依存パッケージ管理方法には、唯一のベストプラクティスがないのです。
よって、本記事ではベストプラクティスを一つに決めることをせず、様々な方法を紹介することにしました。
また、新しい方法を発見した場合は随時追記し、本記事が常にベストプラクティス集であることを目指します。
ベストプラクティス集
Traditional requirements.txt
$ 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 に書かれた情報は、インストール対象には追加されずバージョン制約のみを適用されます。詳しくは解説記事 を参照してください。
pip freeze の結果を書き込むのではなく、手動で requirements.txt を記述します。記述するのは、直接依存するパッケージのみで、バージョンの指定も必要最低限のみにします。多くの場合は、pip install
する代わりに、requirements.txt に記述する程度で十分です。
requirements.txt に詳細な情報を記述しない代わりに、pip freeze の出力を constraints.txt に書き込むことで、バージョンを固定します。
また、必要に応じて複数ファイルに分割することで、グルーピングを行うことが出来ます。
なお、constraints.txt に書かれた内容がインストールされることはないため、requirements.txt をグループ分けした場合にも、constraints.txt は単一ファイルのままで問題ありません。
setup.py + constraints.txt
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
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
$ 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 アプリで依存パッケージを管理するときの、幾つかのベストプラクティスを紹介しました。
現状では、どれがベストということはできないため、プロジェクトの性質に合わせて、最適なものを選ぶしかありません。
できるだけ近いうちに、いずれか方法がデファクトスタンダードとなることを願います。