- uv は高速さ、Python バージョン管理、単一バイナリへの統合が強みだが、保守フェーズのパッケージ管理 UX はまだ粗い
- 古いパッケージは
uv pip list --outdated で確認できるが、トップレベルのコマンドではなく pip 互換名前空間の下にあるため発見しにくい
uv add pydantic はデフォルトで pydantic>=2.13.4 のような 上限なしの制約を追加し、メジャーバージョンの上昇まで許容してしまう
- 一括アップグレードは
uv lock --upgrade で行い、複数の特定パッケージには --upgrade-package の繰り返しが必要で、コマンドが冗長になる
add-bounds = "major" 設定でより安全なデフォルト制約を与えられるが、プレビュー機能であり、アプリケーションにはもっと直感的な更新 UX が必要だ
uvの強みと保守フェーズでの不便さ
- Astral の uv は 高速さ、Python バージョン管理、複数ツールを単一バイナリで置き換えられる点で強みを持つ
- 新しい Python プロジェクトを始めて最初の依存関係を追加する流れは簡単だが、プロジェクトが 保守フェーズに入ると、古いパッケージの確認や定期アップグレードの UX は pnpm や Poetry より粗く感じられる
- 主な不便さは、古いパッケージ確認コマンドの発見しやすさ、デフォルトのバージョン制約に上限がないこと、アップグレードコマンドの冗長さにある
古いパッケージの確認
- JavaScript プロジェクトでは
pnpm outdated で古いパッケージ、現在のバージョン、最新バージョン、制約上許可されるバージョンを簡潔に確認できる
- uv にはトップレベルの
uv outdated コマンドがなく、当初は次のコマンドが代替として使われていた
$ uv tree --outdated --depth 1
uv tree --outdated --depth 1 は古い項目だけを絞り込むのではなく、トップレベル依存関係ツリー全体を出力したうえで、更新可能な項目の横に小さな注釈を付ける
- 依存関係が 50 個あり、古いパッケージが 2 個しかなくても、50 行の一覧を見て回る必要がある
- Poetry の
poetry show --outdated もコマンド名はあまり直感的ではないが、実際の出力は 古いパッケージだけを表示する
デフォルトのバージョン制約の危険性
-
pnpm と Poetry のデフォルト方式
pnpm add は package.json に ^1.23.4 のような キャレット要件を書き込む
^1.23.4 は 1.x.x バージョンを許可するが、2.0.0 には更新しない
- Poetry もデフォルトで
>=1.23.4,<2.0.0 のような形式を使い、表記はやや読みにくいが効果は同じだ
- この 2 つのツールでは、パッケージが SemVer を守るという前提のもと、
pnpm update や poetry update を実行しても 主要 API の変更でビルドが壊れる可能性を減らせる
-
uv のデフォルト方式
uv add pydantic は pyproject.toml に次のように上限のない制約を追加する
dependencies = [
"pydantic>=2.13.4",
]
- この制約では pydantic 2、3、100 の各バージョンがすべて許可される
- 大量更新を実行すると、バグ修正だけを取り込むのではなく、依存関係グラフのすべてのメンテナーが配布した 互換性を壊す変更まで受け入れることになる
- 特にアプリケーション保守では安定性のリスクにつながりうる
アップグレードコマンドのUX
- pnpm と Poetry では、一括更新はそれぞれ次のように簡単だ
$ pnpm update
$ poetry update
$ uv lock --upgrade
uv lock --upgrade は uv update や uv upgrade ではなく、lock コマンドのオプションとして動作するため、人が使うパッケージ管理コマンドとしては直感的でない
- 上限のない制約と組み合わさると、
uv lock --upgrade は lockfile 内のすべてのパッケージを 完全に最新バージョンへ引き上げる選択になる
- この更新には、自分が直接認識していない深くネストした依存関係も含まれうる
- 特定のパッケージだけ更新したい場合、pnpm では次のようにパッケージ名を並べればよい
$ pnpm update pydantic httpx uvicorn
- uv では各パッケージごとに
--upgrade-package フラグを繰り返す必要がある
$ uv lock --upgrade-package pydantic --upgrade-package httpx --upgrade-package uvicorn
- 複数パッケージを一度に上げるとき、この フラグの繰り返しが大きな煩わしさになる
--bounds フラグと設定
- uv には最近
uv add 用の --bounds オプションが追加された
$ uv add pydantic --bounds major
- このコマンドは、より安全な制約である
pydantic>=2.13.4,<3.0.0 を生成する
--bounds major は現在 プレビュー機能であり、コマンドごとに明示的に指定するオプトイン機能だ
- その後、
pyproject.toml にデフォルト値を一度だけ設定できることが分かった
[tool.uv]
add-bounds = "major"
- この設定を使えば、毎回
--bounds major を入力しなくても、その後の uv add でより妥当なデフォルト値を得られる
- アプリケーションではこの動作がデフォルトであるほうが望ましいが、実際の使い勝手は最初に描写されたほど悪くはない
アプリケーションとライブラリの違い
- Python パッケージングの標準的な助言では、PyPI に配布される ライブラリは上限を固定しないほうがよく、この助言には妥当性がある
- すべてのライブラリが上限を固定すると、下流利用者の依存関係ツリーが解決できなくなる可能性がある
- 一方で アプリケーションは依存関係グラフの末端ノードであり、他の利用者がその制約を基準に解決することはない
- アプリケーションでは上限を設けるコストはなく、予期しないメジャーバージョン上昇から保護される
- ここでの範囲は、Web サイト、サービス、社内ツールのようなアプリケーション保守であり、ライブラリ配布には上限なしのデフォルトが合理的かもしれない
訂正された点と残る問題
uv pip list --outdated を使えば、古いパッケージだけを絞り込んで確認できる
$ uv pip list --outdated
- これにより、
uv tree --outdated --depth 1 の うるさい出力に対する批判は弱まる
- 残る問題は、この機能がトップレベルコマンドではなく
pip 互換名前空間の下にあるため、発見しやすさが低いことだ
add-bounds = "major" 設定でデフォルトの bounds を指定できるため、上限を毎回手で編集するかリスクを受け入れるかの二者択一だという見方は正しくない
- それでもこの機能はプレビュー段階であり、アプリケーションのパッケージ管理には、より安全なデフォルト制約とより直感的な更新コマンドが必要だ
望まれる改善の方向性
- 古いパッケージだけを明確に表示する 専用の
uv outdated コマンドが必要だ
- 複数パッケージを更新するときにフラグを繰り返さなくてよい、より人間工学的な
update コマンドが必要だ
- デフォルトのバージョン制約は、セマンティックバージョニング(SemVer)の安定性に対する期待をよりよく反映すべきだ
- 現状では、lockfile の変更の各行を疑いながら確認しなければならない負担が残る
1件のコメント
Hacker Newsのコメント
uv addのデフォルトのバージョン範囲は永続設定として指定できるので、毎回渡す必要はありません参考: https://docs.astral.sh/uv/reference/settings/#add-bounds
デフォルトで上限を入れない理由は、エコシステムに不要な衝突を多く生むからで、Poetry を使っていた頃に関連資料を集めたこともあります: https://github.com/zanieb/poetry-relax#references
Web プロジェクトで依存関係を使う場合は、依存先が SemVer を守る前提で、破壊的変更を防ぐために上限があってほしいです
防御的な上限を入れないよう促し、エコシステムの活発で大きな部分を各リリースごとに継続的にビルドして実際の互換性問題を見つけ、所有者に自動通知を送り、次の「LTS」リリースに残るための明確なスケジュールを提示していました
今では Cabal ソルバーだけでもかなり安定しているようですが、広範なナイトリービルドと可視化された失敗・ブロックは、エコシステムを解決可能な状態に保つのに役立っていたはずです
add-bounds設定は今知りました。正確な依存関係の固定は重要ですが、経験の浅い開発者が見落としやすいプロジェクト、特にライブラリではない最終製品には有用です--native-tlsフラグを永続的に設定する方法があるのか気になります職場の Zscaler 設定のせいで、このフラグなしでは UV は常に失敗します
また、特定アーキテクチャ向けの互換 Python バージョンを指定する機能をサポートする予定があるのかも気になります。社内で保守しているパッケージの 1 つは 32 ビット Python を使う必要があるため、毎回
--python /path/to/32bitを渡さなければなりませんpyproject.tomlのexclude-newerを尊重させる方法があるのか気になりますuv runを実行すると、pyproject.tomlからexclude-newerが削除されます毎回
uv run —-frozenやuv run --exclude-newerを実行することもできますが、それが正しい流れには思えず、見落としている慣用的なやり方があるのか知りたいですuvは単一の解決結果を必要とするため、上限を設けないことは意図的な設計ですnpm ではツリーの異なる部分に別々の解決結果をインストールできますが、Python ではそれは選択肢ではありません。Rye でも同じ判断をする必要があり、これにはより良い解法はありません
上限を入れると、実際にはそれ以上解決できないツリーが生まれることがあります。Python の一部パッケージエコシステムでは、過去に誤った上限を前提に公開された古いパッケージ向けの override まで配布していました
今日の時点では、まだリリースされていないパッケージと自分のパッケージが互換になるかどうかは分かりません
uvから受け取り、必要なら override できる方が良いと思います実行時に追跡しにくいバージョン非互換エラーに遭遇するよりましです
pyproject.tomlに上限がないこと自体が本当の問題ではありません本当の問題は
uv lock —-upgradeが上限のないものをすべて一括アップグレードする点ですメジャーバージョンを上げずにパッケージをアップグレードする方法があれば、このコマンドはずっと安全になるはずです
uv 以前と比べれば大幅に良くなりましたが、エコシステム全体がある程度の破壊的変更を受け入れない限り、完全に良くするのは難しそうです。Python 2 から 3 への移行を考えると、しばらくはそうした変化への意欲も大きくなさそうです
—-boundフラグは役立ちますが、入力して覚えるものが 1 つ増えますuv がそのプロジェクトがライブラリではないと分かるなら、デフォルトで上限を付ける方式もあり得るのではと思います
=に変えて、メジャー以外の更新でも壊れないと信じず、手動でのみ更新することです本番運用中のアプリにPython 依存関係が 257 個あり、そのうち半分以上が直接依存です
pyproject.tomlには上限を設けず、2 週間ごとに GitHub Actions でuv lock --upgradeを実行していますテストカバレッジが高いので壊れればテストが失敗し、AI 補助レビューのフローもあります。アップグレード PR が作成されると、AI ワークフローが Python スクリプトでメジャー・マイナーバージョン更新を列挙し、変更ログを探してリンクと要約を付け、コードベース内でそのパッケージをどう使っているかに基づいて各パッケージのリスクを分析します
概ね苦痛はほとんどなく、パッケージを 1 つずつ上げたり、古いパッケージを確認したり、放置された依存関係に対処したりする必要はありません。依存関係作者の修正が必要で、コード側では解決できないケースは非常にまれで、おおよそ年 1 回程度です。過去 3 か月ではメジャーバージョン上昇が 18 件あり、そのうちコード変更が必要だったのは 1 件だけでした
フロントエンドでもこうしたいですが、安全に回せるほどテストが十分ではありません。バックエンドのテストは書きやすく、より重要なので、すべてのコードベースにあるべきだと思います。テストがあれば、全部自動アップグレードで構いません
少なくとも自然言語の指示を正確なテストに変換するのは非常に得意です
しばらくテストを手で直接書いておらず、以前はいつも不満だった作業ですが、今はそうではありません
UV は Python に多くのものをもたらしましたが、今日はかなり格闘しました
複数のリポジトリに散らばり、時間とともに実装がばらばらになったスクリプトを中央管理しようとしていました
考えていたやり方は
uv run --with $package main --helpで、自動的に 1) なければインストールして実行、2) 最新ならインストールしない、3) 最新でなければ更新、を期待していましたしかし、この 3 つすべてが思ったより厄介でした。基本的に
uv runは毎回再インストールし、仮想環境とインストールに 6 秒かかりましたuvxやuv toolもあまり良くなく、今度はユーザーがアップグレードを受け取れないという新しい問題が生じました結局、スクリプトから CodeArtifact にページネーション付き GET を投げて、より新しい非開発版があれば更新して再実行するようにしました。動作はしますし、6 秒より 200ms の遅延の方がましですが、望んでいた体験ではありませんでした
uv run --with $package main --helpは、その挙動をごく少ないオーバーヘッドで実現するはずなので、少し混乱します毎回再インストールされることはなく、
--with環境はキャッシュに保存されて維持されます。環境自体がキャッシュされていなくても依存関係はキャッシュされており、キャッシュからのインストールは非常に高速です。確かに 200ms 未満であるべきです再現例を詳しく挙げてもらえれば確認できます
uv tool installとuv tool upgradeが合っていそうですただ、この種の小さな不便さは比較的簡単に解決できそうなので、issue を立ててもらえると良いです
uv run mainだけを実行することもできますそうすれば必要な依存関係を自動でインストール・キャッシュして実行します: https://docs.astral.sh/uv/guides/scripts/
uv tool upgradeを実行すればよいのではないでしょうかまったく同じユースケースかは分かりませんが、polyrepo マイクロサービスのエコシステムを同期するには非常に良かったです
古い依存関係の一覧を見るのに
"uv tree --outdated --depth 1"が勧められていたのはかなり意外でした個人的には導入以来ずっと
"uv pip list --outdated"を使ってきましたただ、このくらい重要なコマンドなら独立したトップレベルのサブコマンドを持ってもよいという点には同意します
"uv pip list --outdated"の方がずっと良い出力になるのは確かで、ありがとうございますただ、古いパッケージを見る方法が 2 つもあって、出力が大きく異なるという点が UX がひどいと感じる理由でもあります
"uv tree -od1"でもたぶん動くはずですただ、pacman のようなパッケージマネージャへの批判も、apt のように頻繁に使うコマンドには人間に分かりやすいコマンドを提供すべきだという点でした
タイトルの「ひどい」という表現に比べると、例は引数をいくつか余分に書かなければならない程度です
より良いタイトルはUV にほしい QoL 改善くらいが適切に思えます
フィードバック自体は有用で大半に同意しますが、そういう文句はフィードバックの価値を下げ、防御的な反応を招きます
uv のコマンドラインインターフェースが個人的にも煩雑なのは事実ですが、なぜこう書かれているのかは理解できます
uvは素晴らしいですが、現在の Python パッケージングで最大の問題は依然として科学・機械学習パッケージングをきちんと扱うことですPyTorch を入れたいなら、まずどのバージョンか、CUDA かどうかを考えなければなりません。CUDA なら CUDA バージョンごとに 6 種類あり、ホイールも PyPI に載せるには大きすぎるため直接取得する必要があります
Conda はこの問題を部分的にしか解決しません。Spack は非常に構成可能で、必要な C/C++/Fortran 依存関係やコンパイラツールチェーンを揃えられるので最高性能を引き出すには向いていますが、uv などとはうまく統合されません。そのため、研究者が作った実験的な機械学習プロジェクトを本番まで持っていくのが難しいのです
結局また最初に述べた状況に戻ってしまいます
有用なフィードバックは多いですが、クリックベイト的な表現が混ざっています
pnpm outdatedについては、これまであまり頻繁には話題になっていませんでしたが、合理的な要望に見えます。Python と JavaScript の文化の違いから来ているようです。Python 依存関係が脆弱であるとか壊れているとかでない限り、古いかどうかを気にしたことはほとんどありませんが、JavaScript エコシステムでは機会があればアップグレードするのがかなり一般的なようです。これが悪いという意味ではなく、大きなプログラミングコミュニティ同士で、コマンドラインインターフェースに何を見せるべきかの直感がどれほど違うかを示していますArmin が言うように、uv の上限挙動は意図的であり、Python の解決方式上、機能的に必要な部分です。これは Python が他言語と比べて選んだトレードオフですが、依存ツリーに各依存関係が 1 つずつしかなく、相互に絡むすべての要件がそこへ向けて解決されるという点で、良いトレードオフだと思います
uv lock --upgradeがそうなっている理由は、ユーザー自身の要件ではなくロックファイルをアップグレードするからです。一方pnpm updateはpackage.jsonのユーザー要件を更新するもののようです。混乱はあり得ますが、uv lockの下に置く方が正確です。そうでないとuv upgradeが自分の思っているアップグレードをしてくれないと混乱するユーザーが出るでしょう。それでも、より分かりやすく見せる余地はありますし、要件自体を直接アップグレードする uv サブコマンドへの需要が明確にありましたhttps://news.ycombinator.com/item?id=48230048
uv lockコマンドがロックファイルだけを扱うのは理にかなっていますが、ユーザーには直接・推移依存関係をアップグレードしたいという現実的なニーズがあります。推移依存関係はuv lock --upgrade-packageで可能ですが、少し冗長です。直接依存にも動きますが、pyproject.tomlには触れず、このファイルの方が開発者にはずっと目に入りやすいですuv.lockのパッケージバージョンがpyproject.tomlより先行すると、pyproject.tomlは依存関係の表面積を把握するための案内として信頼しにくくなります。親切なuv upgradeコマンドがあると良いですこれまで見た uv UX 最大の落とし穴は
uv pipです。多くのプロジェクトが開発ではpyproject.tomlとuv.lockで uv を正しく使っている一方、デプロイ用 Dockerfile や CI ツールではuv pip install -r pyproject.tomlを使ってuv.lockを迂回していますコーディングエージェントが学習データ中の
pipの多さのせいで悪いuv pipパターンを勧めてしまうのも問題ですが、uv 側もユーザーを保護する仕組みを提供すべきですuv は素晴らしいツールで、もっと広く使われるべきだと思います: https://aleyan.com/blog/2026-why-arent-we-uv-yet
poetry updateもロックファイルを更新します。uv CLI の構成は作業するにはかなり煩わしいと思います。正確さと機械のために設計されていて、ユーザーフレンドリーさのために設計されている感じではありませんuv pip list --outdatedに戻ることになりますuv upgradeはロードマップにありますまだ実装されていない理由は、優れた体験にするのが難しく、人々が想像するよりもはるかにニュアンスが多く、チームは小さく優先事項も多いからです
pnpm outdated系の機能の用途の 1 つは、"uv sync --update"や"uv lock --update"を実行したときに何が更新されるかを見ることですただし、そのコマンドに確認プロンプトを付ける方が良いかもしれません
Pixi は uv をバックエンドに使っており、古いパッケージを見やすく列挙するタスクエイリアスを簡単に追加できるので、UI が気に入りました
特にPixi-diff-to-markdown は、自動 CI パッケージ更新をざっと確認しやすくしてくれます
古いパッケージのうち更新される項目を見たいなら、プロジェクトに次のようなタスクエイリアスを作れます
pixi task add outdated "pixi update --dry-run --json | pixi exec pixi-diff-to-markdown"そしてプロジェクトで
pixi run outdatedを実行すればよいです出力は、更新されるパッケージ、現在のバージョン、
pixi updateコマンドでインストールされる新しいバージョンを含む、読みやすい Markdown テーブルです。もちろん好みや状況次第ではあります最近
envスクリプトがパスに現れて、普通の UNIXenvコマンドの利用を邪魔していました調べてみると、uv インストーラーが以下のコマンド実行時にこれを作っていました
curl -LsSf [https://astral.sh/uv/install.sh](<https://astral.sh/uv/install.sh>) | sh$HOME/.cargo/bin/にインストールし、$HOME/.cargo/envにシェルスクリプトを作って、$HOME/.cargo/bin/がPATHにない場合は先頭に追加する仕組みですこういう形で基本的な UNIX コマンドを上書きするコードを、いったいどんなプログラマが書くのか理解しがたいです