Skip to content

Commit

Permalink
Implement -d/--depth argument (#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
kemzeb authored Jun 7, 2023
1 parent c010e73 commit b12a6dd
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 11 deletions.
30 changes: 20 additions & 10 deletions src/pipdeptree/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ def reverse(self):
return PackageDAG(dict(m))


def render_text(tree, list_all=True, frozen=False):
def render_text(tree, max_depth, list_all=True, frozen=False):
"""Print tree as text on console
:param dict tree: the package tree
Expand All @@ -471,12 +471,12 @@ def render_text(tree, list_all=True, frozen=False):
nodes = [p for p in nodes if p.key not in branch_keys]

if sys.stdout.encoding.lower() in ("utf-8", "utf-16", "utf-32"):
_render_text_with_unicode(tree, nodes, frozen)
_render_text_with_unicode(tree, nodes, max_depth, frozen)
else:
_render_text_without_unicode(tree, nodes, frozen)
_render_text_without_unicode(tree, nodes, max_depth, frozen)


def _render_text_with_unicode(tree, nodes, frozen):
def _render_text_with_unicode(tree, nodes, max_depth, frozen):
use_bullets = not frozen

def aux(
Expand Down Expand Up @@ -533,7 +533,7 @@ def aux(
parent_is_last_child=is_last_child,
)
for c in children
if c.project_name not in cur_chain
if c.project_name not in cur_chain and depth + 1 <= max_depth
]

result += list(chain.from_iterable(children_strings))
Expand All @@ -543,20 +543,20 @@ def aux(
print("\n".join(lines))


def _render_text_without_unicode(tree, nodes, frozen):
def _render_text_without_unicode(tree, nodes, max_depth, frozen):
use_bullets = not frozen

def aux(node, parent=None, indent=0, cur_chain=None):
def aux(node, parent=None, indent=0, cur_chain=None, depth=0):
cur_chain = cur_chain or []
node_str = node.render(parent, frozen)
if parent:
prefix = " " * indent + ("- " if use_bullets else "")
node_str = prefix + node_str
result = [node_str]
children = [
aux(c, node, indent=indent + 2, cur_chain=cur_chain + [c.project_name])
aux(c, node, indent=indent + 2, cur_chain=cur_chain + [c.project_name], depth=depth + 1)
for c in tree.get_children(node.key)
if c.project_name not in cur_chain
if c.project_name not in cur_chain and depth + 1 <= max_depth
]
result += list(chain.from_iterable(children))
return result
Expand Down Expand Up @@ -971,6 +971,16 @@ def get_parser():
"GraphViz, e.g.: dot, jpeg, pdf, png, svg"
),
)
parser.add_argument(
"-d",
"--depth",
type=lambda x: int(x) if x.isdigit() and (int(x) >= 0) else parser.error("Depth must be a number that is >= 0"),
default=float("inf"),
help=(
"Display dependency tree up to a depth >=0 using the default text display. All other display options"
" ignore this argument."
),
)
return parser


Expand Down Expand Up @@ -1077,6 +1087,6 @@ def main():
output = dump_graphviz(tree, output_format=args.output_format, is_reverse=args.reverse)
print_graphviz(output)
else:
render_text(tree, args.all, args.freeze)
render_text(tree, args.depth, args.all, args.freeze)

return return_code
120 changes: 119 additions & 1 deletion tests/test_pipdeptree.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,104 @@ def test_render_text(capsys, list_all, reverse, unicode, expected_output):
tree = t.reverse() if reverse else t
encoding = "utf-8" if unicode else "ascii"
with mock.patch("sys.stdout", MockStdout(encoding)):
p.render_text(tree, list_all=list_all, frozen=False)
p.render_text(tree, float("inf"), list_all=list_all, frozen=False)
captured = capsys.readouterr()
assert "\n".join(expected_output).strip() == captured.out.strip()


@pytest.mark.parametrize(
("unicode", "level", "expected_output"),
[
(
True,
0,
[
"a==3.4.0",
"b==2.3.1",
"c==5.10.0",
"d==2.35",
"e==0.12.1",
"f==3.1",
"g==6.8.3rc1",
],
),
(
False,
0,
[
"a==3.4.0",
"b==2.3.1",
"c==5.10.0",
"d==2.35",
"e==0.12.1",
"f==3.1",
"g==6.8.3rc1",
],
),
(
True,
2,
[
"a==3.4.0",
"├── b [required: >=2.0.0, installed: 2.3.1]",
"│ └── d [required: >=2.30,<2.42, installed: 2.35]",
"└── c [required: >=5.7.1, installed: 5.10.0]",
" ├── d [required: >=2.30, installed: 2.35]",
" └── e [required: >=0.12.1, installed: 0.12.1]",
"b==2.3.1",
"└── d [required: >=2.30,<2.42, installed: 2.35]",
" └── e [required: >=0.9.0, installed: 0.12.1]",
"c==5.10.0",
"├── d [required: >=2.30, installed: 2.35]",
"│ └── e [required: >=0.9.0, installed: 0.12.1]",
"└── e [required: >=0.12.1, installed: 0.12.1]",
"d==2.35",
"└── e [required: >=0.9.0, installed: 0.12.1]",
"e==0.12.1",
"f==3.1",
"└── b [required: >=2.1.0, installed: 2.3.1]",
" └── d [required: >=2.30,<2.42, installed: 2.35]",
"g==6.8.3rc1",
"├── e [required: >=0.9.0, installed: 0.12.1]",
"└── f [required: >=3.0.0, installed: 3.1]",
" └── b [required: >=2.1.0, installed: 2.3.1]",
],
),
(
False,
2,
[
"a==3.4.0",
" - b [required: >=2.0.0, installed: 2.3.1]",
" - d [required: >=2.30,<2.42, installed: 2.35]",
" - c [required: >=5.7.1, installed: 5.10.0]",
" - d [required: >=2.30, installed: 2.35]",
" - e [required: >=0.12.1, installed: 0.12.1]",
"b==2.3.1",
" - d [required: >=2.30,<2.42, installed: 2.35]",
" - e [required: >=0.9.0, installed: 0.12.1]",
"c==5.10.0",
" - d [required: >=2.30, installed: 2.35]",
" - e [required: >=0.9.0, installed: 0.12.1]",
" - e [required: >=0.12.1, installed: 0.12.1]",
"d==2.35",
" - e [required: >=0.9.0, installed: 0.12.1]",
"e==0.12.1",
"f==3.1",
" - b [required: >=2.1.0, installed: 2.3.1]",
" - d [required: >=2.30,<2.42, installed: 2.35]",
"g==6.8.3rc1",
" - e [required: >=0.9.0, installed: 0.12.1]",
" - f [required: >=3.0.0, installed: 3.1]",
" - b [required: >=2.1.0, installed: 2.3.1]",
],
),
],
)
def test_render_text_given_depth(capsys, unicode, level, expected_output):
encoding = "utf-8" if unicode else "ascii"
with mock.patch("sys.stdout", MockStdout(encoding)):
p.render_text(t, level)
captured = capsys.readouterr()
assert "\n".join(expected_output).strip() == captured.out.strip()

Expand Down Expand Up @@ -776,6 +873,27 @@ def test_parser_svg():
assert not args.json


@pytest.mark.parametrize(
("should_be_error", "depth_arg", "expected_value"),
[
(True, ["-d", "-1"], None),
(True, ["--depth", "string"], None),
(False, ["-d", "0"], 0),
(False, ["--depth", "8"], 8),
(False, [], float("inf")),
],
)
def test_parser_depth(should_be_error, depth_arg, expected_value):
parser = p.get_parser()

if should_be_error:
with pytest.raises(SystemExit):
parser.parse_args(depth_arg)
else:
args = parser.parse_args(depth_arg)
assert args.depth == expected_value


@pytest.mark.parametrize("args_joined", [True, False])
def test_custom_interpreter(tmp_path, monkeypatch, capfd, args_joined):
result = virtualenv.cli_run([str(tmp_path), "--activators", ""])
Expand Down

0 comments on commit b12a6dd

Please sign in to comment.