Coverage for python/lsst/ctrl/mpexec/cli/cmd/commands.py: 64%

92 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-25 02:31 -0800

1# This file is part of ctrl_mpexec. 

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 program is free software: you can redistribute it and/or modify 

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# GNU General Public License for more details. 

18# 

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

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

21 

22import sys 

23from functools import partial 

24from typing import Any 

25 

26import click 

27import lsst.pipe.base.cli.opt as pipeBaseOpts 

28from lsst.ctrl.mpexec.showInfo import ShowInfo 

29from lsst.daf.butler.cli.opt import config_file_option, config_option, confirm_option, options_file_option 

30from lsst.daf.butler.cli.utils import MWCtxObj, catch_and_exit, option_section, unwrap 

31 

32from .. import opt as ctrlMpExecOpts 

33from .. import script 

34from ..script import confirmable 

35from ..utils import _ACTION_CONFIG, _ACTION_CONFIG_FILE, PipetaskCommand, makePipelineActions 

36 

37epilog = unwrap( 

38 """Notes: 

39 

40--task, --delete, --config, --config-file, and --instrument action options can 

41appear multiple times; all values are used, in order left to right. 

42 

43FILE reads command-line options from the specified file. Data may be 

44distributed among multiple lines (e.g. one option per line). Data after # is 

45treated as a comment and ignored. Blank lines and lines starting with # are 

46ignored.) 

47""" 

48) 

49 

50 

51def _collectActions(ctx: click.Context, **kwargs: Any) -> dict[str, Any]: 

52 """Extract pipeline building options, replace them with PipelineActions, 

53 return updated `kwargs`. 

54 

55 Notes 

56 ----- 

57 The pipeline actions (task, delete, config, config_file, and instrument) 

58 must be handled in the order they appear on the command line, but the CLI 

59 specification gives them all different option names. So, instead of using 

60 the individual action options as they appear in kwargs (because 

61 invocation order can't be known), we capture the CLI arguments by 

62 overriding `click.Command.parse_args` and save them in the Context's 

63 `obj` parameter. We use `makePipelineActions` to create a list of 

64 pipeline actions from the CLI arguments and pass that list to the script 

65 function using the `pipeline_actions` kwarg name, and remove the action 

66 options from kwargs. 

67 """ 

68 for pipelineAction in ( 

69 ctrlMpExecOpts.task_option.name(), 

70 ctrlMpExecOpts.delete_option.name(), 

71 config_option.name(), 

72 config_file_option.name(), 

73 pipeBaseOpts.instrument_option.name(), 

74 ): 

75 kwargs.pop(pipelineAction) 

76 

77 actions = makePipelineActions(MWCtxObj.getFrom(ctx).args) 

78 mock_configs = [] 

79 pipeline_actions = [] 

80 for action in actions: 

81 if action.label and action.label.endswith("-mock"): 81 ↛ 82line 81 didn't jump to line 82, because the condition on line 81 was never true

82 if action.action not in (_ACTION_CONFIG.action, _ACTION_CONFIG_FILE.action): 

83 raise ValueError(f"Unexpected option for mock task config overrides: {action}") 

84 mock_configs.append(action) 

85 else: 

86 pipeline_actions.append(action) 

87 

88 kwargs["mock_configs"] = mock_configs 

89 kwargs["pipeline_actions"] = pipeline_actions 

90 return kwargs 

91 

92 

93def _unhandledShow(show: ShowInfo, cmd: str) -> None: 

94 if show.unhandled: 94 ↛ 95line 94 didn't jump to line 95, because the condition on line 94 was never true

95 print( 

96 f"The following '--show' options were not known to the {cmd} command: " 

97 f"{', '.join(show.unhandled)}", 

98 file=sys.stderr, 

99 ) 

100 

101 

102@click.command(cls=PipetaskCommand, epilog=epilog, short_help="Build pipeline definition.") 

103@click.pass_context 

104@ctrlMpExecOpts.show_option() 

105@ctrlMpExecOpts.pipeline_build_options() 

106@option_section(sectionText="") 

107@options_file_option() 

108@catch_and_exit 

109def build(ctx: click.Context, **kwargs: Any) -> None: 

110 """Build and optionally save pipeline definition. 

111 

112 This does not require input data to be specified. 

113 """ 

114 kwargs = _collectActions(ctx, **kwargs) 

115 show = ShowInfo(kwargs.pop("show", [])) 

116 script.build(**kwargs, show=show) 

117 _unhandledShow(show, "build") 

118 

119 

120@click.command(cls=PipetaskCommand, epilog=epilog) 

121@click.pass_context 

122@ctrlMpExecOpts.show_option() 

123@ctrlMpExecOpts.pipeline_build_options() 

124@ctrlMpExecOpts.qgraph_options() 

125@ctrlMpExecOpts.butler_options() 

126@option_section(sectionText="") 

127@options_file_option() 

128@catch_and_exit 

129def qgraph(ctx: click.Context, **kwargs: Any) -> None: 

130 """Build and optionally save quantum graph.""" 

131 kwargs = _collectActions(ctx, **kwargs) 

132 show = ShowInfo(kwargs.pop("show", [])) 

133 pipeline = script.build(**kwargs, show=show) 

134 if show.handled and not show.unhandled: 

135 print( 

136 "No quantum graph generated. The --show option was given and all options were processed.", 

137 file=sys.stderr, 

138 ) 

139 return 

140 if script.qgraph(pipelineObj=pipeline, **kwargs, show=show) is None: 

141 raise click.ClickException("QuantumGraph was empty; CRITICAL logs above should provide details.") 

142 _unhandledShow(show, "qgraph") 

143 

144 

145@click.command(cls=PipetaskCommand, epilog=epilog) 

146@ctrlMpExecOpts.run_options() 

147@catch_and_exit 

148def run(ctx: click.Context, **kwargs: Any) -> None: 

149 """Build and execute pipeline and quantum graph.""" 

150 kwargs = _collectActions(ctx, **kwargs) 

151 show = ShowInfo(kwargs.pop("show", [])) 

152 pipeline = script.build(**kwargs, show=show) 

153 if show.handled and not show.unhandled: 

154 print( 

155 "No quantum graph generated or pipeline executed. " 

156 "The --show option was given and all options were processed.", 

157 file=sys.stderr, 

158 ) 

159 return 

160 if (qgraph := script.qgraph(pipelineObj=pipeline, **kwargs, show=show)) is None: 

161 raise click.ClickException("QuantumGraph was empty; CRITICAL logs above should provide details.") 

162 _unhandledShow(show, "run") 

163 if show.handled: 

164 print( 

165 "No pipeline executed. The --show option was given and all options were processed.", 

166 file=sys.stderr, 

167 ) 

168 return 

169 script.run(qgraphObj=qgraph, **kwargs) 

170 

171 

172@click.command(cls=PipetaskCommand) 

173@ctrlMpExecOpts.butler_config_option() 

174@ctrlMpExecOpts.collection_argument() 

175@confirm_option() 

176@ctrlMpExecOpts.recursive_option( 

177 help="""If the parent CHAINED collection has child CHAINED collections, 

178 search the children until nested chains that start with the parent's name 

179 are removed.""" 

180) 

181def purge(confirm: bool, **kwargs: Any) -> None: 

182 """Remove a CHAINED collection and its contained collections. 

183 

184 COLLECTION is the name of the chained collection to purge. it must not be a 

185 child of any other CHAINED collections 

186 

187 Child collections must be members of exactly one collection. 

188 

189 The collections that will be removed will be printed, there will be an 

190 option to continue or abort (unless using --no-confirm). 

191 """ 

192 confirmable.confirm(partial(script.purge, **kwargs), confirm) 

193 

194 

195@click.command(cls=PipetaskCommand) 

196@ctrlMpExecOpts.butler_config_option() 

197@ctrlMpExecOpts.collection_argument() 

198@confirm_option() 

199def cleanup(confirm: bool, **kwargs: Any) -> None: 

200 """Remove non-members of CHAINED collections. 

201 

202 Removes collections that start with the same name as a CHAINED 

203 collection but are not members of that collection. 

204 """ 

205 confirmable.confirm(partial(script.cleanup, **kwargs), confirm)