From 46abb8a5563dd5be44dadd008fbef91235f3dc28 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Thu, 27 Jan 2022 19:46:06 +0300 Subject: [PATCH 01/13] Add --no-deps per line --- src/pip/_internal/cli/req_command.py | 2 ++ src/pip/_internal/req/constructors.py | 11 +++++++++++ src/pip/_internal/req/req_file.py | 3 +++ src/pip/_internal/req/req_install.py | 2 ++ src/pip/_internal/resolution/resolvelib/candidates.py | 2 ++ src/pip/_internal/resolution/resolvelib/provider.py | 6 +++++- 6 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index 5d4d1f0f45b..bcbc94db21a 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -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, ) @@ -419,6 +420,7 @@ def get_requirements( isolated=options.isolated_mode, use_pep517=options.use_pep517, user_supplied=True, + ignore_dependencies=options.ignore_dependencies, ) requirements.append(req_to_add) diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index 25bfb391d88..4756d660f8c 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -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, ) @@ -380,6 +383,7 @@ def install_req_from_line( constraint: bool = False, line_source: Optional[str] = None, user_supplied: bool = False, + ignore_dependencies: bool = False, ) -> InstallRequirement: """Creates an InstallRequirement from a name, which might be a requirement, directory containing 'setup.py', filename, or URL. @@ -399,6 +403,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 ignore_dependencies, constraint=constraint, extras=parts.extras, user_supplied=user_supplied, @@ -409,6 +416,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: @@ -438,6 +446,7 @@ def install_req_from_req_string( req, comes_from, isolated=isolated, + ignore_dependencies=ignore_dependencies, use_pep517=use_pep517, user_supplied=user_supplied, ) @@ -456,6 +465,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, ) @@ -486,5 +496,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, ) diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py index 03ae50492c5..582c8b0513d 100644 --- a/src/pip/_internal/req/req_file.py +++ b/src/pip/_internal/req/req_file.py @@ -72,6 +72,7 @@ cmdoptions.install_options, cmdoptions.global_options, cmdoptions.hash, + cmdoptions.no_deps, ] # the 'dest' string values @@ -189,6 +190,8 @@ def handle_requirement_line( if options: # Disable wheels if the user has specified build options cmdoptions.check_install_build_global(options, line.opts) + if line.opts.ignore_dependencies: + options.ignore_dependencies = True # get the options that apply to requirements req_options = {} diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 637b6ce10af..acf9045847b 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -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, @@ -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 diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 9b8450e86b8..83dbc09695f 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -68,6 +68,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 @@ -91,6 +92,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, ), ) diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index e6ec9594f62..7725d512b0a 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -233,7 +233,11 @@ 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 + install_req = candidate.get_install_requirement() + if install_req: + with_requires = not install_req.ignore_dependencies + else: + with_requires = not self._ignore_dependencies return [r for r in candidate.iter_dependencies(with_requires) if r is not None] @staticmethod From 9386757a24d96fa7ee2b7967e7d97ecb4286e48a Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Thu, 27 Jan 2022 19:58:18 +0300 Subject: [PATCH 02/13] Add changelog --- news/9948.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/9948.feature.rst diff --git a/news/9948.feature.rst b/news/9948.feature.rst new file mode 100644 index 00000000000..7cf83783f0e --- /dev/null +++ b/news/9948.feature.rst @@ -0,0 +1 @@ +Add a per-requirement ``--no-deps`` option. From a8944bfed7cc201aa692f4dfaa751ccba20ff3a8 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Thu, 27 Jan 2022 22:04:37 +0300 Subject: [PATCH 03/13] Dont check packages with ignore_dependencies --- src/pip/_internal/operations/check.py | 4 +++- src/pip/_internal/req/constructors.py | 2 +- src/pip/_internal/req/req_file.py | 2 -- src/pip/_internal/resolution/resolvelib/candidates.py | 1 + src/pip/_internal/resolution/resolvelib/provider.py | 5 ++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py index fb3ac8b9c9e..dfee2a18883 100644 --- a/src/pip/_internal/operations/check.py +++ b/src/pip/_internal/operations/check.py @@ -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 + ) # Only warn about directly-dependent packages; create a whitelist of them whitelist = _create_whitelist(would_be_installed, package_set) diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index 4756d660f8c..449467d3f77 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -403,7 +403,7 @@ 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) + ignore_dependencies=options.get("ignore_dependencies", ignore_dependencies) if options else ignore_dependencies, constraint=constraint, diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py index 582c8b0513d..e3aabb66662 100644 --- a/src/pip/_internal/req/req_file.py +++ b/src/pip/_internal/req/req_file.py @@ -190,8 +190,6 @@ def handle_requirement_line( if options: # Disable wheels if the user has specified build options cmdoptions.check_install_build_global(options, line.opts) - if line.opts.ignore_dependencies: - options.ignore_dependencies = True # get the options that apply to requirements req_options = {} diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 83dbc09695f..f96ffc5d25a 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -117,6 +117,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 diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index 7725d512b0a..fe417b1d646 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -234,10 +234,9 @@ def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> boo def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]: install_req = candidate.get_install_requirement() - if install_req: + with_requires = not self._ignore_dependencies + if install_req and with_requires: with_requires = not install_req.ignore_dependencies - else: - with_requires = not self._ignore_dependencies return [r for r in candidate.iter_dependencies(with_requires) if r is not None] @staticmethod From f2f6dc5750e074df8069f7277f47cefc4fdb729e Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Fri, 28 Jan 2022 14:30:43 +0300 Subject: [PATCH 04/13] Get ireq from AlreadyInstalledCandidate --- .../_internal/resolution/resolvelib/provider.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index fe417b1d646..b0302473819 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -13,8 +13,10 @@ from pip._vendor.resolvelib.providers import AbstractProvider +from pip._internal.req import InstallRequirement + from .base import Candidate, Constraint, Requirement -from .candidates import REQUIRES_PYTHON_IDENTIFIER +from .candidates import REQUIRES_PYTHON_IDENTIFIER, AlreadyInstalledCandidate from .factory import Factory if TYPE_CHECKING: @@ -233,10 +235,15 @@ def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> boo return requirement.is_satisfied_by(candidate) def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]: - install_req = candidate.get_install_requirement() + ireq = candidate.get_install_requirement() with_requires = not self._ignore_dependencies - if install_req and with_requires: - with_requires = not install_req.ignore_dependencies + if ireq and with_requires: + if ireq.comes_from and isinstance(ireq.comes_from, InstallRequirement): + with_requires = not ireq.comes_from.ignore_dependencies + else: + with_requires = not ireq.ignore_dependencies + elif isinstance(candidate, AlreadyInstalledCandidate): + with_requires = not candidate._ireq.ignore_dependencies return [r for r in candidate.iter_dependencies(with_requires) if r is not None] @staticmethod From 73435777cd7ac13c4e94ceb196bb0289af217861 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Wed, 23 Mar 2022 10:34:44 +0300 Subject: [PATCH 05/13] Refactor and test --- src/pip/_internal/req/req_install.py | 7 +++++++ .../resolution/resolvelib/candidates.py | 10 ++++++++++ .../resolution/resolvelib/provider.py | 16 ++++----------- tests/functional/test_install_reqs.py | 20 +++++++++++++++++++ 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index acf9045847b..52c8deb1952 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -254,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 + @property def has_hash_options(self) -> bool: """Return whether any known-good hashes are specified as options. diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index f96ffc5d25a..40cb7a80337 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -37,6 +37,16 @@ REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "") +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 + + def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]: """The runtime version of BaseCandidate.""" base_candidate_classes = ( diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index b0302473819..a52f54a1c6e 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -13,10 +13,8 @@ from pip._vendor.resolvelib.providers import AbstractProvider -from pip._internal.req import InstallRequirement - from .base import Candidate, Constraint, Requirement -from .candidates import REQUIRES_PYTHON_IDENTIFIER, AlreadyInstalledCandidate +from .candidates import REQUIRES_PYTHON_IDENTIFIER, has_ignore_dependencies from .factory import Factory if TYPE_CHECKING: @@ -235,15 +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]: - ireq = candidate.get_install_requirement() - with_requires = not self._ignore_dependencies - if ireq and with_requires: - if ireq.comes_from and isinstance(ireq.comes_from, InstallRequirement): - with_requires = not ireq.comes_from.ignore_dependencies - else: - with_requires = not ireq.ignore_dependencies - elif isinstance(candidate, AlreadyInstalledCandidate): - with_requires = not candidate._ireq.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 diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index a7f2f46be94..943c8a25b33 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -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") From ea90aeb1db09ead43890d84d189ef162fe0fbcad Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Wed, 23 Mar 2022 11:16:10 +0300 Subject: [PATCH 06/13] Remove unused arg --- src/pip/_internal/cli/req_command.py | 1 - src/pip/_internal/req/constructors.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index bcbc94db21a..acf962d26ae 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -420,7 +420,6 @@ def get_requirements( isolated=options.isolated_mode, use_pep517=options.use_pep517, user_supplied=True, - ignore_dependencies=options.ignore_dependencies, ) requirements.append(req_to_add) diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index 449467d3f77..706a79f4788 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -383,7 +383,6 @@ def install_req_from_line( constraint: bool = False, line_source: Optional[str] = None, user_supplied: bool = False, - ignore_dependencies: bool = False, ) -> InstallRequirement: """Creates an InstallRequirement from a name, which might be a requirement, directory containing 'setup.py', filename, or URL. @@ -403,9 +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", ignore_dependencies) + ignore_dependencies=options.get("ignore_dependencies", False) if options - else ignore_dependencies, + else False, constraint=constraint, extras=parts.extras, user_supplied=user_supplied, From 64ec9d0ae2a7ee2bfe24930478c5da13906be575 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:15:55 +0300 Subject: [PATCH 07/13] Refactor --- src/pip/_internal/operations/check.py | 2 +- src/pip/_internal/req/req_install.py | 9 ++------ .../_internal/resolution/resolvelib/base.py | 4 ++++ .../resolution/resolvelib/candidates.py | 23 +++++++++++-------- .../resolution/resolvelib/provider.py | 6 ++--- tests/functional/test_install_reqs.py | 9 +++++--- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py index dfee2a18883..ecfa51b1b85 100644 --- a/src/pip/_internal/operations/check.py +++ b/src/pip/_internal/operations/check.py @@ -101,7 +101,7 @@ def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDet package_set, _ = create_package_set_from_installed() # Install packages would_be_installed = _simulate_installation_of( - list(filter(lambda x: not x.ignore_dependencies, to_install)), package_set + [x for x in to_install if not x.ignore_dependencies], package_set ) # Only warn about directly-dependent packages; create a whitelist of them diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 52c8deb1952..4d77ebbde29 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -140,6 +140,8 @@ def __init__( self.global_options = global_options if global_options else [] self.hash_options = hash_options if hash_options else {} self.ignore_dependencies = ignore_dependencies + if isinstance(self.comes_from, InstallRequirement): + self.ignore_dependencies = self.comes_from.ignore_dependencies # Set to True after successful preparation of this requirement self.prepared = False # User supplied requirement are explicitly requested for installation @@ -254,13 +256,6 @@ 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 - @property def has_hash_options(self) -> bool: """Return whether any known-good hashes are specified as options. diff --git a/src/pip/_internal/resolution/resolvelib/base.py b/src/pip/_internal/resolution/resolvelib/base.py index b206692a0a9..17938f475dd 100644 --- a/src/pip/_internal/resolution/resolvelib/base.py +++ b/src/pip/_internal/resolution/resolvelib/base.py @@ -131,6 +131,10 @@ def is_editable(self) -> bool: def source_link(self) -> Optional[Link]: raise NotImplementedError("Override in subclass") + @property + def ignore_dependencies(self) -> bool: + raise NotImplementedError("Override in subclass") + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: raise NotImplementedError("Override in subclass") diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 40cb7a80337..d49da31ccf6 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -37,16 +37,6 @@ REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "") -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 - - def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]: """The runtime version of BaseCandidate.""" base_candidate_classes = ( @@ -252,6 +242,10 @@ def _prepare(self) -> BaseDistribution: self._check_metadata_consistency(dist) return dist + @property + def ignore_dependencies(self) -> bool: + return self._ireq.ignore_dependencies + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: requires = self.dist.iter_dependencies() if with_requires else () for r in requires: @@ -390,6 +384,10 @@ def version(self) -> CandidateVersion: def is_editable(self) -> bool: return self.dist.editable + @property + def ignore_dependencies(self) -> bool: + return self._ireq.ignore_dependencies + def format_for_error(self) -> str: return f"{self.name} {self.version} (Installed)" @@ -485,6 +483,10 @@ def is_editable(self) -> bool: def source_link(self) -> Optional[Link]: return self.base.source_link + @property + def ignore_dependencies(self) -> bool: + return self.base.ignore_dependencies + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: factory = self.base._factory @@ -523,6 +525,7 @@ def get_install_requirement(self) -> Optional[InstallRequirement]: class RequiresPythonCandidate(Candidate): is_installed = False source_link = None + ignore_dependencies = False def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None: if py_version_info is not None: diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index a52f54a1c6e..66d7a3da1fe 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -14,7 +14,7 @@ from pip._vendor.resolvelib.providers import AbstractProvider from .base import Candidate, Constraint, Requirement -from .candidates import REQUIRES_PYTHON_IDENTIFIER, has_ignore_dependencies +from .candidates import REQUIRES_PYTHON_IDENTIFIER from .factory import Factory if TYPE_CHECKING: @@ -233,9 +233,7 @@ 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 or has_ignore_dependencies(candidate) - ) + with_requires = not (self._ignore_dependencies or candidate.ignore_dependencies) return [r for r in candidate.iter_dependencies(with_requires) if r is not None] @staticmethod diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index 943c8a25b33..f4f89360fa5 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -819,11 +819,14 @@ def test_location_related_install_option_fails(script: PipTestEnvironment) -> No 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, "A", "0.1.0", depends=["B==0.1.0"], extras={"C": ["C"]} + ) create_basic_wheel_for_package(script, "B", "0.1.0") + create_basic_wheel_for_package(script, "C", "0.1.0") requirements_txt = script.scratch_path / "requirements.txt" - requirements_txt.write_text("A --no-deps") + requirements_txt.write_text("A[C] --no-deps") script.pip( "install", @@ -835,4 +838,4 @@ def test_install_options_no_deps(script: PipTestEnvironment) -> None: "--only-binary=:all:", ) script.assert_installed(A="0.1.0") - script.assert_not_installed("B") + script.assert_not_installed("B", "C") From 003749cbc0346944bfcb12b20840629fa2f1a65f Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:28:31 +0300 Subject: [PATCH 08/13] Test AlreadyInstalledCandidate --- tests/functional/test_install_reqs.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index f4f89360fa5..a6b0d1bef92 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -839,3 +839,15 @@ def test_install_options_no_deps(script: PipTestEnvironment) -> None: ) script.assert_installed(A="0.1.0") script.assert_not_installed("B", "C") + + # AlreadyInstalledCandidate should not install dependencies + script.pip( + "install", + "--no-cache-dir", + "--find-links", + script.scratch_path, + "-r", + requirements_txt, + "--only-binary=:all:", + ) + script.assert_not_installed("B", "C") From 094dd68ca51589c0c3095effae102d596b6e78e9 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Fri, 25 Mar 2022 15:28:23 +0300 Subject: [PATCH 09/13] Update news/9948.feature.rst Co-authored-by: Tzu-ping Chung --- news/9948.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/9948.feature.rst b/news/9948.feature.rst index 7cf83783f0e..643181f7a75 100644 --- a/news/9948.feature.rst +++ b/news/9948.feature.rst @@ -1 +1 @@ -Add a per-requirement ``--no-deps`` option. +Add per-requirement ``--no-deps`` option support in requirements.txt. From 065b50a055ac0d3157fa6ef21bdac24c03736aca Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Fri, 25 Mar 2022 16:26:20 +0300 Subject: [PATCH 10/13] Dont warn about conflicts --- src/pip/_internal/commands/install.py | 6 +++++- src/pip/_internal/req/req_install.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 34e4c2f84c8..a35a13f959c 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -391,7 +391,11 @@ def run(self, options: Values, args: List[str]) -> int: # Check for conflicts in the package set we're installing. conflicts: Optional[ConflictDetails] = None should_warn_about_conflicts = ( - not options.ignore_dependencies and options.warn_about_conflicts + not ( + options.ignore_dependencies + or any((i for i in to_install if i.ignore_dependencies)) + ) + and options.warn_about_conflicts ) if should_warn_about_conflicts: conflicts = self._determine_conflicts(to_install) diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 4d77ebbde29..0e6f6fbce67 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -141,7 +141,7 @@ def __init__( self.hash_options = hash_options if hash_options else {} self.ignore_dependencies = ignore_dependencies if isinstance(self.comes_from, InstallRequirement): - self.ignore_dependencies = self.comes_from.ignore_dependencies + self.ignore_dependencies |= self.comes_from.ignore_dependencies # Set to True after successful preparation of this requirement self.prepared = False # User supplied requirement are explicitly requested for installation From 5f5a9f920c47c4c75235b139e71a23664159c562 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Sat, 26 Mar 2022 13:12:56 +0300 Subject: [PATCH 11/13] Refactor --- src/pip/_internal/req/req_install.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 0e6f6fbce67..21aebd02c87 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -140,8 +140,11 @@ def __init__( self.global_options = global_options if global_options else [] self.hash_options = hash_options if hash_options else {} self.ignore_dependencies = ignore_dependencies - if isinstance(self.comes_from, InstallRequirement): - self.ignore_dependencies |= self.comes_from.ignore_dependencies + if ( + isinstance(comes_from, InstallRequirement) + and comes_from.ignore_dependencies + ): + self.ignore_dependencies = True # Set to True after successful preparation of this requirement self.prepared = False # User supplied requirement are explicitly requested for installation From ccf3d95af4f086a76ca96c3a29c540bb1ef1882f Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Mon, 19 Jun 2023 17:46:55 +0300 Subject: [PATCH 12/13] Raise exception if the legacy resolver is used --- src/pip/_internal/req/req_file.py | 8 ++++++++ tests/functional/test_install_reqs.py | 24 ++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py index 19b89530dee..705f04a4e85 100644 --- a/src/pip/_internal/req/req_file.py +++ b/src/pip/_internal/req/req_file.py @@ -193,6 +193,14 @@ def handle_requirement_line( req_options = {} for dest in SUPPORTED_OPTIONS_REQ_DEST: if dest in line.opts.__dict__ and line.opts.__dict__[dest]: + if ( + dest == "ignore_dependencies" + and options + and "legacy-resolver" in options.deprecated_features_enabled + ): + raise RequirementsFileParseError( + "Cannot ignore dependencies with legacy resolver" + ) req_options[dest] = line.opts.__dict__[dest] line_source = f"line {line.lineno} of {line.filename}" diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index 8c421c9bd9b..a7b30e59bff 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -819,7 +819,9 @@ def test_config_settings_local_to_package( assert "--verbose" not in simple2_args -def test_install_options_no_deps(script: PipTestEnvironment) -> None: +def test_install_options_no_deps( + script: PipTestEnvironment, resolver_variant: ResolverVariant +) -> None: create_basic_wheel_for_package( script, "A", "0.1.0", depends=["B==0.1.0"], extras={"C": ["C"]} ) @@ -829,7 +831,7 @@ def test_install_options_no_deps(script: PipTestEnvironment) -> None: requirements_txt = script.scratch_path / "requirements.txt" requirements_txt.write_text("A[C] --no-deps") - script.pip( + result = script.pip( "install", "--no-cache-dir", "--find-links", @@ -837,12 +839,17 @@ def test_install_options_no_deps(script: PipTestEnvironment) -> None: "-r", requirements_txt, "--only-binary=:all:", + expect_error=(resolver_variant == "legacy"), + allow_stderr_warning=True, ) - script.assert_installed(A="0.1.0") - script.assert_not_installed("B", "C") + if resolver_variant == "legacy": + assert "Cannot ignore dependencies with legacy resolver" in result.stderr + else: + script.assert_installed(A="0.1.0") + script.assert_not_installed("B", "C") # AlreadyInstalledCandidate should not install dependencies - script.pip( + result = script.pip( "install", "--no-cache-dir", "--find-links", @@ -850,5 +857,10 @@ def test_install_options_no_deps(script: PipTestEnvironment) -> None: "-r", requirements_txt, "--only-binary=:all:", + expect_error=(resolver_variant == "legacy"), + allow_stderr_warning=True, ) - script.assert_not_installed("B", "C") + if resolver_variant == "legacy": + assert "Cannot ignore dependencies with legacy resolver" in result.stderr + else: + script.assert_not_installed("B", "C") From 61ab8fbffec23d1e3d2ca6d2b97eb247b0fc2316 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:02:23 +0300 Subject: [PATCH 13/13] Test deps without no-deps flag --- tests/functional/test_install_reqs.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index a7b30e59bff..474da1864a3 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -827,9 +827,11 @@ def test_install_options_no_deps( ) create_basic_wheel_for_package(script, "B", "0.1.0") create_basic_wheel_for_package(script, "C", "0.1.0") + create_basic_wheel_for_package(script, "D", "0.1.0", depends=["E==0.1.0"]) + create_basic_wheel_for_package(script, "E", "0.1.0") requirements_txt = script.scratch_path / "requirements.txt" - requirements_txt.write_text("A[C] --no-deps") + requirements_txt.write_text("A[C] --no-deps\nD") result = script.pip( "install", @@ -845,7 +847,7 @@ def test_install_options_no_deps( if resolver_variant == "legacy": assert "Cannot ignore dependencies with legacy resolver" in result.stderr else: - script.assert_installed(A="0.1.0") + script.assert_installed(A="0.1.0", D="0.1.0", E="0.1.0") script.assert_not_installed("B", "C") # AlreadyInstalledCandidate should not install dependencies @@ -863,4 +865,5 @@ def test_install_options_no_deps( if resolver_variant == "legacy": assert "Cannot ignore dependencies with legacy resolver" in result.stderr else: + script.assert_installed(A="0.1.0", D="0.1.0", E="0.1.0") script.assert_not_installed("B", "C")