Coverage for python/lsst/ctrl/bps/cli/cmd/commands.py: 87%
108 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-02 05:16 -0400
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-02 05:16 -0400
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."""
29from collections.abc import Iterator
30from contextlib import contextmanager
32import click
34from lsst.ctrl.mpexec.cli.opt import save_qgraph_option
35from lsst.daf.butler.cli.utils import MWCommand
37from ... import BpsSubprocessError
38from ...drivers import (
39 acquire_qgraph_driver,
40 batch_acquire_driver,
41 batch_prepare_driver,
42 cancel_driver,
43 cluster_qgraph_driver,
44 ping_driver,
45 prepare_driver,
46 report_driver,
47 restart_driver,
48 status_driver,
49 submit_driver,
50 submitcmd_driver,
51 transform_driver,
52)
53from .. import opt
56@contextmanager
57def catch_errors() -> Iterator[None]:
58 """Handle errors that occurred during command execution.
60 Returns
61 -------
62 context : `contextlib.AbstractContextManager` [ `None` ]
63 A context manager that does not return a value when entered.
65 Notes
66 -----
67 At the moment, the main responsibility of this context manager is to catch
68 the exception related to failures of the commands BPS runs in its
69 subprocesses to force Click CLI report command's actual exit code instead
70 of always returning 1.
71 """
72 try:
73 yield None
74 except BpsSubprocessError as e:
75 click.echo(e)
76 click.get_current_context().exit(e.errno)
77 except BaseException:
78 raise
81class BpsCommand(MWCommand):
82 """Command subclass with bps-command specific overrides."""
84 extra_epilog = "See 'bps --help' for more options."
87@click.command(cls=BpsCommand)
88@opt.config_file_argument(required=True)
89@opt.submission_options()
90def acquire(*args, **kwargs):
91 """Create a new quantum graph or read existing one from a file."""
92 with catch_errors():
93 acquire_qgraph_driver(*args, **kwargs)
96@click.command(cls=BpsCommand)
97@opt.config_file_argument(required=True)
98@opt.submission_options()
99def cluster(*args, **kwargs):
100 """Create a clustered quantum graph."""
101 with catch_errors():
102 cluster_qgraph_driver(*args, **kwargs)
105@click.command(cls=BpsCommand)
106@opt.config_file_argument(required=True)
107@opt.submission_options()
108def transform(*args, **kwargs):
109 """Transform a quantum graph to a generic workflow."""
110 with catch_errors():
111 transform_driver(*args, **kwargs)
114@click.command(cls=BpsCommand)
115@opt.config_file_argument(required=True)
116@opt.wms_service_option()
117@opt.submission_options()
118def prepare(*args, **kwargs):
119 """Prepare a workflow for submission."""
120 with catch_errors():
121 prepare_driver(*args, **kwargs)
124@click.command(cls=BpsCommand)
125@opt.config_file_argument(required=True)
126@opt.wms_service_option()
127@opt.compute_site_option()
128@opt.submission_options()
129def submit(*args, **kwargs):
130 """Submit a workflow for execution."""
131 with catch_errors():
132 submit_driver(*args, **kwargs)
135@click.command(cls=BpsCommand)
136@opt.wms_service_option()
137@click.option("--id", "run_id", help="Run id of workflow to restart.")
138def restart(*args, **kwargs):
139 """Restart a failed workflow."""
140 restart_driver(*args, **kwargs)
143@click.command(cls=BpsCommand)
144@opt.wms_service_option()
145@click.option("--id", "run_id", help="Restrict report to specific WMS run id.")
146@click.option("--user", help="Restrict report to specific user.")
147@click.option("--hist", "hist_days", default=0.0, help="Search WMS history X days for completed info.")
148@click.option("--pass-thru", help="Pass the given string to the WMS service class.")
149@click.option(
150 "--return-exit-codes",
151 is_flag=True,
152 show_default=True,
153 default=False,
154 help="Return exit codes from jobs with a non-success status.",
155)
156@click.option(
157 "--global/--no-global",
158 "is_global",
159 default=False,
160 help="Query all available job queues for job information.",
161)
162def report(*args, **kwargs):
163 """Display execution information for submitted workflows."""
164 report_driver(*args, **kwargs)
167@click.command(cls=BpsCommand)
168@opt.wms_service_option()
169@click.option("--id", "run_id", required=True, help="Restrict report to specific WMS run id.")
170@click.option("--hist", "hist_days", default=0.0, help="Search WMS history X days for completed info.")
171@click.option(
172 "--global/--no-global",
173 "is_global",
174 default=False,
175 help="Query all available job queues for job information.",
176)
177def status(*args, **kwargs):
178 """Exit with execution status of single submitted workflow."""
179 # Note: Using return statement doesn't actually return the value
180 # to the shell. Using click function instead.
181 click.get_current_context().exit(status_driver(*args, **kwargs))
184@click.command(cls=BpsCommand)
185@opt.wms_service_option()
186@click.option("--id", "run_id", help="Run id of workflow to cancel.")
187@click.option("--user", help="User for which to cancel all submitted workflows.")
188@click.option(
189 "--require-bps/--skip-require-bps",
190 "require_bps",
191 default=True,
192 show_default=True,
193 help="Only cancel jobs submitted via bps.",
194)
195@click.option("--pass-thru", "pass_thru", default="", help="Pass the given string to the WMS service.")
196@click.option(
197 "--global/--no-global",
198 "is_global",
199 default=False,
200 help="Cancel jobs matching the search criteria from all job queues.",
201)
202def cancel(*args, **kwargs):
203 """Cancel submitted workflow(s)."""
204 cancel_driver(*args, **kwargs)
207@click.command(cls=BpsCommand)
208@opt.wms_service_option()
209@click.option("--pass-thru", "pass_thru", default="", help="Pass the given string to the WMS service.")
210def ping(*args, **kwargs):
211 """Ping workflow services."""
212 # Note: Using return statement doesn't actually return the value
213 # to the shell. Using click function instead.
214 click.get_current_context().exit(ping_driver(*args, **kwargs))
217@click.command(cls=BpsCommand)
218@opt.config_file_argument(required=True)
219@opt.wms_service_option()
220@opt.compute_site_option()
221@click.option("--dry-run", "dry_run", is_flag=True, help="Prepare workflow but don't submit")
222def submitcmd(*args, **kwargs):
223 """Submit a command for execution."""
224 submitcmd_driver(*args, **kwargs)
227@click.command(cls=BpsCommand)
228@opt.config_file_argument(required=True)
229@opt.submission_options()
230@save_qgraph_option()
231def batch_acquire(*args, **kwargs):
232 """Run inside a batch job to create a new quantum graph."""
233 with catch_errors():
234 batch_acquire_driver(*args, **kwargs)
237@click.command(cls=BpsCommand)
238@opt.config_file_argument(required=True)
239@opt.submission_options()
240def batch_prepare(*args, **kwargs):
241 """Run payload workflow preparation inside a batch job."""
242 with catch_errors():
243 batch_prepare_driver(*args, **kwargs)