lsst-ctrl-mpexec 29.2025.2000__tar.gz → 29.2025.2400__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 (77) hide show
  1. {lsst_ctrl_mpexec-29.2025.2000/python/lsst_ctrl_mpexec.egg-info → lsst_ctrl_mpexec-29.2025.2400}/PKG-INFO +1 -1
  2. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/simple_pipeline_executor.py +140 -3
  3. lsst_ctrl_mpexec-29.2025.2400/python/lsst/ctrl/mpexec/version.py +2 -0
  4. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400/python/lsst_ctrl_mpexec.egg-info}/PKG-INFO +1 -1
  5. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_cmdLineFwk.py +2 -2
  6. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_simple_pipeline_executor.py +22 -38
  7. lsst_ctrl_mpexec-29.2025.2000/python/lsst/ctrl/mpexec/version.py +0 -2
  8. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/COPYRIGHT +0 -0
  9. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/LICENSE +0 -0
  10. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/MANIFEST.in +0 -0
  11. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/README.rst +0 -0
  12. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/bsd_license.txt +0 -0
  13. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/doc/lsst.ctrl.mpexec/CHANGES.rst +0 -0
  14. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/doc/lsst.ctrl.mpexec/configuring-pipetask-tasks.rst +0 -0
  15. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/doc/lsst.ctrl.mpexec/index.rst +0 -0
  16. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/doc/lsst.ctrl.mpexec/pipetask.rst +0 -0
  17. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/gpl-v3.0.txt +0 -0
  18. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/pyproject.toml +0 -0
  19. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/__init__.py +0 -0
  20. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/__init__.py +0 -0
  21. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/__init__.py +0 -0
  22. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/_pipeline_graph_factory.py +0 -0
  23. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/__init__.py +0 -0
  24. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/cmd/__init__.py +0 -0
  25. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/cmd/commands.py +0 -0
  26. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/opt/__init__.py +0 -0
  27. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/opt/arguments.py +0 -0
  28. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/opt/optionGroups.py +0 -0
  29. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/opt/options.py +0 -0
  30. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/pipetask.py +0 -0
  31. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/__init__.py +0 -0
  32. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/build.py +0 -0
  33. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/cleanup.py +0 -0
  34. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/confirmable.py +0 -0
  35. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/pre_exec_init_qbb.py +0 -0
  36. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/purge.py +0 -0
  37. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/qgraph.py +0 -0
  38. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/report.py +0 -0
  39. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/run.py +0 -0
  40. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/run_qbb.py +0 -0
  41. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/script/update_graph_run.py +0 -0
  42. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cli/utils.py +0 -0
  43. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/cmdLineFwk.py +0 -0
  44. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/dotTools.py +0 -0
  45. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/execFixupDataId.py +0 -0
  46. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/executionGraphFixup.py +0 -0
  47. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/log_capture.py +0 -0
  48. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/mpGraphExecutor.py +0 -0
  49. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/preExecInit.py +0 -0
  50. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/py.typed +0 -0
  51. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/quantumGraphExecutor.py +0 -0
  52. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/reports.py +0 -0
  53. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/separablePipelineExecutor.py +0 -0
  54. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/showInfo.py +0 -0
  55. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/singleQuantumExecutor.py +0 -0
  56. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/taskFactory.py +0 -0
  57. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst/ctrl/mpexec/util.py +0 -0
  58. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst_ctrl_mpexec.egg-info/SOURCES.txt +0 -0
  59. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst_ctrl_mpexec.egg-info/dependency_links.txt +0 -0
  60. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst_ctrl_mpexec.egg-info/entry_points.txt +0 -0
  61. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst_ctrl_mpexec.egg-info/requires.txt +0 -0
  62. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst_ctrl_mpexec.egg-info/top_level.txt +0 -0
  63. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/python/lsst_ctrl_mpexec.egg-info/zip-safe +0 -0
  64. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/setup.cfg +0 -0
  65. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_cliCmdCleanup.py +0 -0
  66. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_cliCmdPurge.py +0 -0
  67. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_cliCmdQgraph.py +0 -0
  68. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_cliCmdReport.py +0 -0
  69. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_cliCmdUpdateGraphRun.py +0 -0
  70. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_cliScript.py +0 -0
  71. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_cliUtils.py +0 -0
  72. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_execution_storage_class_conversion.py +0 -0
  73. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_executors.py +0 -0
  74. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_preExecInit.py +0 -0
  75. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_reports.py +0 -0
  76. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_separablePipelineExecutor.py +0 -0
  77. {lsst_ctrl_mpexec-29.2025.2000 → lsst_ctrl_mpexec-29.2025.2400}/tests/test_taskFactory.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-ctrl-mpexec
3
- Version: 29.2025.2000
3
+ Version: 29.2025.2400
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
@@ -31,10 +31,11 @@ __all__ = ("SimplePipelineExecutor",)
31
31
 
32
32
  import datetime
33
33
  import getpass
34
+ import os
34
35
  from collections.abc import Iterable, Iterator, Mapping
35
36
  from typing import Any
36
37
 
37
- from lsst.daf.butler import Butler, CollectionType, Quantum
38
+ from lsst.daf.butler import Butler, CollectionType, DataCoordinate, DatasetRef, Quantum
38
39
  from lsst.pex.config import Config
39
40
  from lsst.pipe.base import ExecutionResources, Instrument, Pipeline, PipelineGraph, PipelineTask, QuantumGraph
40
41
  from lsst.pipe.base.all_dimensions_quantum_graph_builder import AllDimensionsQuantumGraphBuilder
@@ -152,6 +153,8 @@ class SimplePipelineExecutor:
152
153
  resources: ExecutionResources | None = None,
153
154
  raise_on_partial_outputs: bool = True,
154
155
  attach_datastore_records: bool = False,
156
+ output: str | None = None,
157
+ output_run: str | None = None,
155
158
  ) -> SimplePipelineExecutor:
156
159
  """Create an executor by building a QuantumGraph from an on-disk
157
160
  pipeline YAML file.
@@ -179,6 +182,13 @@ class SimplePipelineExecutor:
179
182
  Whether to attach datastore records to the quantum graph. This is
180
183
  usually unnecessary, unless the executor is used to test behavior
181
184
  that depends on datastore records.
185
+ output : `str`, optional
186
+ Name of a new output `~lsst.daf.butler.CollectionType.CHAINED`
187
+ collection to create that will combine both inputs and outputs.
188
+ output_run : `str`, optional
189
+ Name of the output `~lsst.daf.butler.CollectionType.RUN` that will
190
+ directly hold all output datasets. If not provided, a name will
191
+ be created from ``output`` and a timestamp.
182
192
 
183
193
  Returns
184
194
  -------
@@ -196,6 +206,8 @@ class SimplePipelineExecutor:
196
206
  resources=resources,
197
207
  raise_on_partial_outputs=raise_on_partial_outputs,
198
208
  attach_datastore_records=attach_datastore_records,
209
+ output=output,
210
+ output_run=output_run,
199
211
  )
200
212
 
201
213
  @classmethod
@@ -211,6 +223,8 @@ class SimplePipelineExecutor:
211
223
  resources: ExecutionResources | None = None,
212
224
  raise_on_partial_outputs: bool = True,
213
225
  attach_datastore_records: bool = False,
226
+ output: str | None = None,
227
+ output_run: str | None = None,
214
228
  ) -> SimplePipelineExecutor:
215
229
  """Create an executor by building a QuantumGraph from a pipeline
216
230
  containing a single task.
@@ -244,6 +258,13 @@ class SimplePipelineExecutor:
244
258
  Whether to attach datastore records to the quantum graph. This is
245
259
  usually unnecessary, unless the executor is used to test behavior
246
260
  that depends on datastore records.
261
+ output : `str`, optional
262
+ Name of a new output `~lsst.daf.butler.CollectionType.CHAINED`
263
+ collection to create that will combine both inputs and outputs.
264
+ output_run : `str`, optional
265
+ Name of the output `~lsst.daf.butler.CollectionType.RUN` that will
266
+ directly hold all output datasets. If not provided, a name will
267
+ be created from ``output`` and a timestamp.
247
268
 
248
269
  Returns
249
270
  -------
@@ -271,6 +292,8 @@ class SimplePipelineExecutor:
271
292
  resources=resources,
272
293
  raise_on_partial_outputs=raise_on_partial_outputs,
273
294
  attach_datastore_records=attach_datastore_records,
295
+ output=output,
296
+ output_run=output_run,
274
297
  )
275
298
 
276
299
  @classmethod
@@ -284,6 +307,8 @@ class SimplePipelineExecutor:
284
307
  resources: ExecutionResources | None = None,
285
308
  raise_on_partial_outputs: bool = True,
286
309
  attach_datastore_records: bool = False,
310
+ output: str | None = None,
311
+ output_run: str | None = None,
287
312
  ) -> SimplePipelineExecutor:
288
313
  """Create an executor by building a QuantumGraph from an in-memory
289
314
  pipeline.
@@ -313,6 +338,13 @@ class SimplePipelineExecutor:
313
338
  Whether to attach datastore records to the quantum graph. This is
314
339
  usually unnecessary, unless the executor is used to test behavior
315
340
  that depends on datastore records.
341
+ output : `str`, optional
342
+ Name of a new output `~lsst.daf.butler.CollectionType.CHAINED`
343
+ collection to create that will combine both inputs and outputs.
344
+ output_run : `str`, optional
345
+ Name of the output `~lsst.daf.butler.CollectionType.RUN` that will
346
+ directly hold all output datasets. If not provided, a name will
347
+ be created from ``output`` and a timestamp.
316
348
 
317
349
  Returns
318
350
  -------
@@ -330,6 +362,8 @@ class SimplePipelineExecutor:
330
362
  resources=resources,
331
363
  raise_on_partial_outputs=raise_on_partial_outputs,
332
364
  attach_datastore_records=attach_datastore_records,
365
+ output=output,
366
+ output_run=output_run,
333
367
  )
334
368
 
335
369
  @classmethod
@@ -343,6 +377,8 @@ class SimplePipelineExecutor:
343
377
  resources: ExecutionResources | None = None,
344
378
  raise_on_partial_outputs: bool = True,
345
379
  attach_datastore_records: bool = False,
380
+ output: str | None = None,
381
+ output_run: str | None = None,
346
382
  ) -> SimplePipelineExecutor:
347
383
  """Create an executor by building a QuantumGraph from an in-memory
348
384
  pipeline graph.
@@ -373,6 +409,13 @@ class SimplePipelineExecutor:
373
409
  Whether to attach datastore records to the quantum graph. This is
374
410
  usually unnecessary, unless the executor is used to test behavior
375
411
  that depends on datastore records.
412
+ output : `str`, optional
413
+ Name of a new output `~lsst.daf.butler.CollectionType.CHAINED`
414
+ collection to create that will combine both inputs and outputs.
415
+ output_run : `str`, optional
416
+ Name of the output `~lsst.daf.butler.CollectionType.RUN` that will
417
+ directly hold all output datasets. If not provided, a name will
418
+ be created from ``output`` and a timestamp.
376
419
 
377
420
  Returns
378
421
  -------
@@ -381,12 +424,20 @@ class SimplePipelineExecutor:
381
424
  `~lsst.pipe.base.QuantumGraph` and `~lsst.daf.butler.Butler`, ready
382
425
  for `run` to be called.
383
426
  """
427
+ if output_run is None:
428
+ output_run = butler.run
429
+ if output_run is None:
430
+ if output is None:
431
+ raise TypeError("At least one of output or output_run must be provided.")
432
+ output_run = f"{output}/{Instrument.makeCollectionTimestamp()}"
433
+
384
434
  quantum_graph_builder = AllDimensionsQuantumGraphBuilder(
385
- pipeline_graph, butler, where=where, bind=bind
435
+ pipeline_graph, butler, where=where, bind=bind, output_run=output_run
386
436
  )
387
437
  metadata = {
388
438
  "input": list(butler.collections.defaults),
389
- "output_run": butler.run,
439
+ "output": output,
440
+ "output_run": output_run,
390
441
  "skip_existing_in": [],
391
442
  "skip_existing": False,
392
443
  "data_query": where,
@@ -403,6 +454,92 @@ class SimplePipelineExecutor:
403
454
  raise_on_partial_outputs=raise_on_partial_outputs,
404
455
  )
405
456
 
457
+ def use_local_butler(
458
+ self, root: str, register_dataset_types: bool = True, transfer_dimensions: bool = True
459
+ ) -> Butler:
460
+ """Transfer all inputs to a local data repository. and set the executor
461
+ to write outputs to it.
462
+
463
+ Parameters
464
+ ----------
465
+ root : `str`
466
+ Path to the local data repository; created if it does not exist.
467
+ register_dataset_types : `bool`, optional
468
+ Whether to register dataset types in the new repository. If
469
+ `False`, the local data repository must already exist and already
470
+ have all input dataset types registered.
471
+ transfer_dimensions : `bool`, optional
472
+ Whether to transfer dimension records to the new repository. If
473
+ `False`, the local data repository must already exist and already
474
+ have all needed dimension records.
475
+
476
+ Returns
477
+ -------
478
+ butler : `lsst.daf.butler.Butler`
479
+ Writeable butler for local data repository.
480
+
481
+ Notes
482
+ -----
483
+ The input collection structure from the original data repository is not
484
+ preserved by this method (it cannot be reconstructed from the quantum
485
+ graph). Instead, a `~lsst.daf.butler.CollectionType.TAGGED` collection
486
+ is created to gather all inputs, and appended to the output
487
+ `~lsst.daf.butler.CollectionType.CHAINED` collection after the output
488
+ `~lsst.daf.butler.CollectionType.RUN` collection. Calibration inputs
489
+ with the same data ID but multiple validity ranges are *not* included
490
+ in that `~lsst.daf.butler.CollectionType.TAGGED`; they are still
491
+ transferred to the local data repository, but can only be found via the
492
+ quantum graph or their original `~lsst.daf.butler.CollectionType.RUN`
493
+ collections.
494
+ """
495
+ if not os.path.exists(root):
496
+ Butler.makeRepo(root)
497
+ out_butler = Butler.from_config(root, writeable=True)
498
+
499
+ output_run = self.quantum_graph.metadata["output_run"]
500
+ output = self.quantum_graph.metadata["output"]
501
+ inputs = f"{output}/inputs"
502
+ out_butler.collections.register(output_run, CollectionType.RUN)
503
+ out_butler.collections.register(output, CollectionType.CHAINED)
504
+ out_butler.collections.register(inputs, CollectionType.TAGGED)
505
+ out_butler.collections.redefine_chain(output, [output_run, inputs])
506
+
507
+ refs: set[DatasetRef] = set()
508
+ to_tag_by_type: dict[str, dict[DataCoordinate, DatasetRef | None]] = {}
509
+ pipeline_graph = self.quantum_graph.pipeline_graph
510
+ for name, _ in pipeline_graph.iter_overall_inputs():
511
+ to_tag_for_type = to_tag_by_type.setdefault(name, {})
512
+ for task_node in pipeline_graph.consumers_of(name):
513
+ for quantum in self.quantum_graph.get_task_quanta(task_node.label).values():
514
+ refs.update(quantum.inputs[name])
515
+ for ref in quantum.inputs[name]:
516
+ if to_tag_for_type.setdefault(ref.dataId, ref) != ref:
517
+ # There is already a dataset with the same data ID
518
+ # and dataset type, but a different UUID/run. This
519
+ # can only happen for calibrations found in
520
+ # calibration collections, and for now we have no
521
+ # choice but to leave them out of the TAGGED inputs
522
+ # collection in the local butler.
523
+ to_tag_for_type[ref.dataId] = None
524
+
525
+ out_butler.transfer_from(
526
+ self.butler,
527
+ refs,
528
+ register_dataset_types=register_dataset_types,
529
+ transfer_dimensions=transfer_dimensions,
530
+ )
531
+
532
+ to_tag_flat: list[DatasetRef] = []
533
+ for ref_map in to_tag_by_type.values():
534
+ for tag_ref in ref_map.values():
535
+ if tag_ref is not None:
536
+ to_tag_flat.append(tag_ref)
537
+ out_butler.registry.associate(inputs, to_tag_flat)
538
+
539
+ out_butler.registry.defaults = self.butler.registry.defaults.clone(collections=output, run=output_run)
540
+ self.butler = out_butler
541
+ return self.butler
542
+
406
543
  def run(self, register_dataset_types: bool = False, save_versions: bool = True) -> list[Quantum]:
407
544
  """Run all the quanta in the `~lsst.pipe.base.QuantumGraph` in
408
545
  topological order.
@@ -0,0 +1,2 @@
1
+ __all__ = ["__version__"]
2
+ __version__ = "29.2025.2400"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-ctrl-mpexec
3
- Version: 29.2025.2000
3
+ Version: 29.2025.2400
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
@@ -670,7 +670,7 @@ class CmdLineFwkTestCaseWithButler(unittest.TestCase):
670
670
  fwk.runGraphQBB(taskFactory, args)
671
671
 
672
672
  # Transfer the datasets to the butler.
673
- n1 = transfer_from_graph(temp_graph.name, self.root, True, False, False, False)
673
+ n1 = transfer_from_graph(temp_graph.name, self.root, True, False, False, False, ())
674
674
  self.assertEqual(n1, 31)
675
675
 
676
676
  self.assertEqual(taskFactory.countExec, self.nQuanta)
@@ -708,7 +708,7 @@ class CmdLineFwkTestCaseWithButler(unittest.TestCase):
708
708
  fwk.runGraphQBB(taskFactory, args)
709
709
 
710
710
  # Transfer the datasets to the butler.
711
- n2 = transfer_from_graph(temp_graph.name, self.root, True, False, False, False)
711
+ n2 = transfer_from_graph(temp_graph.name, self.root, True, False, False, False, ())
712
712
  self.assertEqual(n1, n2)
713
713
 
714
714
  def testEmptyQGraph(self):
@@ -179,51 +179,35 @@ class SimplePipelineExecutorTests(lsst.utils.tests.TestCase):
179
179
  # The final output was written.
180
180
  self.assertTrue(self.butler.exists("output"))
181
181
 
182
- def test_from_pipeline_file(self):
182
+ def test_from_pipeline_file(self) -> None:
183
183
  """Test executing a two quanta from different configurations of the
184
184
  same task, with an executor created by the `from_pipeline_filename`
185
185
  factory method, and the `SimplePipelineExecutor.run` method.
186
186
  """
187
- filename = os.path.join(self.path, "pipeline.yaml")
188
- with open(filename, "w") as f:
189
- f.write(
190
- """
191
- description: test
192
- tasks:
193
- a:
194
- class: "lsst.pipe.base.tests.mocks.DynamicTestPipelineTask"
195
- config:
196
- python: |
197
- from lsst.pipe.base.tests.mocks import DynamicConnectionConfig
198
- config.inputs["i"] = DynamicConnectionConfig(
199
- dataset_type_name="input",
200
- storage_class="StructuredDataDict",
201
- mock_storage_class=False,
202
- )
203
- config.outputs["o"] = DynamicConnectionConfig(
204
- dataset_type_name="intermediate",
205
- storage_class="StructuredDataDict",
206
- )
207
- b:
208
- class: "lsst.pipe.base.tests.mocks.DynamicTestPipelineTask"
209
- config:
210
- python: |
211
- from lsst.pipe.base.tests.mocks import DynamicConnectionConfig
212
- config.inputs["i"] = DynamicConnectionConfig(
213
- dataset_type_name="intermediate",
214
- storage_class="StructuredDataDict",
215
- )
216
- config.outputs["o"] = DynamicConnectionConfig(
217
- dataset_type_name="output",
218
- storage_class="StructuredDataDict",
219
- )
220
- """
221
- )
187
+ filename = os.path.join(TESTDIR, "pipeline_simple.yaml")
222
188
  executor = SimplePipelineExecutor.from_pipeline_filename(filename, butler=self.butler)
189
+ self._test_pipeline_file(executor)
190
+
191
+ def test_use_local_butler(self) -> None:
192
+ """Test generating a local butler repository from a pipeline, then
193
+ running that pipeline using the local butler.
194
+ """
195
+ filename = os.path.join(TESTDIR, "pipeline_simple.yaml")
196
+ executor = SimplePipelineExecutor.from_pipeline_filename(
197
+ filename, butler=self.butler, output="u/someone/pipeline"
198
+ )
199
+ with tempfile.TemporaryDirectory() as tempdir:
200
+ root = os.path.join(tempdir, "butler_root")
201
+ executor.use_local_butler(root)
202
+ self._test_pipeline_file(executor)
203
+
204
+ def _test_pipeline_file(self, executor: SimplePipelineExecutor) -> None:
223
205
  quanta = executor.run(register_dataset_types=True, save_versions=False)
224
206
  self.assertEqual(len(quanta), 2)
225
- self.assertEqual(self.butler.get("intermediate").storage_class, get_mock_name("StructuredDataDict"))
226
- self.assertEqual(self.butler.get("output").storage_class, get_mock_name("StructuredDataDict"))
207
+ self.assertEqual(
208
+ executor.butler.get("intermediate").storage_class, get_mock_name("StructuredDataDict")
209
+ )
210
+ self.assertEqual(executor.butler.get("output").storage_class, get_mock_name("StructuredDataDict"))
227
211
 
228
212
  def test_partial_outputs_success(self):
229
213
  """Test executing two quanta where the first raises
@@ -1,2 +0,0 @@
1
- __all__ = ["__version__"]
2
- __version__ = "29.2025.2000"