Skip to content
Open
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
1 change: 1 addition & 0 deletions news/12018.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow URL constraints to apply to requirements with extras.
20 changes: 16 additions & 4 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,16 +376,28 @@ def _iter_candidates_from_constraints(
This creates "fake" InstallRequirement objects that are basically clones
of what "should" be the template, but with original_link set to link.
"""
extras: frozenset[str] = frozenset()
base_identifier = identifier
with contextlib.suppress(InvalidRequirement):
parsed_requirement = get_requirement(identifier)
if parsed_requirement.name != identifier:
base_identifier = canonicalize_name(parsed_requirement.name)
extras = frozenset(parsed_requirement.extras)

for link in constraint.links:
self._fail_if_link_is_unsupported_wheel(link)
candidate = self._make_base_candidate_from_link(
base_candidate = self._make_base_candidate_from_link(
link,
template=install_req_from_link_and_ireq(link, template),
name=canonicalize_name(identifier),
name=canonicalize_name(base_identifier),
version=None,
)
if candidate:
yield candidate
if base_candidate is None:
continue
if extras:
yield self._make_extras_candidate(base_candidate, extras)
else:
yield base_candidate

def find_candidates(
self,
Expand Down
29 changes: 29 additions & 0 deletions tests/functional/test_install_reqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,35 @@ def test_install_with_extras_from_install(script: PipTestEnvironment) -> None:
result.did_create(script.site_packages / "singlemodule.py")


def test_install_with_extras_and_url_constraint(
script: PipTestEnvironment,
) -> None:
"""Regression test for https://github.com/pypa/pip/issues/12018.

A URL constraint for the base package plus a requirement that asks for
the same package with extras used to trigger an AssertionError in
LinkCandidate (``'name[extra]' != 'name' for wheel``).
"""
create_basic_wheel_for_package(
script,
name="LocalExtras",
version="0.0.1",
extras={"baz": ["singlemodule"]},
)
wheel_path = next(script.scratch_path.glob("LocalExtras-0.0.1-*.whl"))
script.scratch_path.joinpath("constraints.txt").write_text(
f"LocalExtras @ {wheel_path.as_uri()}"
)
result = script.pip_install_local(
"--find-links",
script.scratch_path,
"-c",
script.scratch_path / "constraints.txt",
"LocalExtras[baz]",
)
result.did_create(script.site_packages / "singlemodule.py")


def test_install_with_extras_joined(
script: PipTestEnvironment, data: TestData, resolver_variant: ResolverVariant
) -> None:
Expand Down