Add handling of inconsistent root requirements

This commit is contained in:
Paul Moore 2020-06-05 12:56:43 +01:00
parent 09d311594e
commit 2795742b31
5 changed files with 70 additions and 10 deletions

View File

@ -37,6 +37,10 @@ class Requirement(object):
# type: () -> CandidateLookup
raise NotImplementedError("Subclass should override")
def format_for_error(self):
# type: () -> str
raise NotImplementedError("Subclass should override")
class Candidate(object):
@property
@ -61,3 +65,7 @@ class Candidate(object):
def get_install_requirement(self):
# type: () -> Optional[InstallRequirement]
raise NotImplementedError("Override in subclass")
def format_for_error(self):
# type: () -> str
raise NotImplementedError("Subclass should override")

View File

@ -162,6 +162,14 @@ class _InstallRequirementBackedCandidate(Candidate):
self._version = self.dist.parsed_version
return self._version
def format_for_error(self):
# type: () -> str
return "{} {} (from {})".format(
self.name,
self.version,
self.link.file_path
)
def _prepare_abstract_distribution(self):
# type: () -> AbstractDistribution
raise NotImplementedError("Override in subclass")
@ -336,6 +344,10 @@ class AlreadyInstalledCandidate(Candidate):
# type: () -> _BaseVersion
return self.dist.parsed_version
def format_for_error(self):
# type: () -> str
return "{} {} (Installed)".format(self.name, self.version)
def iter_dependencies(self):
# type: () -> Iterable[Optional[Requirement]]
for r in self.dist.requires():
@ -413,6 +425,13 @@ class ExtrasCandidate(Candidate):
# type: () -> _BaseVersion
return self.base.version
def format_for_error(self):
# type: () -> str
return "{} [{}]".format(
self.base.format_for_error(),
", ".join(sorted(self.extras))
)
@property
def is_installed(self):
# type: () -> _BaseVersion
@ -479,6 +498,10 @@ class RequiresPythonCandidate(Candidate):
# type: () -> _BaseVersion
return self._version
def format_for_error(self):
# type: () -> str
return "Python {}".format(self.version)
def iter_dependencies(self):
# type: () -> Iterable[Optional[Requirement]]
return ()

View File

@ -391,23 +391,26 @@ class Factory(object):
# type: (Candidate) -> str
return "{} {}".format(cand.name, cand.version)
msg = "Cannot install {} because these package versions " \
"have conflicting dependencies.".format(
text_join([
readable_form(parent)
for req, parent in e.causes
if parent
])
)
if any(parent for _, parent in e.causes):
msg = "Cannot install {} because these package versions " \
"have conflicting dependencies.".format(
text_join([
readable_form(parent)
for req, parent in e.causes
if parent
])
)
msg = msg + "\nThe conflict is caused by:"
else:
msg = "The following requirements are inconsistent:"
msg = msg + "\nThe conflict is caused by:"
for req, parent in e.causes:
msg = msg + "\n "
if parent:
msg = msg + readable_form(parent) + " depends on "
else:
msg = msg + "The user requested "
msg = msg + str(req)
msg = msg + req.format_for_error()
msg = msg + "\n\n" + \
"There are a number of possible solutions. " + \

View File

@ -30,6 +30,10 @@ class ExplicitRequirement(Requirement):
# No need to canonicalise - the candidate did this
return self.candidate.name
def format_for_error(self):
# type: () -> str
return self.candidate.format_for_error()
def get_candidate_lookup(self):
# type: () -> CandidateLookup
return self.candidate, None
@ -63,6 +67,10 @@ class SpecifierRequirement(Requirement):
canonical_name = canonicalize_name(self._ireq.req.name)
return format_name(canonical_name, self._extras)
def format_for_error(self):
# type: () -> str
return str(self)
def get_candidate_lookup(self):
# type: () -> CandidateLookup
return None, self._ireq
@ -99,6 +107,10 @@ class RequiresPythonRequirement(Requirement):
# type: () -> str
return self._candidate.name
def format_for_error(self):
# type: () -> str
return "Python " + str(self.specifier)
def get_candidate_lookup(self):
# type: () -> CandidateLookup
if self.specifier.contains(self._candidate.version, prereleases=True):

View File

@ -925,3 +925,17 @@ def test_new_resolver_upgrade_same_version(script):
"pkg",
)
assert_installed(script, pkg="2")
def test_new_resolver_local_and_req(script):
source_dir = create_test_package_with_setup(
script,
name="pkg",
version="0.1.0",
)
script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
source_dir, "pkg!=0.1.0",
expect_error=True,
)