Repository: pdm-project/pdm
Pull request #3015 (2024-07-12 03:31:32)

fix: don't skip validation when allow all env is passed
Signed-off-by: Frost Ming <me@frostming.com>

## Pull Request Checklist

- [ ] A news fragment is added in `news/` describing what is new.
- [ ] Test cases added for changed code.

## Describe what you have changed in this PR.



Patch:
diff --git a/news/3009.bugfix.md b/news/3009.bugfix.md
new file mode 100644
index 00000000..5aba7432
--- /dev/null
+++ b/news/3009.bugfix.md
@@ -0,0 +1,1 @@
+Fix the local path issue when `-p` is passed to change the project root.
diff --git a/src/pdm/models/candidates.py b/src/pdm/models/candidates.py
index c3b4b4a2..e3819573 100644
--- a/src/pdm/models/candidates.py
+++ b/src/pdm/models/candidates.py
@@ -27,7 +27,6 @@ from pdm.models.requirements import (
 from pdm.models.setup import Setup
 from pdm.models.specifiers import PySpecSet
 from pdm.utils import (
-    cd,
     comparable_version,
     convert_hashes,
     filtered_sources,
@@ -262,11 +261,10 @@ class Candidate:
             if not self.req.editable:
                 result.update(revision=self.get_revision())
         elif not self.req.is_named:
-            with cd(project_root):
-                if self.req.is_file_or_url and self.req.is_local:
-                    result.update(path=self.req.str_path)
-                else:
-                    result.update(url=self.req.url)
+            if self.req.is_file_or_url and self.req.is_local:
+                result.update(path=self.req.str_path)
+            else:
+                result.update(url=self.req.url)
         return {k: v for k, v in result.items() if v}
 
     def format(self) -> str:
diff --git a/src/pdm/models/finder.py b/src/pdm/models/finder.py
index 3cc7a480..bef1675f 100644
--- a/src/pdm/models/finder.py
+++ b/src/pdm/models/finder.py
@@ -37,7 +37,7 @@ class PDMEvaluator(Evaluator):
         self.env_spec = env_spec
 
     def check_requires_python(self, link: unearth.Link) -> None:
-        if not self.ignore_compatibility and link.requires_python:
+        if link.requires_python:
             try:
                 requires_python = parse_version_specifier(link.requires_python)
             except (InvalidSpecifier, PkgInvalidSpecifier) as e:
@@ -48,11 +48,9 @@ class PDMEvaluator(Evaluator):
                 )
 
     def check_wheel_tags(self, filename: str) -> None:
-        if self.ignore_compatibility:
-            return
         if self.env_spec.wheel_compatibility(filename) is None:
             raise LinkMismatchError(
-                f"The wheel file {filename} is not compatible with the target environment {self.env_spec.platform}."
+                f"The wheel file {filename} is not compatible with the target environment {self.env_spec}."
             )
 
 
@@ -74,7 +72,6 @@ class PDMPackageFinder(unearth.PackageFinder):
         return PDMEvaluator(
             package_name=package_name,
             target_python=self.target_python,
-            ignore_compatibility=self.env_spec.is_allow_all(),
             allow_yanked=allow_yanked,
             format_control=format_control,
             exclude_newer_than=self.exclude_newer_than,
diff --git a/src/pdm/models/requirements.py b/src/pdm/models/requirements.py
index 4ce3fa21..c2d75fed 100644
--- a/src/pdm/models/requirements.py
+++ b/src/pdm/models/requirements.py
@@ -244,6 +244,7 @@ class FileRequirement(Requirement):
     path: Path | None = None
     subdirectory: str | None = None
     check_installable: bool = True
+    _root: Path = dataclasses.field(default_factory=Path.cwd, repr=False)
 
     def __post_init__(self) -> None:
         super().__post_init__()
@@ -295,7 +296,7 @@ class FileRequirement(Requirement):
             return None
         if self.path.is_absolute():
             try:
-                result = self.path.relative_to(Path.cwd()).as_posix()
+                result = self.path.relative_to(self._root).as_posix()
             except ValueError:
                 return self.path.as_posix()
         else:
@@ -332,14 +333,19 @@ class FileRequirement(Requirement):
         if path == ".":
             path = ""
         self.url = backend.relative_path_to_url(path)
+        self._root = backend.root
+
+    @property
+    def absolute_path(self) -> Path | None:
+        return self._root.joinpath(self.path) if self.path else None
 
     @property
     def is_local(self) -> bool:
-        return self.path and self.path.exists() or False
+        return (path := self.absolute_path) is not None and path.exists()
 
     @property
     def is_local_dir(self) -> bool:
-        return self.is_local and cast(Path, self.path).is_dir()
+        return self.is_local and cast(Path, self.absolute_path).is_dir()
 
     def as_file_link(self) -> Link:
         from unearth import Link
diff --git a/src/pdm/project/core.py b/src/pdm/project/core.py
index 8ebc60fe..6ed00466 100644
--- a/src/pdm/project/core.py
+++ b/src/pdm/project/core.py
@@ -339,17 +339,14 @@ class Project:
         result = []
         with cd(self.root):
             for line in deps:
-                if line.startswith("-e "):
-                    if in_metadata:
-                        self.core.ui.warn(
-                            f"Skipping editable dependency [b]{line}[/] in the"
-                            r" [success]\[project][/] table. Please move it to the "
-                            r"[success]\[tool.pdm.dev-dependencies][/] table"
-                        )
-                        continue
-                    req = parse_requirement(line[3:].strip(), True)
-                else:
-                    req = parse_requirement(line)
+                if line.startswith("-e ") and in_metadata:
+                    self.core.ui.warn(
+                        f"Skipping editable dependency [b]{line}[/] in the"
+                        r" [success]\[project][/] table. Please move it to the "
+                        r"[success]\[tool.pdm.dev-dependencies][/] table"
+                    )
+                    continue
+                req = parse_line(line)
                 req.groups = [group]
                 # make editable packages behind normal ones to override correctly.
                 result.append(req)
@@ -601,25 +598,27 @@ class Project:
             )
             requirements = requirements.values()
         deps, setter = self.use_pyproject_dependencies(to_group, dev)
-        parsed_deps = [parse_line(dep) for dep in deps]
         updated_indices: set[int] = set()
 
-        for req in requirements:
-            if isinstance(req, str):
-                req = parse_line(req)
-            matched_index = next(
-                (i for i, r in enumerate(deps) if req.matches(r) and i not in updated_indices),
-                None,
-            )
-            dep = req.as_line()
-            if matched_index is None:
-                updated_indices.add(len(deps))
-                deps.append(dep)
-                parsed_deps.append(req)
-            else:
-                deps[matched_index] = dep
-                parsed_deps[matched_index] = req
-                updated_indices.add(matched_index)
+        with cd(self.root):
+            parsed_deps = [parse_line(dep) for dep in deps]
+
+            for req in requirements:
+                if isinstance(req, str):
+                    req = parse_line(req)
+                matched_index = next(
+                    (i for i, r in enumerate(deps) if req.matches(r) and i not in updated_indices),
+                    None,
+                )
+                dep = req.as_line()
+                if matched_index is None:
+                    updated_indices.add(len(deps))
+                    deps.append(dep)
+                    parsed_deps.append(req)
+                else:
+                    deps[matched_index] = dep
+                    parsed_deps[matched_index] = req
+                    updated_indices.add(matched_index)
         setter(cast(Array, deps).multiline(True))
         if write:
             self.pyproject.write(show_message)


Test patch:
diff --git a/tests/models/test_candidates.py b/tests/models/test_candidates.py
index 1bdee680..fd60d8a2 100644
--- a/tests/models/test_candidates.py
+++ b/tests/models/test_candidates.py
@@ -5,6 +5,7 @@ import shutil
 import pytest
 from unearth import Link
 
+from pdm.exceptions import CandidateNotFound
 from pdm.models.candidates import Candidate
 from pdm.models.requirements import Requirement, parse_requirement
 from pdm.models.specifiers import PySpecSet
@@ -271,8 +272,8 @@ def test_legacy_pep345_tag_link(project):
     project.project_config["pypi.url"] = "https://my.pypi.org/simple"
     req = parse_requirement("pep345-legacy")
     repo = project.get_repository()
-    candidate = next(iter(repo.find_candidates(req)))
-    assert candidate.requires_python == ">=3,<4"
+    with pytest.raises(CandidateNotFound):
+        _ = next(iter(repo.find_candidates(req)))
 
 
 @pytest.mark.filterwarnings("ignore::FutureWarning")
@@ -280,8 +281,8 @@ def test_ignore_invalid_py_version(project):
     project.project_config["pypi.url"] = "https://my.pypi.org/simple"
     req = parse_requirement("wheel")
     repo = project.get_repository()
-    candidate = next(iter(repo.find_candidates(req)))
-    assert not candidate.requires_python
+    with pytest.raises(CandidateNotFound):
+        _ = next(iter(repo.find_candidates(req)))
 
 
 def test_find_candidates_from_find_links(project):
@@ -291,7 +292,8 @@ def test_find_candidates_from_find_links(project):
     project.pyproject.write(False)
     repo = project.get_repository()
     candidates = list(repo.find_candidates(parse_requirement("demo")))
-    assert len(candidates) == 2
+    assert len(candidates) == 1
+    assert candidates[0].link.filename == "demo-0.0.1-py2.py3-none-any.whl"
 
 
 def test_parse_metadata_from_pep621(project, mocker):