Coverage for python / lsst / ctrl / bps / cli / cmd / commands.py: 76%
94 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 08:53 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 08:53 +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."""
29from collections.abc import Iterator
30from contextlib import contextmanager
32import click
34from lsst.daf.butler.cli.utils import MWCommand
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
53@contextmanager
54def catch_errors() -> Iterator[None]:
55 """Handle errors that occurred during command execution.
57 Returns
58 -------
59 context : `contextlib.AbstractContextManager` [ `None` ]
60 A context manager that does not return a value when entered.
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
78class BpsCommand(MWCommand):
79 """Command subclass with bps-command specific overrides."""
81 extra_epilog = "See 'bps --help' for more options."
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)
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)
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)
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)
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)
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)
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)
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))
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)
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))
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)