From 9f0e439226ef4b7b97b46ca070a143127026b244 Mon Sep 17 00:00:00 2001 From: ciarancourtney Date: Sun, 11 Mar 2018 17:53:23 +0000 Subject: [PATCH 1/8] [closes #69] Add '--exclude' arg to exclude CSV list of packages and children from output --- README.rst | 3 +++ pipdeptree.py | 18 +++++++++++++++--- tests/test_pipdeptree.py | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 8657020..1d6c190 100644 --- a/README.rst +++ b/README.rst @@ -263,6 +263,9 @@ Usage -p PACKAGES, --packages PACKAGES Comma separated list of select packages to show in the output. If set, --all will be ignored. + -e PACKAGES, --exclude PACKAGES + Comma separated list of select packages to exclude from + the output. If set, --all will be ignored. -j, --json Display dependency tree as json. This will yield "raw" output that may be used by external tools. This option overrides all other options. diff --git a/pipdeptree.py b/pipdeptree.py index 7283281..b4dba41 100644 --- a/pipdeptree.py +++ b/pipdeptree.py @@ -280,8 +280,8 @@ class ReqPackage(Package): 'required_version': self.version_spec} -def render_tree(tree, list_all=True, show_only=None, frozen=False): - """Convert to tree to string representation +def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=[]): + """Convert tree to string representation :param dict tree: the package tree :param bool list_all: whether to list all the pgks at the root @@ -310,6 +310,8 @@ def render_tree(tree, list_all=True, show_only=None, frozen=False): nodes = [p for p in nodes if p.key not in branch_keys] def aux(node, parent=None, indent=0, chain=None): + if node.key in exclude or node.project_name in exclude: + return [] if chain is None: chain = [node.project_name] node_str = node.render(parent, frozen) @@ -527,6 +529,11 @@ def get_parser(): 'Comma separated list of select packages to show ' 'in the output. If set, --all will be ignored.' )) + parser.add_argument('-e', '--exclude', + help=( + 'Comma separated list of select packages to exclude ' + 'from the output. If set, --all will be ignored.' + ), metavar='PACKAGES') parser.add_argument('-j', '--json', action='store_true', default=False, help=( 'Display dependency tree as json. This will yield ' @@ -600,10 +607,15 @@ def main(): return_code = 1 show_only = set(args.packages.split(',')) if args.packages else None + exclude = set(args.exclude.split(',')) if args.exclude else [] + + if show_only and exclude: + print('Cannot use --packages and --exclude args together.', file=sys.stderr) + sys.exit(1) tree = render_tree(tree if not args.reverse else reverse_tree(tree), list_all=args.all, show_only=show_only, - frozen=args.freeze) + frozen=args.freeze, exclude=exclude) print(tree) return return_code diff --git a/tests/test_pipdeptree.py b/tests/test_pipdeptree.py index 1e13920..682526f 100644 --- a/tests/test_pipdeptree.py +++ b/tests/test_pipdeptree.py @@ -122,6 +122,24 @@ def test_render_tree_list_all(): assert 'itsdangerous==0.23' in lines +def test_render_tree_exclude(): + tree_str = render_tree(tree, list_all=True, exclude=['itsdangerous', 'SQLAlchemy', 'Flask', 'markupsafe']) + assert tree_str == """\ +alembic==0.6.2 + - Mako [required: Any, installed: 0.9.1] +Flask-Script==0.6.6 +gnureadline==6.3.8 +ipython==2.0.0 +Jinja2==2.7.2 +Lookupy==0.1 +Mako==0.9.1 +psycopg2==2.7.3.2 +redis==2.9.1 +slugify==0.0.1 +Werkzeug==0.9.4 +wheel==0.30.0""" + + def test_render_tree_freeze(): tree_str = render_tree(tree, list_all=False, frozen=True) lines = set() From d22cffb19ffc813daca447a6a05e0b394dab4155 Mon Sep 17 00:00:00 2001 From: ciarancourtney Date: Mon, 12 Mar 2018 09:31:01 +0000 Subject: [PATCH 2/8] upgrade gnureadline 6.3.3 > 6.3.8 to avoid install issues (has wheels for py27 > py36) --- tests/virtualenvs/testenv_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/virtualenvs/testenv_requirements.txt b/tests/virtualenvs/testenv_requirements.txt index eeae223..4e9b266 100644 --- a/tests/virtualenvs/testenv_requirements.txt +++ b/tests/virtualenvs/testenv_requirements.txt @@ -7,7 +7,7 @@ MarkupSafe==0.18 SQLAlchemy==0.9.1 Werkzeug==0.9.4 alembic==0.6.2 -gnureadline==6.3.3 +gnureadline==6.3.8 ipython==2.0.0 itsdangerous==0.23 psycopg2==2.7.3.2 From 6910831af2c71eb12d58c5961f555ab533c26d4f Mon Sep 17 00:00:00 2001 From: ciarancourtney Date: Sun, 8 Apr 2018 21:59:39 +0100 Subject: [PATCH 3/8] Remove wheel from tests (auto-updates self) --- tests/test_pipdeptree.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_pipdeptree.py b/tests/test_pipdeptree.py index 682526f..3df2905 100644 --- a/tests/test_pipdeptree.py +++ b/tests/test_pipdeptree.py @@ -123,7 +123,7 @@ def test_render_tree_list_all(): def test_render_tree_exclude(): - tree_str = render_tree(tree, list_all=True, exclude=['itsdangerous', 'SQLAlchemy', 'Flask', 'markupsafe']) + tree_str = render_tree(tree, list_all=True, exclude=['itsdangerous', 'SQLAlchemy', 'Flask', 'markupsafe', 'wheel']) assert tree_str == """\ alembic==0.6.2 - Mako [required: Any, installed: 0.9.1] @@ -136,8 +136,7 @@ Mako==0.9.1 psycopg2==2.7.3.2 redis==2.9.1 slugify==0.0.1 -Werkzeug==0.9.4 -wheel==0.30.0""" +Werkzeug==0.9.4""" def test_render_tree_freeze(): From eb886d9a2f0430c9e7bc04d6390d58edf6db103d Mon Sep 17 00:00:00 2001 From: ciarancourtney Date: Sun, 8 Apr 2018 22:00:45 +0100 Subject: [PATCH 4/8] Add test for --exclude and --reverse --- tests/test_pipdeptree.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_pipdeptree.py b/tests/test_pipdeptree.py index 3df2905..5b9db46 100644 --- a/tests/test_pipdeptree.py +++ b/tests/test_pipdeptree.py @@ -139,6 +139,25 @@ slugify==0.0.1 Werkzeug==0.9.4""" +def test_render_tree_exclude_reverse(): + rtree = reverse_tree(tree) + + tree_str = render_tree(rtree, list_all=True, exclude=['itsdangerous', 'SQLAlchemy', 'Flask', 'markupsafe', 'wheel']) + assert tree_str == """\ +alembic==0.6.2 +Flask-Script==0.6.6 +gnureadline==6.3.8 +ipython==2.0.0 +Jinja2==2.7.2 +Lookupy==0.1 +Mako==0.9.1 + - alembic==0.6.2 [requires: Mako] +psycopg2==2.7.3.2 +redis==2.9.1 +slugify==0.0.1 +Werkzeug==0.9.4""" + + def test_render_tree_freeze(): tree_str = render_tree(tree, list_all=False, frozen=True) lines = set() From 1bcd65dc3a3b0d71fafc74a44e652d01a8b6ae8d Mon Sep 17 00:00:00 2001 From: ciarancourtney Date: Sun, 15 Apr 2018 22:03:08 +0100 Subject: [PATCH 5/8] Using --packages and --exclude together is fine as long as they are distinct * pass args into main() to make testing easier --- pipdeptree.py | 19 ++++++++++--------- tests/test_pipdeptree.py | 26 +++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/pipdeptree.py b/pipdeptree.py index b4dba41..98e145a 100644 --- a/pipdeptree.py +++ b/pipdeptree.py @@ -280,7 +280,7 @@ class ReqPackage(Package): 'required_version': self.version_spec} -def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=[]): +def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=set()): """Convert tree to string representation :param dict tree: the package tree @@ -291,6 +291,8 @@ def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=[]): output. This is optional arg, default: None. :param bool frozen: whether or not show the names of the pkgs in the output that's favourable to pip --freeze + :param set exclude: set of select packages to be excluded from the + output. This is optional arg, default: empty set(). :returns: string representation of the tree :rtype: str @@ -555,10 +557,7 @@ def get_parser(): return parser -def main(): - parser = get_parser() - args = parser.parse_args() - +def main(args): pkgs = get_installed_distributions(local_only=args.local_only, user_only=args.user_only) @@ -607,10 +606,10 @@ def main(): return_code = 1 show_only = set(args.packages.split(',')) if args.packages else None - exclude = set(args.exclude.split(',')) if args.exclude else [] + exclude = set(args.exclude.split(',')) if args.exclude else set() - if show_only and exclude: - print('Cannot use --packages and --exclude args together.', file=sys.stderr) + if show_only and (show_only & exclude): + print('Conflicting packages found in --packages and --exclude lists.', file=sys.stderr) sys.exit(1) tree = render_tree(tree if not args.reverse else reverse_tree(tree), @@ -621,4 +620,6 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + parser = get_parser() + args = parser.parse_args() + sys.exit(main(args)) diff --git a/tests/test_pipdeptree.py b/tests/test_pipdeptree.py index 5b9db46..04beb3c 100644 --- a/tests/test_pipdeptree.py +++ b/tests/test_pipdeptree.py @@ -6,11 +6,13 @@ from contextlib import contextmanager from tempfile import NamedTemporaryFile from operator import attrgetter +import pytest + from pipdeptree import (build_dist_index, construct_tree, DistPackage, ReqPackage, render_tree, reverse_tree, cyclic_deps, conflicting_deps, get_parser, render_json, render_json_tree, - dump_graphviz, print_graphviz) + dump_graphviz, print_graphviz, main) def venv_fixture(pickle_file): @@ -334,3 +336,25 @@ def test_conflicting_deps(): flask: [jinja], uritemplate: [simplejson], } + + +def test_main_basic(): + parser = get_parser() + args = parser.parse_args('') + + assert main(args) == 0 + + +def test_main_show_only_and_exclude_ok(): + parser = get_parser() + args = parser.parse_args('--packages Flask --exclude Jinja2'.split()) + + assert main(args) == 0 + + +def test_main_show_only_and_exclude_fails(): + parser = get_parser() + args = parser.parse_args('--packages Flask --exclude Jinja2,Flask'.split()) + + with pytest.raises(SystemExit): + main(args) From 1e075432e4f98345aef32c636d6d4650997321a4 Mon Sep 17 00:00:00 2001 From: ciarancourtney Date: Tue, 1 May 2018 19:27:59 +0100 Subject: [PATCH 6/8] exclude should default to None like show_only --- pipdeptree.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pipdeptree.py b/pipdeptree.py index 98e145a..c5ab50d 100644 --- a/pipdeptree.py +++ b/pipdeptree.py @@ -280,7 +280,7 @@ class ReqPackage(Package): 'required_version': self.version_spec} -def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=set()): +def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=None): """Convert tree to string representation :param dict tree: the package tree @@ -292,7 +292,7 @@ def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=set() :param bool frozen: whether or not show the names of the pkgs in the output that's favourable to pip --freeze :param set exclude: set of select packages to be excluded from the - output. This is optional arg, default: empty set(). + output. This is optional arg, default: None. :returns: string representation of the tree :rtype: str @@ -312,7 +312,7 @@ def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=set() nodes = [p for p in nodes if p.key not in branch_keys] def aux(node, parent=None, indent=0, chain=None): - if node.key in exclude or node.project_name in exclude: + if exclude and (node.key in exclude or node.project_name in exclude): return [] if chain is None: chain = [node.project_name] @@ -606,9 +606,9 @@ def main(args): return_code = 1 show_only = set(args.packages.split(',')) if args.packages else None - exclude = set(args.exclude.split(',')) if args.exclude else set() + exclude = set(args.exclude.split(',')) if args.exclude else None - if show_only and (show_only & exclude): + if show_only and exclude and (show_only & exclude): print('Conflicting packages found in --packages and --exclude lists.', file=sys.stderr) sys.exit(1) From 1a15137510f422d456e818079cbb5febfd5c419a Mon Sep 17 00:00:00 2001 From: ciarancourtney Date: Tue, 1 May 2018 19:29:13 +0100 Subject: [PATCH 7/8] restore arg-less main(), abstart args to _get_args() and monkeypatch in tests --- pipdeptree.py | 12 ++++++++---- tests/test_pipdeptree.py | 24 ++++++++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/pipdeptree.py b/pipdeptree.py index c5ab50d..4d09141 100644 --- a/pipdeptree.py +++ b/pipdeptree.py @@ -557,7 +557,13 @@ def get_parser(): return parser -def main(args): +def _get_args(): + parser = get_parser() + return parser.parse_args() + + +def main(): + args = _get_args() pkgs = get_installed_distributions(local_only=args.local_only, user_only=args.user_only) @@ -620,6 +626,4 @@ def main(args): if __name__ == '__main__': - parser = get_parser() - args = parser.parse_args() - sys.exit(main(args)) + sys.exit(main()) diff --git a/tests/test_pipdeptree.py b/tests/test_pipdeptree.py index 04beb3c..b53bd5d 100644 --- a/tests/test_pipdeptree.py +++ b/tests/test_pipdeptree.py @@ -338,23 +338,35 @@ def test_conflicting_deps(): } -def test_main_basic(): +def test_main_basic(monkeypatch): parser = get_parser() args = parser.parse_args('') - assert main(args) == 0 + def _get_args(): + return args + monkeypatch.setattr('pipdeptree._get_args', _get_args) + + assert main() == 0 -def test_main_show_only_and_exclude_ok(): +def test_main_show_only_and_exclude_ok(monkeypatch): parser = get_parser() args = parser.parse_args('--packages Flask --exclude Jinja2'.split()) - assert main(args) == 0 + def _get_args(): + return args + monkeypatch.setattr('pipdeptree._get_args', _get_args) + + assert main() == 0 -def test_main_show_only_and_exclude_fails(): +def test_main_show_only_and_exclude_fails(monkeypatch): parser = get_parser() args = parser.parse_args('--packages Flask --exclude Jinja2,Flask'.split()) + def _get_args(): + return args + monkeypatch.setattr('pipdeptree._get_args', _get_args) + with pytest.raises(SystemExit): - main(args) + main() From d4bc9f487c10bff1f845098df8dd5234a830621d Mon Sep 17 00:00:00 2001 From: ciarancourtney Date: Tue, 1 May 2018 19:36:47 +0100 Subject: [PATCH 8/8] tests: render_tree() expects exclude arg to be set, not list --- tests/test_pipdeptree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pipdeptree.py b/tests/test_pipdeptree.py index b53bd5d..94b4f49 100644 --- a/tests/test_pipdeptree.py +++ b/tests/test_pipdeptree.py @@ -125,7 +125,7 @@ def test_render_tree_list_all(): def test_render_tree_exclude(): - tree_str = render_tree(tree, list_all=True, exclude=['itsdangerous', 'SQLAlchemy', 'Flask', 'markupsafe', 'wheel']) + tree_str = render_tree(tree, list_all=True, exclude={'itsdangerous', 'SQLAlchemy', 'Flask', 'markupsafe', 'wheel'}) assert tree_str == """\ alembic==0.6.2 - Mako [required: Any, installed: 0.9.1] @@ -144,7 +144,7 @@ Werkzeug==0.9.4""" def test_render_tree_exclude_reverse(): rtree = reverse_tree(tree) - tree_str = render_tree(rtree, list_all=True, exclude=['itsdangerous', 'SQLAlchemy', 'Flask', 'markupsafe', 'wheel']) + tree_str = render_tree(rtree, list_all=True, exclude={'itsdangerous', 'SQLAlchemy', 'Flask', 'markupsafe', 'wheel'}) assert tree_str == """\ alembic==0.6.2 Flask-Script==0.6.6