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 --ignore-packages to pip check #11159

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
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/11157.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``--ignore-packages`` flag to ``pip check`` to ignore specific packages.
32 changes: 30 additions & 2 deletions src/pip/_internal/commands/check.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import functools
import logging
import operator
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
Expand All @@ -20,10 +22,36 @@ class CheckCommand(Command):
usage = """
%prog [options]"""

def add_options(self) -> None:
self.cmd_opts.add_option(
"--ignore-packages",
dest="ignore_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)

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)
missing, conflicting = check_package_set(package_set)
should_ignore: Optional[Callable[[str], bool]] = (
functools.partial(operator.contains, options.ignore_packages)
if options.ignore_packages
else None
)
missing, conflicting = check_package_set(
package_set,
should_ignore,
should_ignore if options.recursive_ignore else None,
)

for project_name in missing:
version = package_set[project_name].version
Expand Down
15 changes: 12 additions & 3 deletions src/pip/_internal/operations/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ 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,
Comment on lines +56 to +57
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have a docstring on this function explaining how the two arguments are different

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like this?

) -> 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)
Expand All @@ -76,6 +81,10 @@ def check_package_set(
for req in package_detail.dependencies:
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
q0w marked this conversation as resolved.
Show resolved Hide resolved

# Check if it's missing
if name not in package_set:
missed = True
Expand Down
41 changes: 41 additions & 0 deletions tests/functional/test_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,44 @@ 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

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