きたない requirements.txt から Pipenv への移行
ソフトウェアエンジニアの花岡です。今回は、きたない requirements.txt から Pipenv に移行し pipenv graph
を使って依存ライブラリを管理しやすくする方法について書きました。
きたない requirements.txt とは、依存ライブラリとその依存ライブラリの指定が混在している requirements.txt ファイルのことです。たとえば
$ pip install flask
$ pip freeze > requirements.txt
のように生成した requirements.txt を使っているとき、本記事ではその requirements.txt を、きたない requirements.txt と呼ぶことにします。
きたなくない requirements.txt とは pip の constraints の正しい用途のように、直接依存しているライブラリのみ指定された requirements.txt のことで、pip freeze
の結果は別に管理します。
Pipenv の基本的な使い方については、本記事では触れないため公式サイトを参照してください。
Pipenv とは
Pipenv は Python のパッケージ管理ツールです。公式サイトに
Pipenv: Python Dev Workflow for Humans
Pipenv is a tool that aims to bring the best of all packaging worlds (bundler, composer, npm, cargo, yarn, etc.) to the Python world.
とあるように、ひとのための Python の開発ワークフローを掲げて、いろいろなパッケージングツールの良いところを集結しようとしています。
現在は https://github.com/pypa/pipenv がリポジトリです。pypa は Python Packaging Authority の略で setuptools、pip、virtualenv なども管理しています。とても安心感がありますね。Pip Integration (eventual)によると、最終的には pip
コマンドで -p
/--pipfile
オプションが使えるようになるらしいです。
Pipenv の良いところのひとつは、Pipfile.lock
として pip freeze
の結果を管理してくれるところと、Pipenv(pip --pipfile
)が標準になるはずというところだと思います。Python のパッケージング管理も Bundler や npm に追いついてきました。
きたない requirements.txt から Pipenv への移行
説明のために、きたない requirements.txt を生成します。
$ pip install flask # 本番用のライブラリ
$ pip install pytest pytest-cov # テスト用のライブラリ
$ pip freeze | tee requirements.txt
atomicwrites==1.1.5
attrs==18.1.0
click==6.7
coverage==4.5.1
Flask==1.0.2
funcsigs==1.0.2
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
more-itertools==4.2.0
pluggy==0.6.0
py==1.5.4
pytest==3.6.3
pytest-cov==2.5.1
six==1.11.0
Werkzeug==0.14.1
ここでは、ついでにテスト用のライブラリも指定しました。きたないですね。
まずこの requirements.txt を pipenv install -r requirements.txt
でインストールします。
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
atomicwrites = "==1.1.5"
attrs = "==18.1.0"
click = "==6.7"
coverage = "==4.5.1"
funcsigs = "==1.0.2"
itsdangerous = "==0.24"
more-itertools = "==4.2.0"
pluggy = "==0.6.0"
py = "==1.5.4"
pytest = "==3.6.3"
pytest-cov = "==2.5.1"
six = "==1.11.0"
Flask = "==1.0.2"
"Jinja2" = "==2.10"
MarkupSafe = "==1.0"
Werkzeug = "==0.14.1"
[dev-packages]
[requires]
python_version = "3.7"
という内容の Pipfile
(と Pipfile.lock
)が生成されます。ここで pipenv graph
を実行すると
$ pipenv graph
Flask==1.0.2
- click [required: >=5.1, installed: 6.7]
- itsdangerous [required: >=0.24, installed: 0.24]
- Jinja2 [required: >=2.10, installed: 2.10]
- MarkupSafe [required: >=0.23, installed: 1.0]
- Werkzeug [required: >=0.14, installed: 0.14.1]
pytest-cov==2.5.1
- coverage [required: >=3.7.1, installed: 4.5.1]
- pytest [required: >=2.6.0, installed: 3.6.3]
- atomicwrites [required: >=1.0, installed: 1.1.5]
- attrs [required: >=17.4.0, installed: 18.1.0]
- funcsigs [required: Any, installed: 1.0.2]
- more-itertools [required: >=4.0.0, installed: 4.2.0]
- six [required: >=1.0.0,<2.0.0, installed: 1.11.0]
- pluggy [required: >=0.5,<0.7, installed: 0.6.0]
- py [required: >=1.5.0, installed: 1.5.4]
- setuptools [required: Any, installed: 40.0.0]
- six [required: >=1.10.0, installed: 1.11.0]
のように依存関係のグラフが表示されます。この結果から Pipfile
を
[packages]
Flask = "==1.0.2"
[dev-packages]
pytest-cov = "==2.5.1"
のように書き換えて pipenv install -d
するのが最短です。これでテストもパスするのであれば何も問題ありません。
しかし、たとえばこの場合 pytest は pytest-cov によって >=2.6.0
に依存しているので、もし pytest 4.0.0 がリリースされ破壊的変更があれば、pipenv install -d
してテストがパスしないかもしれません。そのような場合は
[dev-packages]
pytest = "==3.6.3"
pytest-cov = "==2.5.1"
のように pytest も指定しておく必要があります。このようにテストがパスしなかった場合には以下のように、pipenv graph
を利用して最初の Pipfile
から徐々に整理し最適化していくことができます。
まずテスト用のライブラリを [packages]
から [dev-packages]
に移動します。今回は pipenv graph
の結果がシンプルなので手動で移動することができます。
[packages]
click = "==6.7"
itsdangerous = "==0.24"
Flask = "==1.0.2"
"Jinja2" = "==2.10"
MarkupSafe = "==1.0"
Werkzeug = "==0.14.1"
[dev-packages]
atomicwrites = "==1.1.5"
attrs = "==18.1.0"
coverage = "==4.5.1"
funcsigs = "==1.0.2"
more-itertools = "==4.2.0"
pluggy = "==0.6.0"
py = "==1.5.4"
pytest = "==3.6.3"
pytest-cov = "==2.5.1"
six = "==1.11.0"
パッケージ数が多くなってくると手動で移動するのは大変かもしれません。そういう場合は --json
または --json-tree
というオプションがあるので、自動で振り分けてくれるようなスクリプトを書いて公開してください。
それまでの間は、逆向きの依存関係を出力する --reverse
というオプションを使うことにします。
$ pipenv graph --reverse
atomicwrites==1.1.5
- pytest==3.6.3 [requires: atomicwrites>=1.0]
- pytest-cov==2.5.1 [requires: pytest>=2.6.0]
attrs==18.1.0
- pytest==3.6.3 [requires: attrs>=17.4.0]
- pytest-cov==2.5.1 [requires: pytest>=2.6.0]
click==6.7
- Flask==1.0.2 [requires: click>=5.1]
coverage==4.5.1
- pytest-cov==2.5.1 [requires: coverage>=3.7.1]
funcsigs==1.0.2
- pytest==3.6.3 [requires: funcsigs]
- pytest-cov==2.5.1 [requires: pytest>=2.6.0]
itsdangerous==0.24
- Flask==1.0.2 [requires: itsdangerous>=0.24]
MarkupSafe==1.0
- Jinja2==2.10 [requires: MarkupSafe>=0.23]
- Flask==1.0.2 [requires: Jinja2>=2.10]
pip==10.0.1
pluggy==0.6.0
- pytest==3.6.3 [requires: pluggy>=0.5,<0.7]
- pytest-cov==2.5.1 [requires: pytest>=2.6.0]
py==1.5.4
- pytest==3.6.3 [requires: py>=1.5.0]
- pytest-cov==2.5.1 [requires: pytest>=2.6.0]
setuptools==40.0.0
- pytest==3.6.3 [requires: setuptools]
- pytest-cov==2.5.1 [requires: pytest>=2.6.0]
six==1.11.0
- more-itertools==4.2.0 [requires: six>=1.0.0,<2.0.0]
- pytest==3.6.3 [requires: more-itertools>=4.0.0]
- pytest-cov==2.5.1 [requires: pytest>=2.6.0]
- pytest==3.6.3 [requires: six>=1.10.0]
- pytest-cov==2.5.1 [requires: pytest>=2.6.0]
Werkzeug==0.14.1
- Flask==1.0.2 [requires: Werkzeug>=0.14]
この結果を利用して、たとえば atomicwrites
は pytest からのみ依存されていることがわかるので [dev-packages]
に移動、という作業を繰り返します。
後は間接依存ライブラリをそれぞれ Pipfile
では指定せずにテストしていきます。もしテストがパスしなければ、そのバージョンを Pipfile
で指定したままにしておきテストがパスしない理由を調査します。
さいごに
不要なエントリを削除した後は pipenv update
でライブラリの更新ができるように、バージョンの指定を調整するといいと思います。たとえば
[packages]
Flask = "~=1.0"
[dev-packages]
pytest-cov = "~=2.5"
のようにすると pipenv update
したときに、条件にあった新しいリリースがあれば Pipfile.lock
が更新されてインストールされます。バージョンのフォーマットについては [PEP 440][] と [RFC 3986][] で定義されていると [PEP 508][] で定義されています。
弊社では Python できれいなコードを書きたいエンジニアを募集しています。興味のある方は是非こちらからご応募ください。
その他の記事
Other Articles
関連職種
Recruit