rcmdnk's blog

20241108_uv_200_200

Pythonのプロジェクト管理ツールをPoetryからuvに色々移行中。

uv

uvはRust製のPythonのプロジェクト管理ツールです。

もともとuvはpipのようなパッケージインストーラーとしての機能や pyenvのようなPython自体を管理する機能を持っているものとして開発されていた パッケージ管理ツールです。

一方、RyeというこれもRust製のプロジェクト管理ツールが ちょっと前にPoetryの対抗馬ようなツールとして出てきました。

uvが出た所でRyeでuvがpipの代わりとして使えるようになったりしていましたが、 Ryeの機能をuvに統合するような形に進み、現在はuv自体がプロジェクト管理も出来るツールになっています1

そんな感じですが、Ryeをちゃんと使ってみたいなと思って長い事放置していたのですが、 uvのパッケージインストールなどの速度が明らかにpipと比べると速く、 lockファイルのアップデートなどもpoetryに比べて圧倒的に速いとのことなので そろそろ本格的に使ってみようかな、と。

すでに一般的な使い方などはいろいろな人が書いていますが、 ここではPoetryからの移項に関しての自分的なメモを書いておきます。

pyproject.toml

Poetryもuvもpyproject.tomlを使ってプロジェクトの設定を行います。

以下は主に移行するときに必要な違いについて。

プロジェクト情報セクション

Poetryはtool.poetryというセクションにプロジェクトの情報を書いて tool.poetry.dependenciesというセクションに依存関係を書きます。 Pythonのバージョン指定もtool.poetry.dependenciesで描きます。

The pyproject.toml file Documentation Poetry - Python dependency management and packaging made easy

uvでは最初にprojectというセクションでプロジェクトの情報に加えて Pythonのバージョンや依存関係の情報も requires-python, dependenciesというキーで書きます。

また、homepage, repositoryといったURL情報はproject.urlsセクションに書きます。

これは Python Packaging User Guide (PEP 621) に従う形式です。

情報セクション内のauthorsの書き方

authorsの書き方がちょっと違います。

  • Poetry: authors = ["rcmdnk <[email protected]>"]のようにuser <email>という形式で文字列のリストとして書く。
  • uv: authors = [ { name = "rcmdnk", email = "[email protected]" } ]のようにテーブルのリストとして書く。

といった違いがあります。

project.urlsセクションの書き方

Poetryではtool.poetryセクション内にhomepage, repository, documentationのURL情報を書きます。 それら以外はtool.poetry.urlsセクションに書くようになっています。

一方、uvではproject.urlsセクションにすべてのこれらのURL情報を書きます。

1
2
3
4
5
[project.urls]
Homepage = "https://github.com/rcmdnk/homebrew-file"
Documentation = "https://homebrew-file.readthedocs.io"
Repository = "https://github.com/rcmdnk/homebrew-file"
Issue = "https://github.com/rcmdnk/homebrew-file/issues"

これらはPyPIに公開する場合にはPyPIのページのProject URLsに表示されます。 poetryの場合、homepageなどキーが小文字で書かれていても、ここではHomepageのように大文字開始で表示されますが、 uvの場合はそのままのキーで表示されるようです。

なので上の様な感じで大文字開始で書いておいた方が良いかと。

パッケージバージョンの指定方法

Poetryでは tool.poetry.dependenciesセクションなどに

1
2
[tool.poetry.dependencies]
gitpython = "^3.1.41"

といった形でパッケージごとに指定を行います。

バージョンの指定は他の言語でもよくあるようなキャレットなどを使って^3.1.41 (= >=3.1.41, <4.0.0)のような指定も可能です2

また、条件指定やextraの指定があるさいには

1
2
tomli = { version = "^2.0.1", python = "<3.11"}
pyproject-pre-commit = { version = "^0.3.0", extras = ["ruff"]}

といった形でテーブル形式で渡します。

一方、uvではprojectセクションのdependenciesというキーにリストで書きます。

1
2
3
4
5
6
[project]
dependencies = [
  "gitpython >=3.1.41, <4.0.0",
  "tomli >= 2.0.1; python_version < '3.11'",
  "pyproject-pre-commit[ruff] >= 0.3.0, < 0.4.0",
]

バージョンの指定は Dependency specifiers - Python Packaging User Guide に従う形で^などは使えません。 条件指定は;の後に指定。 extraの指定はpipでインストールするときと同じように後ろに括弧付きで指定します。

開発用パッケージの指定

開発用パッケージはPoetryではtool.poetry.group.dev.dependenciesのセクションに書きます。 書き方はtool.poetry.dependenciesと同じです。

uvではdependency-groupsというセクションに、devというグループを作って書きます。

1
2
3
4
[dependency-groups]
dev = [
  "pytest >= 8.0.0, < 9.0.0",
]

同様にたのグループ化された依存関係を書く場合、 uvではdependency-groupsセクションに別の名前のキーでリストを書くことで グループを作ることが出来ます。

スクリプトの指定

実行コマンドがある場合、Poetryではtool.poetry.scriptsセクションに書きます。

1
2
[tool.poetry.scripts]
mycmd = "myproject:main"

一方、uvではproject.scriptsセクションに書きます。

1
2
[project.scripts]
mycmd = "myproject:main"

指定方法は同じ。

ビルドシステムの指定

Poetryでは自前のpoetry-coreを使います。

1
2
3
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

一方、uvでは通常hatchlingを使います 3

1
2
3
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

書き換え例

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[tool.poetry]
name = "myproject"
version = "0.1.0"
description = ""
authors = ["rcmdnk <[email protected]>"]
readme = "README.md"
license = "apache-2.0"

[tool.poetry.dependencies]
python = ">=3.10,<3.14"
gitpython = "^3.1.41"

[tool.poetry.scripts]
mycmd = "myproject:main"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
tomli = { version = "^2.0.1", python = "<3.11"}
pyproject-pre-commit = { version = "^0.3.0", extras = ["ruff"]}

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

上記のPoetry用のpyproject.tomlと同じ内容をuv用に書き換えると以下のようになります。

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[project]
name = "myproject"
version = "0.1.0"
description = ""
authors = [ { name = "rcmdnk", email = "[email protected]" } ]
readme = "README.md"
license = "apache-2.0"
requires-python = ">=3.10,<3.14"
dependencies = [
    "gitpython >= 3.1.41, < 4.0.0",
]

[project.scripts]
mycmd = "myproject:main"

[dependency-groups]
dev = [
  "pytest >= 8.0.0, < 9.0.0",
  "tomli >= 2.0.1; python_version < '3.11'",
  "pyproject-pre-commit[ruff] >= 0.3.0, < 0.4.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

コマンド比較

よく使うコマンドのざっくり比較(微妙に正確でない部分もありますが)。

動作 Poetry uv
プロジェクト仮想環境下でのコマンド実行 poetry run <command> uv run <command>
プロジェクト仮想環境に入る poetry shell source .venv/bin/activate (プロジェクトのルートディレクトリにいるとして)
パッケージ追加 poetry add <package> uv add <package>
パッケージ削除 poetry remove <package> uv remove <package>
依存パッケージのインストール poetry install uv sync
依存パッケージのアップデート poetry update uv lock --upgrade
仮想環境のPythonバージョン変更 poetry env use <version> uv python pin <version>
パッケージビルド poetry build uv build
PyPIなどへの公開 poetry publish uv publish dist/<file>

publishの際の違い

PyPIなどへパッケージを公開する際、publishコマンドを使いますが、 Poetryだとpoetry publish --buildでビルドしてから公開することが可能ですが、 uvではuv buildを先に実行しておく必要があります。

また、poetry publishでは送られるファイルは現在のバージョンのもののみですが、 uv publishでは何も指定しないとdistディレクトリ内の全てのファイルが送られます。 したがってuv publish dist/<file>のように送りたいファイルを指定する必要があります。

古いファイルがある場合でもすでに送られたものであれば全部送ろうとしてもそれほど問題ないですが、 同じバージョン名で違うファイルを作ってしまった場合にはエラーになります。

公開に必要なトークンは、Poetryでは

1
poetry config pypi-token.pypi "<token>"

で登録でき、macOSではキーチェーンで安全に管理できます。

uvではそのような機能はないので、環境変数にトークンをセットしておく必要があります。

1
export UV_PUBLISH_TOKEN="<token>"

もしくは直接

1
uv publish --token "<token>"

Poetryにはtokenという引数はありませんが、ユーザー名に__token__を使うことでトークンを使うことが出来ます。

1
poetry publish --username __token__ --password "<token>"

uvでも--user __token__ --password "<token>"という形でトークンを使うことも出来ます。

開発仮想環境のPythonバージョン指定

Poetryではpoetry env

1
poetry env use 3.12.2

などとして開発環境のPythonバージョンを指定することが出来ます。

uvではuv python

1
uv python pin 3.12.2

のように指定します。この際、.python-versionというファイルが作成され中にバージョンが書かれています。 これを書き換えることで開発環境のPythonバージョンを変更することも出来ます。

uvの利点として、uv自体がPythonの管理を行えるため、現環境にないバージョンのPythonを指定することも可能です

1
warning: No interpreter found for Python 3.10 in managed installations or search path

といった警告が出ますが、uv syncすると指定のバージョンのPythonもuvの管理下にインストールされ使えるようになります。

Python用開発ツールのアップデート

自分で作ってるPython用のツールもuvを使えるようにアップデート。

python-templateはPythonのプロジェクトを開始する際に使うGitHubレポジトリのテンプレートです。

GitHub上でテンプレートとして使用して新しいレポジトリを作り、 そのレポジトリでsetup.shを実行してプロジェクトを開始することが出来ます。

この際、project.tomlなどを必要な状態に書き換えますが、 PROJECT_MANAGERuvとして指定するとuv用の設定になります。 poetryにすればPoetry用の設定になります。

Pythonのプロジェクトでpytestなどを実行するためのGitHub Actionです。

setup-typeとして、poetry, uv, pipを指定でき、必要なツールでのセットアップを行えるようにしました。

開発仮想環境の切り替え

現状唯一Poetryで出来てuvで出来ないと思われるもの(少なくとも自分が使う範囲では)。

Poetryではデフォルトで仮想環境をプロジェクトのディレクトリとは別に作成します4。 この際、Pythonのバージョン毎(major.minorまで)に別の名前で仮想環境が作成されます。 これにより、別のPython 3.11と3.12の環境を別途作ることも出来、 poetry env use <version>で簡単に切り替えることが出来ます。

一方、uvでは仮想環境はプロジェクトのディレクトリ内の.venvディレクトリに作成されます。 このため、Pythonのバージョンを切り替えると新たな仮想環境が.venvに作り直されます。

これに関してはIssueが立っているようです。

Option/setting to manage virtualenvs centrally · Issue #1495 · astral-sh/uv

実行時間

上の書き換え例で手元の環境でやってみると、

Poetryだと7秒位、uvだと3秒位でインストールが終わりました。

また、一旦パッケージをインストールするとcacheが作られますが、 cacheを使った際にはuvは一瞬で終わります。

上の例では依存パッケージはかなり少ないほうだと思いますが、 数が増えるとより差が出てきます。

いずれにしろ色々やっているとPoetryよりも圧倒的に速く、 一度uvを使ったらPoetryの速度には戻れないかもしれません。

まとめ

実際にいくつかのプロジェクトで移行してみましたが、 uv sync, uv lock --upgradeなどの速度がPoetryに比べて圧倒的に速いです。

必要なPythonのバージョンを別途用意する必要がないのもかなり大きいところです。 Pythonのインストール自体も高速です。

現状Poetryでやっていたことで出来ないことはPythonバージョンの違う開発環境の切り替えくらいですが、 それもuv syncが圧倒的に高速なことで直接作り直しても大した時間がかからないのと、 それほど頻繁に行うことでもないので大きな問題にはなりません。

というわけで、今後は基本的にはuvを使っていこうと思います。

一部、Poetry(というかpip)とuvでベータ版などバージョンがある場合にパッケージ依存の解釈が異なる部分もあるみたいなのでそこだけちょっと注意が必要です。

Rust製のPythonパッケージ管理ツール「uv」を使ってみよう gihyo.jp

Sponsored Links
  1. Dependency specification Documentation Poetry - Python dependency management and packaging made easy

  2. https://docs.astral.sh/uv/concepts/projects/#libraries

  3. Poetryでもin-projectというオプションによってプロジェクト内に.venvを作って仮想環境を作ることも可能。 この場合はuvと同じようにPythonのバージョンを切り替えるたびに新しい仮想環境を作り直すことになります。

Sponsored Links

« shell-logger: シェルスクリプトのロガーツールにファイル出力機能を追加 Python開発環境でのblack, isort, flake8からruffへの移行 »

}