2 ポイント 投稿者 GN⁺ 5 시간 전 | 1件のコメント | WhatsAppで共有
  • 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 addpackage.json^1.23.4 のような キャレット要件を書き込む
    • ^1.23.4 は 1.x.x バージョンを許可するが、2.0.0 には更新しない
    • Poetry もデフォルトで >=1.23.4,<2.0.0 のような形式を使い、表記はやや読みにくいが効果は同じだ
    • この 2 つのツールでは、パッケージが SemVer を守るという前提のもと、pnpm updatepoetry update を実行しても 主要 API の変更でビルドが壊れる可能性を減らせる
  • uv のデフォルト方式

    • uv add pydanticpyproject.toml に次のように上限のない制約を追加する
dependencies = [
    "pydantic>=2.13.4",
]
  • この制約では pydantic 2、3、100 の各バージョンがすべて許可される
  • 大量更新を実行すると、バグ修正だけを取り込むのではなく、依存関係グラフのすべてのメンテナーが配布した 互換性を壊す変更まで受け入れることになる
  • 特にアプリケーション保守では安定性のリスクにつながりうる

アップグレードコマンドのUX

  • pnpm と Poetry では、一括更新はそれぞれ次のように簡単だ
$ pnpm update
$ poetry update
  • uv では一括アップグレードに次のコマンドを使う
$ uv lock --upgrade
  • uv lock --upgradeuv updateuv 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件のコメント

 
GN⁺ 5 시간 전
Hacker Newsのコメント
  • uv add のデフォルトのバージョン範囲は永続設定として指定できるので、毎回渡す必要はありません
    参考: https://docs.astral.sh/uv/reference/settings/#add-bounds
    デフォルトで上限を入れない理由は、エコシステムに不要な衝突を多く生むからで、Poetry を使っていた頃に関連資料を集めたこともあります: https://github.com/zanieb/poetry-relax#references

    • ライブラリを配布するときに上限バージョンを外すことが重要なのは理解していますが、この記事はライブラリではなく Web サイトを作る立場から書かれています
      Web プロジェクトで依存関係を使う場合は、依存先が SemVer を守る前提で、破壊的変更を防ぐために上限があってほしいです
    • Haskell コミュニティもこの問題に何年も取り組んでいて、かつて最も成功したアプローチはStackageでした
      防御的な上限を入れないよう促し、エコシステムの活発で大きな部分を各リリースごとに継続的にビルドして実際の互換性問題を見つけ、所有者に自動通知を送り、次の「LTS」リリースに残るための明確なスケジュールを提示していました
      今では Cabal ソルバーだけでもかなり安定しているようですが、広範なナイトリービルドと可視化された失敗・ブロックは、エコシステムを解決可能な状態に保つのに役立っていたはずです
    • add-bounds 設定は今知りました。正確な依存関係の固定は重要ですが、経験の浅い開発者が見落としやすいプロジェクト、特にライブラリではない最終製品には有用です
    • --native-tls フラグを永続的に設定する方法があるのか気になります
      職場の Zscaler 設定のせいで、このフラグなしでは UV は常に失敗します
      また、特定アーキテクチャ向けの互換 Python バージョンを指定する機能をサポートする予定があるのかも気になります。社内で保守しているパッケージの 1 つは 32 ビット Python を使う必要があるため、毎回 --python /path/to/32bit を渡さなければなりません
    • 少しまとまりのない質問かもしれませんが、uv に pyproject.tomlexclude-newer を尊重させる方法があるのか気になります
      uv run を実行すると、pyproject.toml から exclude-newer が削除されます
      毎回 uv run —-frozenuv run --exclude-newer を実行することもできますが、それが正しい流れには思えず、見落としている慣用的なやり方があるのか知りたいです
  • uv は単一の解決結果を必要とするため、上限を設けないことは意図的な設計です
    npm ではツリーの異なる部分に別々の解決結果をインストールできますが、Python ではそれは選択肢ではありません。Rye でも同じ判断をする必要があり、これにはより良い解法はありません
    上限を入れると、実際にはそれ以上解決できないツリーが生まれることがあります。Python の一部パッケージエコシステムでは、過去に誤った上限を前提に公開された古いパッケージ向けの override まで配布していました
    今日の時点では、まだリリースされていないパッケージと自分のパッケージが互換になるかどうかは分かりません

    • 個人的には、更新時にパッケージ同士が互換でないというエラーを uv から受け取り、必要なら override できる方が良いと思います
      実行時に追跡しにくいバージョン非互換エラーに遭遇するよりましです
    • pyproject.toml に上限がないこと自体が本当の問題ではありません
      本当の問題は uv lock —-upgrade が上限のないものをすべて一括アップグレードする点です
      メジャーバージョンを上げずにパッケージをアップグレードする方法があれば、このコマンドはずっと安全になるはずです
    • uv は状況をかなり改善しましたが、根本的にはツールだけでは解決できない部分がかなりあると思います
      uv 以前と比べれば大幅に良くなりましたが、エコシステム全体がある程度の破壊的変更を受け入れない限り、完全に良くするのは難しそうです。Python 2 から 3 への移行を考えると、しばらくはそうした変化への意欲も大きくなさそうです
    • ライブラリ作者にはその通りですが、Web サイトを作って複数のパッケージに依存している場合は、アップグレード時の安全性のために上限がほしいです
      —-bound フラグは役立ちますが、入力して覚えるものが 1 つ増えます
      uv がそのプロジェクトがライブラリではないと分かるなら、デフォルトで上限を付ける方式もあり得るのではと思います
    • 実際のところ、uv でも npm でも正しい使い方は全部 = に変えて、メジャー以外の更新でも壊れないと信じず、手動でのみ更新することです
  • 本番運用中のアプリにPython 依存関係が 257 個あり、そのうち半分以上が直接依存です
    pyproject.toml には上限を設けず、2 週間ごとに GitHub Actions で uv lock --upgrade を実行しています
    テストカバレッジが高いので壊れればテストが失敗し、AI 補助レビューのフローもあります。アップグレード PR が作成されると、AI ワークフローが Python スクリプトでメジャー・マイナーバージョン更新を列挙し、変更ログを探してリンクと要約を付け、コードベース内でそのパッケージをどう使っているかに基づいて各パッケージのリスクを分析します
    概ね苦痛はほとんどなく、パッケージを 1 つずつ上げたり、古いパッケージを確認したり、放置された依存関係に対処したりする必要はありません。依存関係作者の修正が必要で、コード側では解決できないケースは非常にまれで、おおよそ年 1 回程度です。過去 3 か月ではメジャーバージョン上昇が 18 件あり、そのうちコード変更が必要だったのは 1 件だけでした
    フロントエンドでもこうしたいですが、安全に回せるほどテストが十分ではありません。バックエンドのテストは書きやすく、より重要なので、すべてのコードベースにあるべきだと思います。テストがあれば、全部自動アップグレードで構いません

    • テスト作成はAI エージェントが得意なことでもあります
      少なくとも自然言語の指示を正確なテストに変換するのは非常に得意です
      しばらくテストを手で直接書いておらず、以前はいつも不満だった作業ですが、今はそうではありません
  • UV は Python に多くのものをもたらしましたが、今日はかなり格闘しました
    複数のリポジトリに散らばり、時間とともに実装がばらばらになったスクリプトを中央管理しようとしていました
    考えていたやり方は uv run --with $package main --help で、自動的に 1) なければインストールして実行、2) 最新ならインストールしない、3) 最新でなければ更新、を期待していました
    しかし、この 3 つすべてが思ったより厄介でした。基本的に uv run は毎回再インストールし、仮想環境とインストールに 6 秒かかりました
    uvxuv tool もあまり良くなく、今度はユーザーがアップグレードを受け取れないという新しい問題が生じました
    結局、スクリプトから CodeArtifact にページネーション付き GET を投げて、より新しい非開発版があれば更新して再実行するようにしました。動作はしますし、6 秒より 200ms の遅延の方がましですが、望んでいた体験ではありませんでした

    • uv run --with $package main --help は、その挙動をごく少ないオーバーヘッドで実現するはずなので、少し混乱します
      毎回再インストールされることはなく、--with 環境はキャッシュに保存されて維持されます。環境自体がキャッシュされていなくても依存関係はキャッシュされており、キャッシュからのインストールは非常に高速です。確かに 200ms 未満であるべきです
      再現例を詳しく挙げてもらえれば確認できます
    • その用途なら uv tool installuv tool upgrade が合っていそうです
      ただ、この種の小さな不便さは比較的簡単に解決できそうなので、issue を立ててもらえると良いです
    • ファイル先頭のドキュメントブロックに必要な依存関係を定義してから uv run main だけを実行することもできます
      そうすれば必要な依存関係を自動でインストール・キャッシュして実行します: https://docs.astral.sh/uv/guides/scripts/
    • ユーザーが単に uv tool upgrade を実行すればよいのではないでしょうか
    • https://copier.readthedocs.io/en/stable/ も見てみる価値があります
      まったく同じユースケースかは分かりませんが、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 などとはうまく統合されません。そのため、研究者が作った実験的な機械学習プロジェクトを本番まで持っていくのが難しいのです

    • 以前は Anaconda で回避していましたが、付いてくる雑多なものが多すぎるうえ、開発環境が本番環境とまったく違ってしまうので、それもあまり良くありませんでした
      結局また最初に述べた状況に戻ってしまいます
  • 有用なフィードバックは多いですが、クリックベイト的な表現が混ざっています
    pnpm outdated については、これまであまり頻繁には話題になっていませんでしたが、合理的な要望に見えます。Python と JavaScript の文化の違いから来ているようです。Python 依存関係が脆弱であるとか壊れているとかでない限り、古いかどうかを気にしたことはほとんどありませんが、JavaScript エコシステムでは機会があればアップグレードするのがかなり一般的なようです。これが悪いという意味ではなく、大きなプログラミングコミュニティ同士で、コマンドラインインターフェースに何を見せるべきかの直感がどれほど違うかを示しています
    Armin が言うように、uv の上限挙動は意図的であり、Python の解決方式上、機能的に必要な部分です。これは Python が他言語と比べて選んだトレードオフですが、依存ツリーに各依存関係が 1 つずつしかなく、相互に絡むすべての要件がそこへ向けて解決されるという点で、良いトレードオフだと思います
    uv lock --upgrade がそうなっている理由は、ユーザー自身の要件ではなくロックファイルをアップグレードするからです。一方 pnpm updatepackage.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.tomluv.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 の構成は作業するにはかなり煩わしいと思います。正確さと機械のために設計されていて、ユーザーフレンドリーさのために設計されている感じではありません
    • pip から uv に移った立場としては、その情報が必要なときは 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 スクリプトがパスに現れて、普通の UNIX env コマンドの利用を邪魔していました
    調べてみると、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 コマンドを上書きするコードを、いったいどんなプログラマが書くのか理解しがたいです