From 77eeb90dde1a7043633f0d61ff23f71552d5eb3a Mon Sep 17 00:00:00 2001 From: gvgeorge Date: Tue, 4 Mar 2025 16:39:32 +0300 Subject: [PATCH 1/4] uv resolver now respects UV_INDEX_STRATEGY variable --- src/pdm/resolver/uv.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/pdm/resolver/uv.py b/src/pdm/resolver/uv.py index 091562ab54..1f02ab19c3 100644 --- a/src/pdm/resolver/uv.py +++ b/src/pdm/resolver/uv.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import os import re import subprocess from dataclasses import dataclass, replace @@ -26,6 +27,8 @@ GIT_URL = re.compile(r"(?P[^:/]+://[^\?#]+)(?:\?rev=(?P[^#]+?))?(?:#(?P[a-f0-9]+))$") +ALLOWED_INDEX_STRATEGIES = {"first-index", "unsafe-first-match", "unsafe-best-match"} + @dataclass class UvResolver(Resolver): @@ -69,10 +72,14 @@ def _build_lock_command(self) -> list[str]: first_index = False else: cmd.extend(["--extra-index-url", source.url]) - if self.project.pyproject.settings.get("resolution", {}).get("respect-source-order", False): - cmd.append("--index-strategy=unsafe-first-match") + if index_strategy:=os.environ.get("UV_INDEX_STRATEGY"): + if index_strategy not in ALLOWED_INDEX_STRATEGIES: + raise ValueError(f"UV_INDEX_STRATEGY should be one of {' '.join(ALLOWED_INDEX_STRATEGIES)}") + elif self.project.pyproject.settings.get("resolution", {}).get("respect-source-order", False): + index_strategy = "unsafe-first-match" else: - cmd.append("--index-strategy=unsafe-best-match") + index_strategy = "unsafe-best-match" + cmd.append(f"--index-strategy={index_strategy}") if self.update_strategy != "all": for name in self.tracked_names: cmd.extend(["-P", name]) From d7c5bad37ca6237f94b78caabd407a97f531b93e Mon Sep 17 00:00:00 2001 From: gvgeorge Date: Tue, 4 Mar 2025 18:00:57 +0300 Subject: [PATCH 2/4] added test for uv index strategy behaviour --- tests/resolver/test_uv_resolver.py | 51 ++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/tests/resolver/test_uv_resolver.py b/tests/resolver/test_uv_resolver.py index ee50a8f995..54e263b3e4 100644 --- a/tests/resolver/test_uv_resolver.py +++ b/tests/resolver/test_uv_resolver.py @@ -1,28 +1,38 @@ +import os + import pytest from pdm.models.markers import EnvSpec from pdm.models.requirements import parse_requirement +from tests.test_installer import environment + pytestmark = [pytest.mark.network, pytest.mark.uv] -def resolve(environment, requirements, target=None): +def get_resolver(environment, requirements, target=None): from pdm.resolver.uv import UvResolver - reqs = [] - for req in requirements: - if isinstance(req, str): - req = parse_requirement(req) - req.groups = ["default"] - reqs.append(req) - resolver = UvResolver( environment, - requirements=reqs, + requirements=requirements, target=target or environment.spec, update_strategy="all", strategies=set(), ) + + return resolver + + +def resolve(environment, requirements, target=None): + + reqs = [] + for req in requirements: + if isinstance(req, str): + req = parse_requirement(req) + req.groups = ["default"] + reqs.append(req) + resolver = get_resolver(environment, reqs, target) return resolver.resolve() @@ -80,3 +90,26 @@ def test_resolve_dependencies_with_overrides(project, overrides): mapping = {p.candidate.identify(): p.candidate for p in resolution.packages} assert mapping["requests"].version == "2.31.0" + + +def test_index_strategy(project, monkeypatch): + from pdm.resolver.uv import ALLOWED_INDEX_STRATEGIES + + environment = project.environment + resolver = get_resolver(environment, [], None) + + command = resolver._build_lock_command() + assert "--index-strategy=unsafe-best-match" in command + + project.pyproject.settings["resolution"] = {"respect-source-order": True} + command = resolver._build_lock_command() + assert "--index-strategy=unsafe-first-match" in command + + for index_strategy in ALLOWED_INDEX_STRATEGIES: + monkeypatch.setenv("UV_INDEX_STRATEGY", index_strategy) + command = resolver._build_lock_command() + assert r"--index-strategy={strategy}" in command + + with pytest.raises(ValueError): + monkeypatch.setenv("UV_INDEX_STRATEGY", "abcd") + command = resolver._build_lock_command() From 27fd3e33ff99fa8d41d1d002f7d740dda7460c50 Mon Sep 17 00:00:00 2001 From: gvgeorge Date: Tue, 4 Mar 2025 18:18:27 +0300 Subject: [PATCH 3/4] ruff --- src/pdm/resolver/uv.py | 2 +- tests/resolver/test_uv_resolver.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/pdm/resolver/uv.py b/src/pdm/resolver/uv.py index 1f02ab19c3..87a2a901ae 100644 --- a/src/pdm/resolver/uv.py +++ b/src/pdm/resolver/uv.py @@ -72,7 +72,7 @@ def _build_lock_command(self) -> list[str]: first_index = False else: cmd.extend(["--extra-index-url", source.url]) - if index_strategy:=os.environ.get("UV_INDEX_STRATEGY"): + if index_strategy := os.environ.get("UV_INDEX_STRATEGY"): if index_strategy not in ALLOWED_INDEX_STRATEGIES: raise ValueError(f"UV_INDEX_STRATEGY should be one of {' '.join(ALLOWED_INDEX_STRATEGIES)}") elif self.project.pyproject.settings.get("resolution", {}).get("respect-source-order", False): diff --git a/tests/resolver/test_uv_resolver.py b/tests/resolver/test_uv_resolver.py index 54e263b3e4..dc9544ac00 100644 --- a/tests/resolver/test_uv_resolver.py +++ b/tests/resolver/test_uv_resolver.py @@ -1,11 +1,7 @@ -import os - import pytest from pdm.models.markers import EnvSpec from pdm.models.requirements import parse_requirement -from tests.test_installer import environment - pytestmark = [pytest.mark.network, pytest.mark.uv] @@ -25,7 +21,6 @@ def get_resolver(environment, requirements, target=None): def resolve(environment, requirements, target=None): - reqs = [] for req in requirements: if isinstance(req, str): From 8779f59c275ee463953abc68d7b62b39258c2657 Mon Sep 17 00:00:00 2001 From: gvgeorge Date: Tue, 4 Mar 2025 19:54:49 +0300 Subject: [PATCH 4/4] added news segment --- news/3410.feature.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 news/3410.feature.md diff --git a/news/3410.feature.md b/news/3410.feature.md new file mode 100644 index 0000000000..be9f16b399 --- /dev/null +++ b/news/3410.feature.md @@ -0,0 +1,3 @@ +When use_uv = True will try to choose index-strategy for uv based on UV_INDEX_STRATEGY environments variable; +if this variable isn't set will default to existing strategy which is "unsafe-first-match" +if resolution.respect-source-order is set to True in pyproject.toml and "unsafe-best-match" otherwise \ No newline at end of file