Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show constraint in error message #9300

Merged
merged 3 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/9300.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
New resolver: Show relevant entries from user-supplied constraint files in the
error message to improve debuggability.
38 changes: 27 additions & 11 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,24 @@ def _report_requires_python_error(
)
return UnsupportedPythonVersion(message)

def get_installation_error(self, e):
# type: (ResolutionImpossible) -> InstallationError
def _report_single_requirement_conflict(self, req, parent):
# type: (Requirement, Candidate) -> DistributionNotFound
if parent is None:
req_disp = str(req)
else:
req_disp = f"{req} (from {parent.name})"
logger.critical(
"Could not find a version that satisfies the requirement %s",
req_disp,
)
return DistributionNotFound(f"No matching distribution found for {req}")

def get_installation_error(
self,
e, # type: ResolutionImpossible
constraints, # type: Dict[str, Constraint]
):
# type: (...) -> InstallationError

assert e.causes, "Installation error reported with no cause"

Expand All @@ -425,15 +441,8 @@ def get_installation_error(self, e):
# satisfied. We just report that case.
if len(e.causes) == 1:
req, parent = e.causes[0]
if parent is None:
req_disp = str(req)
else:
req_disp = f"{req} (from {parent.name})"
logger.critical(
"Could not find a version that satisfies the requirement %s",
req_disp,
)
return DistributionNotFound(f"No matching distribution found for {req}")
if req.name not in constraints:
return self._report_single_requirement_conflict(req, parent)

# OK, we now have a list of requirements that can't all be
# satisfied at once.
Expand Down Expand Up @@ -475,13 +484,20 @@ def describe_trigger(parent):
)
logger.critical(msg)
msg = "\nThe conflict is caused by:"

relevant_constraints = set()
for req, parent in e.causes:
if req.name in constraints:
relevant_constraints.add(req.name)
msg = msg + "\n "
if parent:
msg = msg + "{} {} depends on ".format(parent.name, parent.version)
else:
msg = msg + "The user requested "
msg = msg + req.format_for_error()
for key in relevant_constraints:
spec = constraints[key].specifier
msg += f"\n The user requested (constraint) {key}{spec}"

msg = (
msg
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_internal/resolution/resolvelib/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def resolve(self, root_reqs, check_supported_wheels):
)

except ResolutionImpossible as e:
error = self.factory.get_installation_error(e)
error = self.factory.get_installation_error(e, constraints)
six.raise_from(error, e)

req_set = RequirementSet(check_supported_wheels=check_supported_wheels)
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_install_reqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ def test_constraints_local_editable_install_causes_error(
assert 'Could not satisfy constraints' in result.stderr, str(result)
else:
# Because singlemodule only has 0.0.1 available.
assert 'No matching distribution found' in result.stderr, str(result)
assert 'Cannot install singlemodule 0.0.1' in result.stderr, str(result)


@pytest.mark.network
Expand Down Expand Up @@ -386,7 +386,7 @@ def test_constraints_local_install_causes_error(
assert 'Could not satisfy constraints' in result.stderr, str(result)
else:
# Because singlemodule only has 0.0.1 available.
assert 'No matching distribution found' in result.stderr, str(result)
assert 'Cannot install singlemodule 0.0.1' in result.stderr, str(result)


def test_constraints_constrain_to_local_editable(
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/test_new_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ def test_new_resolver_constraint_on_dependency(script):
@pytest.mark.parametrize(
"constraint_version, expect_error, message",
[
("1.0", True, "ERROR: No matching distribution found for foo 2.0"),
("1.0", True, "Cannot install foo 2.0"),
("2.0", False, "Successfully installed foo-2.0"),
],
)
Expand Down
21 changes: 21 additions & 0 deletions tests/functional/test_new_resolver_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,24 @@ def test_new_resolver_conflict_requirements_file(tmpdir, script):

message = "package versions have conflicting dependencies"
assert message in result.stderr, str(result)


def test_new_resolver_conflict_constraints_file(tmpdir, script):
create_basic_wheel_for_package(script, "pkg", "1.0")

constrats_file = tmpdir.joinpath("constraints.txt")
constrats_file.write_text("pkg!=1.0")

result = script.pip(
"install",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"-c", constrats_file,
"pkg==1.0",
expect_error=True,
)

assert "ResolutionImpossible" in result.stderr, str(result)

message = "The user requested (constraint) pkg!=1.0"
assert message in result.stdout, str(result)