1
1
"""This file contains the command and code for drawing the DAG."""
2
- import shutil
2
+ import sys
3
3
from pathlib import Path
4
4
from typing import Any
5
5
from typing import Dict
6
6
7
7
import click
8
8
import networkx as nx
9
+ from _pytask .compat import check_for_optional_program
10
+ from _pytask .compat import import_optional_dependency
9
11
from _pytask .config import hookimpl
10
12
from _pytask .console import console
11
13
from _pytask .dag import descending_tasks
18
20
from _pytask .session import Session
19
21
from _pytask .shared import get_first_non_none_value
20
22
from _pytask .shared import reduce_names_of_multiple_nodes
23
+ from _pytask .traceback import remove_internal_traceback_frames_from_exc_info
24
+ from rich .traceback import Traceback
21
25
22
26
23
27
@hookimpl (tryfirst = True )
@@ -61,9 +65,50 @@ def pytask_parse_config(config, config_from_cli, config_from_file):
61
65
@click .option ("-o" , "--output-path" , type = str , default = None , help = _HELP_TEXT_OUTPUT )
62
66
def dag (** config_from_cli ):
63
67
"""Create a visualization of the project's DAG."""
64
- session = _create_session (config_from_cli )
65
- dag = _refine_dag (session )
66
- _write_graph (dag , session .config ["output_path" ], session .config ["layout" ])
68
+ try :
69
+ pm = get_plugin_manager ()
70
+ from _pytask import cli
71
+
72
+ pm .register (cli )
73
+ pm .hook .pytask_add_hooks (pm = pm )
74
+
75
+ config = pm .hook .pytask_configure (pm = pm , config_from_cli = config_from_cli )
76
+
77
+ session = Session .from_config (config )
78
+
79
+ except (ConfigurationError , Exception ):
80
+ console .print_exception ()
81
+ session = Session ({}, None )
82
+ session .exit_code = ExitCode .CONFIGURATION_FAILED
83
+
84
+ else :
85
+ try :
86
+ session .hook .pytask_log_session_header (session = session )
87
+ import_optional_dependency ("pydot" )
88
+ check_for_optional_program (
89
+ session .config ["layout" ],
90
+ extra = "The layout program is part of the graphviz package which you "
91
+ "can install with conda." ,
92
+ )
93
+ session .hook .pytask_collect (session = session )
94
+ session .hook .pytask_resolve_dependencies (session = session )
95
+ dag = _refine_dag (session )
96
+ _write_graph (dag , session .config ["output_path" ], session .config ["layout" ])
97
+
98
+ except CollectionError :
99
+ session .exit_code = ExitCode .COLLECTION_FAILED
100
+
101
+ except ResolvingDependenciesError :
102
+ session .exit_code = ExitCode .RESOLVING_DEPENDENCIES_FAILED
103
+
104
+ except Exception :
105
+ session .exit_code = ExitCode .FAILED
106
+ exc_info = remove_internal_traceback_frames_from_exc_info (sys .exc_info ())
107
+ console .print ()
108
+ console .print (Traceback .from_exception (* exc_info ))
109
+ console .rule (style = ColorCode .FAILED )
110
+
111
+ sys .exit (session .exit_code )
67
112
68
113
69
114
def build_dag (config_from_cli : Dict [str , Any ]) -> "pydot.Dot" : # noqa: F821
@@ -87,9 +132,40 @@ def build_dag(config_from_cli: Dict[str, Any]) -> "pydot.Dot": # noqa: F821
87
132
A preprocessed graph which can be customized and exported.
88
133
89
134
"""
90
- session = _create_session (config_from_cli )
91
- dag = _refine_dag (session )
92
- return dag
135
+ try :
136
+ pm = get_plugin_manager ()
137
+ from _pytask import cli
138
+
139
+ pm .register (cli )
140
+ pm .hook .pytask_add_hooks (pm = pm )
141
+
142
+ config = pm .hook .pytask_configure (pm = pm , config_from_cli = config_from_cli )
143
+
144
+ session = Session .from_config (config )
145
+
146
+ except (ConfigurationError , Exception ):
147
+ console .print_exception ()
148
+ session = Session ({}, None )
149
+ session .exit_code = ExitCode .CONFIGURATION_FAILED
150
+
151
+ else :
152
+ try :
153
+ session .hook .pytask_log_session_header (session = session )
154
+ import_optional_dependency ("pydot" )
155
+ check_for_optional_program (
156
+ session .config ["layout" ],
157
+ extra = "The layout program is part of the graphviz package which you "
158
+ "can install with conda." ,
159
+ )
160
+ session .hook .pytask_collect (session = session )
161
+ session .hook .pytask_resolve_dependencies (session = session )
162
+ dag = _refine_dag (session )
163
+
164
+ except Exception :
165
+ raise
166
+
167
+ else :
168
+ return dag
93
169
94
170
95
171
def _refine_dag (session ):
@@ -122,6 +198,8 @@ def _create_session(config_from_cli: Dict[str, Any]) -> nx.DiGraph:
122
198
else :
123
199
try :
124
200
session .hook .pytask_log_session_header (session = session )
201
+ import_optional_dependency ("pydot" )
202
+ check_for_optional_program (session .config ["layout" ])
125
203
session .hook .pytask_collect (session = session )
126
204
session .hook .pytask_resolve_dependencies (session = session )
127
205
@@ -186,21 +264,6 @@ def _escape_node_names_with_colons(dag: nx.DiGraph):
186
264
187
265
188
266
def _write_graph (dag : nx .DiGraph , path : Path , layout : str ) -> None :
189
- try :
190
- import pydot # noqa: F401
191
- except ImportError :
192
- raise ImportError (
193
- "To visualize the project's DAG you need to install pydot which is "
194
- "available with pip and conda. For example, use 'conda install -c "
195
- "conda-forge pydot'."
196
- ) from None
197
- if shutil .which (layout ) is None :
198
- raise RuntimeError (
199
- f"The layout program '{ layout } ' could not be found on your PATH. Please, "
200
- "install graphviz. For example, use 'conda install -c conda-forge "
201
- "graphivz'."
202
- )
203
-
204
267
path .parent .mkdir (exist_ok = True , parents = True )
205
268
graph = nx .nx_pydot .to_pydot (dag )
206
269
graph .write (path , prog = layout , format = path .suffix [1 :])
0 commit comments