From fba43277e6fa8db3b604e7d658d43a929b60016e Mon Sep 17 00:00:00 2001 From: Dmitrii Pukhov Date: Tue, 26 Mar 2019 01:29:49 +0900 Subject: [PATCH 1/3] cli: add pipeline show --tree Add option to unfold dependencies into a tree. Fixes #1038 --- dvc/command/pipeline.py | 39 ++++++++++++++++++++++++++++++++++++--- requirements.txt | 1 + tests/test_pipeline.py | 4 ++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/dvc/command/pipeline.py b/dvc/command/pipeline.py index 401ef4dcec..c594eb6bef 100644 --- a/dvc/command/pipeline.py +++ b/dvc/command/pipeline.py @@ -71,23 +71,46 @@ def __build_graph(self, target, commands, outs): else: edges.append((from_stage.relpath, to_stage.relpath)) - return nodes, edges + return nodes, edges, networkx.is_tree(G) def _show_ascii(self, target, commands, outs): from dvc.dagascii import draw - nodes, edges = self.__build_graph(target, commands, outs) + nodes, edges, _ = self.__build_graph(target, commands, outs) if not nodes: return draw(nodes, edges) + def _show_dependencies_tree(self, target, commands, outs): + from treelib import Tree + + nodes, edges, is_tree = self.__build_graph(target, commands, outs) + if not nodes: + return + if not is_tree: + raise ValueError( + "DAG is not a tree, can not print it in tree-structure way, " + "please use --ascii instead" + ) + tree = Tree() + tree.create_node(target, target) # Root node + observe_list = [target] + while len(observe_list) > 0: + current_root = observe_list[0] + for edge in edges: + if edge[0] == current_root: + tree.create_node(edge[1], edge[1], parent=current_root) + observe_list.append(edge[1]) + observe_list.pop(0) + tree.show() + def __write_dot(self, target, commands, outs, filename): import networkx from networkx.drawing.nx_pydot import write_dot - _, edges = self.__build_graph(target, commands, outs) + _, edges, _ = self.__build_graph(target, commands, outs) edges = [edge[::-1] for edge in edges] simple_g = networkx.DiGraph() @@ -111,6 +134,10 @@ def run(self, unlock=False): self.args.outs, self.args.dot, ) + elif self.args.tree: + self._show_dependencies_tree( + target, self.args.commands, self.args.outs + ) else: self._show(target, self.args.commands, self.args.outs) except DvcException: @@ -185,6 +212,12 @@ def add_parser(subparsers, parent_parser): pipeline_show_parser.add_argument( "--dot", help="Write DAG in .dot format." ) + pipeline_show_parser.add_argument( + "--tree", + action="store_true", + default=False, + help="Output DAG as Dependencies Tree.", + ) pipeline_show_parser.add_argument( "targets", nargs="*", help="DVC files. 'Dvcfile' by default." ) diff --git a/requirements.txt b/requirements.txt index 0eb848d45a..80c639d862 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,3 +25,4 @@ pydot>=1.2.4 asciimatics>=1.10.0 distro>=1.3.0 appdirs>=1.4.3 +treelib>=1.5.5 diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index f59e6afed7..0bb531454b 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -34,6 +34,10 @@ def test_dot(self): self.assertEqual(ret, 0) self.assertTrue(os.path.isfile(self.dotFile)) + def test_tree(self): + ret = main(["pipeline", "show", "--tree", self.stage]) + self.assertEqual(ret, 0) + def test_ascii_commands(self): ret = main(["pipeline", "show", "--ascii", self.stage, "--commands"]) self.assertEqual(ret, 0) From 5346fc44bc6e4aeae1262bb86323d8bb69c97669 Mon Sep 17 00:00:00 2001 From: Dmitrii Pukhov Date: Tue, 26 Mar 2019 13:51:29 +0900 Subject: [PATCH 2/3] Add treelib dependency to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index ce656dabd6..07fe236bac 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ "asciimatics>=1.10.0", "distro>=1.3.0", "appdirs>=1.4.3", + "treelib>=1.5.5", ] # Extra dependencies for remote integrations From 263dee8650aca16315a3713fb8e83919fecffa40 Mon Sep 17 00:00:00 2001 From: Dmitrii Pukhov Date: Tue, 26 Mar 2019 22:39:41 +0900 Subject: [PATCH 3/3] Replace ValueError with DvcException --- dvc/command/pipeline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dvc/command/pipeline.py b/dvc/command/pipeline.py index c594eb6bef..f6b33e9b2f 100644 --- a/dvc/command/pipeline.py +++ b/dvc/command/pipeline.py @@ -90,10 +90,11 @@ def _show_dependencies_tree(self, target, commands, outs): if not nodes: return if not is_tree: - raise ValueError( + raise DvcException( "DAG is not a tree, can not print it in tree-structure way, " "please use --ascii instead" ) + tree = Tree() tree.create_node(target, target) # Root node observe_list = [target]