lsst-ctrl-mpexec 29.2025.3100__tar.gz → 29.2025.3200__tar.gz

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 (72) hide show
  1. {lsst_ctrl_mpexec-29.2025.3100/python/lsst_ctrl_mpexec.egg-info → lsst_ctrl_mpexec-29.2025.3200}/PKG-INFO +1 -1
  2. lsst_ctrl_mpexec-29.2025.3200/python/lsst/ctrl/mpexec/cli/butler_factory.py +464 -0
  3. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/cmd/commands.py +6 -0
  4. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/opt/optionGroups.py +0 -13
  5. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/opt/options.py +0 -46
  6. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/build.py +49 -36
  7. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/qgraph.py +0 -25
  8. lsst_ctrl_mpexec-29.2025.3200/python/lsst/ctrl/mpexec/cmdLineFwk.py +534 -0
  9. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/showInfo.py +2 -2
  10. lsst_ctrl_mpexec-29.2025.3200/python/lsst/ctrl/mpexec/version.py +2 -0
  11. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200/python/lsst_ctrl_mpexec.egg-info}/PKG-INFO +1 -1
  12. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/SOURCES.txt +1 -0
  13. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliScript.py +0 -1
  14. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cmdLineFwk.py +39 -36
  15. lsst_ctrl_mpexec-29.2025.3100/python/lsst/ctrl/mpexec/cmdLineFwk.py +0 -1060
  16. lsst_ctrl_mpexec-29.2025.3100/python/lsst/ctrl/mpexec/version.py +0 -2
  17. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/COPYRIGHT +0 -0
  18. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/LICENSE +0 -0
  19. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/MANIFEST.in +0 -0
  20. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/README.rst +0 -0
  21. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/bsd_license.txt +0 -0
  22. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/doc/lsst.ctrl.mpexec/CHANGES.rst +0 -0
  23. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/doc/lsst.ctrl.mpexec/configuring-pipetask-tasks.rst +0 -0
  24. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/doc/lsst.ctrl.mpexec/index.rst +0 -0
  25. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/doc/lsst.ctrl.mpexec/pipetask.rst +0 -0
  26. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/gpl-v3.0.txt +0 -0
  27. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/pyproject.toml +0 -0
  28. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/__init__.py +0 -0
  29. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/__init__.py +0 -0
  30. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/__init__.py +0 -0
  31. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/_pipeline_graph_factory.py +0 -0
  32. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/__init__.py +0 -0
  33. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/cmd/__init__.py +0 -0
  34. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/opt/__init__.py +0 -0
  35. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/opt/arguments.py +0 -0
  36. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/pipetask.py +0 -0
  37. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/__init__.py +0 -0
  38. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/cleanup.py +0 -0
  39. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/confirmable.py +0 -0
  40. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/pre_exec_init_qbb.py +0 -0
  41. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/purge.py +0 -0
  42. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/report.py +0 -0
  43. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/run.py +0 -0
  44. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/run_qbb.py +0 -0
  45. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/update_graph_run.py +0 -0
  46. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/utils.py +0 -0
  47. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/execFixupDataId.py +0 -0
  48. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/executionGraphFixup.py +0 -0
  49. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/log_capture.py +0 -0
  50. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/mpGraphExecutor.py +0 -0
  51. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/preExecInit.py +0 -0
  52. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/py.typed +0 -0
  53. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/quantumGraphExecutor.py +0 -0
  54. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/reports.py +0 -0
  55. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/separablePipelineExecutor.py +0 -0
  56. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/simple_pipeline_executor.py +0 -0
  57. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/singleQuantumExecutor.py +0 -0
  58. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/taskFactory.py +0 -0
  59. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/util.py +0 -0
  60. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/dependency_links.txt +0 -0
  61. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/entry_points.txt +0 -0
  62. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/requires.txt +0 -0
  63. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/top_level.txt +0 -0
  64. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/zip-safe +0 -0
  65. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/setup.cfg +0 -0
  66. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdCleanup.py +0 -0
  67. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdPurge.py +0 -0
  68. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdQgraph.py +0 -0
  69. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdReport.py +0 -0
  70. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdUpdateGraphRun.py +0 -0
  71. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliUtils.py +0 -0
  72. {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_preExecInit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-ctrl-mpexec
3
- Version: 29.2025.3100
3
+ Version: 29.2025.3200
4
4
  Summary: Pipeline execution infrastructure for the Rubin Observatory LSST Science Pipelines.
5
5
  Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
6
6
  License: BSD 3-Clause License
@@ -0,0 +1,464 @@
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 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 <http://www.gnu.org/licenses/>.
27
+
28
+ from __future__ import annotations
29
+
30
+ __all__ = [
31
+ "ButlerFactory",
32
+ "OutputChainedCollectionInfo",
33
+ "OutputRunCollectionInfo",
34
+ ]
35
+
36
+ import atexit
37
+ import shutil
38
+ from collections.abc import Sequence
39
+ from types import SimpleNamespace
40
+
41
+ from lsst.daf.butler import Butler, CollectionType
42
+ from lsst.daf.butler.datastore.cache_manager import DatastoreCacheManager
43
+ from lsst.daf.butler.registry import MissingCollectionError, RegistryDefaults
44
+ from lsst.daf.butler.registry.wildcards import CollectionWildcard
45
+ from lsst.pipe.base import Instrument, PipelineGraph
46
+ from lsst.pipe.base.pipeline_graph import NodeType
47
+ from lsst.utils.logging import getLogger
48
+
49
+ _LOG = getLogger(__name__)
50
+
51
+
52
+ class OutputChainedCollectionInfo:
53
+ """A helper class for handling command-line arguments related to an output
54
+ `~lsst.daf.butler.CollectionType.CHAINED` collection.
55
+
56
+ Parameters
57
+ ----------
58
+ butler : `lsst.daf.butler.Butler`
59
+ Butler that collections will be added to and/or queried from.
60
+ name : `str`
61
+ Name of the collection given on the command line.
62
+ """
63
+
64
+ def __init__(self, butler: Butler, name: str):
65
+ self.name = name
66
+ try:
67
+ self.chain = tuple(butler.collections.get_info(name).children)
68
+ self.exists = True
69
+ except MissingCollectionError:
70
+ self.chain = ()
71
+ self.exists = False
72
+
73
+ def __str__(self) -> str:
74
+ return self.name
75
+
76
+ name: str
77
+ """Name of the collection provided on the command line (`str`).
78
+ """
79
+
80
+ exists: bool
81
+ """Whether this collection already exists in the butler (`bool`).
82
+ """
83
+
84
+ chain: tuple[str, ...]
85
+ """The definition of the collection, if it already exists (`tuple`[`str`]).
86
+
87
+ Empty if the collection does not already exist.
88
+ """
89
+
90
+
91
+ class OutputRunCollectionInfo:
92
+ """A helper class for handling command-line arguments related to an output
93
+ `~lsst.daf.butler.CollectionType.RUN` collection.
94
+
95
+ Parameters
96
+ ----------
97
+ butler : `lsst.daf.butler.Butler`
98
+ Butler that collections will be added to and/or queried from.
99
+ name : `str`
100
+ Name of the collection given on the command line.
101
+ """
102
+
103
+ def __init__(self, butler: Butler, name: str):
104
+ self.name = name
105
+ try:
106
+ actual_type = butler.collections.get_info(name).type
107
+ if actual_type is not CollectionType.RUN:
108
+ raise TypeError(f"Collection '{name}' exists but has type {actual_type.name}, not RUN.")
109
+ self.exists = True
110
+ except MissingCollectionError:
111
+ self.exists = False
112
+
113
+ name: str
114
+ """Name of the collection provided on the command line (`str`).
115
+ """
116
+
117
+ exists: bool
118
+ """Whether this collection already exists in the butler (`bool`).
119
+ """
120
+
121
+
122
+ class ButlerFactory:
123
+ """A helper class for processing command-line arguments related to input
124
+ and output collections.
125
+
126
+ Parameters
127
+ ----------
128
+ butler : `lsst.daf.butler.Butler`
129
+ Butler that collections will be added to and/or queried from.
130
+
131
+ args : `types.SimpleNamespace`
132
+ Parsed command-line arguments. The following attributes are used,
133
+ either at construction or in later methods.
134
+
135
+ ``output``
136
+ The name of a `~lsst.daf.butler.CollectionType.CHAINED`
137
+ input/output collection.
138
+
139
+ ``output_run``
140
+ The name of a `~lsst.daf.butler.CollectionType.RUN` input/output
141
+ collection.
142
+
143
+ ``extend_run``
144
+ A boolean indicating whether ``output_run`` should already exist
145
+ and be extended.
146
+
147
+ ``replace_run``
148
+ A boolean indicating that (if `True`) ``output_run`` should already
149
+ exist but will be removed from the output chained collection and
150
+ replaced with a new one.
151
+
152
+ ``prune_replaced``
153
+ A boolean indicating whether to prune the replaced run (requires
154
+ ``replace_run``).
155
+
156
+ ``rebase``
157
+ A boolean indicating whether to force the ``output`` collection
158
+ to be consistent with ``inputs`` and ``output`` run such that the
159
+ ``output`` collection has output run collections first (i.e. those
160
+ that start with the same prefix), then the new inputs, then any
161
+ original inputs not included in the new inputs.
162
+
163
+ ``inputs``
164
+ Input collections of any type; see
165
+ :ref:`daf_butler_ordered_collection_searches` for details.
166
+
167
+ ``butler_config``
168
+ Path to a data repository root or configuration file.
169
+
170
+ writeable : `bool`
171
+ If `True`, a `~lsst.daf.butler.Butler` is being initialized in a
172
+ context where actual writes should happens, and hence no output run
173
+ is necessary.
174
+
175
+ Raises
176
+ ------
177
+ ValueError
178
+ Raised if ``writeable is True`` but there are no output collections.
179
+ """
180
+
181
+ def __init__(self, butler: Butler, args: SimpleNamespace, writeable: bool):
182
+ if args.output is not None:
183
+ self.output = OutputChainedCollectionInfo(butler, args.output)
184
+ else:
185
+ self.output = None
186
+ if args.output_run is not None:
187
+ if args.rebase and self.output and not args.output_run.startswith(self.output.name):
188
+ raise ValueError("Cannot rebase if output run does not start with output collection name.")
189
+ self.output_run = OutputRunCollectionInfo(butler, args.output_run)
190
+ elif self.output is not None:
191
+ if args.extend_run:
192
+ if not self.output.chain:
193
+ raise ValueError("Cannot use --extend-run option with non-existing or empty output chain")
194
+ run_name = self.output.chain[0]
195
+ else:
196
+ run_name = f"{self.output}/{Instrument.makeCollectionTimestamp()}"
197
+ self.output_run = OutputRunCollectionInfo(butler, run_name)
198
+ elif not writeable:
199
+ # If we're not writing yet, ok to have no output run.
200
+ self.output_run = None
201
+ else:
202
+ raise ValueError("Cannot write without at least one of (--output, --output-run).")
203
+ # Recursively flatten any input CHAINED collections. We do this up
204
+ # front so we can tell if the user passes the same inputs on subsequent
205
+ # calls, even though we also flatten when we define the output CHAINED
206
+ # collection.
207
+ self.inputs = tuple(butler.collections.query(args.input, flatten_chains=True)) if args.input else ()
208
+
209
+ # If things are inconsistent and user has asked for a rebase then
210
+ # construct the new output chain.
211
+ if args.rebase and self._check_output_input_consistency():
212
+ assert self.output is not None
213
+ newOutputChain = [item for item in self.output.chain if item.startswith(self.output.name)]
214
+ newOutputChain.extend([item for item in self.inputs if item not in newOutputChain])
215
+ newOutputChain.extend([item for item in self.output.chain if item not in newOutputChain])
216
+ self.output.chain = tuple(newOutputChain)
217
+
218
+ def check(self, args: SimpleNamespace) -> None:
219
+ """Check command-line options for consistency with each other and the
220
+ data repository.
221
+
222
+ Parameters
223
+ ----------
224
+ args : `types.SimpleNamespace`
225
+ Parsed command-line arguments. See class documentation for the
226
+ construction parameter of the same name.
227
+ """
228
+ assert not (args.extend_run and args.replace_run), "In mutually-exclusive group in ArgumentParser."
229
+ if consistencyError := self._check_output_input_consistency():
230
+ raise ValueError(consistencyError)
231
+
232
+ if args.extend_run:
233
+ if self.output_run is None:
234
+ raise ValueError("Cannot --extend-run when no output collection is given.")
235
+ elif not self.output_run.exists:
236
+ raise ValueError(
237
+ f"Cannot --extend-run; output collection '{self.output_run.name}' does not exist."
238
+ )
239
+ if not args.extend_run and self.output_run is not None and self.output_run.exists:
240
+ raise ValueError(
241
+ f"Output run '{self.output_run.name}' already exists, but --extend-run was not given."
242
+ )
243
+ if args.prune_replaced and not args.replace_run:
244
+ raise ValueError("--prune-replaced requires --replace-run.")
245
+ if args.replace_run and (self.output is None or not self.output.exists):
246
+ raise ValueError("--output must point to an existing CHAINED collection for --replace-run.")
247
+
248
+ def _check_output_input_consistency(self) -> str | None:
249
+ if self.inputs and self.output is not None and self.output.exists:
250
+ # Passing the same inputs that were used to initialize the output
251
+ # collection is allowed; this means the inputs must appear as a
252
+ # contiguous subsequence of outputs (normally they're also at the
253
+ # end, but --rebase will in general put them in the middle).
254
+ for n in reversed(range(1 + len(self.output.chain) - len(self.inputs))):
255
+ if self.inputs == self.output.chain[n : n + len(self.inputs)]:
256
+ return None
257
+ return (
258
+ f"Output CHAINED collection {self.output.name!r} exists and does not include the "
259
+ f"same sequence of (flattened) input collections {self.inputs} as a contiguous "
260
+ "subsequence. "
261
+ "Use --rebase to ignore this problem and reset the output collection, but note that "
262
+ "this may obfuscate what inputs were actually used to produce these outputs."
263
+ )
264
+ return None
265
+
266
+ @classmethod
267
+ def _make_read_parts(cls, args: SimpleNamespace) -> tuple[Butler, Sequence[str], ButlerFactory]:
268
+ """Parse arguments to support implementations of `make_read_butler` and
269
+ `make_butler_and_collections`.
270
+
271
+ Parameters
272
+ ----------
273
+ args : `types.SimpleNamespace`
274
+ Parsed command-line arguments. See class documentation for the
275
+ construction parameter of the same name.
276
+
277
+ Returns
278
+ -------
279
+ butler : `lsst.daf.butler.Butler`
280
+ A read-only butler constructed from the repo at
281
+ ``args.butler_config``, but with no default collections.
282
+ inputs : `~collections.abc.Sequence` [ `str` ]
283
+ A collection search path constructed according to ``args``.
284
+ self : `ButlerFactory`
285
+ A new `ButlerFactory` instance representing the processed version
286
+ of ``args``.
287
+ """
288
+ butler = Butler.from_config(args.butler_config, writeable=False)
289
+ self = cls(butler, args, writeable=False)
290
+ self.check(args)
291
+ if self.output and self.output.exists:
292
+ if args.replace_run:
293
+ replaced = self.output.chain[0]
294
+ inputs = list(self.output.chain[1:])
295
+ _LOG.debug(
296
+ "Simulating collection search in '%s' after removing '%s'.", self.output.name, replaced
297
+ )
298
+ else:
299
+ inputs = [self.output.name]
300
+ else:
301
+ inputs = list(self.inputs)
302
+ if args.extend_run:
303
+ assert self.output_run is not None, "Output collection has to be specified."
304
+ inputs.insert(0, self.output_run.name)
305
+ collSearch = CollectionWildcard.from_expression(inputs).require_ordered()
306
+ return butler, collSearch, self
307
+
308
+ @classmethod
309
+ def make_read_butler(cls, args: SimpleNamespace) -> Butler:
310
+ """Construct a read-only butler according to the given command-line
311
+ arguments.
312
+
313
+ Parameters
314
+ ----------
315
+ args : `types.SimpleNamespace`
316
+ Parsed command-line arguments. See class documentation for the
317
+ construction parameter of the same name.
318
+
319
+ Returns
320
+ -------
321
+ butler : `lsst.daf.butler.Butler`
322
+ A read-only butler initialized with the collections specified by
323
+ ``args``.
324
+ """
325
+ cls.define_datastore_cache() # Ensure that this butler can use a shared cache.
326
+ butler, inputs, _ = cls._make_read_parts(args)
327
+ _LOG.debug("Preparing butler to read from %s.", inputs)
328
+ return Butler.from_config(butler=butler, collections=inputs)
329
+
330
+ @classmethod
331
+ def make_butler_and_collections(cls, args: SimpleNamespace) -> tuple[Butler, Sequence[str], str | None]:
332
+ """Return a read-only butler, a collection search path, and the name
333
+ of the run to be used for future writes.
334
+
335
+ Parameters
336
+ ----------
337
+ args : `types.SimpleNamespace`
338
+ Parsed command-line arguments. See class documentation for the
339
+ construction parameter of the same name.
340
+
341
+ Returns
342
+ -------
343
+ butler : `lsst.daf.butler.Butler`
344
+ A read-only butler that collections will be added to and/or queried
345
+ from.
346
+ inputs : `Sequence` [ `str` ]
347
+ Collections to search for datasets.
348
+ run : `str` or `None`
349
+ Name of the output `~lsst.daf.butler.CollectionType.RUN` collection
350
+ if it already exists, or `None` if it does not.
351
+ """
352
+ butler, inputs, self = cls._make_read_parts(args)
353
+ run: str | None = None
354
+ if args.extend_run:
355
+ assert self.output_run is not None, "Output collection has to be specified."
356
+ if self.output_run is not None:
357
+ run = self.output_run.name
358
+ _LOG.debug("Preparing butler to read from %s and expect future writes to '%s'.", inputs, run)
359
+ return butler, inputs, run
360
+
361
+ @staticmethod
362
+ def define_datastore_cache() -> None:
363
+ """Define where datastore cache directories should be found.
364
+
365
+ Notes
366
+ -----
367
+ All the jobs should share a datastore cache if applicable. This
368
+ method asks for a shared fallback cache to be defined and then
369
+ configures an exit handler to clean it up.
370
+ """
371
+ defined, cache_dir = DatastoreCacheManager.set_fallback_cache_directory_if_unset()
372
+ if defined:
373
+ atexit.register(shutil.rmtree, cache_dir, ignore_errors=True)
374
+ _LOG.debug("Defining shared datastore cache directory to %s", cache_dir)
375
+
376
+ @classmethod
377
+ def make_write_butler(cls, args: SimpleNamespace, pipeline_graph: PipelineGraph) -> Butler:
378
+ """Return a read-write butler initialized to write to and read from
379
+ the collections specified by the given command-line arguments.
380
+
381
+ Parameters
382
+ ----------
383
+ args : `types.SimpleNamespace`
384
+ Parsed command-line arguments. See class documentation for the
385
+ construction parameter of the same name.
386
+ pipeline_graph : `lsst.pipe.base.PipelineGraph`
387
+ Definitions for tasks in a pipeline.
388
+
389
+ Returns
390
+ -------
391
+ butler : `lsst.daf.butler.Butler`
392
+ A read-write butler initialized according to the given arguments.
393
+ """
394
+ cls.define_datastore_cache() # Ensure that this butler can use a shared cache.
395
+ butler = Butler.from_config(args.butler_config, writeable=True)
396
+ self = cls(butler, args, writeable=True)
397
+ self.check(args)
398
+ assert self.output_run is not None, "Output collection has to be specified." # for mypy
399
+ if self.output is not None:
400
+ chain_definition = list(
401
+ butler.collections.query(
402
+ self.output.chain if self.output.exists else self.inputs,
403
+ flatten_chains=True,
404
+ include_chains=False,
405
+ )
406
+ )
407
+ if args.replace_run:
408
+ replaced = chain_definition.pop(0)
409
+ if args.prune_replaced == "unstore":
410
+ # Remove datasets from datastore
411
+ with butler.transaction():
412
+ # we want to remove regular outputs from this pipeline,
413
+ # but keep initOutputs, configs, and versions.
414
+ refs = [
415
+ ref
416
+ for ref in butler.registry.queryDatasets(..., collections=replaced)
417
+ if (
418
+ (producer := pipeline_graph.producer_of(ref.datasetType.name)) is not None
419
+ and producer.key.node_type is NodeType.TASK # i.e. not TASK_INIT
420
+ )
421
+ ]
422
+ butler.pruneDatasets(refs, unstore=True, disassociate=False)
423
+ elif args.prune_replaced == "purge":
424
+ # Erase entire collection and all datasets, need to remove
425
+ # collection from its chain collection first.
426
+ with butler.transaction():
427
+ butler.collections.redefine_chain(self.output.name, chain_definition)
428
+ butler.removeRuns([replaced], unstore=True)
429
+ elif args.prune_replaced is not None:
430
+ raise NotImplementedError(f"Unsupported --prune-replaced option '{args.prune_replaced}'.")
431
+ if not self.output.exists:
432
+ butler.collections.register(self.output.name, CollectionType.CHAINED)
433
+ if not args.extend_run:
434
+ butler.collections.register(self.output_run.name, CollectionType.RUN)
435
+ chain_definition.insert(0, self.output_run.name)
436
+ butler.collections.redefine_chain(self.output.name, chain_definition)
437
+ _LOG.debug(
438
+ "Preparing butler to write to '%s' and read from '%s'=%s",
439
+ self.output_run.name,
440
+ self.output.name,
441
+ chain_definition,
442
+ )
443
+ butler.registry.defaults = RegistryDefaults(
444
+ run=self.output_run.name, collections=self.output.name
445
+ )
446
+ else:
447
+ inputs = (self.output_run.name,) + self.inputs
448
+ _LOG.debug("Preparing butler to write to '%s' and read from %s.", self.output_run.name, inputs)
449
+ butler.registry.defaults = RegistryDefaults(run=self.output_run.name, collections=inputs)
450
+ return butler
451
+
452
+ output: OutputChainedCollectionInfo | None
453
+ """Information about the output chained collection, if there is or will be
454
+ one (`OutputChainedCollectionInfo` or `None`).
455
+ """
456
+
457
+ output_run: OutputRunCollectionInfo | None
458
+ """Information about the output run collection, if there is or will be
459
+ one (`OutputRunCollectionInfo` or `None`).
460
+ """
461
+
462
+ inputs: tuple[str, ...]
463
+ """Input collections provided directly by the user (`tuple` [ `str` ]).
464
+ """
@@ -191,7 +191,13 @@ def qgraph(ctx: click.Context, **kwargs: Any) -> None:
191
191
  summary = kwargs.pop("summary", None)
192
192
  with coverage_context(kwargs):
193
193
  show = ShowInfo(kwargs.pop("show", []))
194
+ # The only reason 'build' might want a butler is to resolve the
195
+ # pipeline graph for its own 'show' options, which wouldn't run in this
196
+ # context. Take it out of the kwargs so it doesn't instantiate a
197
+ # butler unnecessarily.
198
+ butler_config = kwargs.pop("butler_config", None)
194
199
  pipeline_graph_factory = script.build(**kwargs, show=show)
200
+ kwargs["butler_config"] = butler_config
195
201
  if show.handled and not show.unhandled:
196
202
  print(
197
203
  "No quantum graph generated. The --show option was given and all options were processed.",
@@ -41,7 +41,6 @@ import click
41
41
 
42
42
  import lsst.daf.butler.cli.opt as dafButlerOpts
43
43
  import lsst.pipe.base.cli.opt as pipeBaseOpts
44
- from lsst.daf.butler.cli.opt import transfer_option_no_short
45
44
  from lsst.daf.butler.cli.utils import OptionGroup, option_section, unwrap
46
45
 
47
46
  from . import options as ctrlMpExecOpts
@@ -72,7 +71,6 @@ class pipeline_build_options(OptionGroup): # noqa: N801
72
71
  metavar="LABEL:FILE",
73
72
  multiple=True,
74
73
  ),
75
- ctrlMpExecOpts.order_pipeline_option(),
76
74
  ctrlMpExecOpts.save_pipeline_option(),
77
75
  ctrlMpExecOpts.select_tasks_option(),
78
76
  ctrlMpExecOpts.pipeline_dot_option(),
@@ -110,20 +108,9 @@ class qgraph_options(OptionGroup): # noqa: N801
110
108
  ctrlMpExecOpts.skip_existing_option(),
111
109
  ctrlMpExecOpts.clobber_outputs_option(),
112
110
  ctrlMpExecOpts.save_qgraph_option(),
113
- ctrlMpExecOpts.save_single_quanta_option(),
114
111
  ctrlMpExecOpts.qgraph_dot_option(),
115
112
  ctrlMpExecOpts.qgraph_mermaid_option(),
116
113
  ctrlMpExecOpts.summary_option(),
117
- ctrlMpExecOpts.save_execution_butler_option(),
118
- ctrlMpExecOpts.clobber_execution_butler_option(),
119
- ctrlMpExecOpts.target_datastore_root_option(),
120
- transfer_option_no_short(
121
- help=unwrap(
122
- """Data transfer mode for the execution butler datastore.
123
- Defaults to "copy" if --target-datastore-root is provided.
124
- """
125
- ),
126
- ),
127
114
  ctrlMpExecOpts.dataset_query_constraint(),
128
115
  ctrlMpExecOpts.data_id_table_option(),
129
116
  ctrlMpExecOpts.qgraph_header_data_option(),
@@ -148,17 +148,6 @@ no_versions_option = MWOptionDecorator(
148
148
  )
149
149
 
150
150
 
151
- order_pipeline_option = MWOptionDecorator(
152
- "--order-pipeline",
153
- help=unwrap(
154
- """Order tasks in pipeline based on their data
155
- dependencies, ordering is performed as last step before saving or
156
- executing pipeline."""
157
- ),
158
- is_flag=True,
159
- )
160
-
161
-
162
151
  output_option = MWOptionDecorator(
163
152
  "-o",
164
153
  "--output",
@@ -322,17 +311,6 @@ save_qgraph_option = MWOptionDecorator(
322
311
  )
323
312
 
324
313
 
325
- save_single_quanta_option = MWOptionDecorator(
326
- "--save-single-quanta",
327
- help=unwrap(
328
- """Format string of locations for storing individual
329
- quantum graph definition (pickle files). The curly
330
- brace {} in the input string will be replaced by a
331
- quantum number. Can be a URI."""
332
- ),
333
- )
334
-
335
-
336
314
  show_option = MWOptionDecorator(
337
315
  "--show",
338
316
  callback=split_commas,
@@ -505,11 +483,6 @@ raise_on_partial_outputs_option = MWOptionDecorator(
505
483
  default=True,
506
484
  )
507
485
 
508
- save_execution_butler_option = MWOptionDecorator(
509
- "--save-execution-butler",
510
- help="Export location for an execution-specific butler after making QuantumGraph",
511
- )
512
-
513
486
  mock_option = MWOptionDecorator(
514
487
  "--mock",
515
488
  help="Mock pipeline execution.",
@@ -586,25 +559,6 @@ mock_failure_option = MWOptionDecorator(
586
559
  ),
587
560
  )
588
561
 
589
-
590
- clobber_execution_butler_option = MWOptionDecorator(
591
- "--clobber-execution-butler",
592
- help=unwrap(
593
- """When creating execution butler overwrite
594
- any existing products"""
595
- ),
596
- is_flag=True,
597
- )
598
-
599
- target_datastore_root_option = MWOptionDecorator(
600
- "--target-datastore-root",
601
- help=unwrap(
602
- """Root directory for datastore of execution butler.
603
- Default is to use the original datastore.
604
- """
605
- ),
606
- )
607
-
608
562
  dataset_query_constraint = MWOptionDecorator(
609
563
  "--dataset-query-constraint",
610
564
  help=unwrap(