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.
- {lsst_ctrl_mpexec-29.2025.3100/python/lsst_ctrl_mpexec.egg-info → lsst_ctrl_mpexec-29.2025.3200}/PKG-INFO +1 -1
- lsst_ctrl_mpexec-29.2025.3200/python/lsst/ctrl/mpexec/cli/butler_factory.py +464 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/cmd/commands.py +6 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/opt/optionGroups.py +0 -13
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/opt/options.py +0 -46
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/build.py +49 -36
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/qgraph.py +0 -25
- lsst_ctrl_mpexec-29.2025.3200/python/lsst/ctrl/mpexec/cmdLineFwk.py +534 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/showInfo.py +2 -2
- lsst_ctrl_mpexec-29.2025.3200/python/lsst/ctrl/mpexec/version.py +2 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200/python/lsst_ctrl_mpexec.egg-info}/PKG-INFO +1 -1
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/SOURCES.txt +1 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliScript.py +0 -1
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cmdLineFwk.py +39 -36
- lsst_ctrl_mpexec-29.2025.3100/python/lsst/ctrl/mpexec/cmdLineFwk.py +0 -1060
- lsst_ctrl_mpexec-29.2025.3100/python/lsst/ctrl/mpexec/version.py +0 -2
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/COPYRIGHT +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/LICENSE +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/MANIFEST.in +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/README.rst +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/bsd_license.txt +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/doc/lsst.ctrl.mpexec/CHANGES.rst +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/doc/lsst.ctrl.mpexec/configuring-pipetask-tasks.rst +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/doc/lsst.ctrl.mpexec/index.rst +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/doc/lsst.ctrl.mpexec/pipetask.rst +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/gpl-v3.0.txt +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/pyproject.toml +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/__init__.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/__init__.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/__init__.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/_pipeline_graph_factory.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/__init__.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/cmd/__init__.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/opt/__init__.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/opt/arguments.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/pipetask.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/__init__.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/cleanup.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/confirmable.py +0 -0
- {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
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/purge.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/report.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/run.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/run_qbb.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/script/update_graph_run.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/cli/utils.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/execFixupDataId.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/executionGraphFixup.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/log_capture.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/mpGraphExecutor.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/preExecInit.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/py.typed +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/quantumGraphExecutor.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/reports.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/separablePipelineExecutor.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/simple_pipeline_executor.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/singleQuantumExecutor.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/taskFactory.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst/ctrl/mpexec/util.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/dependency_links.txt +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/entry_points.txt +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/requires.txt +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/top_level.txt +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/python/lsst_ctrl_mpexec.egg-info/zip-safe +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/setup.cfg +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdCleanup.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdPurge.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdQgraph.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdReport.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliCmdUpdateGraphRun.py +0 -0
- {lsst_ctrl_mpexec-29.2025.3100 → lsst_ctrl_mpexec-29.2025.3200}/tests/test_cliUtils.py +0 -0
- {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.
|
|
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(
|