From ea17d2e5586e40e4f03079d9c9f450f1f25734c1 Mon Sep 17 00:00:00 2001 From: Vineet Naik Date: Sat, 6 Aug 2016 15:52:29 +0530 Subject: [PATCH] Simplify code to show confusing deps --- pipdeptree.py | 59 ++++++++++++---------------------------- tests/test_pipdeptree.py | 13 ++++++++- 2 files changed, 30 insertions(+), 42 deletions(-) diff --git a/pipdeptree.py b/pipdeptree.py index 2c8db46..6e44c0b 100644 --- a/pipdeptree.py +++ b/pipdeptree.py @@ -1,8 +1,9 @@ from __future__ import print_function import sys -from itertools import chain, tee +from itertools import chain from collections import defaultdict import argparse +from operator import attrgetter import json import pip @@ -300,41 +301,14 @@ def cyclic_deps(tree): :rtype: generator """ - nodes = tree.keys() key_tree = dict((k.key, v) for k, v in tree.items()) - - def get_children(n): - return key_tree[n.key] - - def aux(node, chain): - if node.dist: - for c in get_children(node): - if c.project_name in chain: - yield ' => '.join([str(p) for p in chain] + [str(c)]) - else: - for cycle in aux(c, chain=chain+[c.project_name]): - yield cycle - - for cycle in flatten([aux(n, chain=[]) for n in nodes]): - yield cycle - - -def peek_into(iterator): - """Peeks into an iterator to check if it's empty - - :param iterator: an iterator - :returns: tuple of boolean representing whether the iterator is - empty or not and the iterator itself. - :rtype: tuple - - """ - a, b = tee(iterator) - is_empty = False - try: - next(a) - except StopIteration: - is_empty = True - return is_empty, b + get_children = lambda n: key_tree.get(n.key, []) + cyclic = [] + for p, rs in tree.items(): + for req in rs: + if p.key in map(attrgetter('key'), get_children(req)): + cyclic.append((p, req, p)) + return cyclic def main(): @@ -415,14 +389,17 @@ def main(): print(' - %s' % req_str, file=sys.stderr) print('-'*72, file=sys.stderr) - is_empty, cyclic = peek_into(cyclic_deps(tree)) - if not is_empty: - print('Warning!!! Cyclic dependencies found:', file=sys.stderr) - for xs in cyclic: - print('- {0}'.format(xs), file=sys.stderr) + cyclic = cyclic_deps(tree) + if cyclic: + print('Warning!! Cyclic dependencies found:', file=sys.stderr) + for a, b, c in cyclic: + print('* {0} => {1} => {2}'.format(a.project_name, + b.project_name, + c.project_name), + file=sys.stderr) print('-'*72, file=sys.stderr) - if args.warn == 'fail' and (conflicting or not is_empty): + if args.warn == 'fail' and (conflicting or cyclic): return_code = 1 show_only = set(args.packages.split(',')) if args.packages else None diff --git a/tests/test_pipdeptree.py b/tests/test_pipdeptree.py index 73ce72b..fe642a8 100644 --- a/tests/test_pipdeptree.py +++ b/tests/test_pipdeptree.py @@ -1,8 +1,9 @@ import pickle +from operator import itemgetter, attrgetter from pipdeptree import (build_dist_index, construct_tree, peek_into, DistPackage, ReqPackage, render_tree, - reverse_tree, conflicting_deps) + reverse_tree, cyclic_deps, conflicting_deps) def venv_fixture(pickle_file): @@ -131,6 +132,16 @@ def test_render_tree_freeze(): assert 'itsdangerous==0.23' not in lines +def test_cyclic_dependencies(): + cyclic_pkgs, dist_index, tree = venv_fixture('tests/virtualenvs/cyclicenv.pickle') + cyclic = [map(attrgetter('key'), cs) for cs in cyclic_deps(tree)] + assert len(cyclic) == 2 + a, b, c = cyclic[0] + x, y, z = cyclic[1] + assert a == c == y + assert x == z == b + + def test_render_tree_cyclic_dependency(): cyclic_pkgs, dist_index, tree = venv_fixture('tests/virtualenvs/cyclicenv.pickle') tree_str = render_tree(tree, list_all=True)