mirror of https://github.com/pypa/pip
Merge pull request #9096 from uranusjr/new-resolver-constrainting-extraed
Constrainting an extra-ed dependency
This commit is contained in:
commit
84382715f0
|
@ -58,6 +58,13 @@ class Constraint(object):
|
|||
hashes = self.hashes & other.hashes(trust_internet=False)
|
||||
return Constraint(specifier, hashes)
|
||||
|
||||
def is_satisfied_by(self, candidate):
|
||||
# type: (Candidate) -> bool
|
||||
# We can safely always allow prereleases here since PackageFinder
|
||||
# already implements the prerelease logic, and would have filtered out
|
||||
# prerelease candidates if the user does not expect them.
|
||||
return self.specifier.contains(candidate.version, prereleases=True)
|
||||
|
||||
|
||||
class Requirement(object):
|
||||
@property
|
||||
|
|
|
@ -143,6 +143,10 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
self._version = version
|
||||
self._dist = None # type: Optional[Distribution]
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return "{} {}".format(self.name, self.version)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "{class_name}({link!r})".format(
|
||||
|
@ -359,6 +363,10 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
skip_reason = "already satisfied"
|
||||
factory.preparer.prepare_installed_requirement(self._ireq, skip_reason)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return str(self.dist)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "{class_name}({distribution!r})".format(
|
||||
|
@ -445,6 +453,11 @@ class ExtrasCandidate(Candidate):
|
|||
self.base = base
|
||||
self.extras = extras
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
name, rest = str(self.base).split(" ", 1)
|
||||
return "{}[{}] {}".format(name, ",".join(self.extras), rest)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "{class_name}(base={base!r}, extras={extras!r})".format(
|
||||
|
@ -554,6 +567,10 @@ class RequiresPythonCandidate(Candidate):
|
|||
# only one RequiresPythonCandidate in a resolution, i.e. the host Python.
|
||||
# The built-in object.__eq__() and object.__ne__() do exactly what we want.
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return "Python {}".format(self._version)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
# type: () -> str
|
||||
|
|
|
@ -235,16 +235,10 @@ class Factory(object):
|
|||
prefers_installed,
|
||||
)
|
||||
|
||||
if constraint:
|
||||
name = explicit_candidates.pop().name
|
||||
raise InstallationError(
|
||||
"Could not satisfy constraints for {!r}: installation from "
|
||||
"path or url cannot be constrained to a version".format(name)
|
||||
)
|
||||
|
||||
return (
|
||||
c for c in explicit_candidates
|
||||
if all(req.is_satisfied_by(c) for req in requirements)
|
||||
if constraint.is_satisfied_by(c)
|
||||
and all(req.is_satisfied_by(c) for req in requirements)
|
||||
)
|
||||
|
||||
def make_requirement_from_install_req(self, ireq, requested_extras):
|
||||
|
|
|
@ -17,6 +17,10 @@ class ExplicitRequirement(Requirement):
|
|||
# type: (Candidate) -> None
|
||||
self.candidate = candidate
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return str(self.candidate)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "{class_name}({candidate!r})".format(
|
||||
|
@ -106,6 +110,10 @@ class RequiresPythonRequirement(Requirement):
|
|||
self.specifier = specifier
|
||||
self._candidate = match
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return "Python {}".format(self.specifier)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "{class_name}({specifier!r})".format(
|
||||
|
@ -120,7 +128,7 @@ class RequiresPythonRequirement(Requirement):
|
|||
|
||||
def format_for_error(self):
|
||||
# type: () -> str
|
||||
return "Python " + str(self.specifier)
|
||||
return str(self)
|
||||
|
||||
def get_candidate_lookup(self):
|
||||
# type: () -> CandidateLookup
|
||||
|
|
|
@ -341,7 +341,11 @@ def test_constraints_only_causes_error(script, data):
|
|||
assert 'installed requiresupper' not in result.stdout
|
||||
|
||||
|
||||
def test_constraints_local_editable_install_causes_error(script, data):
|
||||
def test_constraints_local_editable_install_causes_error(
|
||||
script,
|
||||
data,
|
||||
resolver_variant,
|
||||
):
|
||||
script.scratch_path.joinpath("constraints.txt").write_text(
|
||||
"singlemodule==0.0.0"
|
||||
)
|
||||
|
@ -350,7 +354,11 @@ def test_constraints_local_editable_install_causes_error(script, data):
|
|||
'install', '--no-index', '-f', data.find_links, '-c',
|
||||
script.scratch_path / 'constraints.txt', '-e',
|
||||
to_install, expect_error=True)
|
||||
assert 'Could not satisfy constraints for' in result.stderr
|
||||
if resolver_variant == "legacy-resolver":
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.network
|
||||
|
@ -362,7 +370,11 @@ def test_constraints_local_editable_install_pep518(script, data):
|
|||
'install', '--no-index', '-f', data.find_links, '-e', to_install)
|
||||
|
||||
|
||||
def test_constraints_local_install_causes_error(script, data):
|
||||
def test_constraints_local_install_causes_error(
|
||||
script,
|
||||
data,
|
||||
resolver_variant,
|
||||
):
|
||||
script.scratch_path.joinpath("constraints.txt").write_text(
|
||||
"singlemodule==0.0.0"
|
||||
)
|
||||
|
@ -371,7 +383,11 @@ def test_constraints_local_install_causes_error(script, data):
|
|||
'install', '--no-index', '-f', data.find_links, '-c',
|
||||
script.scratch_path / 'constraints.txt',
|
||||
to_install, expect_error=True)
|
||||
assert 'Could not satisfy constraints for' in result.stderr
|
||||
if resolver_variant == "legacy-resolver":
|
||||
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)
|
||||
|
||||
|
||||
def test_constraints_constrain_to_local_editable(
|
||||
|
|
|
@ -712,22 +712,40 @@ def test_new_resolver_constraint_on_dependency(script):
|
|||
assert_installed(script, dep="2.0")
|
||||
|
||||
|
||||
def test_new_resolver_constraint_on_path(script):
|
||||
@pytest.mark.parametrize(
|
||||
"constraint_version, expect_error, message",
|
||||
[
|
||||
("1.0", True, "ERROR: No matching distribution found for foo 2.0"),
|
||||
("2.0", False, "Successfully installed foo-2.0"),
|
||||
],
|
||||
)
|
||||
def test_new_resolver_constraint_on_path_empty(
|
||||
script,
|
||||
constraint_version,
|
||||
expect_error,
|
||||
message,
|
||||
):
|
||||
"""A path requirement can be filtered by a constraint.
|
||||
"""
|
||||
setup_py = script.scratch_path / "setup.py"
|
||||
text = "from setuptools import setup\nsetup(name='foo', version='2.0')"
|
||||
setup_py.write_text(text)
|
||||
|
||||
constraints_txt = script.scratch_path / "constraints.txt"
|
||||
constraints_txt.write_text("foo==1.0")
|
||||
constraints_txt.write_text("foo=={}".format(constraint_version))
|
||||
|
||||
result = script.pip(
|
||||
"install",
|
||||
"--no-cache-dir", "--no-index",
|
||||
"-c", constraints_txt,
|
||||
str(script.scratch_path),
|
||||
expect_error=True,
|
||||
expect_error=expect_error,
|
||||
)
|
||||
|
||||
msg = "installation from path or url cannot be constrained to a version"
|
||||
assert msg in result.stderr, str(result)
|
||||
if expect_error:
|
||||
assert message in result.stderr, str(result)
|
||||
else:
|
||||
assert message in result.stdout, str(result)
|
||||
|
||||
|
||||
def test_new_resolver_constraint_only_marker_match(script):
|
||||
|
@ -1132,3 +1150,46 @@ def test_new_resolver_check_wheel_version_normalized(
|
|||
"simple"
|
||||
)
|
||||
assert_installed(script, simple="0.1.0+local.1")
|
||||
|
||||
|
||||
def test_new_resolver_contraint_on_dep_with_extra(script):
|
||||
create_basic_wheel_for_package(
|
||||
script,
|
||||
name="simple",
|
||||
version="1",
|
||||
depends=["dep[x]"],
|
||||
)
|
||||
create_basic_wheel_for_package(
|
||||
script,
|
||||
name="dep",
|
||||
version="1",
|
||||
extras={"x": ["depx==1"]},
|
||||
)
|
||||
create_basic_wheel_for_package(
|
||||
script,
|
||||
name="dep",
|
||||
version="2",
|
||||
extras={"x": ["depx==2"]},
|
||||
)
|
||||
create_basic_wheel_for_package(
|
||||
script,
|
||||
name="depx",
|
||||
version="1",
|
||||
)
|
||||
create_basic_wheel_for_package(
|
||||
script,
|
||||
name="depx",
|
||||
version="2",
|
||||
)
|
||||
|
||||
constraints_txt = script.scratch_path / "constraints.txt"
|
||||
constraints_txt.write_text("dep==1")
|
||||
|
||||
script.pip(
|
||||
"install",
|
||||
"--no-cache-dir", "--no-index",
|
||||
"--find-links", script.scratch_path,
|
||||
"--constraint", constraints_txt,
|
||||
"simple",
|
||||
)
|
||||
assert_installed(script, simple="1", dep="1", depx="1")
|
||||
|
|
Loading…
Reference in New Issue