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 --exclude option to pip freeze and pip list commands #9073

Merged
merged 1 commit into from
Oct 31, 2020
Merged
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/4256.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``--exclude`` option to ``pip freeze`` and ``pip list`` commands to explicitly exclude packages from the output.
2 changes: 2 additions & 0 deletions news/4256.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``pip freeze`` will stop filtering the ``pip``, ``setuptools``, ``distribute`` and ``wheel`` packages from ``pip freeze`` output in a future version.
To keep the previous behavior, users should use the new ``--exclude`` option.
21 changes: 20 additions & 1 deletion src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from optparse import SUPPRESS_HELP, Option, OptionGroup
from textwrap import dedent

from pip._vendor.packaging.utils import canonicalize_name

from pip._internal.cli.progress_bars import BAR_TYPES
from pip._internal.exceptions import CommandError
from pip._internal.locations import USER_CACHE_DIR, get_src_prefix
Expand Down Expand Up @@ -133,9 +135,15 @@ def _path_option_check(option, opt, value):
return os.path.expanduser(value)


def _package_name_option_check(option, opt, value):
# type: (Option, str, str) -> str
return canonicalize_name(value)


class PipOption(Option):
TYPES = Option.TYPES + ("path",)
TYPES = Option.TYPES + ("path", "package_name")
TYPE_CHECKER = Option.TYPE_CHECKER.copy()
TYPE_CHECKER["package_name"] = _package_name_option_check
TYPE_CHECKER["path"] = _path_option_check


Expand Down Expand Up @@ -866,6 +874,17 @@ def check_list_path_option(options):
)


list_exclude = partial(
PipOption,
'--exclude',
dest='excludes',
action='append',
metavar='package',
type='package_name',
help="Exclude specified package from the output",
) # type: Callable[..., Option]


no_python_version_warning = partial(
Option,
'--no-python-version-warning',
Expand Down
4 changes: 4 additions & 0 deletions src/pip/_internal/commands/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def add_options(self):
dest='exclude_editable',
action='store_true',
help='Exclude editable package from output.')
self.cmd_opts.add_option(cmdoptions.list_exclude())

self.parser.insert_option_group(0, self.cmd_opts)

Expand All @@ -85,6 +86,9 @@ def run(self, options, args):
if not options.freeze_all:
skip.update(DEV_PKGS)

if options.excludes:
skip.update(options.excludes)

cmdoptions.check_list_path_option(options)

if options.find_links:
Expand Down
7 changes: 7 additions & 0 deletions src/pip/_internal/commands/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pip._internal.index.collector import LinkCollector
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.utils.compat import stdlib_pkgs
from pip._internal.utils.misc import (
dist_is_editable,
get_installed_distributions,
Expand Down Expand Up @@ -114,6 +115,7 @@ def add_options(self):
help='Include editable package from output.',
default=True,
)
self.cmd_opts.add_option(cmdoptions.list_exclude())
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group, self.parser
)
Expand Down Expand Up @@ -147,12 +149,17 @@ def run(self, options, args):

cmdoptions.check_list_path_option(options)

skip = set(stdlib_pkgs)
if options.excludes:
skip.update(options.excludes)

packages = get_installed_distributions(
local_only=options.local,
user_only=options.user,
editables_only=options.editable,
include_editables=options.include_editable,
paths=options.path,
skip=skip,
)

# get_not_required must be called firstly in order to find and
Expand Down
20 changes: 20 additions & 0 deletions tests/functional/test_freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
need_mercurial,
need_svn,
path_to_url,
wheel,
)

distribute_re = re.compile('^distribute==[0-9.]+\n', re.MULTILINE)
Expand Down Expand Up @@ -80,6 +81,25 @@ def test_freeze_with_pip(script):
assert 'pip==' in result.stdout


def test_exclude_and_normalization(script, tmpdir):
req_path = wheel.make_wheel(
name="Normalizable_Name", version="1.0").save_to_dir(tmpdir)
script.pip("install", "--no-index", req_path)
result = script.pip("freeze")
assert "Normalizable-Name" in result.stdout
result = script.pip("freeze", "--exclude", "normalizablE-namE")
assert "Normalizable-Name" not in result.stdout


def test_freeze_multiple_exclude_with_all(script, with_wheel):
result = script.pip('freeze', '--all')
assert 'pip==' in result.stdout
assert 'wheel==' in result.stdout
result = script.pip('freeze', '--all', '--exclude', 'pip', '--exclude', 'wheel')
assert 'pip==' not in result.stdout
assert 'wheel==' not in result.stdout


def test_freeze_with_invalid_names(script):
"""
Test that invalid names produce warnings and are passed over gracefully.
Expand Down
15 changes: 14 additions & 1 deletion tests/functional/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from tests.lib import create_test_package_with_setup
from tests.lib import create_test_package_with_setup, wheel
from tests.lib.path import Path


Expand Down Expand Up @@ -94,6 +94,19 @@ def test_local_columns_flag(simple_script):
assert 'simple 1.0' in result.stdout, str(result)


def test_multiple_exclude_and_normalization(script, tmpdir):
req_path = wheel.make_wheel(
name="Normalizable_Name", version="1.0").save_to_dir(tmpdir)
script.pip("install", "--no-index", req_path)
result = script.pip("list")
print(result.stdout)
assert "Normalizable-Name" in result.stdout
assert "pip" in result.stdout
result = script.pip("list", "--exclude", "normalizablE-namE", "--exclude", "pIp")
assert "Normalizable-Name" not in result.stdout
assert "pip" not in result.stdout


@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
def test_user_flag(script, data):
Expand Down