はじめに
絵羽模様😭😭😭 nikkieです。
uvで環境構築したPythonプロジェクトのDockerイメージの作り方を考えていきます。
今回はライブラリ編です
目次
uvのライブラリ
uv 0.4.0から、プロジェクトをアプリケーション(--app
)またはライブラリ(--lib
)と区別してuv init
できるようになりました
- アプリケーションは、配布を想定していないPythonプロジェクト
- ライブラリは、配布を想定しているPythonプロジェクト
- 他のPythonプロジェクトにインストールして、importできるということです
この記事では、ライブラリ用のDockerイメージを考えていきます。
ちなみに、アプリケーション用のDockerイメージの提案はこちらです。
さて、サンプルライブラリを用意しました(uv init --lib mylib --no-readme
1)
% uv --version uv 0.4.9 (Homebrew 2024-09-10)
mylib/ ├── .venv/ ├── .python-version ├── src/ │ └── mylib/ │ ├── __init__.py │ └── py.typed ├── pyproject.toml └── uv.lock
src/mylib/__main__.py
を追加しています
from the_solitary_castle_in_the_mirror import characters from mylib import hello def main(): print(hello()) print(characters) if __name__ == "__main__": main()
python -m
で、パッケージの__main__.py
を実行します。
ref: https://docs.python.org/ja/3/using/cmdline.html#cmdoption-m
% uv run python -m mylib Hello from mylib! ['kokoro', 'aki', 'fuka', 'rion', 'subaru', 'masamune', 'ureshino']
結論
⚠️以下のDockerfileは、uvの開発者の方やuvを愛用している方には、刺激が強すぎるかもしれません🙇♂️
% docker run --rm uv-practice-lib:0.1.0 Hello from mylib! ['kokoro', 'aki', 'fuka', 'rion', 'subaru', 'masamune', 'ureshino']
全容はこちらからどうぞ
思想の対立:私 vs uv
2者間で相容れない部分があり、今回は私がuvをねじ伏せた形となります💪
小さいサイズのDockerイメージを作りたい私
小さいは正義!
アプリケーション編でも示しましたが、uv自体はDockerイメージには不要という立場です。
uv run <command>
はpyproject.toml
に宣言した環境でコマンドを実行すると理解しています。
上記のuv run python -m mylib
は、pyproject.toml
に宣言した環境(.venv
)をuvが用意した上で、コマンドを実行しました。
ところで、ライブラリに必要な依存がインストールされている仮想環境のPython処理系を直接指定してもpython -m
を実行できます
% .venv/bin/python -m mylib Hello from mylib! ['kokoro', 'aki', 'fuka', 'rion', 'subaru', 'masamune', 'ureshino']
仮想環境の処理系を直接指定できるという見方をしているので、Dockerイメージには以下だけを含めるのが自然に思えてきました。
「ただしゆーゔい、テメーはダメだ」(この見方ではuvは過剰なんです)
uvはライブラリ自体をeditable installのみ
uvのライブラリ自体のインストールなのですが、editable installのみのようでした2。
mylibの行です
% uv pip list Package Version Editable project location ------------ ------- -------------------------------------- kojo-fan-art 0.1.1 mylib 0.1.0 /.../uv-practice/mylib
uvがPythonプロジェクト自体をインストールするか(私の理解)
ref: https://docs.astral.sh/uv/concepts/projects/#build-systems
- プロジェクトがアプリケーションのとき
pyproject.toml
に[build-system]
テーブルなし- -> プロジェクト自体をインストールしない
- プロジェクトがライブラリのとき
pyproject.toml
に[build-system]
テーブルあり- -> プロジェクト自体のeditable installのみサポート
If a build system is defined, uv will build and install the project into the project environment. Projects are installed in editable mode so changes to the source code are reflected immediately, without reinstallation.
(参照したドキュメントにあるのですが、tool.uv.package = true
のとき、editable installなのか、コードをコピーするインストールなのかは未検証です)
editable installのみだと、私の求める小さいサイズのDockerイメージには少しだけ届きません。
開発中のライブラリだからといって特別扱いせずに、Dockerイメージの中には他のライブラリ同様、editableではない(=ソースコードを配置する)インストールをしたいのです!
uvは管理しているライブラリのeditableではないインストールを(今はまだ)サポートしていないようなので、無理やりpipにやってもらいました(この筋の通し方、サイコパスみがあります。もう完全にuvに喧嘩を売りました3)
.venv/bin/python -m ensurepip .venv/bin/python -m pip install --no-deps .
- uvが管理する仮想環境(
.venv
)にはPython標準のパッケージ管理ツールpipが含まれていません- そこでensurepipでpipを用意(uv開発者は卒倒していそうですね。見逃してちょ🙏)
pip install
は-e
を指定しなければ、editableではないインストールとなります!- これでライブラリ自体のソースコードをコピーしてインストールを果たせました
- 依存ライブラリはuvで先にsyncしておき、pipではインストールしない(
--no-deps
)
依存ライブラリのインストールではuv sync --no-install-project
で、プロジェクトの依存だけをインストールし、開発中のライブラリ自体(=プロジェクト)をインストールしませんでした。
https://docs.astral.sh/uv/reference/cli/#uv-sync
全てはpipでeditableでないインストールをするための布石です。
こうしてできあがったDockerイメージは、開発中のライブラリ自体もeditableでないインストールがされ、私が求めていたものは手に入りました🙌
% docker run --rm -it uv-practice-lib:0.1.0 bash root@a32c70ecb551:/# python -m pip list Package Version ------------ ------- kojo-fan-art 0.1.1 mylib 0.1.0 pip 24.2
思考ログ
- アプリケーション編からの差分として、uv-docker-exampleで知った環境変数は、ドキュメントからどうやら有用そうと分かったので指定しました
UV_COMPILE_BYTECODE
UV_LINK_MODE
uv pip install
には、仮想環境ではなくシステムにインストールするための--system
フラグがあります4- https://docs.astral.sh/uv/guides/integration/docker/#installing-a-package
- これに渡す
requirements.txt
をuv export
で作れる、かも(editableでないインストールができるかは要検証)
終わりに
uvで環境構築したPythonライブラリのDockerイメージについて、現在の考えをまとめました。
- マルチステージビルドで成果物イメージからuvは除く
- ライブラリ自体をeditableでないインストールするために、uvのDockerイメージ内でpipを使ってねじ伏せた
- 開発中のライブラリを含むすべての依存ライブラリをステージ間でコピーした
uvがライブラリをeditable installのみという振る舞いは経緯を知りたい気持ちはあります。
また、uvでeditableでないインストールもできるという情報をご存知の方は、(私としても和解したいので)ぜひお知らせください
-
↩uv 0.4.9から、uv init --libでプロジェクトをライブラリとして作ったときに、py.typed(空ファイル)も作るようになってる!(なお最新は0.4.10)https://t.co/04iFhnJmAD
— nikkie / にっきー 技書博 け-04 Python型ヒント本 (@ftnext) 2024年9月14日
GitHubちょっと探しましたが、なんでこうしたかという経緯は分からない感じ。
憶測:内部からだからプルリク無言で済んだ? - 他にドキュメントとしては https://docs.astral.sh/uv/concepts/dependencies/#editable-dependencies も見ています。「Editable installations solve this problem by adding a link to the project within the virtual environment (a .pth file), which instructs the interpreter to include the source files directly.」↩
- (バスターコール怖い。ガクブル)↩
- Dockerイメージに使っている例 ↩