nikkie-ftnextの日記

イベントレポートや読書メモを発信

pip-tools体験記:pip-compileで作ったrequirements.txtの通りに環境が同期(pip-sync)する!

はじめに

1日目、nikkieです。

Pythonの依存管理ツールとして名前だけ聞いていたpip-toolsを触りました。

目次

pip-toolsとは

2つのコマンドからなります。

pip-tools = pip-compile + pip-sync

pip install pip-toolsでインストールできます。
インストールするとコマンド2つとも使えます1

前回のpip-tools

過去記事にちょっとだけ関係しています

requirements.inというファイル

requirements.txtについて文献調査した記事でpip-toolsが登場しています。

直接の依存(例:Flask==2.2.2)をrequirements.inに書き、pip-compile
生成されるrequirements.txtには、直接の依存が依存するライブラリ(他動 transitive の依存)も具体的なバージョン込みで記載されます。
これでpip install -r requirements.txtしました。

今回は上の記事にないpip-syncも触っていきます

Ryeが使っている

先日素振りしたRye

https://rye-up.com/guide/sync/ によると

Rye currently uses pip-tools to download and install dependencies.

現在(v1.16.0)ではpip-toolsを使っている、とのことです。

pip-toolsの使い方:2つのコマンドはどう使う?

使い方はリポジトリの図に示されています。
https://github.com/jazzband/pip-tools/blob/7.3.0/img/pip-tools-overview.svg より引用

  1. まずpip-compile。requirements.txtを生成
  2. pip-syncで環境をrequirements.txtと同期する

pip-compile

requirements.txtを生成します。
pip-toolsを使わない場合、必要なパッケージをpip installしていき最後にpip freezeして、バージョン込みでパッケージ情報を残すと思います。
それに対してpip-toolsでは必要なパッケージのバージョン解決だけを行って2、先にパッケージ情報のファイル(requirements.txt)ができます

入力するファイルは幅広くサポート

  • pyproject.toml
  • setup.cfg
  • setup.py
  • requirements.in

requirements.in を作らなくても、Pythonプロジェクトに置いてあるパッケージのメタデータファイルと一緒に使い出せます。
dependencies(pyproject.toml)やinstall_requires(setup.py)を見てrequirements.txtを生成してくれます

パッケージの依存にはoptional-dependencies3(extras_require)もありますよね。
extraごとのcompileもサポートしています4
コマンド例:pip-compile --extra dev --output-file dev-requirements.txt

生成されるrequirement.txtはリポジトリコミット推奨
後述するpip-syncで見事に環境が再現させられるので、これは納得でした。
https://github.com/jazzband/pip-tools/tree/7.3.0#should-i-commit-requirementsin-and-requirementstxt-to-source-control

If you want a reproducible environment installation available from your source control, then yes, you should commit both requirements.in and requirements.txt to source control.

すでにrequirements.txtをcompileしていて、(全部ではなくて)特定の依存ライブラリのバージョンだけを上げたい場合は、--upgrade-packageを指定。
https://github.com/jazzband/pip-tools/tree/7.3.0#updating-requirements
この引数は複数回指定できます。
全部一度に上げたら一発ではうまくいかないかもしれませんが、1つずつ上げていけるのは便利そうでした

コマンドの引数についてはこちらにまとまっています:
https://pip-tools.readthedocs.io/en/stable/cli/pip-compile/

pip-sync

requirements.txtがある前提で、環境(仮想環境含む)を同期します。
これはPythonを使ったこれまでの開発で体験したことのない衝撃でした。
環境のパッケージをインストール・アンインストールが走って、まじで一致するんですよ!

pip-syncには複数のrequirements.txtが渡せます

コマンドの引数についてはこちらにまとまっています:
https://pip-tools.readthedocs.io/en/stable/cli/pip-sync/

自作ライブラリに導入して手を動かす

OCRツールをまとめたライブラリを少しだけ開発しました(時期的にお正月ハッカソン?)

pyproject.tomlはこんな感じです5

[project.optional-dependencies]
google = ["google-cloud-vision"]
tesseract = ["pytesseract"]
dev = [
    "taskipy",
    # 長いので省略します
]

同じ階層にsetup.pyがあります。

.
├── ocroy
├── pyproject.toml
├── setup.py
├── tests
└── venv

(雰囲気が伝わるように抜粋して示しました)

新たに仮想環境を切り、pip-toolsを導入して素振りします6

% python -m venv piptools_env --upgrade-deps
% source piptools_env/bin/activate

以降はpiptools_env仮想環境です

% python -V
Python 3.11.4
% pip install pip-tools
% pip freeze
build==1.0.3
click==8.1.7
packaging==23.2
pip-tools==7.3.0
pyproject_hooks==1.0.0

extraごとにcompile

pip-compileでsetup.pyやpyproject.tomlに基づいてcompileされますが、今回dependenciesの定義がないので空ファイルです。

extraを指定していきます。

% pip-compile --extra google --output-file google-requirements.txt

% pip-compile --extra tesseract --output-file tesseract-requirements.txt

% pip-compile --extra dev --output-file dev-requirements.txt

仮想環境を同期

生成したrequirements.txt全部と同期させましょう。

% pip-sync *-requirements.txt

追加でインストールではなく、同期します。
ただし、pip-toolsは残ります。
pyproject.tomlのdevのextraにpip-toolsを書いておくのがよさそうな気がしますね7

自作ライブラリに必要な依存関係はバッチリ管理できるようになったと思います!

宿題事項

  • 一部の依存のバージョンを上げて同期するフローは、一定期間使ってみて体験したい
  • editable install、どうやるんだろう
    • 自作ライブラリの依存管理はバッチリだが、上の手順では自作ライブラリ自体をインストールできていない(追加でpip install -e .?)
    • Ryeによると、requirements.txtに-e file:.と書くらしい8
    • これはpip-compileでは書かれないのかな
  • extraが複数あるとき、それぞれのrequirements.txtをどう管理するのがいいんだろう
  • 今回はdependenciesがなかったが、これがあった場合、全部のrequirements.txtに書かれるのかな
    • 重複っぽい印象を受けるけれど、そういうものなのかな

終わりに

pip-toolsを触りました。
自作ライブラリのextraごとに依存ライブラリのバージョンを記載したrequirements.txtを生成し、全部渡して同期しました。

同期の感触はなかなかよいです。
ライブラリを開発するPython使い9にはなかなかいいツールなのではと第一印象はよいです(宿題があるので判断は変わるかもですが)。
ちょっとした素振りでは判断しきれないところもあるので、一定期間試してみて判断したいですね


  1. python -m piptools compilepython -m piptools syncのようにも使えます。ref: https://github.com/jazzband/pip-tools/tree/7.3.0#example-usage-for-pip-compile
  2. ドキュメントを読んだ上でやや想像が入っているので、ソースコードを読んでみたら正しくないかもしれません
  3. https://packaging.python.org/ja/latest/guides/writing-pyproject-toml/#dependencies-optional-dependencies
  4. https://github.com/jazzband/pip-tools/tree/7.3.0#requirements-from-pyprojecttoml
  5. https://github.com/ftnext/ocroy/blob/7bc66efa5a870fdfa09a06659096bf2873c60c3d/pyproject.toml#L32-L48
  6. pyproject.tomlにtool.setuptools.packages.findの指定が必要でした。piptools_envディレクトリが増えて、setuptoolsのAutomatic discoveryでパッケージのディレクトリを1つに決められないためという理解です
  7. 書いている例 How to Publish an Open-Source Python Package to PyPI – Real Python
  8. https://github.com/jazzband/pip-tools/issues/1968#issuecomment-1684251342 が見つかりました
  9. ライブラリを開発しない場合は、pip-toolsはtoo muchだと思います。ライブラリを開発するようになって初めて同期するありがたさなど分かるんじゃないでしょうか