nikkie-ftnextの日記

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

pip freezeして作られたであろう requirements.txt からdirectな依存を pipdeptree で取り出す

はじめに

盂蘭盆会👋 nikkieです。

Pythonの環境、特に依存ライブラリを再現する経験を最近しました。

目次

Pythonの伸びしろ:requirements.txt

Pythonはとても私の手に馴染む言語で今後も使い続けると思いますが、明確に伸びしろと感じている点があります。
それが環境の再現
ここ最近は仮想環境について話すことが多いですが、チュートリアルで案内される方法は「パッケージマネージャ、」です(つまりは人力ということ😫)

環境の再現も私たち(=人力)頼みで案内されます1
https://docs.python.org/ja/3/tutorial/venv.html#managing-packages-with-pip

$ python -m pip freeze > requirements.txt
$ python -m pip install -r requirements.txt

requirements.txt をバージョン管理システムにコミットして、アプリケーションの一部として配布することができます。


https://ftnext.github.io/2024-slides/pyconkyushu/virtual-environment-with-pip-tools.html#/8/2

このrequirements.txtなのですが、人間が指定するので表記揺れがありえます2
またpip freezeせずにも作れるので、全ての依存を含まないrequirements.txtが生まれてしまうこともあります。


https://ftnext.github.io/2024-slides/techramen/python-package-management-tools.html#/5/7

いま手元には、全ての依存を含んでいると思われるrequirements.txtがあるとします(先人に誠に感謝)。
このrequirements.txtで環境を再現したうえで、ライブラリを切り替えやすい状況にしていきます。

2種類の依存:direct と transitive

さて、ここで「依存」について少し掘り下げます。
依存には、実は2種類あります

  • directな依存
    • 私たちが pip install で指定するパッケージ
  • transitiveな依存
    • directな依存が依存するパッケージ
    • used with a direct object (Oxford辞書より)


https://ftnext.github.io/2024-slides/pyconkyushu/virtual-environment-with-pip-tools.html#/9

pip freezeして作られたrequirements.txtには、directな依存もtransitiveな依存も含まれます
ここからdirectな依存(と関連するtransitiveな依存)をアンインストールするのが難しいと感じます

directな依存とtransitiveな依存を区別すれば、新たなdirectな依存を追加したり、使わなくなったdirectな依存を削除したりといったことはだいぶやりやすくなります。
そこで、requirements.txtからdirectな依存の仕分けを実施します。

pipdeptree でdirectな依存を取り出す

今回のユースケースはREADMEに取り上げられていました。
https://github.com/tox-dev/pipdeptree/tree/2.23.1?tab=readme-ov-file#using-pipdeptree-to-write-requirementstxt-file

If you wish to track only top level packages in your requirements.txt file, it's possible by grep-ing

空の仮想環境にrequirements.txtをインストールし、pipdeptreeを実行します。
pipdeptreepipxでインストールしています。

% python -m venv .venv --upgrade-deps
% source .venv/bin/activate
(.venv) % python -m pip install -r requirements.txt
(.venv) % pipdeptree --python .venv/bin/python

これで依存ツリーが出力されます。
ここからgrepを使ってdirectな依存(=上記の「top level packages」)を取り出します。

(.venv) % pipdeptree --python .venv/bin/python | grep -E '^\w+' | ggrep -o -P '.*(?===)'

ggrepの部分は、バージョンを削って出力するための肯定先読みです(awesomelib==0.1.0awesomelibだけ取り出す)

directな依存を取り出した後は吟味しました。
作業を終えて気付いたのは、pip freezeしたrequirements.txtにはextra (=optional dependencies) が記録されていないということです。
そのためdirectな依存っぽくないパッケージを除く必要がありました。
しかしここはPython開発経験が必要で、誰でもできるかは分からない手順になっちゃってますね。

例えば手元で見つけたのは、cryptography
これはextraの指定で入ってきていたことが分かったのですが、pip freezeしたrequirements.txtではその情報は消えていました。

手元に残ったdirectな依存を元に、パッケージ管理ツールを導入します。
今回のシーンでは、pip-toolsを選択しました。

pip-toolsはrequirements.inやpyproject.tomlを元にrequirements.txtを生成(pip-compile)してから、依存をインストールする(pip-sync)点が、開発をとてもやりやすくしてくれると感じます(pip freezeとは真逆!)

終わりに

全ての依存が出力された(pip freeze製の)requirements.txtから、仮想環境を再現し、directな依存をpipdeptreeで仕分けました。
directな依存を特定した後は、(本文ではpip-toolsでしたが)お好みのパッケージ管理ツールを導入できると思います。

pip freezeの出力のrequirements.txtがあること自体はありがたいのですが、pip freezeの時点でdirectな依存のextraの指定情報は消えてしまっているので、やはり言語として伸びしろがあると考えます(アイデアはいまはないけど、プルリクチャンス!)

pip freezeの出力のrequirements.txtはあくまで一時的な組合せにすぎず、依存ライブラリのバージョンアップに追従していきたいと考えています3
更新していく必要があるという点で、directな依存は、パッケージにおける依存(pyproject.tomlのdependencies)に近いように思われました

関連記事

スイッチサイエンスさんのテックブログより

importlib.metadataを使った例です

(pipdeptreeも中ではimportlib.metadataを使っているのかな?)


  1. チュートリアルでは、なんらかのパッケージマネージャを使った例が好ましいのではないかと私は最近考えるようになりました
  2. requirements.txtのファイル名について調べました。
  3. Rustの状況を知った影響もあるかもしれません