Pythonプロジェクトのディレクトリ構成 という記事を見かけたのですが、どうも constraints の使い方について誤解が広まっているようです。
この記事で利用されている requirements.txt と constraints.txt の正しい実現方法と、 constraints の本来の使い方を紹介しておきます。
バージョンロックの実現方法
プロジェクトで Flask を使うとしましょう。特にバージョンに関して意見が無いので requirements.txt はこの状態で始めます。
Flask
さて、 Flask をインストールしましょう。新しい仮想環境の中で次のコマンドを実行します。
$ pip install -r requirements.txt
$ pip install flask
Collecting flask
Downloading Flask-0.12.2-py2.py3-none-any.whl (83kB)
100% |████████████████████████████████| 92kB 3.7MB/s
Collecting Jinja2>=2.4 (from flask)
Using cached Jinja2-2.10-py2.py3-none-any.whl
Collecting click>=2.0 (from flask)
Downloading click-6.7-py2.py3-none-any.whl (71kB)
100% |████████████████████████████████| 71kB 3.8MB/s
...
Successfully installed Jinja2-2.10 MarkupSafe-1.0 Werkzeug-0.14.1 click-6.7 flask-0.12.2 itsdangerous-0.24
CI環境と本番環境では固定された、再現性のあるバージョンで実行したいとします。その場合は pip freeze
を使います。
$ pip freeze > requirements.lock
click==6.7
Flask==0.12.2
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
Werkzeug==0.14.1
新しい仮想環境に同じバージョンをインストールするのには、 requirements.txt の代わりに requirements.lock ファイルを使います。
$ pip install -r requirements.lock
ライブラリの更新
ライブラリのバージョンアップは大事です。しておかないとセキュリティFixを見逃す恐れがあります。定期的に lock ファイルを更新しましょう。そのためには新しい仮想環境で lock ファイルを作り直すだけです。
$ rm -rf venv
$ python -m venv venv
$ venv/bin/pip install -r requirements.txt
$ venv/bin/pip freeze > requirements.lock
$ rm -rf venv
利用バージョンのゆるい制限
このままライブラリの更新を頻繁にしていると、後方互換性のないメジャーバージョンアップも実行される可能性があります。
例えばFlask 0.12.3 が出たらアップデートするけど、 Flask 0.13 や 1.0 へのアップデートは通常のライブラリの更新フローで更新したくない場合は、 requirements.txt 側に version specifier を書いておきましょう。
Flask ~= 0.12.2
あるいは、 0.13 なら大きな非互換はないから、メジャーバージョンだけ固定したいというなら次のように書くこともできます。
Flask == 0.*
Flask が依存しているライブラリについても、同じように requirements.txt でメジャーバージョンやマイナーバージョンが上がらない用に指定することができます。
constraints の使い方
constraints は、 pip install の -c オプションを使います。 -r オプションと違い、これで指定しただけではライブラリはインストール されません。ただバージョンを制限するだけです。
$ pip install -c requirements.lock
You must give at least one requirement to install (see "pip help install")
constraints は OpenStack からの要望で追加された機能です。
OpenStack にはたくさんの Python プロジェクトがありますが、ある程度利用するライブラリとそのバージョンを共通化しています。
例えば、 eventlet の 0.17.4 を共通で使うとしましょう。しかし、すべてのプロジェクトが eventlet を使うとは限りません。その場合に、全体で共通の constraints.txt にこう書いておきます。
eventlet == 0.17.4
これを使うときはこうします。
$ pip install -r プロジェクトのrequirements.txt -c 共通のconstraints.txt
これで、プロジェクトのrequirements.txt に eventlet (や eventletに依存するパッケージ)が書かれていたらそのバージョンが 0.17.4 に固定化され、なければ eventlet はインストールされません。
constraints.txt はこのようにして使うものなので、世の中の殆どの Python 製アプリの開発では使う必要がないでしょう。
最近は Pipenv が流行ってきているので、この誤解も含めてすべてをリセットしてわかりやすくなることを期待しています。