解決
解決は、要件のリストを取得し、それらの要件を満たすパッケージバージョンのリストに変換するプロセスです。解決には、要求された要件が満たされ、要求されたパッケージの要件が互換性があることを確認しながら、パッケージの互換性のあるバージョンを再帰的に検索する必要があります。
依存関係
ほとんどのプロジェクトやパッケージには依存関係があります。依存関係とは、現在のパッケージが動作するために必要な他のパッケージのことです。パッケージは、その依存関係を requirements として定義します。これは、大まかに言えば、パッケージ名と許容されるバージョンの組み合わせです。現在のプロジェクトによって定義された依存関係は direct dependencies と呼ばれます。現在のプロジェクトの各依存関係によって追加された依存関係は indirect または transitive dependencies と呼ばれます。
Note
依存関係の詳細については、Python Packagingのドキュメントの依存関係指定子ページを参照してください。
基本的な例
解決プロセスを示すために、次の依存関係を考えてみましょう:
- プロジェクトは
foo
とbar
に依存しています。 foo
には1つのバージョン、1.0.0があります:foo 1.0.0
はlib>=1.0.0
に依存しています。
bar
には1つのバージョン、1.0.0があります:bar 1.0.0
はlib>=2.0.0
に依存しています。
lib
には2つのバージョン、1.0.0と2.0.0があります。どちらのバージョンも依存関係はありません。
この例では、 resolver はプロジェクトの要件を満たすパッケージのバージョンのセットを見つけなければなりません。 foo
と bar
のバージョンはそれぞれ 1 つしかないので、それらが使用されます。解決には推移的な依存関係も含める必要があるので、 lib
のバージョンを選択しなければなりません。 foo 1.0.0
は利用可能なすべての lib
のバージョンを許容しますが、 bar 1.0.0
は lib>=2.0.0
を必要とするので、 lib 2.0.0
を使用しなければなりません。
いくつかの解決では、有効な解決策が複数ある場合があります。次の依存関係を考えてみましょう:
- プロジェクトは
foo
とbar
に依存しています。 foo
には2つのバージョン、1.0.0と2.0.0があります:foo 1.0.0
には依存関係がありません。foo 2.0.0
はlib==2.0.0
に依存しています。
bar
には2つのバージョン、1.0.0と2.0.0があります:bar 1.0.0
には依存関係がありません。bar 2.0.0
はlib==1.0.0
に依存しています。
lib
には2つのバージョン、1.0.0と2.0.0があります。どちらのバージョンも依存関係はありません。
この例では、foo
および bar
のいずれかのバージョンを選択する必要があります。しかし、どのバージョンを選択するかを決定するためには、各 foo
と bar
のバージョンの依存関係を考慮する必要があります。foo 2.0.0
と bar 2.0.0
は、必要とする lib
のバージョンが競合するため一緒にインストールできません。そのため、リゾルバは foo 1.0.0
(および bar 2.0.0
)または bar 1.0.0
(および foo 1.0.0
)のいずれかを選択しなければなりません。どちらも有効な解決策であり、異なる解決アルゴリズムによってどちらかの結果が得られる可能性があります。
プラットフォームマーカー
マーカーは、依存関係がいつ使用されるべきかを示す条件式を要件に付加することを可能にします。例えば、 bar ; python_version < "3.9"
は bar
が Python 3.8 以前のバージョンでのみインストールされるべきことを示します。
マーカーは、現在の環境やプラットフォームに基づいてパッケージの依存関係を調整するために使用されます。例えば、マーカーを使用して、オペレーティングシステム、CPU アーキテクチャ、Python バージョン、Python 実装などに応じて依存関係を変更することができます。
Note
マーカーの詳細については、Python Packagingのドキュメントの環境マーカーセクションを参照してください。
マーカーは解決にとって重要です。なぜなら、その値が必要な依存関係を変更するからです。通常、Pythonパッケージリゾルバは、パッケージが現在のプラットフォームにインストールされているため、_現在の_プラットフォームのマーカーを使用してどの依存関係を使用するかを決定します。ただし、依存関係を_ロック_する場合、これは問題です。ロックファイルは、ロックファイルが作成されたのと同じプラットフォームを使用する開発者にのみ機能します。この問題を解決するために、プラットフォームに依存しない、または「ユニバーサル」リゾルバが存在します。
uvは、プラットフォーム固有およびユニバーサルの解決の両方をサポートしています。
ユニバーサル解決
uvのロックファイル(uv.lock
)はユニバーサル解決で作成され、プラットフォーム間で移植可能です。これにより、オペレーティングシステム、アーキテクチャ、Pythonバージョンに関係なく、プロジェクトに取り組むすべての人の依存関係がロックされます。uvロックファイルは、uv lock
、uv sync
、uv add
などのプロジェクトコマンドによって作成および変更されます。
ユニバーサル解決は、--universal
フラグを使用して、uvのpipインターフェース(つまり、uv pip compile
)でも利用できます。結果の要件ファイルには、各依存関係がどのプラットフォームに関連するかを示すマーカーが含まれます。
ユニバーサル解決中、異なるプラットフォームに異なるバージョンが必要な場合、パッケージは異なるバージョンやURLで複数回リストされることがあります。マーカーはどのバージョンが使用されるかを決定します。ユニバーサル解決は、すべてのマーカーの要件を考慮する必要があるため、プラットフォーム固有の解決よりも制約が多いことがよくあります。
ユニバーサル解決中には、最低限必要なPythonバージョンを指定する必要があります。プロジェクトコマンドは、pyproject.toml
の project.requires-python
から最低必要バージョンを読み取ります。uvのpipインターフェースを使用する場合は、--python-version
オプションで値を指定してください。そうしないと、現在のPythonバージョンが下限として扱われます。例えば、--universal --python-version 3.9
はPython 3.9以降のユニバーサル解決を実行します。
ユニバーサル解決中、すべての選択された依存関係のバージョンは、pyproject.toml
に宣言された requires-python
範囲の 全体 と互換性がなければなりません。例えば、プロジェクトの requires-python
が >=3.8
の場合、uvはPython 3.8の下限と互換性がないため、例えばPython 3.9以降に限定された依存関係のバージョンを許可しません。言い換えれば、プロジェクトの requires-python
はすべての依存関係の requires-python
のサブセットでなければなりません。
依存関係の requires-python
範囲を評価する際、uvは下限のみを考慮し、上限は完全に無視します。例えば、>=3.8, <4
は >=3.8
として扱われます。
プラットフォーム固有の解決
デフォルトでは、uvのpipインターフェース(つまり、uv pip compile
)は、pip-tools
のようにプラットフォーム固有の解決を生成します。uvのプロジェクトインターフェースでは、プラットフォーム固有の解決を使用する方法はありません。
uvは、--python-platform
および--python-version
オプションを使用して、特定の代替プラットフォームおよびPythonバージョンの解決もサポートしています。たとえば、macOSでPython 3.12を使用している場合、uv pip compile --python-platform linux --python-version 3.10 requirements.in
を使用して、Linux上のPython 3.10の解決を生成できます。ユニバーサル解決とは異なり、プラットフォーム固有の解決中に提供される--python-version
は、使用する正確なPythonバージョンです。下限ではありません。
Note
Pythonの環境マーカーは、現在のマシンに関する情報を--python-platform
引数で表現できるよりもはるかに多く公開します。たとえば、macOSのplatform_version
マーカーには、カーネルがビルドされた時刻が含まれており、理論的にはパッケージ要件にエンコードできます。uvのリゾルバは、ターゲット--python-platform
で実行される任意のマシンと互換性のある解決を生成するために最善の努力を行います。これはほとんどのユースケースに十分ですが、複雑なパッケージとプラットフォームの組み合わせでは忠実度が低下する可能性があります。
依存関係の優先順位
解決出力ファイル(つまり、uvロックファイル(uv.lock
)または要件出力ファイル(requirements.txt
))が存在する場合、uvはそこにリストされている依存関係のバージョンを_優先_します。同様に、仮想環境にパッケージをインストールする場合、uvは既にインストールされているバージョンが存在する場合、それを優先します。これにより、互換性のないバージョンが要求されるか、--upgrade
を明示的に要求されない限り、ロックされたバージョンやインストールされたバージョンは変更されません。
解決戦略
デフォルトでは、uvは各パッケージの最新バージョンを使用しようとします。たとえば、uv pip install flask>=2.0.0
はFlaskの最新バージョン、つまり3.0.0をインストールします。flask>=2.0.0
がプロジェクトの依存関係である場合、flask
3.0.0のみが使用されます。これは重要です。たとえば、テストを実行する際に、プロジェクトが実際にflask
2.0.0の下限と互換性があるかどうかを確認しないためです。
--resolution lowest
を使用すると、uvはすべての依存関係(直接および間接(推移的))の最も低い可能なバージョンをインストールします。代わりに、--resolution lowest-direct
を使用すると、すべての直接依存関係の最も低い互換バージョンを使用し、他のすべての依存関係の最新の互換バージョンを使用します。uvは常にビルド依存関係の最新バージョンを使用します。
たとえば、次のrequirements.in
ファイルがあるとします:
uv pip compile requirements.in
を実行すると、次のrequirements.txt
ファイルが生成されます:
# このファイルは、次のコマンドを使用してuvによって自動生成されました:
# uv pip compile requirements.in
blinker==1.7.0
# flask経由
click==8.1.7
# flask経由
flask==3.0.0
itsdangerous==2.1.2
# flask経由
jinja2==3.1.2
# flask経由
markupsafe==2.1.3
# jinja2経由
# werkzeug経由
werkzeug==3.0.1
# flask経由
ただし、uv pip compile --resolution lowest requirements.in
を実行すると、代わりに次のようになります:
# このファイルは、次のコマンドを使用してuvによって自動生成されました:
# uv pip compile requirements.in --resolution lowest
click==7.1.2
# flask経由
flask==2.0.0
itsdangerous==2.0.0
# flask経由
jinja2==3.0.0
# flask経由
markupsafe==2.0.0
# jinja2経由
werkzeug==2.0.0
# flask経由
ライブラリを公開する場合、継続的インテグレーションで--resolution lowest
または--resolution lowest-direct
を使用してテストを個別に実行し、宣言された下限との互換性を確認することをお勧めします。
プレリリースの取り扱い
デフォルトでは、uvは次の2つの場合に依存関係の解決中にプレリリースバージョンを受け入れます:
- パッケージが直接依存関係であり、そのバージョン指定子にプレリリース指定子が含まれている場合(例:
flask>=2.0.0rc1
)。 - パッケージのすべての公開バージョンがプレリリースである場合。
推移的プレリリースによる依存関係の解決が失敗した場合、uvはすべての依存関係に対してプレリリースを許可するために--prerelease allow
の使用を促します。
代わりに、推移的依存関係を制約またはプレリリースバージョン指定子(例:flask>=2.0.0rc1
)を持つ直接依存関係(つまり、requirements.in
またはpyproject.toml
)として追加して、その特定の依存関係に対するプレリリースサポートをオプトインできます。
プレリリースは、他のパッケージングツールでのバグの頻繁な原因であるため、uvのプレリリースの取り扱いは意図的に制限されており、正確性を確保するためにプレリリースに対するユーザーのオプトインが必要です。
詳細については、プレリリースの互換性を参照してください。
依存関係の制約
pipと同様に、uvは指定されたパッケージの許容バージョンの範囲を狭める制約ファイル(--constraint constraints.txt
)をサポートしています。制約ファイルは要件ファイルに似ていますが、制約としてリストされているだけでは、パッケージが解決に含まれることはありません。代わりに、制約は要求されたパッケージが直接または推移的依存関係として既に取り込まれている場合にのみ効果を発揮します。制約は、推移的依存関係の利用可能なバージョンの範囲を減らすのに役立ちます。また、解決を他の解決済みバージョンセットと同期させるためにも使用できます。
依存関係のオーバーライド
依存関係のオーバーライドは、パッケージの宣言された依存関係をオーバーライドすることによって、失敗したり望ましくない解決を回避することを可能にします。オーバーライドは、メタデータがそう示していても、依存関係が特定のバージョンのパッケージと互換性があることを 知っている 場合の最後の手段として有用です。
たとえば、推移的依存関係がpydantic>=1.0,<2.0
の要件を宣言しているが、pydantic>=2.0
と互換性がある場合、ユーザーはオーバーライドにpydantic>=1.0,<3
を含めることで、リゾルバが新しいバージョンのpydantic
を選択できるようにします。
具体的には、pydantic>=1.0,<3
がオーバーライドとして含まれている場合、uvはpydantic
に対するすべての宣言された要件を無視し、それらをオーバーライドに置き換えます。上記の例では、pydantic>=1.0,<2.0
の要件は完全に無視され、代わりにpydantic>=1.0,<3
に置き換えられます。
制約はパッケージの許容バージョンのセットを減らすことしかできませんが、オーバーライドは許容バージョンのセットを拡張でき、誤った上限バージョンのエスケープハッチを提供します。制約と同様に、オーバーライドはパッケージに依存関係を追加せず、直接または推移的依存関係で要求された場合にのみ効果を発揮します。
pyproject.toml
では、tool.uv.override-dependencies
を使用してオーバーライドのリストを定義します。pip互換のインターフェースでは、--override
オプションを使用して、制約ファイルと同じ形式のファイルを渡すことができます。
同じパッケージに対して複数のオーバーライドが提供されている場合、それらはマーカーで区別する必要があります。パッケージにマーカー付きの依存関係がある場合、オーバーライドを使用する際には、マーカーがtrueまたはfalseに評価されるかどうかに関係なく、無条件に置き換えられます。
依存関係のメタデータ
解決中、uvは各パッケージのメタデータを解決する必要があります。これにより、その依存関係を特定できます。このメタデータは、パッケージインデックスに静的ファイルとして提供されることがよくあります。ただし、ソースディストリビューションのみを提供するパッケージの場合、メタデータは事前に利用できない場合があります。
そのような場合、uvはメタデータを特定するためにパッケージをビルドする必要があります(例:setup.py
を呼び出す)。これにより、解決中にパフォーマンスのペナルティが発生する可能性があります。さらに、パッケージがすべてのプラットフォームでビルドできる必要があるという要件が課されますが、これは必ずしも真ではありません。
たとえば、Linuxでのみビルドおよびインストールされるべきパッケージがあり、macOSやWindowsではビルドに失敗する場合があります。このシナリオの有効なロックファイルを構築することは可能ですが、パッケージをビルドする必要があり、非Linuxプラットフォームでは失敗します。
tool.uv.dependency-metadata
テーブルを使用して、そのような依存関係の静的メタデータを事前に提供し、uvがビルドステップをスキップして提供されたメタデータを使用できるようにします。
たとえば、chumpy
のメタデータを事前に提供するには、pyproject.toml
にそのdependency-metadata
を含めます:
[[tool.uv.dependency-metadata]]
name = "chumpy"
version = "0.70"
requires-dist = ["numpy>=1.8.1", "scipy>=0.13.0", "six>=1.11.0"]
これらの宣言は、パッケージが事前に静的メタデータを宣言しない場合を意図していますが、ビルドの分離を無効にする必要があるパッケージにも役立ちます。そのような場合、パッケージのメタデータを事前に宣言する方が、パッケージを解決する前にカスタムビルド環境を作成するよりも簡単です。
たとえば、flash-attn
のメタデータを宣言し、uvがソースからパッケージをビルドせずに解決できるようにします(これ自体がtorch
のインストールを必要とします):
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["flash-attn"]
[tool.uv.sources]
flash-attn = { git = "https://github.com/Dao-AILab/flash-attention", tag = "v2.6.3" }
[[tool.uv.dependency-metadata]]
name = "flash-attn"
version = "2.6.3"
requires-dist = ["torch", "einops"]
依存関係のオーバーライドと同様に、tool.uv.dependency-metadata
は、パッケージのメタデータが正しくないか不完全な場合、またはパッケージがパッケージインデックスに存在しない場合にも使用できます。依存関係のオーバーライドは、パッケージの許容バージョンをグローバルにオーバーライドすることを許可しますが、メタデータのオーバーライドは、特定のパッケージの宣言されたメタデータをオーバーライドすることを許可します。
Note
tool.uv.dependency-metadata
のversion
フィールドは、レジストリベースの依存関係に対してはオプションです(省略された場合、uvはメタデータがパッケージのすべてのバージョンに適用されると見なします)。ただし、直接URL依存関係(Git依存関係など)に対しては必須です。
tool.uv.dependency-metadata
テーブルのエントリは、Metadata 2.3仕様に従いますが、uvが読み取るのはname
、version
、requires-dist
、requires-python
、およびprovides-extra
のみです。version
フィールドもオプションと見なされます。省略された場合、メタデータは指定されたパッケージのすべてのバージョンに使用されます。
下限
デフォルトでは、uv add
は依存関係に下限を追加し、uvを使用してプロジェクトを管理する場合、uvは直接依存関係に下限がない場合に警告します。
下限は「ハッピーパス」では重要ではありませんが、依存関係の競合がある場合には重要です。たとえば、プロジェクトが2つのパッケージを必要とし、それらのパッケージが競合する依存関係を持っている場合、リゾルバは2つのパッケージの制約内のすべてのバージョンの組み合わせをチェックする必要があります。すべてが競合する場合、依存関係が満たされないため、エラーが報告されます。下限がない場合、リゾルバは(しばしば)パッケージの最古のバージョンまでバックトラックします。これは遅いだけでなく、古いバージョンのパッケージがビルドに失敗することがよくあり、リゾルバが競合するパッケージに依存しない古いバージョンを選択することがありますが、コードと互換性がないこともあります。
ライブラリを書く場合、下限は特に重要です。各依存関係に対してライブラリが動作する最も低いバージョンを宣言し、その境界が正しいことを確認することが重要です。そうしないと、ユーザーはライブラリの依存関係の古い互換性のないバージョンを受け取り、予期しないエラーでライブラリが失敗します。
再現可能な解決
uvは、特定の日付前に公開されたディストリビューションに解決を制限する--exclude-newer
オプションをサポートしており、新しいパッケージリリースに関係なくインストールを再現できます。日付はRFC 3339タイムスタンプ(例:2006-12-02T02:07:43Z
)またはシステムの設定されたタイムゾーンの同じ形式のローカル日付(例:2006-12-02
)として指定できます。
パッケージインデックスは、PEP 700
で指定されたupload-time
フィールドをサポートする必要があります。フィールドが特定のディストリビューションに存在しない場合、そのディストリビューションは利用できないと見なされます。PyPIはすべてのパッケージに対してupload-time
を提供します。
再現性を確保するために、満たされない解決のメッセージには、--exclude-newer
フラグのためにディストリビューションが除外されたことは言及されません。新しいディストリビューションは存在しないかのように扱われます。
Note
--exclude-newer
オプションは、レジストリから読み取られたパッケージにのみ適用されます(例:Git依存関係など)。さらに、uv pip
インターフェースを使用する場合、--reinstall
フラグが提供されない限り、uvは以前にインストールされたパッケージをダウングレードしません。この場合、uvは新しい解決を行います。
ソースディストリビューション
PEP 625は、パッケージがソースディストリビューションをgzip tarball(.tar.gz
)アーカイブとして配布する必要があると規定しています。この仕様の前は、後方互換性のためにサポートする必要がある他のアーカイブ形式も許可されていました。uvは、次の形式のアーカイブの読み取りと抽出をサポートしています:
- gzip tarball(
.tar.gz
、.tgz
) - bzip2 tarball(
.tar.bz2
、.tbz
) - xz tarball(
.tar.xz
、.txz
) - zstd tarball(
.tar.zst
) - lzip tarball(
.tar.lz
) - lzma tarball(
.tar.lzma
) - zip(
.zip
)
詳細を学ぶ
For more details about the internals of the resolver, see the resolver reference documentation.
ロックファイルのバージョニング
uv.lock
ファイルはバージョン管理されたスキーマを使用します。スキーマバージョンはロックファイルの version
フィールドに含まれています。
任意のバージョンのuvは、同じスキーマバージョンのロックファイルを読み書きできますが、より高いスキーマバージョンのロックファイルは拒否されます。たとえば、uvのバージョンがスキーマv1をサポートしている場合、既存のスキーマv2のロックファイルに遭遇すると uv lock
はエラーを返します。
スキーマv2をサポートするuvバージョンは、スキーマ更新が後方互換性を持つ場合に限り、スキーマv1のロックファイルを読み取ることができるかもしれません。しかし、これは保証されておらず、uvは古いスキーマバージョンのロックファイルに遭遇するとエラーで終了する可能性があります。
スキーマバージョンは公開APIの一部と見なされるため、マイナーリリースでのみ破壊的変更としてバージョンが上がります(バージョニング を参照)。したがって、特定のマイナー uv リリース内のすべての uv パッチバージョンは完全なロックファイル互換性を保証します。言い換えれば、ロックファイルはマイナーリリース間でのみ拒否される可能性があります。