Python の lock file について調べた
Pythonのロックファイル周りについて調べたので雑にまとめる。
Python でロックファイルを生成してくれるツールをいくつかピックアップし生成されるロックファイルがどうなっているか、どのように運用すれば良いかについてまとめてみました。
検証環境
- WSL: Ubuntu 22.04.3 LTS
- Python 3.12.1
- それぞれのツールのバージョンは時点での最新版です
Table of contents
アプリケーションとライブラリ
基本的にロックファイルを望んでいるのはアプリケーション開発者であると思います。
ライブラリ開発者は適切な依存関係のセットを pyproject.toml に記述しておけば基本は良いはずなので、基本的にはアプリケーション開発者の視点でまとめていきます。
今回とりあげるツール
※ 気が向いたら追加するかもです
pip
みんな大好き pip です。
ロックファイル(的なもの)を生成するには freeze コマンドを使います。
requirements.txt はこんな感じで特にバージョンは指定しない
opencv-python
# py312 venv 環境内での操作
pip install -r requirements.txt
pip freeze > requirements-py312.lock
cat requirements-py312.lock
numpy==1.26.4
opencv-python==4.9.0.80
Python 3.8 環境でも同様の操作をやってみる
# 省略
pip freeze > requirements-py38.lock
cat requirements-py38.lock
numpy==1.24.4
opencv-python==4.9.0.80
見ての通り、numpy のバージョンが異なります。 このように、現在の開発環境次第で lock ファイルにブレが生じてしまうことがあるというのがわかりました(OSによって違う依存が含まれる可能性もあるかもしれません)。
ただ、この問題は Docker コンテナを使えば割りと簡単に解決できると思います。
例えば、WebAPI を開発していて本番環境は Docker コンテナで動いていたとします。 このとき、開発用コンテナも本番と同じ環境にしておいて、そのコンテナ内で requirements.lock を生成すれば開発マシンに関係なく同じ lock ファイルが生成されるはずです。
自分で書いていてこの運用楽そうだなと思いました(実際には他の課題もあって rye を使いたいとか言い出しそうですが...)。
Rye with uv
❯ rye --version
rye 0.24.0
commit: 0.24.0 (8d56aa18a 2024-02-15)
platform: linux (x86_64)
self-python: [email protected]
symlink support: true
uv の生成する lock ファイルについて、README の制限事項に書いてあります。
pip-compile のように、uv はプラットフォーム固有の requirements.txt ファイルを生成します(例えば、poetry や pdm がプラットフォームに依存しない poetry.lock や pdm.lock ファイルを生成するのとは異なります)。そのため、uvのrequirements.txtファイルはプラットフォームやPythonのバージョン間で移植性がないかもしれません。
DeepL 翻訳: https://github.com/astral-sh/uv?tab=readme-ov-file#limitations
project.requires-python = ">=3.8"
rye init rye-lock
cd rye-lock
rye add opencv-python
# python 3.12.1 環境での操作
rye pin 3.12.1
rye sync
cat requirements.lock
-e file:.
numpy==1.26.4
# via opencv-python
opencv-python==4.9.0.80
# via rye-lock
# python 3.8 環境での操作
rye pin 3.8
# 省略
-e file:.
numpy==1.24.4
# via opencv-python
opencv-python==4.9.0.80
# via rye-lock
結局は pip と同様の課題があるようです。
例えば CI システムでこのプロジェクトをビルドしようとしたとき、その CI プラットフォームに合わせて lock ファイルを生成する必要があるかもしれません。
その時は自分なら以下のように解決します。
- rye lock で生成された lock ファイルは使わない
- CI システム側で uv を使って pyproject.toml から lock ファイルを別途生成する(そのプラットフォームにあったバージョンが選ばれるはず)
- その lock ファイルを使って pip install する
これの個人的な利点としては、uv pip compile
で生成される lock ファイルはパッケージの editable install を lock ファイルに追加しないので、CI 時に不要な editable install が走らないということです。
多分以下のような step を CI に実装すれば良いと思います。
pip install uv
uv pip compile pyproject.toml -o requirements-ci.lock
pip install -r requirements-ci.lock
...
# test や lint, build など
PDM
❯ pdm --version
PDM, version 2.12.3
取り上げた中で唯一、クロスプラットフォームな lock ファイルを生成する。
そもそも pip 互換な requirements.txt が生成されるツールはロックファイルが基本プラットフォーム依存になるので、そもそものアプローチが違う(先駆けは Poetry なのかな?)。
# pdm use で 3.12 を選んだ
❯ pdm add opencv-python
Adding packages to default dependencies: opencv-python
/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/resolver/providers.py:202: PackageWarning: Skipping [email protected] because it requires Python>=3.9 but the
project claims to work with Python>=3.8. Instead, another version of numpy that supports Python>=3.8 will be used.
If you want to install [email protected], narrow down the `requires-python` range to include this version. For example, ">=3.9" should work.
return self.repository.find_candidates(
/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/resolver/providers.py:202: PackageWarning: Skipping [email protected] because it requires Python>=3.9 but the
project claims to work with Python>=3.8. Instead, another version of numpy that supports Python>=3.8 will be used.
If you want to install [email protected], narrow down the `requires-python` range to include this version. For example, ">=3.9" should work.
return self.repository.find_candidates(
/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/resolver/providers.py:202: PackageWarning: Skipping [email protected] because it requires Python>=3.9 but the
project claims to work with Python>=3.8. Instead, another version of numpy that supports Python>=3.8 will be used.
If you want to install [email protected], narrow down the `requires-python` range to include this version. For example, ">=3.9" should work.
return self.repository.find_candidates(
/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/resolver/providers.py:202: PackageWarning: Skipping [email protected] because it requires Python<3.13,>=3.9 but
the project claims to work with Python>=3.8. Instead, another version of numpy that supports Python>=3.8 will be used.
If you want to install [email protected], narrow down the `requires-python` range to include this version. For example, ">=3.9,<3.13" should work.
return self.repository.find_candidates(
/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/resolver/providers.py:202: PackageWarning: Skipping [email protected] because it requires Python<3.13,>=3.9 but
the project claims to work with Python>=3.8. Instead, another version of numpy that supports Python>=3.8 will be used.
If you want to install [email protected], narrow down the `requires-python` range to include this version. For example, ">=3.9,<3.13" should work.
return self.repository.find_candidates(
/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/resolver/providers.py:202: PackageWarning: Skipping [email protected] because it requires Python>=3.9 but the
project claims to work with Python>=3.8. Instead, another version of numpy that supports Python>=3.8 will be used.
If you want to install [email protected], narrow down the `requires-python` range to include this version. For example, ">=3.9" should work.
return self.repository.find_candidates(
/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/resolver/providers.py:202: PackageWarning: Skipping [email protected] because it requires Python>=3.9 but the
project claims to work with Python>=3.8. Instead, another version of numpy that supports Python>=3.8 will be used.
If you want to install [email protected], narrow down the `requires-python` range to include this version. For example, ">=3.9" should work.
return self.repository.find_candidates(
/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/resolver/providers.py:202: PackageWarning: Skipping [email protected] because it requires Python>=3.9 but the
project claims to work with Python>=3.8. Instead, another version of numpy that supports Python>=3.8 will be used.
If you want to install [email protected], narrow down the `requires-python` range to include this version. For example, ">=3.9" should work.
return self.repository.find_candidates(
INFO: Use `-q/--quiet` to suppress these warnings, or ignore them per-package with `ignore_package_warnings` config in [tool.pdm] table.
🔒 Lock successful
Changes are written to pyproject.toml.
Synchronizing working set with resolved packages: 2 to add, 0 to update, 0 to remove
このブログの趣旨からはずれるが面白かったので残しておきます。
上から読んでいくと、numpy 1.25.0 以降は Python 3.9 以上が必要なので、project.requires-python = ">=3.8"
と設定していることが原因で、numpy 1.24.4 が選ばれたようです。
がこのあと、Python3.12 でインストールしようとして以下のエラーが出ます。これは Python 3.12 で distutils
が削除されたためです。Python の依存管理地味に難しいですね。
ERRORS:
add numpy failed:
Traceback (most recent call last):
File "/home/zztkm/.rye/py/[email protected]/install/lib/python3.12/concurrent/futures/thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/installers/synchronizers.py", line 286, in install_candidate
self.manager.install(can)
File "/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/installers/manager.py", line 34, in install
dist_info = installer(str(prepared.build()), self.environment, prepared.direct_url())
^^^^^^^^^^^^^^^^
File "/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/models/candidates.py", line 418, in build
self.wheel = Path(builder.build(build_dir, metadata_directory=self._metadata_dir))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pdm/builders/wheel.py", line 26, in build
requires = self._hook.get_requires_for_build_wheel(config_settings)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pyproject_hooks/_impl.py", line 166, in get_requires_for_build_wheel
return self._call_hook('get_requires_for_build_wheel', {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pyproject_hooks/_impl.py", line 321, in _call_hook
raise BackendUnavailable(data.get('traceback', ''))
pyproject_hooks._impl.BackendUnavailable: Traceback (most recent call last):
File "/home/zztkm/.rye/tools/pdm/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 77, in _build_backend
obj = import_module(mod_path)
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/zztkm/.rye/py/[email protected]/install/lib/python3.12/importlib/__init__.py", line 90, in import_module
return _bootstrap._gcd_import(name, package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 994, in exec_module
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "/tmp/pdm-build-env-3x4_vnfi-shared/lib/python3.12/site-packages/setuptools/__init__.py", line 10, in <module>
import distutils.core
ModuleNotFoundError: No module named 'distutils'
面倒くさとなったので、PDM は気が向いたらちゃんとまとめます。
まとめ
- アプリケーション開発者の立場でいうと、ロックファイルは Docker などを使ってデプロイしたいプラットフォームに合わせて生成するのが良さそう
- 場合によっては、CI システム側で pyproject.toml から lock ファイルを生成して pip install するのが良い場面もありそう
- requirements.txt のみで運用してる場合は例外
- 厳密な依存管理は大変なので、適度に柔軟にやっていくのが良さそう
- lock ファイルはプラットフォーム依存になりがちなので、その辺りを意識して運用するのが良さそう(大事なこと)
気が向いたらちょこちょこふかぼります。