Coverage for python/lsst/pipe/base/pipeline_graph/visualization/_show.py: 20%

52 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-23 10:54 +0000

1# This file is part of pipe_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <http://www.gnu.org/licenses/>. 

27from __future__ import annotations 

28 

29__all__ = ("show",) 

30 

31import sys 

32from collections.abc import Sequence 

33from shutil import get_terminal_size 

34from typing import Literal, TextIO 

35 

36from .._nodes import NodeKey 

37from .._pipeline_graph import PipelineGraph 

38from .._tasks import TaskInitNode, TaskNode 

39from ._formatting import GetNodeText, get_node_symbol 

40from ._layout import ColumnSelector, Layout 

41from ._merge import ( 

42 MergedNodeKey, 

43 merge_graph_input_trees, 

44 merge_graph_intermediates, 

45 merge_graph_output_trees, 

46) 

47from ._options import NodeAttributeOptions 

48from ._printer import make_default_printer 

49 

50DisplayNodeKey = NodeKey | MergedNodeKey 

51 

52 

53def show( 

54 pipeline_graph: PipelineGraph, 

55 stream: TextIO = sys.stdout, 

56 *, 

57 dataset_types: bool = False, 

58 init: bool | None = False, 

59 color: bool | Sequence[str] | None = None, 

60 dimensions: Literal["full"] | Literal["concise"] | Literal[False] | None = None, 

61 task_classes: Literal["full"] | Literal["concise"] | Literal[False] = False, 

62 storage_classes: bool | None = None, 

63 merge_input_trees: int = 4, 

64 merge_output_trees: int = 4, 

65 merge_intermediates: bool = True, 

66 width: int = -1, 

67 include_automatic_connections: bool = False, 

68 column_interior_penalty: int = 1, 

69 column_crossing_penalty: int = 1, 

70 column_insertion_penalty: int = 2, 

71) -> None: 

72 """Print a text-based ~.PipelineGraph` visualization. 

73 

74 Parameters 

75 ---------- 

76 pipeline_graph : `PipelineGraph` 

77 Graph to display. 

78 stream : `TextIO`, optional 

79 Output stream. Defaults to STDOUT. 

80 dataset_types : `bool`, optional 

81 Whether to include dataset type nodes (default is `False`). 

82 init : `bool`, optional 

83 Whether to include task initialization nodes (i.e. the producers of 

84 init-input and init-output dataset types). Default is `False`. `None` 

85 will show both runtime and init nodes, while `True` will show only 

86 init nodes. 

87 color : `bool` or `~collections.abc.Sequence` [ `str` ] 

88 Whether to use tto add color to the graph. Default is to add colors 

89 only if the `colorama` package can be imported. `False` disables colors 

90 unconditionally, while `True` or a sequence of colors (see 

91 `make_colorama_printer`) will result in `ImportError` being propagated 

92 up if `colorama` is unavailable. 

93 dimensions : `str` or `False`, optional 

94 How to display the dimensions of tasks and dataset types. 

95 

96 Possible values include: 

97 

98 - ``"full"``: report fully-expanded dimensions. 

99 - ``"concise"``: report only dimensions that are not required or 

100 implied dependencies of any reported dimension. 

101 - `False`: do not report dimensions at all. 

102 - `None` (default): report concise dimensions only if 

103 `PipelineGraph.is_fully_resolved` is `True`. 

104 

105 This also sets whether merge options consider dimensions when merging 

106 nodes. 

107 task_classes : `str` or `False`, optional 

108 How to display task class names (not task labels, which are always 

109 shown). 

110 

111 Possible values include: 

112 

113 - ``"full"``: report the fully-qualified task class name. 

114 - ``"concise"``: report the task class name with no module 

115 or package. 

116 - `False`: (default) do not report task classes at all. 

117 

118 This also sets whether merge options consider task classes when merging 

119 nodes. It is ignored if ``tasks=False``. 

120 storage_classes : `bool`, optional 

121 Whether to display storage classes in dataset type nodes. This also 

122 sets whether merge options consider storage classes when merging nodes. 

123 It is ignored if ``dataset_types=False``. 

124 merge_input_trees : `int`, optional 

125 If positive, merge input trees of the graph whose nodes have the same 

126 outputs and other properties (dimensions, task classes, storage 

127 classes), traversing this many nodes deep into the graph from the 

128 beginning. Default is ``4``. 

129 merge_output_trees : `int`, optional 

130 If positive, merge output trees of the graph whose nodes have the same 

131 outputs and other properties (dimensions, task classes, storage 

132 classes), traversing this many nodes deep into the graph from the end. 

133 Default is ``4``. 

134 merge_intermediates : `bool`, optional 

135 If `True` (default) merge interior parallel nodes with the same inputs, 

136 outputs, and other properties (dimensions, task classes, storage 

137 classes). 

138 width : `int`, optional 

139 Number of columns the full graph should occupy, including text 

140 descriptions on the right. If ``0``, there is no limit (and hence no 

141 text-wrapping). If negative (default) use the current terminal width. 

142 include_automatic_connections : `bool`, optional 

143 Whether to include automatically-added connections like the config, 

144 log, and metadata dataset types for each task. Default is `False`. 

145 column_interior_penalty : `int` 

146 Penalty applied to a prospective column for a node when that column is 

147 between two existing columns. 

148 column_crossing_penalty : `int` 

149 Penalty applied to a prospective column for a node for each ongoing 

150 (vertical) edge that node's incoming edges would have to "hop". 

151 column_insertion_penalty : `int` 

152 Penalty applied to a prospective column for a node when considering a 

153 new columns on the sides or between two existing columns. 

154 """ 

155 if init is None: 

156 if not dataset_types: 

157 raise ValueError("Cannot show init and runtime graphs unless dataset types are shown.") 

158 xgraph = pipeline_graph.make_xgraph() 

159 elif dataset_types: 

160 xgraph = pipeline_graph.make_bipartite_xgraph(init) 

161 else: 

162 xgraph = pipeline_graph.make_task_xgraph(init) 

163 storage_classes = False 

164 

165 options = NodeAttributeOptions( 

166 dimensions=dimensions, storage_classes=storage_classes, task_classes=task_classes 

167 ) 

168 options = options.checked(pipeline_graph.is_fully_resolved) 

169 

170 if dataset_types and not include_automatic_connections: 

171 taskish_nodes: list[TaskNode | TaskInitNode] = [] 

172 for task_node in pipeline_graph.tasks.values(): 

173 if init is None or init is False: 

174 taskish_nodes.append(task_node) 

175 if init is None or init is True: 

176 taskish_nodes.append(task_node.init) 

177 for t in taskish_nodes: 

178 xgraph.remove_nodes_from( 

179 edge.dataset_type_key 

180 for edge in t.iter_all_outputs() 

181 if edge.connection_name not in t.outputs and not xgraph.out_degree(edge.dataset_type_key) 

182 ) 

183 

184 if merge_input_trees: 

185 merge_graph_input_trees(xgraph, options, depth=merge_input_trees) 

186 if merge_output_trees: 

187 merge_graph_output_trees(xgraph, options, depth=merge_output_trees) 

188 if merge_intermediates: 

189 merge_graph_intermediates(xgraph, options) 

190 

191 column_selector = ColumnSelector( 

192 interior_penalty=column_interior_penalty, 

193 crossing_penalty=column_crossing_penalty, 

194 insertion_penalty=column_insertion_penalty, 

195 ) 

196 layout = Layout[DisplayNodeKey](xgraph, column_selector) 

197 

198 if width < 0: 

199 width, _ = get_terminal_size() 

200 

201 printer = make_default_printer(layout.width, color, stream) 

202 printer.get_symbol = get_node_symbol 

203 

204 get_text = GetNodeText(xgraph, options, (width - printer.width) if width else 0) 

205 printer.get_text = get_text 

206 

207 printer.print(stream, layout) 

208 for line in get_text.format_deferrals(width): 

209 print(line, file=stream)