Skip to content

Commit 890b185

Browse files
committed
Improve and migrate editable errors to diagnostic format
1 parent 47ffcdd commit 890b185

File tree

5 files changed

+77
-15
lines changed

5 files changed

+77
-15
lines changed

news/10421.feature.rst

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Present clearer errors when an invalid editable requirement is given or
2+
when a project's build backend does not support editable installs (PEP 660).

src/pip/_internal/exceptions.py

+40
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import contextlib
1010
import locale
1111
import logging
12+
import os
1213
import pathlib
1314
import re
1415
import sys
@@ -775,3 +776,42 @@ def __init__(self, *, distribution: "BaseDistribution") -> None:
775776
),
776777
hint_stmt=None,
777778
)
779+
780+
781+
class InvalidEditableRequirement(DiagnosticPipError):
782+
reference = "invalid-editable-requirement"
783+
784+
def __init__(self, *, requirement: str, vcs_schemes: List[str]) -> None:
785+
if os.path.sep in requirement and "://" not in requirement:
786+
hint = (
787+
"It appears to be a path, but does not exist. Was the right path given?"
788+
)
789+
else:
790+
hint = (
791+
"It should either be a path to a local project or a VCS URL "
792+
f"(beginning with {', '.join(vcs_schemes)})."
793+
)
794+
795+
super().__init__(
796+
message=Text(f"{requirement} is not a valid editable requirement"),
797+
context=(
798+
"There would be no source tree that can be edited after installation."
799+
),
800+
hint_stmt=hint,
801+
)
802+
803+
804+
class EditableUnsupportedByBackend(DiagnosticPipError):
805+
reference = "editable-mode-unsupported-by-backend"
806+
807+
def __init__(self, *, requirement: "InstallRequirement", backend: str) -> None:
808+
super().__init__(
809+
message=f"Cannot install {requirement} in editable mode",
810+
context=(
811+
f"The project's build backend ([magenta]{backend}[/]) does "
812+
"not support editable installs.\n"
813+
"Falling back to a setuptools legacy editable install is "
814+
"unsupported as neither a 'setup.py' nor a 'setup.cfg' was found."
815+
),
816+
hint_stmt=Text("Consider using a build backend that supports PEP 660."),
817+
)

src/pip/_internal/req/constructors.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
2020
from pip._vendor.packaging.specifiers import Specifier
2121

22-
from pip._internal.exceptions import InstallationError
22+
from pip._internal.exceptions import InstallationError, InvalidEditableRequirement
2323
from pip._internal.models.index import PyPI, TestPyPI
2424
from pip._internal.models.link import Link
2525
from pip._internal.models.wheel import Wheel
@@ -123,11 +123,8 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
123123
link = Link(url)
124124

125125
if not link.is_vcs:
126-
backends = ", ".join(vcs.all_schemes)
127-
raise InstallationError(
128-
f"{editable_req} is not a valid editable requirement. "
129-
f"It should either be a path to a local project or a VCS URL "
130-
f"(beginning with {backends})."
126+
raise InvalidEditableRequirement(
127+
requirement=editable_req, vcs_schemes=vcs.all_schemes
131128
)
132129

133130
package_name = link.egg_fragment

src/pip/_internal/req/req_install.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
1919

2020
from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
21-
from pip._internal.exceptions import InstallationError, PreviousBuildDirError
21+
from pip._internal.exceptions import (
22+
EditableUnsupportedByBackend,
23+
InstallationError,
24+
PreviousBuildDirError,
25+
)
2226
from pip._internal.locations import get_scheme
2327
from pip._internal.metadata import (
2428
BaseDistribution,
@@ -541,12 +545,9 @@ def isolated_editable_sanity_check(self) -> None:
541545
and not os.path.isfile(self.setup_py_path)
542546
and not os.path.isfile(self.setup_cfg_path)
543547
):
544-
raise InstallationError(
545-
f"Project {self} has a 'pyproject.toml' and its build "
546-
f"backend is missing the 'build_editable' hook. Since it does not "
547-
f"have a 'setup.py' nor a 'setup.cfg', "
548-
f"it cannot be installed in editable mode. "
549-
f"Consider using a build backend that supports PEP 660."
548+
assert self.pep517_backend is not None, "backend should be loaded!"
549+
raise EditableUnsupportedByBackend(
550+
requirement=self, backend=self.pep517_backend.build_backend
550551
)
551552

552553
def prepare_metadata(self) -> None:

tests/functional/test_pep660.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,17 @@ def build_editable(wheel_directory, config_settings=None, metadata_directory=Non
6262

6363

6464
def _make_project(
65-
tmpdir: Path, backend_code: str, with_setup_py: bool, with_pyproject: bool = True
65+
tmpdir: Path,
66+
backend_code: str,
67+
*,
68+
with_setup_py: bool,
69+
with_setup_cfg: bool = True,
70+
with_pyproject: bool = True,
6671
) -> Path:
6772
project_dir = tmpdir / "project"
6873
project_dir.mkdir()
69-
project_dir.joinpath("setup.cfg").write_text(SETUP_CFG)
74+
if with_setup_cfg:
75+
project_dir.joinpath("setup.cfg").write_text(SETUP_CFG)
7076
if with_setup_py:
7177
project_dir.joinpath("setup.py").write_text(SETUP_PY)
7278
if backend_code:
@@ -259,3 +265,19 @@ def test_download_editable_pep660_basic(
259265
_assert_hook_not_called(project_dir, "prepare_metadata_for_build_editable")
260266
_assert_hook_called(project_dir, "prepare_metadata_for_build_wheel")
261267
assert len(os.listdir(str(download_dir))) == 1, "a zip should have been created"
268+
269+
270+
def test_install_editable_unsupported_by_backend(
271+
tmpdir: Path, script: PipTestEnvironment
272+
) -> None:
273+
"""
274+
Check that pip errors out when installing a project whose backend does not
275+
support PEP 660 and falling back to a legacy editable install is impossible
276+
(no 'setup.py' or 'setup.py.cfg').
277+
"""
278+
project_dir = _make_project(
279+
tmpdir, BACKEND_WITHOUT_PEP660, with_setup_py=False, with_setup_cfg=False
280+
)
281+
result = script.pip("install", "--editable", project_dir, expect_error=True)
282+
assert "editable-mode-unsupported-by-backend" in result.stderr
283+
assert "Consider using a build backend that supports PEP 660" in result.stderr

0 commit comments

Comments
 (0)