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

Add per-requirement --no-deps option support in requirements.txt #10837

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions news/9948.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a per-requirement ``--no-deps`` option.
q0w marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ def make_resolver(
"""
make_install_req = partial(
install_req_from_req_string,
ignore_dependencies=options.ignore_dependencies,
isolated=options.isolated_mode,
use_pep517=use_pep517,
)
Expand Down
4 changes: 3 additions & 1 deletion src/pip/_internal/operations/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDet
# Start from the current state
package_set, _ = create_package_set_from_installed()
# Install packages
would_be_installed = _simulate_installation_of(to_install, package_set)
would_be_installed = _simulate_installation_of(
list(filter(lambda x: not x.ignore_dependencies, to_install)), package_set
q0w marked this conversation as resolved.
Show resolved Hide resolved
)

# Only warn about directly-dependent packages; create a whitelist of them
whitelist = _create_whitelist(would_be_installed, package_set)
Expand Down
10 changes: 10 additions & 0 deletions src/pip/_internal/req/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ def install_req_from_editable(
install_options=options.get("install_options", []) if options else [],
global_options=options.get("global_options", []) if options else [],
hash_options=options.get("hashes", {}) if options else {},
ignore_dependencies=options.get("ignore_dependencies", False)
if options
else False,
extras=parts.extras,
)

Expand Down Expand Up @@ -399,6 +402,9 @@ def install_req_from_line(
install_options=options.get("install_options", []) if options else [],
global_options=options.get("global_options", []) if options else [],
hash_options=options.get("hashes", {}) if options else {},
ignore_dependencies=options.get("ignore_dependencies", False)
if options
else False,
constraint=constraint,
extras=parts.extras,
user_supplied=user_supplied,
Expand All @@ -409,6 +415,7 @@ def install_req_from_req_string(
req_string: str,
comes_from: Optional[InstallRequirement] = None,
isolated: bool = False,
ignore_dependencies: bool = False,
use_pep517: Optional[bool] = None,
user_supplied: bool = False,
) -> InstallRequirement:
Expand Down Expand Up @@ -438,6 +445,7 @@ def install_req_from_req_string(
req,
comes_from,
isolated=isolated,
ignore_dependencies=ignore_dependencies,
use_pep517=use_pep517,
user_supplied=user_supplied,
)
Expand All @@ -456,6 +464,7 @@ def install_req_from_parsed_requirement(
use_pep517=use_pep517,
constraint=parsed_req.constraint,
isolated=isolated,
options=parsed_req.options,
user_supplied=user_supplied,
)

Expand Down Expand Up @@ -486,5 +495,6 @@ def install_req_from_link_and_ireq(
isolated=ireq.isolated,
install_options=ireq.install_options,
global_options=ireq.global_options,
ignore_dependencies=ireq.ignore_dependencies,
hash_options=ireq.hash_options,
)
1 change: 1 addition & 0 deletions src/pip/_internal/req/req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
cmdoptions.install_options,
cmdoptions.global_options,
cmdoptions.hash,
cmdoptions.no_deps,
]

# the 'dest' string values
Expand Down
9 changes: 9 additions & 0 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def __init__(
install_options: Optional[List[str]] = None,
global_options: Optional[List[str]] = None,
hash_options: Optional[Dict[str, List[str]]] = None,
ignore_dependencies: bool = False,
constraint: bool = False,
extras: Collection[str] = (),
user_supplied: bool = False,
Expand Down Expand Up @@ -138,6 +139,7 @@ def __init__(
self.install_options = install_options if install_options else []
self.global_options = global_options if global_options else []
self.hash_options = hash_options if hash_options else {}
self.ignore_dependencies = ignore_dependencies
# Set to True after successful preparation of this requirement
self.prepared = False
# User supplied requirement are explicitly requested for installation
Expand Down Expand Up @@ -252,6 +254,13 @@ def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> boo
else:
return True

@property
def has_ignore_dependencies(self) -> bool:
if isinstance(self.comes_from, InstallRequirement):
return self.comes_from.ignore_dependencies
else:
return self.ignore_dependencies
q0w marked this conversation as resolved.
Show resolved Hide resolved

@property
def has_hash_options(self) -> bool:
"""Return whether any known-good hashes are specified as options.
Expand Down
13 changes: 13 additions & 0 deletions src/pip/_internal/resolution/resolvelib/candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")


def has_ignore_dependencies(candidate: Candidate) -> bool:
ireq = candidate.get_install_requirement()
if ireq:
return ireq.has_ignore_dependencies
elif isinstance(candidate, AlreadyInstalledCandidate):
return candidate._ireq.has_ignore_dependencies
else:
return False
q0w marked this conversation as resolved.
Show resolved Hide resolved


def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
"""The runtime version of BaseCandidate."""
base_candidate_classes = (
Expand Down Expand Up @@ -68,6 +78,7 @@ def make_install_req_from_link(
install_options=template.install_options,
global_options=template.global_options,
hashes=template.hash_options,
ignore_dependencies=template.ignore_dependencies,
),
)
ireq.original_link = template.original_link
Expand All @@ -91,6 +102,7 @@ def make_install_req_from_editable(
install_options=template.install_options,
global_options=template.global_options,
hashes=template.hash_options,
ignore_dependencies=template.ignore_dependencies,
),
)

Expand All @@ -115,6 +127,7 @@ def _make_install_req_from_dist(
install_options=template.install_options,
global_options=template.global_options,
hashes=template.hash_options,
ignore_dependencies=template.ignore_dependencies,
),
)
ireq.satisfied_by = dist
Expand Down
6 changes: 4 additions & 2 deletions src/pip/_internal/resolution/resolvelib/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from pip._vendor.resolvelib.providers import AbstractProvider

from .base import Candidate, Constraint, Requirement
from .candidates import REQUIRES_PYTHON_IDENTIFIER
from .candidates import REQUIRES_PYTHON_IDENTIFIER, has_ignore_dependencies
from .factory import Factory

if TYPE_CHECKING:
Expand Down Expand Up @@ -233,7 +233,9 @@ def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> boo
return requirement.is_satisfied_by(candidate)

def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]:
with_requires = not self._ignore_dependencies
with_requires = not (
self._ignore_dependencies or has_ignore_dependencies(candidate)
)
return [r for r in candidate.iter_dependencies(with_requires) if r is not None]

@staticmethod
Expand Down
20 changes: 20 additions & 0 deletions tests/functional/test_install_reqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,3 +816,23 @@ def test_location_related_install_option_fails(script: PipTestEnvironment) -> No
expect_error=True,
)
assert "['--home'] from simple" in result.stderr


def test_install_options_no_deps(script: PipTestEnvironment) -> None:
create_basic_wheel_for_package(script, "A", "0.1.0", depends=["B==0.1.0"])
create_basic_wheel_for_package(script, "B", "0.1.0")

requirements_txt = script.scratch_path / "requirements.txt"
requirements_txt.write_text("A --no-deps")

script.pip(
"install",
"--no-cache-dir",
"--find-links",
script.scratch_path,
"-r",
requirements_txt,
"--only-binary=:all:",
)
script.assert_installed(A="0.1.0")
script.assert_not_installed("B")