11"""This file contains the command and code for drawing the DAG."""
2- import shutil
2+ import sys
33from pathlib import Path
44from typing import Any
55from typing import Dict
66
77import click
88import networkx as nx
9+ from _pytask .compat import check_for_optional_program
10+ from _pytask .compat import import_optional_dependency
911from _pytask .config import hookimpl
1012from _pytask .console import console
1113from _pytask .dag import descending_tasks
1820from _pytask .session import Session
1921from _pytask .shared import get_first_non_none_value
2022from _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
2125
2226
2327@hookimpl (tryfirst = True )
@@ -61,9 +65,50 @@ def pytask_parse_config(config, config_from_cli, config_from_file):
6165@click .option ("-o" , "--output-path" , type = str , default = None , help = _HELP_TEXT_OUTPUT )
6266def dag (** config_from_cli ):
6367 """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 )
67112
68113
69114def 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
87132 A preprocessed graph which can be customized and exported.
88133
89134 """
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
93169
94170
95171def _refine_dag (session ):
@@ -122,6 +198,8 @@ def _create_session(config_from_cli: Dict[str, Any]) -> nx.DiGraph:
122198 else :
123199 try :
124200 session .hook .pytask_log_session_header (session = session )
201+ import_optional_dependency ("pydot" )
202+ check_for_optional_program (session .config ["layout" ])
125203 session .hook .pytask_collect (session = session )
126204 session .hook .pytask_resolve_dependencies (session = session )
127205
@@ -186,21 +264,6 @@ def _escape_node_names_with_colons(dag: nx.DiGraph):
186264
187265
188266def _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-
204267 path .parent .mkdir (exist_ok = True , parents = True )
205268 graph = nx .nx_pydot .to_pydot (dag )
206269 graph .write (path , prog = layout , format = path .suffix [1 :])
0 commit comments