lsst-ctrl-mpexec 29.2025.3400__py3-none-any.whl → 29.2025.3600__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. lsst/ctrl/mpexec/__init__.py +0 -1
  2. lsst/ctrl/mpexec/cli/butler_factory.py +253 -95
  3. lsst/ctrl/mpexec/cli/cmd/commands.py +2 -2
  4. lsst/ctrl/mpexec/cli/opt/optionGroups.py +0 -1
  5. lsst/ctrl/mpexec/cli/opt/options.py +0 -7
  6. lsst/ctrl/mpexec/cli/script/pre_exec_init_qbb.py +25 -12
  7. lsst/ctrl/mpexec/cli/script/qgraph.py +177 -89
  8. lsst/ctrl/mpexec/cli/script/run.py +211 -99
  9. lsst/ctrl/mpexec/cli/script/run_qbb.py +166 -31
  10. lsst/ctrl/mpexec/cli/utils.py +49 -0
  11. lsst/ctrl/mpexec/showInfo.py +17 -15
  12. lsst/ctrl/mpexec/version.py +1 -1
  13. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/METADATA +1 -1
  14. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/RECORD +22 -23
  15. lsst/ctrl/mpexec/cmdLineFwk.py +0 -534
  16. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/WHEEL +0 -0
  17. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/entry_points.txt +0 -0
  18. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/licenses/COPYRIGHT +0 -0
  19. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/licenses/LICENSE +0 -0
  20. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/licenses/bsd_license.txt +0 -0
  21. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/licenses/gpl-v3.0.txt +0 -0
  22. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/top_level.txt +0 -0
  23. {lsst_ctrl_mpexec-29.2025.3400.dist-info → lsst_ctrl_mpexec-29.2025.3600.dist-info}/zip-safe +0 -0
@@ -25,47 +25,68 @@
25
25
  # You should have received a copy of the GNU General Public License
26
26
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
27
27
 
28
- import logging
29
- from types import SimpleNamespace
28
+ from __future__ import annotations
30
29
 
31
- from lsst.pipe.base.all_dimensions_quantum_graph_builder import DatasetQueryConstraintVariant
30
+ __all__ = ("qgraph",)
32
31
 
33
- from ... import CmdLineFwk
32
+ import uuid
33
+ from collections.abc import Iterable, Mapping, Sequence
34
+ from typing import TYPE_CHECKING
34
35
 
35
- _log = logging.getLogger(__name__)
36
+ from astropy.table import Table
36
37
 
38
+ from lsst.pipe.base import BuildId, QuantumGraph
39
+ from lsst.pipe.base.all_dimensions_quantum_graph_builder import (
40
+ AllDimensionsQuantumGraphBuilder,
41
+ DatasetQueryConstraintVariant,
42
+ )
43
+ from lsst.pipe.base.dot_tools import graph2dot
44
+ from lsst.pipe.base.mermaid_tools import graph2mermaid
45
+ from lsst.resources import ResourcePath, ResourcePathExpression
46
+ from lsst.utils.iteration import ensure_iterable
47
+ from lsst.utils.logging import getLogger
37
48
 
38
- def qgraph( # type: ignore
39
- pipeline_graph_factory,
49
+ from ..._pipeline_graph_factory import PipelineGraphFactory
50
+ from ...showInfo import ShowInfo
51
+ from ..butler_factory import ButlerFactory
52
+ from ..utils import summarize_quantum_graph
53
+
54
+ if TYPE_CHECKING:
55
+ from lsst.pipe.base.tests.mocks import ForcedFailure # this monkey patches; only import for annotation!
56
+
57
+ _LOG = getLogger(__name__)
58
+
59
+
60
+ def qgraph(
61
+ pipeline_graph_factory: PipelineGraphFactory | None,
40
62
  *,
41
- qgraph,
42
- qgraph_id,
43
- qgraph_node_id,
44
- qgraph_datastore_records,
45
- skip_existing_in,
46
- skip_existing,
47
- save_qgraph,
48
- qgraph_dot,
49
- qgraph_mermaid,
50
- butler_config,
51
- input,
52
- output,
53
- output_run,
54
- extend_run,
55
- replace_run,
56
- prune_replaced,
57
- data_query,
58
- data_id_table=(),
59
- show,
60
- clobber_outputs,
61
- dataset_query_constraint,
62
- rebase,
63
- show_qgraph_header=False,
64
- mock=False,
65
- unmocked_dataset_types=(),
66
- mock_failure=(),
67
- **kwargs,
68
- ):
63
+ qgraph: QuantumGraph | ResourcePathExpression | None,
64
+ qgraph_id: str | None,
65
+ qgraph_node_id: Iterable[uuid.UUID | str] | None,
66
+ qgraph_datastore_records: bool,
67
+ skip_existing_in: Iterable[str] | None,
68
+ skip_existing: bool,
69
+ save_qgraph: ResourcePathExpression | None,
70
+ qgraph_dot: str | None,
71
+ qgraph_mermaid: str | None,
72
+ butler_config: ResourcePathExpression,
73
+ input: Iterable[str] | str,
74
+ output: str | None,
75
+ output_run: str | None,
76
+ extend_run: bool,
77
+ replace_run: bool,
78
+ prune_replaced: str | None,
79
+ data_query: str | None,
80
+ data_id_table: Iterable[ResourcePathExpression],
81
+ show: ShowInfo,
82
+ clobber_outputs: bool,
83
+ dataset_query_constraint: str,
84
+ rebase: bool,
85
+ mock: bool = False,
86
+ unmocked_dataset_types: Sequence[str],
87
+ mock_failure: Mapping[str, ForcedFailure],
88
+ **kwargs: object,
89
+ ) -> QuantumGraph | None:
69
90
  """Implement the command line interface `pipetask qgraph` subcommand.
70
91
 
71
92
  Should only be called by command line tools and unit test code that test
@@ -73,50 +94,47 @@ def qgraph( # type: ignore
73
94
 
74
95
  Parameters
75
96
  ----------
76
- pipeline_graph_factory : `..PipelineGraphFactory` or None
97
+ pipeline_graph_factory : `..PipelineGraphFactory` or `None`
77
98
  A factory that holds the pipeline and can produce a pipeline graph.
78
99
  If this is not `None` then `qgraph` should be `None`.
79
- qgraph : `str` or `None`
80
- URI location for a serialized quantum graph definition as a pickle
81
- file. If this option is not None then ``pipeline_graph_factory`` should
82
- be `None`.
100
+ qgraph : `lsst.pipe.base.QuantumGraph`, convertible to \
101
+ `lsst.resources.ResourcePath` or `None`
102
+ URI location for a serialized quantum graph definition. If this option
103
+ is not `None` then ``pipeline_graph_factory`` should be `None`.
83
104
  qgraph_id : `str` or `None`
84
105
  Quantum graph identifier, if specified must match the identifier of the
85
106
  graph loaded from a file. Ignored if graph is not loaded from a file.
86
- qgraph_node_id : `list` of `int`, optional
107
+ qgraph_node_id : `~collections.abc.Iterable` [`str` | `uuid.UUID`] or \
108
+ `None`
87
109
  Only load a specified set of nodes if graph is loaded from a file,
88
110
  nodes are identified by integer IDs.
89
111
  qgraph_datastore_records : `bool`
90
- If True then include datastore records into generated quanta.
91
- skip_existing_in : `list` [ `str` ]
112
+ If `True` then include datastore records into generated quanta.
113
+ skip_existing_in : `~collections.abc.Iterable` [ `str` ] or `None`
92
114
  Accepts list of collections, if all Quantum outputs already exist in
93
115
  the specified list of collections then that Quantum will be excluded
94
116
  from the QuantumGraph.
95
117
  skip_existing : `bool`
96
118
  Appends output RUN collection to the ``skip_existing_in`` list.
97
- save_qgraph : `str` or `None`
98
- URI location for storing a serialized quantum graph definition as a
99
- pickle file.
119
+ save_qgraph : convertible to `lsst.resources.ResourcePath` or `None`
120
+ URI location for saving the quantum graph.
100
121
  qgraph_dot : `str` or `None`
101
122
  Path location for storing GraphViz DOT representation of a quantum
102
123
  graph.
103
124
  qgraph_mermaid : `str` or `None`
104
125
  Path location for storing Mermaid representation of a quantum graph.
105
- butler_config : `str`, `dict`, or `lsst.daf.butler.Config`
106
- If `str`, `butler_config` is the path location of the gen3
107
- butler/registry config file. If `dict`, `butler_config` is key value
108
- pairs used to init or update the `lsst.daf.butler.Config` instance. If
109
- `Config`, it is the object used to configure a Butler.
110
- input : `list` [ `str` ]
126
+ butler_config : convertible to `lsst.resources.ResourcePath`
127
+ Path to butler repository configuration.
128
+ input : `~collections.abc.Iterable` [ `str` ] or `None`
111
129
  List of names of the input collection(s).
112
- output : `str`
130
+ output : `str` or `None`
113
131
  Name of the output CHAINED collection. This may either be an existing
114
132
  CHAINED collection to use as both input and output (if `input` is
115
133
  `None`), or a new CHAINED collection created to include all inputs
116
134
  (if `input` is not `None`). In both cases, the collection's children
117
135
  will start with an output RUN collection that directly holds all new
118
136
  datasets (see `output_run`).
119
- output_run : `str`
137
+ output_run : `str` or `None`
120
138
  Name of the new output RUN collection. If not provided then `output`
121
139
  must be provided and a new RUN collection will be created by appending
122
140
  a timestamp to the value passed with `output`. If this collection
@@ -139,7 +157,8 @@ def qgraph( # type: ignore
139
157
  ``replace_run`` to be `True`.
140
158
  data_query : `str`
141
159
  User query selection expression.
142
- data_id_table : `~collections.abc.Iterable` [`str`]
160
+ data_id_table : `~collections.abc.Iterable` [convertible to \
161
+ `lsst.resources.ResourcePath`]
143
162
  Paths to data ID tables to join in.
144
163
  show : `lsst.ctrl.mpexec.showInfo.ShowInfo`
145
164
  Descriptions of what to dump to stdout.
@@ -154,61 +173,130 @@ def qgraph( # type: ignore
154
173
  rebase : `bool`
155
174
  If `True` then reset output collection chain if it is inconsistent with
156
175
  the ``inputs``.
157
- show_qgraph_header : bool, optional
158
- Controls if the headerData of a QuantumGraph should be printed to the
159
- terminal. Defaults to False.
160
- mock : `bool`, optional
176
+ mock : `bool`
161
177
  If True, use a mocked version of the pipeline.
162
178
  unmocked_dataset_types : `collections.abc.Sequence` [ `str` ], optional
163
179
  List of overall-input dataset types that should not be mocked.
164
- mock_failure : `~collections.abc.Sequence`, optional
165
- List of quanta that should raise exceptions.
166
- **kwargs : `dict` [`str`, `str`]
180
+ mock_failure : `~collections.abc.Mapping`
181
+ Quanta that should raise exceptions.
182
+ **kwargs : `object`
167
183
  Ignored; click commands may accept options for more than one script
168
184
  function and pass all the option kwargs to each of the script functions
169
185
  which ignore these unused kwargs.
170
186
 
171
187
  Returns
172
188
  -------
173
- qgraph : `lsst.pipe.base.QuantumGraph`
189
+ qgraph : `lsst.pipe.base.QuantumGraph` or `None`
174
190
  The qgraph object that was created.
175
191
  """
176
- dataset_query_constraint = DatasetQueryConstraintVariant.fromExpression(dataset_query_constraint)
177
- args = SimpleNamespace(
178
- qgraph=qgraph,
179
- qgraph_id=qgraph_id,
180
- qgraph_node_id=qgraph_node_id,
181
- qgraph_datastore_records=qgraph_datastore_records,
182
- save_qgraph=save_qgraph,
183
- qgraph_dot=qgraph_dot,
184
- qgraph_mermaid=qgraph_mermaid,
185
- butler_config=butler_config,
186
- input=input,
192
+ # make sure that --extend-run always enables --skip-existing
193
+ if extend_run:
194
+ skip_existing = True
195
+
196
+ skip_existing_in = tuple(skip_existing_in) if skip_existing_in is not None else ()
197
+ if data_query is None:
198
+ data_query = ""
199
+ inputs = list(ensure_iterable(input)) if input else []
200
+ del input
201
+
202
+ butler, collections, run = ButlerFactory.make_butler_and_collections(
203
+ butler_config,
187
204
  output=output,
188
205
  output_run=output_run,
206
+ inputs=inputs,
189
207
  extend_run=extend_run,
208
+ rebase=rebase,
190
209
  replace_run=replace_run,
191
210
  prune_replaced=prune_replaced,
192
- data_query=data_query,
193
- data_id_table=data_id_table,
194
- skip_existing_in=skip_existing_in,
195
- skip_existing=skip_existing,
196
- clobber_outputs=clobber_outputs,
197
- dataset_query_constraint=dataset_query_constraint,
198
- rebase=rebase,
199
- show_qgraph_header=show_qgraph_header,
200
- mock=mock,
201
- unmocked_dataset_types=list(unmocked_dataset_types),
202
- mock_failure=mock_failure,
203
211
  )
204
212
 
205
- f = CmdLineFwk()
206
- qgraph = f.makeGraph(pipeline_graph_factory, args)
213
+ if skip_existing and run:
214
+ skip_existing_in += (run,)
215
+
216
+ if qgraph is not None:
217
+ if not isinstance(qgraph, QuantumGraph):
218
+ # click passes empty tuple as default value for qgraph_node_id
219
+ nodes = qgraph_node_id or None
220
+ qgraph = QuantumGraph.loadUri(
221
+ qgraph,
222
+ butler.dimensions,
223
+ nodes=nodes,
224
+ graphID=BuildId(qgraph_id) if qgraph_id is not None else None,
225
+ )
226
+
227
+ # pipeline can not be provided in this case
228
+ if pipeline_graph_factory:
229
+ raise ValueError(
230
+ "Pipeline must not be given when quantum graph is read from "
231
+ f"file: {bool(pipeline_graph_factory)}"
232
+ )
233
+ else:
234
+ if pipeline_graph_factory is None:
235
+ raise ValueError("Pipeline must be given when quantum graph is not read from file.")
236
+ # We can't resolve the pipeline graph if we're mocking until after
237
+ # we've done the mocking (and the QG build will resolve on its own
238
+ # anyway).
239
+ pipeline_graph = pipeline_graph_factory(resolve=False)
240
+ if mock:
241
+ from lsst.pipe.base.tests.mocks import mock_pipeline_graph
242
+
243
+ pipeline_graph = mock_pipeline_graph(
244
+ pipeline_graph,
245
+ unmocked_dataset_types=unmocked_dataset_types,
246
+ force_failures=mock_failure,
247
+ )
248
+ data_id_tables = []
249
+ for table_file in data_id_table:
250
+ with ResourcePath(table_file).as_local() as local_path:
251
+ table = Table.read(local_path.ospath)
252
+ # Add the filename to the metadata for more logging
253
+ # information down in the QG builder.
254
+ table.meta["filename"] = table_file
255
+ data_id_tables.append(table)
256
+ # make execution plan (a.k.a. DAG) for pipeline
257
+ graph_builder = AllDimensionsQuantumGraphBuilder(
258
+ pipeline_graph,
259
+ butler,
260
+ where=data_query,
261
+ skip_existing_in=skip_existing_in,
262
+ clobber=clobber_outputs,
263
+ dataset_query_constraint=DatasetQueryConstraintVariant.fromExpression(dataset_query_constraint),
264
+ input_collections=collections,
265
+ output_run=run,
266
+ data_id_tables=data_id_tables,
267
+ )
268
+ # accumulate metadata
269
+ metadata = {
270
+ "input": inputs,
271
+ "output": output,
272
+ "butler_argument": str(butler_config),
273
+ "output_run": run,
274
+ "extend_run": extend_run,
275
+ "skip_existing_in": skip_existing_in,
276
+ "skip_existing": skip_existing,
277
+ "data_query": data_query,
278
+ }
279
+ assert run is not None, "Butler output run collection must be defined"
280
+ qgraph = graph_builder.build(metadata, attach_datastore_records=qgraph_datastore_records)
207
281
 
208
- if qgraph is None:
282
+ if len(qgraph) == 0:
283
+ # Nothing to do.
209
284
  return None
285
+ summarize_quantum_graph(qgraph)
286
+
287
+ if save_qgraph:
288
+ _LOG.verbose("Writing QuantumGraph to %r.", save_qgraph)
289
+ qgraph.saveUri(save_qgraph)
290
+
291
+ if qgraph_dot:
292
+ _LOG.verbose("Writing quantum graph DOT visualization to %r.", qgraph_dot)
293
+ graph2dot(qgraph, qgraph_dot)
294
+
295
+ if qgraph_mermaid:
296
+ _LOG.verbose("Writing quantum graph Mermaid visualization to %r.", qgraph_mermaid)
297
+ graph2mermaid(qgraph, qgraph_mermaid)
210
298
 
211
299
  # optionally dump some info.
212
- show.show_graph_info(qgraph, args)
300
+ show.show_graph_info(qgraph, butler_config)
213
301
 
214
302
  return qgraph