Skip to content

Commit 89baaad

Browse files
committed
Implement diagnostic errors for build-system.requires issues
1 parent 9f6239f commit 89baaad

File tree

4 files changed

+74
-42
lines changed

4 files changed

+74
-42
lines changed

src/pip/_internal/exceptions.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,41 @@ class UninstallationError(PipError):
153153
"""General exception during uninstallation"""
154154

155155

156+
class MissingPyProjectBuildRequires(DiagnosticPipError):
157+
"""Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
158+
159+
reference = "missing-pyproject-build-system-requires"
160+
161+
def __init__(self, *, package: str) -> None:
162+
super().__init__(
163+
message=f"Can not process {package}",
164+
context=(
165+
"This package has an invalid pyproject.toml file.\n"
166+
"The [build-system] table is missing the mandatory `requires` key."
167+
),
168+
attention_stmt="This is an issue with the package noted above, not pip.",
169+
hint_stmt="See PEP 518 for the detailed specification.",
170+
)
171+
172+
173+
class InvalidPyProjectBuildRequires(DiagnosticPipError):
174+
"""Raised when pyproject.toml an invalid `build-system.requires`."""
175+
176+
reference = "invalid-pyproject-build-system-requires"
177+
178+
def __init__(self, *, package: str, reason: str) -> None:
179+
super().__init__(
180+
message=f"Can not process {package}",
181+
context=(
182+
"This package has an invalid `build-system.requires` key in "
183+
"pyproject.toml.\n"
184+
f"{reason}"
185+
),
186+
hint_stmt="See PEP 518 for the detailed specification.",
187+
attention_stmt="This is an issue with the package noted above, not pip.",
188+
)
189+
190+
156191
class MetadataFileIsMissingError(DiagnosticPipError, InstallationError):
157192
"""Raised when a distribution does not have a "METADATA" or "PKG-INFO" file."""
158193

src/pip/_internal/pyproject.py

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
from pip._vendor import tomli
66
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
77

8-
from pip._internal.exceptions import InstallationError
8+
from pip._internal.exceptions import (
9+
InstallationError,
10+
InvalidPyProjectBuildRequires,
11+
MissingPyProjectBuildRequires,
12+
)
913

1014

1115
def _is_list_of_str(obj: Any) -> bool:
@@ -113,47 +117,28 @@ def load_pyproject_toml(
113117

114118
# Ensure that the build-system section in pyproject.toml conforms
115119
# to PEP 518.
116-
error_template = (
117-
"{package} has a pyproject.toml file that does not comply "
118-
"with PEP 518: {reason}"
119-
)
120120

121121
# Specifying the build-system table but not the requires key is invalid
122122
if "requires" not in build_system:
123-
raise InstallationError(
124-
error_template.format(
125-
package=req_name,
126-
reason=(
127-
"it has a 'build-system' table but not "
128-
"'build-system.requires' which is mandatory in the table"
129-
),
130-
)
131-
)
123+
raise MissingPyProjectBuildRequires(package=req_name)
132124

133125
# Error out if requires is not a list of strings
134126
requires = build_system["requires"]
135127
if not _is_list_of_str(requires):
136-
raise InstallationError(
137-
error_template.format(
138-
package=req_name,
139-
reason="'build-system.requires' is not a list of strings.",
140-
)
128+
raise InvalidPyProjectBuildRequires(
129+
package=req_name,
130+
reason="It is not a list of strings.",
141131
)
142132

143133
# Each requirement must be valid as per PEP 508
144134
for requirement in requires:
145135
try:
146136
Requirement(requirement)
147-
except InvalidRequirement:
148-
raise InstallationError(
149-
error_template.format(
150-
package=req_name,
151-
reason=(
152-
"'build-system.requires' contains an invalid "
153-
"requirement: {!r}".format(requirement)
154-
),
155-
)
156-
)
137+
except InvalidRequirement as error:
138+
raise InvalidPyProjectBuildRequires(
139+
package=req_name,
140+
reason=f"It contains an invalid requirement: {requirement!r}",
141+
) from error
157142

158143
backend = build_system.get("build-backend")
159144
backend_path = build_system.get("backend-path", [])

tests/functional/test_install.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,12 @@ def test_pep518_refuses_invalid_requires(script, data, common_wheels):
108108
expect_error=True,
109109
)
110110
assert result.returncode == 1
111-
assert "does not comply with PEP 518" in result.stderr
111+
112+
# Ensure the relevant things are mentioned.
113+
assert "PEP 518" in result.stderr
114+
assert "not a list of strings" in result.stderr
115+
assert "build-system.requires" in result.stderr
116+
assert "pyproject.toml" in result.stderr
112117

113118

114119
def test_pep518_refuses_invalid_build_system(script, data, common_wheels):
@@ -120,7 +125,12 @@ def test_pep518_refuses_invalid_build_system(script, data, common_wheels):
120125
expect_error=True,
121126
)
122127
assert result.returncode == 1
123-
assert "does not comply with PEP 518" in result.stderr
128+
129+
# Ensure the relevant things are mentioned.
130+
assert "PEP 518" in result.stderr
131+
assert "mandatory `requires` key" in result.stderr
132+
assert "[build-system] table" in result.stderr
133+
assert "pyproject.toml" in result.stderr
124134

125135

126136
def test_pep518_allows_missing_requires(script, data, common_wheels):
@@ -132,7 +142,7 @@ def test_pep518_allows_missing_requires(script, data, common_wheels):
132142
expect_stderr=True,
133143
)
134144
# Make sure we don't warn when this occurs.
135-
assert "does not comply with PEP 518" not in result.stderr
145+
assert "PEP 518" not in result.stderr
136146

137147
# We want it to go through isolation for now.
138148
assert "Installing build dependencies" in result.stdout, result.stdout

tests/unit/test_pep517.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from pip._internal.exceptions import InstallationError
5+
from pip._internal.exceptions import InstallationError, InvalidPyProjectBuildRequires
66
from pip._internal.req import InstallRequirement
77
from tests.lib import TestData
88
from tests.lib.path import Path
@@ -60,19 +60,21 @@ def test_pep517_parsing_checks_requirements(tmpdir: Path, spec: str) -> None:
6060
tmpdir.joinpath("pyproject.toml").write_text(
6161
dedent(
6262
"""
63-
[build-system]
64-
requires = [{!r}]
65-
build-backend = "foo"
66-
""".format(
67-
spec
68-
)
63+
[build-system]
64+
requires = [{spec!r}]
65+
build-backend = "foo"
66+
"""
6967
)
7068
)
7169
req = InstallRequirement(None, None)
7270
req.source_dir = tmpdir # make req believe it has been unpacked
7371

74-
with pytest.raises(InstallationError) as e:
72+
with pytest.raises(InvalidPyProjectBuildRequires) as e:
7573
req.load_pyproject_toml()
7674

77-
err_msg = e.value.args[0]
78-
assert "contains an invalid requirement" in err_msg
75+
error = e.value
76+
77+
assert str(req) in error.message
78+
assert "build-system.requires" in error.context
79+
assert "contains an invalid requirement" in error.context
80+
assert "PEP 518" in error.hint_stmt

0 commit comments

Comments
 (0)