diff --git a/README.rst b/README.rst index 9e37b65..867af6e 100644 --- a/README.rst +++ b/README.rst @@ -201,6 +201,16 @@ tools. $ python pipdeptree.py --json +The dependency graph can be layed out as any of the formats supported by +GraphViz`GraphViz`_: + +.. code-block:: bash + + $ pipdeptree --graph-output dot > dependencies.dot + $ pipdeptree --graph-output pdf > dependencies.pdf + $ pipdeptree --graph-output png > dependencies.png + $ pipdeptree --graph-output svg > dependencies.svg + Usage ----- @@ -233,6 +243,10 @@ Usage -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. + --graph-output OUTPUT_FORMAT + Print a dependency graph in the specified output + format. Available are all formats supported by + GraphViz, e.g.: dot, jpeg, pdf, png, svg Known Issues diff --git a/pipdeptree.py b/pipdeptree.py index 877a1f3..e7f3ba7 100644 --- a/pipdeptree.py +++ b/pipdeptree.py @@ -14,6 +14,8 @@ except ImportError: import pip import pkg_resources +# inline: +# from graphviz import backend, Digraph __version__ = '0.8.0' @@ -339,6 +341,52 @@ def jsonify_tree(tree, indent): indent=indent) +def dump_graphviz(tree, output_format='dot'): + """Output dependency graph as one of the supported GraphViz output formats. + + :param dict tree: dependency graph + :param string output_format: output format + :returns: representation of tree in the specified output format + :rtype: str or binary representation depending on the output format + + """ + try: + from graphviz import backend, Digraph + except ImportError: + print('graphviz is not available, but necessary for the output ' + 'option. Please install it.', file=sys.stderr) + sys.exit(1) + + if output_format not in backend.FORMATS: + print('{} is no supported output format.'.format(output_format), + file=sys.stderr) + print('Supported formats are: {}'.format( + ', '.join(sorted(backend.FORMATS))), file=sys.stderr) + sys.exit(1) + + graph = Digraph(format=output_format) + for package, deps in tree.items(): + project_name = package.project_name + label = '{}\n{}'.format(project_name, package.version) + graph.node(project_name, label=label) + for dep in deps: + label = dep.version_spec + if not label: + label = 'any' + graph.edge(project_name, dep.project_name, label=label) + + # Allow output of dot format, even if GraphViz isn't installed. + if output_format == 'dot': + return graph.source + + # As it's unknown if the selected output format is binary or not, try to + # decode it as UTF8 and only print it out in binary if that's not possible. + try: + return graph.pipe().decode('utf-8') + except UnicodeDecodeError: + return graph.pipe() + + def conflicting_deps(tree): """Returns dependencies which are not present or conflict with the requirements of other packages. @@ -418,6 +466,12 @@ def main(): '"raw" output that may be used by external tools. ' 'This option overrides all other options.' )) + parser.add_argument('--graph-output', dest='output_format', + help=( + 'Print a dependency graph in the specified output ' + 'format. Available are all formats supported by ' + 'GraphViz, e.g.: dot, jpeg, pdf, png, svg' + )) args = parser.parse_args() pkgs = pip.get_installed_distributions(local_only=args.local_only) @@ -428,6 +482,9 @@ def main(): if args.json: print(jsonify_tree(tree, indent=4)) return 0 + elif args.output_format: + print(dump_graphviz(tree, output_format=args.output_format)) + return 0 return_code = 0 @@ -440,10 +497,10 @@ def main(): file=sys.stderr) for p, reqs in conflicting.items(): pkg = p.render_as_root(False) - print('* %s' % pkg, file=sys.stderr) + print('* {}'.format(pkg), file=sys.stderr) for req in reqs: req_str = req.render_as_branch(False) - print(' - %s' % req_str, file=sys.stderr) + print(' - {}'.format(req_str), file=sys.stderr) print('-'*72, file=sys.stderr) cyclic = cyclic_deps(tree) diff --git a/setup.py b/setup.py index 4de03de..91183fb 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ setup( description='Command line utility to show dependency tree of packages', long_description=long_desc, install_requires=install_requires, + extras_require={'graphviz': ['graphviz']}, py_modules=['pipdeptree'], entry_points={ 'console_scripts': [