From 90cf29b0a509a4835c457a0a4f86301c82e4cf15 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Wed, 1 Jun 2022 09:38:34 +0300 Subject: [PATCH 1/8] Add `--ignore-packages` to `pip check` --- news/11157.feature.rst | 1 + src/pip/_internal/commands/check.py | 20 ++++++++++++++++++-- tests/functional/test_check.py | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 news/11157.feature.rst diff --git a/news/11157.feature.rst b/news/11157.feature.rst new file mode 100644 index 00000000000..2d67b4daedd --- /dev/null +++ b/news/11157.feature.rst @@ -0,0 +1 @@ +Add ``--ignore-packages`` flag to ``pip check`` to ignore specific packages. diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py index 3864220b2b4..93746abd7e4 100644 --- a/src/pip/_internal/commands/check.py +++ b/src/pip/_internal/commands/check.py @@ -1,6 +1,6 @@ import logging from optparse import Values -from typing import List +from typing import Callable, List, Optional from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS @@ -19,10 +19,26 @@ class CheckCommand(Command): usage = """ %prog [options]""" + def add_options(self) -> None: + self.cmd_opts.add_option( + "--ignore-packages", + action="append", + metavar="PACKAGE", + dest="ignore_packages", + default=[], + help="Ignore packages.", + ) + self.parser.insert_option_group(0, self.cmd_opts) + def run(self, options: Values, args: List[str]) -> int: package_set, parsing_probs = create_package_set_from_installed() - missing, conflicting = check_package_set(package_set) + should_ignore: Optional[Callable[[str], bool]] = ( + (lambda p: p in options.ignore_packages) + if options.ignore_packages + else None + ) + missing, conflicting = check_package_set(package_set, should_ignore) for project_name in missing: version = package_set[project_name].version diff --git a/tests/functional/test_check.py b/tests/functional/test_check.py index e2b1c60ef3a..13fea7b8c17 100644 --- a/tests/functional/test_check.py +++ b/tests/functional/test_check.py @@ -309,3 +309,21 @@ def test_check_include_work_dir_pkg(script: PipTestEnvironment) -> None: expected_lines = ("simple 1.0 requires missing, which is not installed.",) assert matches_expected_lines(result.stdout, expected_lines) assert result.returncode == 1 + + +def test_check_ignore_packages(script: PipTestEnvironment) -> None: + package_a_path = create_test_package_with_setup( + script, + name="package_A", + version="1.0", + install_requires=["missing>=1.0"], + ) + + # Without dependency + result = script.pip("install", "--no-index", package_a_path, "--no-deps") + assert "Successfully installed package-A-1.0" in result.stdout, str(result) + + result = script.pip("check", "--ignore-packages=package-a") + expected_lines = ("No broken requirements found.",) + assert matches_expected_lines(result.stdout, expected_lines) + assert result.returncode == 0 From cf65b19904ab5284b1bc49c050c36e48fe81fd7f Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Thu, 2 Jun 2022 20:45:50 +0300 Subject: [PATCH 2/8] Check dependencies --- src/pip/_internal/commands/check.py | 16 ++++++++++++---- src/pip/_internal/operations/check.py | 3 +++ tests/functional/test_check.py | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py index 93746abd7e4..133e50b6944 100644 --- a/src/pip/_internal/commands/check.py +++ b/src/pip/_internal/commands/check.py @@ -1,5 +1,5 @@ import logging -from optparse import Values +from optparse import Option, OptionParser, Values from typing import Callable, List, Optional from pip._internal.cli.base_command import Command @@ -13,6 +13,13 @@ logger = logging.getLogger(__name__) +def _handle_ignore_packages( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + assert option.dest is not None + setattr(parser.values, option.dest, set(value.split(","))) + + class CheckCommand(Command): """Verify installed packages have compatible dependencies.""" @@ -22,10 +29,11 @@ class CheckCommand(Command): def add_options(self) -> None: self.cmd_opts.add_option( "--ignore-packages", - action="append", - metavar="PACKAGE", + action="callback", + callback=_handle_ignore_packages, + metavar="package", + type="str", dest="ignore_packages", - default=[], help="Ignore packages.", ) self.parser.insert_option_group(0, self.cmd_opts) diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py index fb3ac8b9c9e..f75e1c8ee93 100644 --- a/src/pip/_internal/operations/check.py +++ b/src/pip/_internal/operations/check.py @@ -71,6 +71,9 @@ def check_package_set( for req in package_detail.dependencies: name = canonicalize_name(req.name) + if should_ignore and should_ignore(name): + continue + # Check if it's missing if name not in package_set: missed = True diff --git a/tests/functional/test_check.py b/tests/functional/test_check.py index 13fea7b8c17..3deca0f62c4 100644 --- a/tests/functional/test_check.py +++ b/tests/functional/test_check.py @@ -323,7 +323,7 @@ def test_check_ignore_packages(script: PipTestEnvironment) -> None: result = script.pip("install", "--no-index", package_a_path, "--no-deps") assert "Successfully installed package-A-1.0" in result.stdout, str(result) - result = script.pip("check", "--ignore-packages=package-a") + result = script.pip("check", "--ignore-packages=missing") expected_lines = ("No broken requirements found.",) assert matches_expected_lines(result.stdout, expected_lines) assert result.returncode == 0 From 3948fd655a06902f054d9b284614c10df4437800 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Tue, 14 Jun 2022 20:06:57 +0300 Subject: [PATCH 3/8] Add should_ignore_dependencies --- src/pip/_internal/commands/check.py | 13 ++++++------- src/pip/_internal/operations/check.py | 6 ++++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py index 133e50b6944..390421ba505 100644 --- a/src/pip/_internal/commands/check.py +++ b/src/pip/_internal/commands/check.py @@ -1,6 +1,6 @@ import logging from optparse import Option, OptionParser, Values -from typing import Callable, List, Optional +from typing import Callable, List from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS @@ -31,22 +31,21 @@ def add_options(self) -> None: "--ignore-packages", action="callback", callback=_handle_ignore_packages, + default=set(), metavar="package", type="str", dest="ignore_packages", - help="Ignore packages.", + help="Ignore comma-separated packages.", ) self.parser.insert_option_group(0, self.cmd_opts) def run(self, options: Values, args: List[str]) -> int: package_set, parsing_probs = create_package_set_from_installed() - should_ignore: Optional[Callable[[str], bool]] = ( - (lambda p: p in options.ignore_packages) - if options.ignore_packages - else None + should_ignore: Callable[[str], bool] = lambda p: p in options.ignore_packages + missing, conflicting = check_package_set( + package_set, should_ignore, should_ignore ) - missing, conflicting = check_package_set(package_set, should_ignore) for project_name in missing: version = package_set[project_name].version diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py index f75e1c8ee93..4ebd4833acb 100644 --- a/src/pip/_internal/operations/check.py +++ b/src/pip/_internal/operations/check.py @@ -49,7 +49,9 @@ def create_package_set_from_installed() -> Tuple[PackageSet, bool]: def check_package_set( - package_set: PackageSet, should_ignore: Optional[Callable[[str], bool]] = None + package_set: PackageSet, + should_ignore: Optional[Callable[[str], bool]] = None, + should_ignore_dependencies: Optional[Callable[[str], bool]] = None, ) -> CheckResult: """Check if a package set is consistent @@ -71,7 +73,7 @@ def check_package_set( for req in package_detail.dependencies: name = canonicalize_name(req.name) - if should_ignore and should_ignore(name): + if should_ignore_dependencies and should_ignore_dependencies(name): continue # Check if it's missing From b7f608ae1cb9037a865529fa871b9b49957da6a9 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Wed, 15 Jun 2022 05:07:11 +0300 Subject: [PATCH 4/8] Set default should_ignore to None --- src/pip/_internal/commands/check.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py index 390421ba505..896078b64e6 100644 --- a/src/pip/_internal/commands/check.py +++ b/src/pip/_internal/commands/check.py @@ -1,6 +1,6 @@ import logging from optparse import Option, OptionParser, Values -from typing import Callable, List +from typing import Callable, List, Optional from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS @@ -31,7 +31,6 @@ def add_options(self) -> None: "--ignore-packages", action="callback", callback=_handle_ignore_packages, - default=set(), metavar="package", type="str", dest="ignore_packages", @@ -42,7 +41,11 @@ def add_options(self) -> None: def run(self, options: Values, args: List[str]) -> int: package_set, parsing_probs = create_package_set_from_installed() - should_ignore: Callable[[str], bool] = lambda p: p in options.ignore_packages + should_ignore: Optional[Callable[[str], bool]] = ( + (lambda p: p in options.ignore_packages) + if options.ignore_packages + else None + ) missing, conflicting = check_package_set( package_set, should_ignore, should_ignore ) From 5f5354276a99d8edf607c085c89b3c3fe98a1c29 Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Thu, 28 Jul 2022 07:00:53 +0300 Subject: [PATCH 5/8] Add recursive-ignore flag --- src/pip/_internal/commands/check.py | 28 ++++++++++++++-------------- tests/functional/test_check.py | 25 ++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py index 896078b64e6..d047ba33842 100644 --- a/src/pip/_internal/commands/check.py +++ b/src/pip/_internal/commands/check.py @@ -1,5 +1,5 @@ import logging -from optparse import Option, OptionParser, Values +from optparse import Values from typing import Callable, List, Optional from pip._internal.cli.base_command import Command @@ -13,13 +13,6 @@ logger = logging.getLogger(__name__) -def _handle_ignore_packages( - option: Option, opt_str: str, value: str, parser: OptionParser -) -> None: - assert option.dest is not None - setattr(parser.values, option.dest, set(value.split(","))) - - class CheckCommand(Command): """Verify installed packages have compatible dependencies.""" @@ -29,12 +22,17 @@ class CheckCommand(Command): def add_options(self) -> None: self.cmd_opts.add_option( "--ignore-packages", - action="callback", - callback=_handle_ignore_packages, - metavar="package", - type="str", dest="ignore_packages", - help="Ignore comma-separated packages.", + action="append", + default=[], + help="Ignore packages.", + ) + self.cmd_opts.add_option( + "--recursive-ignore", + dest="recursive_ignore", + action="store_true", + default=False, + help="Ignore sub-dependencies.", ) self.parser.insert_option_group(0, self.cmd_opts) @@ -47,7 +45,9 @@ def run(self, options: Values, args: List[str]) -> int: else None ) missing, conflicting = check_package_set( - package_set, should_ignore, should_ignore + package_set, + should_ignore, + should_ignore if options.recursive_ignore else None, ) for project_name in missing: diff --git a/tests/functional/test_check.py b/tests/functional/test_check.py index 3deca0f62c4..e72adfb018a 100644 --- a/tests/functional/test_check.py +++ b/tests/functional/test_check.py @@ -323,7 +323,30 @@ def test_check_ignore_packages(script: PipTestEnvironment) -> None: result = script.pip("install", "--no-index", package_a_path, "--no-deps") assert "Successfully installed package-A-1.0" in result.stdout, str(result) - result = script.pip("check", "--ignore-packages=missing") + result = script.pip("check", "--ignore-packages=package-a") + expected_lines = ("No broken requirements found.",) + assert matches_expected_lines(result.stdout, expected_lines) + assert result.returncode == 0 + + result = script.pip("check", "--ignore-packages=missing", expect_error=True) + expected_lines = ("package-a 1.0 requires missing, which is not installed.",) + assert matches_expected_lines(result.stdout, expected_lines) + assert result.returncode == 1 + + +def test_check_ignore_packages_recursive(script: PipTestEnvironment) -> None: + package_a_path = create_test_package_with_setup( + script, + name="package_A", + version="1.0", + install_requires=["missing>=1.0"], + ) + + # Without dependency + result = script.pip("install", "--no-index", package_a_path, "--no-deps") + assert "Successfully installed package-A-1.0" in result.stdout, str(result) + + result = script.pip("check", "--ignore-packages=missing", "--recursive-ignore") expected_lines = ("No broken requirements found.",) assert matches_expected_lines(result.stdout, expected_lines) assert result.returncode == 0 From 092f41f8218dbf4a5878f1b1204d0f62a6f16d5d Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Fri, 28 Jul 2023 07:49:00 +0300 Subject: [PATCH 6/8] Log ignored sub-dependencies --- src/pip/_internal/operations/check.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py index de8148fa268..f774f6d51af 100644 --- a/src/pip/_internal/operations/check.py +++ b/src/pip/_internal/operations/check.py @@ -79,6 +79,7 @@ def check_package_set( name = canonicalize_name(req.name) if should_ignore_dependencies and should_ignore_dependencies(name): + logger.debug("%s was ignored because --recursive-ignore is set", name) continue # Check if it's missing From 232aff2a7ede57915591c32a4417b79f795d790e Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 28 Jul 2023 12:56:32 +0800 Subject: [PATCH 7/8] Use operator.contains instead of lambda --- src/pip/_internal/commands/check.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py index ebc5ccb29fa..d5422ad293a 100644 --- a/src/pip/_internal/commands/check.py +++ b/src/pip/_internal/commands/check.py @@ -1,4 +1,6 @@ +import functools import logging +import operator from optparse import Values from typing import Callable, List, Optional @@ -41,7 +43,7 @@ def run(self, options: Values, args: List[str]) -> int: package_set, parsing_probs = create_package_set_from_installed() warn_legacy_versions_and_specifiers(package_set) should_ignore: Optional[Callable[[str], bool]] = ( - (lambda p: p in options.ignore_packages) + functools.partial(operator.contains, options.ignore_packages) if options.ignore_packages else None ) From 871ee502bf37a71799c3b4a714ee76938cf5878f Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:50:59 +0300 Subject: [PATCH 8/8] Add docstring --- src/pip/_internal/operations/check.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py index f774f6d51af..5d467233c43 100644 --- a/src/pip/_internal/operations/check.py +++ b/src/pip/_internal/operations/check.py @@ -58,8 +58,11 @@ def check_package_set( ) -> CheckResult: """Check if a package set is consistent - If should_ignore is passed, it should be a callable that takes a - package name and returns a boolean. + If should_ignore/should_ignore_dependencies is passed, it should + be a callable that takes a package name and returns a boolean. + + should_ignore_dependencies should be used to filter out dependencies + of packages specified in package_set. """ warn_legacy_versions_and_specifiers(package_set)