Coverage for python / lsst / ctrl / bps / cli / cmd / commands.py: 76%

94 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-06 08:35 +0000

1# This file is part of ctrl_bps. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://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 <https://www.gnu.org/licenses/>. 

27"""Subcommand definitions.""" 

28 

29from collections.abc import Iterator 

30from contextlib import contextmanager 

31 

32import click 

33 

34from lsst.daf.butler.cli.utils import MWCommand 

35 

36from ... import BpsSubprocessError 

37from ...drivers import ( 

38 acquire_qgraph_driver, 

39 cancel_driver, 

40 cluster_qgraph_driver, 

41 ping_driver, 

42 prepare_driver, 

43 report_driver, 

44 restart_driver, 

45 status_driver, 

46 submit_driver, 

47 submitcmd_driver, 

48 transform_driver, 

49) 

50from .. import opt 

51 

52 

53@contextmanager 

54def catch_errors() -> Iterator[None]: 

55 """Handle errors that occurred during command execution. 

56 

57 Returns 

58 ------- 

59 context : `contextlib.AbstractContextManager` [ `None` ] 

60 A context manager that does not return a value when entered. 

61 

62 Notes 

63 ----- 

64 At the moment, the main responsibility of this context manager is to catch 

65 the exception related to failures of the commands BPS runs in its 

66 subprocesses to force Click CLI report command's actual exit code instead 

67 of always returning 1. 

68 """ 

69 try: 

70 yield None 

71 except BpsSubprocessError as e: 

72 click.echo(e) 

73 click.get_current_context().exit(e.errno) 

74 except BaseException: 

75 raise 

76 

77 

78class BpsCommand(MWCommand): 

79 """Command subclass with bps-command specific overrides.""" 

80 

81 extra_epilog = "See 'bps --help' for more options." 

82 

83 

84@click.command(cls=BpsCommand) 

85@opt.config_file_argument(required=True) 

86@opt.submission_options() 

87def acquire(*args, **kwargs): 

88 """Create a new quantum graph or read existing one from a file.""" 

89 with catch_errors(): 

90 acquire_qgraph_driver(*args, **kwargs) 

91 

92 

93@click.command(cls=BpsCommand) 

94@opt.config_file_argument(required=True) 

95@opt.submission_options() 

96def cluster(*args, **kwargs): 

97 """Create a clustered quantum graph.""" 

98 with catch_errors(): 

99 cluster_qgraph_driver(*args, **kwargs) 

100 

101 

102@click.command(cls=BpsCommand) 

103@opt.config_file_argument(required=True) 

104@opt.submission_options() 

105def transform(*args, **kwargs): 

106 """Transform a quantum graph to a generic workflow.""" 

107 with catch_errors(): 

108 transform_driver(*args, **kwargs) 

109 

110 

111@click.command(cls=BpsCommand) 

112@opt.config_file_argument(required=True) 

113@opt.wms_service_option() 

114@opt.submission_options() 

115def prepare(*args, **kwargs): 

116 """Prepare a workflow for submission.""" 

117 with catch_errors(): 

118 prepare_driver(*args, **kwargs) 

119 

120 

121@click.command(cls=BpsCommand) 

122@opt.config_file_argument(required=True) 

123@opt.wms_service_option() 

124@opt.compute_site_option() 

125@opt.submission_options() 

126def submit(*args, **kwargs): 

127 """Submit a workflow for execution.""" 

128 with catch_errors(): 

129 submit_driver(*args, **kwargs) 

130 

131 

132@click.command(cls=BpsCommand) 

133@opt.wms_service_option() 

134@click.option("--id", "run_id", help="Run id of workflow to restart.") 

135def restart(*args, **kwargs): 

136 """Restart a failed workflow.""" 

137 restart_driver(*args, **kwargs) 

138 

139 

140@click.command(cls=BpsCommand) 

141@opt.wms_service_option() 

142@click.option("--id", "run_id", help="Restrict report to specific WMS run id.") 

143@click.option("--user", help="Restrict report to specific user.") 

144@click.option("--hist", "hist_days", default=0.0, help="Search WMS history X days for completed info.") 

145@click.option("--pass-thru", help="Pass the given string to the WMS service class.") 

146@click.option( 

147 "--return-exit-codes", 

148 is_flag=True, 

149 show_default=True, 

150 default=False, 

151 help="Return exit codes from jobs with a non-success status.", 

152) 

153@click.option( 

154 "--global/--no-global", 

155 "is_global", 

156 default=False, 

157 help="Query all available job queues for job information.", 

158) 

159def report(*args, **kwargs): 

160 """Display execution information for submitted workflows.""" 

161 report_driver(*args, **kwargs) 

162 

163 

164@click.command(cls=BpsCommand) 

165@opt.wms_service_option() 

166@click.option("--id", "run_id", required=True, help="Restrict report to specific WMS run id.") 

167@click.option("--hist", "hist_days", default=0.0, help="Search WMS history X days for completed info.") 

168@click.option( 

169 "--global/--no-global", 

170 "is_global", 

171 default=False, 

172 help="Query all available job queues for job information.", 

173) 

174def status(*args, **kwargs): 

175 """Exit with execution status of single submitted workflow.""" 

176 # Note: Using return statement doesn't actually return the value 

177 # to the shell. Using click function instead. 

178 click.get_current_context().exit(status_driver(*args, **kwargs)) 

179 

180 

181@click.command(cls=BpsCommand) 

182@opt.wms_service_option() 

183@click.option("--id", "run_id", help="Run id of workflow to cancel.") 

184@click.option("--user", help="User for which to cancel all submitted workflows.") 

185@click.option( 

186 "--require-bps/--skip-require-bps", 

187 "require_bps", 

188 default=True, 

189 show_default=True, 

190 help="Only cancel jobs submitted via bps.", 

191) 

192@click.option("--pass-thru", "pass_thru", default="", help="Pass the given string to the WMS service.") 

193@click.option( 

194 "--global/--no-global", 

195 "is_global", 

196 default=False, 

197 help="Cancel jobs matching the search criteria from all job queues.", 

198) 

199def cancel(*args, **kwargs): 

200 """Cancel submitted workflow(s).""" 

201 cancel_driver(*args, **kwargs) 

202 

203 

204@click.command(cls=BpsCommand) 

205@opt.wms_service_option() 

206@click.option("--pass-thru", "pass_thru", default="", help="Pass the given string to the WMS service.") 

207def ping(*args, **kwargs): 

208 """Ping workflow services.""" 

209 # Note: Using return statement doesn't actually return the value 

210 # to the shell. Using click function instead. 

211 click.get_current_context().exit(ping_driver(*args, **kwargs)) 

212 

213 

214@click.command(cls=BpsCommand) 

215@opt.config_file_argument(required=True) 

216@opt.wms_service_option() 

217@opt.compute_site_option() 

218@click.option("--dry-run", "dry_run", is_flag=True, help="Prepare workflow but don't submit") 

219def submitcmd(*args, **kwargs): 

220 """Submit a command for execution.""" 

221 submitcmd_driver(*args, **kwargs)