lsst-pipe-base 30.0.0rc2__py3-none-any.whl → 30.0.1rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. lsst/pipe/base/_instrument.py +31 -20
  2. lsst/pipe/base/_quantumContext.py +3 -3
  3. lsst/pipe/base/_status.py +43 -10
  4. lsst/pipe/base/_task_metadata.py +2 -2
  5. lsst/pipe/base/all_dimensions_quantum_graph_builder.py +8 -3
  6. lsst/pipe/base/automatic_connection_constants.py +20 -1
  7. lsst/pipe/base/cli/cmd/__init__.py +18 -2
  8. lsst/pipe/base/cli/cmd/commands.py +149 -4
  9. lsst/pipe/base/connectionTypes.py +72 -160
  10. lsst/pipe/base/connections.py +6 -9
  11. lsst/pipe/base/execution_reports.py +0 -5
  12. lsst/pipe/base/graph/graph.py +11 -10
  13. lsst/pipe/base/graph/quantumNode.py +4 -4
  14. lsst/pipe/base/graph_walker.py +8 -10
  15. lsst/pipe/base/log_capture.py +40 -80
  16. lsst/pipe/base/log_on_close.py +76 -0
  17. lsst/pipe/base/mp_graph_executor.py +51 -15
  18. lsst/pipe/base/pipeline.py +5 -6
  19. lsst/pipe/base/pipelineIR.py +2 -8
  20. lsst/pipe/base/pipelineTask.py +5 -7
  21. lsst/pipe/base/pipeline_graph/_dataset_types.py +2 -2
  22. lsst/pipe/base/pipeline_graph/_edges.py +32 -22
  23. lsst/pipe/base/pipeline_graph/_mapping_views.py +4 -7
  24. lsst/pipe/base/pipeline_graph/_pipeline_graph.py +14 -7
  25. lsst/pipe/base/pipeline_graph/expressions.py +2 -2
  26. lsst/pipe/base/pipeline_graph/io.py +7 -10
  27. lsst/pipe/base/pipeline_graph/visualization/_dot.py +13 -12
  28. lsst/pipe/base/pipeline_graph/visualization/_layout.py +16 -18
  29. lsst/pipe/base/pipeline_graph/visualization/_merge.py +4 -7
  30. lsst/pipe/base/pipeline_graph/visualization/_printer.py +10 -10
  31. lsst/pipe/base/pipeline_graph/visualization/_status_annotator.py +7 -0
  32. lsst/pipe/base/prerequisite_helpers.py +2 -1
  33. lsst/pipe/base/quantum_graph/_common.py +19 -20
  34. lsst/pipe/base/quantum_graph/_multiblock.py +37 -31
  35. lsst/pipe/base/quantum_graph/_predicted.py +113 -15
  36. lsst/pipe/base/quantum_graph/_provenance.py +1136 -45
  37. lsst/pipe/base/quantum_graph/aggregator/__init__.py +0 -1
  38. lsst/pipe/base/quantum_graph/aggregator/_communicators.py +204 -289
  39. lsst/pipe/base/quantum_graph/aggregator/_config.py +87 -9
  40. lsst/pipe/base/quantum_graph/aggregator/_ingester.py +13 -12
  41. lsst/pipe/base/quantum_graph/aggregator/_scanner.py +49 -235
  42. lsst/pipe/base/quantum_graph/aggregator/_structs.py +6 -116
  43. lsst/pipe/base/quantum_graph/aggregator/_supervisor.py +29 -39
  44. lsst/pipe/base/quantum_graph/aggregator/_workers.py +303 -0
  45. lsst/pipe/base/quantum_graph/aggregator/_writer.py +34 -351
  46. lsst/pipe/base/quantum_graph/formatter.py +171 -0
  47. lsst/pipe/base/quantum_graph/ingest_graph.py +413 -0
  48. lsst/pipe/base/quantum_graph/visualization.py +5 -1
  49. lsst/pipe/base/quantum_graph_builder.py +33 -9
  50. lsst/pipe/base/quantum_graph_executor.py +116 -13
  51. lsst/pipe/base/quantum_graph_skeleton.py +31 -35
  52. lsst/pipe/base/quantum_provenance_graph.py +29 -12
  53. lsst/pipe/base/separable_pipeline_executor.py +19 -3
  54. lsst/pipe/base/single_quantum_executor.py +67 -42
  55. lsst/pipe/base/struct.py +4 -0
  56. lsst/pipe/base/testUtils.py +3 -3
  57. lsst/pipe/base/tests/mocks/_storage_class.py +2 -1
  58. lsst/pipe/base/version.py +1 -1
  59. {lsst_pipe_base-30.0.0rc2.dist-info → lsst_pipe_base-30.0.1rc1.dist-info}/METADATA +3 -3
  60. lsst_pipe_base-30.0.1rc1.dist-info/RECORD +129 -0
  61. {lsst_pipe_base-30.0.0rc2.dist-info → lsst_pipe_base-30.0.1rc1.dist-info}/WHEEL +1 -1
  62. lsst_pipe_base-30.0.0rc2.dist-info/RECORD +0 -125
  63. {lsst_pipe_base-30.0.0rc2.dist-info → lsst_pipe_base-30.0.1rc1.dist-info}/entry_points.txt +0 -0
  64. {lsst_pipe_base-30.0.0rc2.dist-info → lsst_pipe_base-30.0.1rc1.dist-info}/licenses/COPYRIGHT +0 -0
  65. {lsst_pipe_base-30.0.0rc2.dist-info → lsst_pipe_base-30.0.1rc1.dist-info}/licenses/LICENSE +0 -0
  66. {lsst_pipe_base-30.0.0rc2.dist-info → lsst_pipe_base-30.0.1rc1.dist-info}/licenses/bsd_license.txt +0 -0
  67. {lsst_pipe_base-30.0.0rc2.dist-info → lsst_pipe_base-30.0.1rc1.dist-info}/licenses/gpl-v3.0.txt +0 -0
  68. {lsst_pipe_base-30.0.0rc2.dist-info → lsst_pipe_base-30.0.1rc1.dist-info}/top_level.txt +0 -0
  69. {lsst_pipe_base-30.0.0rc2.dist-info → lsst_pipe_base-30.0.1rc1.dist-info}/zip-safe +0 -0
@@ -27,23 +27,113 @@
27
27
 
28
28
  from __future__ import annotations
29
29
 
30
- __all__ = ["QuantumExecutor", "QuantumGraphExecutor"]
30
+ __all__ = ["QuantumExecutionResult", "QuantumExecutor", "QuantumGraphExecutor"]
31
31
 
32
32
  from abc import ABC, abstractmethod
33
- from typing import TYPE_CHECKING
33
+ from typing import TYPE_CHECKING, Self
34
+
35
+ from lsst.daf.butler import Quantum
34
36
 
35
37
  from .quantum_reports import QuantumReport, Report
36
38
 
37
39
  if TYPE_CHECKING:
38
40
  import uuid
39
41
 
40
- from lsst.daf.butler import Quantum
42
+ from lsst.daf.butler.logging import ButlerLogRecords
41
43
 
44
+ from ._task_metadata import TaskMetadata
42
45
  from .graph import QuantumGraph
43
46
  from .pipeline_graph import TaskNode
44
47
  from .quantum_graph import PredictedQuantumGraph
45
48
 
46
49
 
50
+ class QuantumExecutionResult(tuple[Quantum, QuantumReport | None]):
51
+ """A result struct that captures information about a single quantum's
52
+ execution.
53
+
54
+ Parameters
55
+ ----------
56
+ quantum : `lsst.daf.butler.Quantum`
57
+ Quantum that was executed.
58
+ report : `.quantum_reports.QuantumReport`
59
+ Report with basic information about the execution.
60
+ task_metadata : `TaskMetadata`, optional
61
+ Metadata saved by the task and executor during execution.
62
+ skipped_existing : `bool`, optional
63
+ If `True`, this quantum was not executed because it appeared to have
64
+ already been executed successfully.
65
+ adjusted_no_work : `bool`, optional
66
+ If `True`, this quantum was not executed because the
67
+ `PipelineTaskConnections.adjustQuanta` hook raised `NoWorkFound`.
68
+
69
+ Notes
70
+ -----
71
+ For backwards compatibility, this class is a two-element tuple that allows
72
+ the ``quantum`` and ``report`` attributes to be unpacked. Additional
73
+ regular attributes may be added by executors (but the tuple must remain
74
+ only two elements to enable the current unpacking interface).
75
+ """
76
+
77
+ def __new__(
78
+ cls,
79
+ quantum: Quantum,
80
+ report: QuantumReport | None,
81
+ *,
82
+ task_metadata: TaskMetadata | None = None,
83
+ skipped_existing: bool | None = None,
84
+ adjusted_no_work: bool | None = None,
85
+ ) -> Self:
86
+ return super().__new__(cls, (quantum, report))
87
+
88
+ # We need to define both __init__ and __new__ because tuple inheritance
89
+ # requires __new__ and numpydoc requires __init__.
90
+
91
+ def __init__(
92
+ self,
93
+ quantum: Quantum,
94
+ report: QuantumReport | None,
95
+ *,
96
+ task_metadata: TaskMetadata | None = None,
97
+ skipped_existing: bool | None = None,
98
+ adjusted_no_work: bool | None = None,
99
+ ):
100
+ self._task_metadata = task_metadata
101
+ self._skipped_existing = skipped_existing
102
+ self._adjusted_no_work = adjusted_no_work
103
+
104
+ @property
105
+ def quantum(self) -> Quantum:
106
+ """The quantum actually executed."""
107
+ return self[0]
108
+
109
+ @property
110
+ def report(self) -> QuantumReport | None:
111
+ """Structure describing the status of the execution of a quantum.
112
+
113
+ This is `None` if the implementation does not support this feature.
114
+ """
115
+ return self[1]
116
+
117
+ @property
118
+ def task_metadata(self) -> TaskMetadata | None:
119
+ """Metadata saved by the task and executor during execution."""
120
+ return self._task_metadata
121
+
122
+ @property
123
+ def skipped_existing(self) -> bool | None:
124
+ """If `True`, this quantum was not executed because it appeared to have
125
+ already been executed successfully.
126
+ """
127
+ return self._skipped_existing
128
+
129
+ @property
130
+ def adjusted_no_work(self) -> bool | None:
131
+ """If `True`, this quantum was not executed because the
132
+ `PipelineTaskConnections.adjustQuanta` hook raised `NoWorkFound`.
133
+ """
134
+ return self._adjusted_no_work
135
+
136
+
47
137
  class QuantumExecutor(ABC):
48
138
  """Class which abstracts execution of a single Quantum.
49
139
 
@@ -55,8 +145,14 @@ class QuantumExecutor(ABC):
55
145
 
56
146
  @abstractmethod
57
147
  def execute(
58
- self, task_node: TaskNode, /, quantum: Quantum, quantum_id: uuid.UUID | None = None
59
- ) -> tuple[Quantum, QuantumReport | None]:
148
+ self,
149
+ task_node: TaskNode,
150
+ /,
151
+ quantum: Quantum,
152
+ quantum_id: uuid.UUID | None = None,
153
+ *,
154
+ log_records: ButlerLogRecords | None = None,
155
+ ) -> QuantumExecutionResult:
60
156
  """Execute single quantum.
61
157
 
62
158
  Parameters
@@ -67,15 +163,18 @@ class QuantumExecutor(ABC):
67
163
  Quantum for this execution.
68
164
  quantum_id : `uuid.UUID` or `None`, optional
69
165
  The ID of the quantum to be executed.
166
+ log_records : `lsst.daf.butler.ButlerLogRecords`, optional
167
+ Container that should be used to store logs in memory before
168
+ writing them to the butler. This disables streaming log (since
169
+ we'd have to store them in memory anyway), but it permits the
170
+ caller to prepend logs to be stored in the butler and allows task
171
+ logs to be inspected by the caller after execution is complete.
70
172
 
71
173
  Returns
72
174
  -------
73
- quantum : `~lsst.daf.butler.Quantum`
74
- The quantum actually executed.
75
- report : `~.quantum_reports.QuantumReport`
76
- Structure describing the status of the execution of a quantum.
77
- `None` is returned if implementation does not support this
78
- feature.
175
+ result : `QuantumExecutionResult`
176
+ Result struct. May also be unpacked as a 2-tuple (see type
177
+ documentation).
79
178
 
80
179
  Notes
81
180
  -----
@@ -93,7 +192,9 @@ class QuantumGraphExecutor(ABC):
93
192
  """
94
193
 
95
194
  @abstractmethod
96
- def execute(self, graph: QuantumGraph | PredictedQuantumGraph) -> None:
195
+ def execute(
196
+ self, graph: QuantumGraph | PredictedQuantumGraph, *, provenance_graph_file: str | None = None
197
+ ) -> None:
97
198
  """Execute whole graph.
98
199
 
99
200
  Implementation of this method depends on particular execution model
@@ -103,8 +204,10 @@ class QuantumGraphExecutor(ABC):
103
204
 
104
205
  Parameters
105
206
  ----------
106
- graph : `.QuantumGraph`
207
+ graph : `.QuantumGraph` or `.quantum_graph.PredictedQuantumGraph`
107
208
  Execution graph.
209
+ provenance_graph_file : `str`, optional
210
+ A filename to write provenance to.
108
211
  """
109
212
  raise NotImplementedError()
110
213
 
@@ -42,7 +42,7 @@ __all__ = (
42
42
  import dataclasses
43
43
  from collections import defaultdict
44
44
  from collections.abc import Iterable, Iterator, MutableMapping, Set
45
- from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeAlias
45
+ from typing import TYPE_CHECKING, Any, ClassVar, Literal
46
46
 
47
47
  import networkx
48
48
 
@@ -145,7 +145,7 @@ class PrerequisiteDatasetKey:
145
145
  is_prerequisite: ClassVar[Literal[True]] = True
146
146
 
147
147
 
148
- Key: TypeAlias = QuantumKey | TaskInitKey | DatasetKey | PrerequisiteDatasetKey
148
+ type Key = QuantumKey | TaskInitKey | DatasetKey | PrerequisiteDatasetKey
149
149
 
150
150
 
151
151
  class QuantumGraphSkeleton:
@@ -383,12 +383,6 @@ class QuantumGraphSkeleton:
383
383
  The dataset ref of the prerequisite.
384
384
  **attrs : `~typing.Any`
385
385
  Additional attributes for the node.
386
-
387
- Notes
388
- -----
389
- This automatically sets the 'existing_input' ref attribute (see
390
- `set_existing_input_ref`), since prerequisites are always overall
391
- inputs.
392
386
  """
393
387
  key = PrerequisiteDatasetKey(ref.datasetType.name, ref.id.bytes)
394
388
  self._xgraph.add_node(key, data_id=ref.dataId, ref=ref, **attrs)
@@ -577,12 +571,13 @@ class QuantumGraphSkeleton:
577
571
  def set_dataset_ref(
578
572
  self, ref: DatasetRef, key: DatasetKey | PrerequisiteDatasetKey | None = None
579
573
  ) -> None:
580
- """Associate a dataset node with a `DatasetRef` instance.
574
+ """Associate a dataset node with a `~lsst.daf.butler.DatasetRef`
575
+ instance.
581
576
 
582
577
  Parameters
583
578
  ----------
584
- ref : `DatasetRef`
585
- `DatasetRef` to associate with the node.
579
+ ref : `~lsst.daf.butler.DatasetRef`
580
+ `~lsst.daf.butler.DatasetRef` to associate with the node.
586
581
  key : `DatasetKey` or `PrerequisiteDatasetKey`, optional
587
582
  Identifier for the graph node. If not provided, a `DatasetKey`
588
583
  is constructed from the dataset type name and data ID of ``ref``.
@@ -592,32 +587,33 @@ class QuantumGraphSkeleton:
592
587
  self._xgraph.nodes[key]["ref"] = ref
593
588
 
594
589
  def set_output_for_skip(self, ref: DatasetRef) -> None:
595
- """Associate a dataset node with a `DatasetRef` that represents an
596
- existing output in a collection where such outputs can cause a quantum
597
- to be skipped.
590
+ """Associate a dataset node with a `~lsst.daf.butler.DatasetRef` that
591
+ represents an existing output in a collection where such outputs can
592
+ cause a quantum to be skipped.
598
593
 
599
594
  Parameters
600
595
  ----------
601
- ref : `DatasetRef`
602
- `DatasetRef` to associate with the node.
596
+ ref : `~lsst.daf.butler.DatasetRef`
597
+ `~lsst.daf.butler.DatasetRef` to associate with the node.
603
598
  """
604
599
  key = DatasetKey(ref.datasetType.name, ref.dataId.required_values)
605
600
  self._xgraph.nodes[key]["output_for_skip"] = ref
606
601
 
607
602
  def set_output_in_the_way(self, ref: DatasetRef) -> None:
608
- """Associate a dataset node with a `DatasetRef` that represents an
609
- existing output in the output RUN collectoin.
603
+ """Associate a dataset node with a `~lsst.daf.butler.DatasetRef` that
604
+ represents an existing output in the output RUN collection.
610
605
 
611
606
  Parameters
612
607
  ----------
613
- ref : `DatasetRef`
614
- `DatasetRef` to associate with the node.
608
+ ref : `~lsst.daf.butler.DatasetRef`
609
+ `~lsst.daf.butler.DatasetRef` to associate with the node.
615
610
  """
616
611
  key = DatasetKey(ref.datasetType.name, ref.dataId.required_values)
617
612
  self._xgraph.nodes[key]["output_in_the_way"] = ref
618
613
 
619
614
  def get_dataset_ref(self, key: DatasetKey | PrerequisiteDatasetKey) -> DatasetRef | None:
620
- """Return the `DatasetRef` associated with the given node.
615
+ """Return the `~lsst.daf.butler.DatasetRef` associated with the given
616
+ node.
621
617
 
622
618
  This does not return "output for skip" and "output in the way"
623
619
  datasets.
@@ -629,14 +625,14 @@ class QuantumGraphSkeleton:
629
625
 
630
626
  Returns
631
627
  -------
632
- ref : `DatasetRef` or `None`
628
+ ref : `~lsst.daf.butler.DatasetRef` or `None`
633
629
  Dataset reference associated with the node.
634
630
  """
635
631
  return self._xgraph.nodes[key].get("ref")
636
632
 
637
633
  def get_output_for_skip(self, key: DatasetKey) -> DatasetRef | None:
638
- """Return the `DatasetRef` associated with the given node in a
639
- collection where it could lead to a quantum being skipped.
634
+ """Return the `~lsst.daf.butler.DatasetRef` associated with the given
635
+ node in a collection where it could lead to a quantum being skipped.
640
636
 
641
637
  Parameters
642
638
  ----------
@@ -645,14 +641,14 @@ class QuantumGraphSkeleton:
645
641
 
646
642
  Returns
647
643
  -------
648
- ref : `DatasetRef` or `None`
644
+ ref : `~lsst.daf.butler.DatasetRef` or `None`
649
645
  Dataset reference associated with the node.
650
646
  """
651
647
  return self._xgraph.nodes[key].get("output_for_skip")
652
648
 
653
649
  def get_output_in_the_way(self, key: DatasetKey) -> DatasetRef | None:
654
- """Return the `DatasetRef` associated with the given node in the
655
- output RUN collection.
650
+ """Return the `~lsst.daf.butler.DatasetRef` associated with the given
651
+ node in the output RUN collection.
656
652
 
657
653
  Parameters
658
654
  ----------
@@ -661,16 +657,16 @@ class QuantumGraphSkeleton:
661
657
 
662
658
  Returns
663
659
  -------
664
- ref : `DatasetRef` or `None`
660
+ ref : `~lsst.daf.butler.DatasetRef` or `None`
665
661
  Dataset reference associated with the node.
666
662
  """
667
663
  return self._xgraph.nodes[key].get("output_in_the_way")
668
664
 
669
665
  def discard_output_in_the_way(self, key: DatasetKey) -> None:
670
- """Drop any `DatasetRef` associated with this node in the output RUN
671
- collection.
666
+ """Drop any `~lsst.daf.butler.DatasetRef` associated with this node in
667
+ the output RUN collection.
672
668
 
673
- Does nothing if there is no such `DatasetRef`.
669
+ Does nothing if there is no such `~lsst.daf.butler.DatasetRef`.
674
670
 
675
671
  Parameters
676
672
  ----------
@@ -682,8 +678,8 @@ class QuantumGraphSkeleton:
682
678
  def set_data_id(self, key: Key, data_id: DataCoordinate) -> None:
683
679
  """Set the data ID associated with a node.
684
680
 
685
- This updates the data ID in any `DatasetRef` objects associated with
686
- the node via `set_ref`, `set_output_for_skip`, or
681
+ This updates the data ID in any `~lsst.daf.butler.DatasetRef` objects
682
+ associated with the node via `set_ref`, `set_output_for_skip`, or
687
683
  `set_output_in_the_way` as well, assuming it is an expanded version
688
684
  of the original data ID.
689
685
 
@@ -691,7 +687,7 @@ class QuantumGraphSkeleton:
691
687
  ----------
692
688
  key : `Key`
693
689
  Identifier for the graph node.
694
- data_id : `DataCoordinate`
690
+ data_id : `~lsst.daf.butler.DataCoordinate`
695
691
  Data ID for the node.
696
692
  """
697
693
  state: MutableMapping[str, Any] = self._xgraph.nodes[key]
@@ -716,7 +712,7 @@ class QuantumGraphSkeleton:
716
712
 
717
713
  Returns
718
714
  -------
719
- data_id : `DataCoordinate`
715
+ data_id : `~lsst.daf.butler.DataCoordinate`
720
716
  Expanded data ID for the node, if one is available.
721
717
 
722
718
  Raises
@@ -79,6 +79,7 @@ from .automatic_connection_constants import (
79
79
  METADATA_OUTPUT_CONNECTION_NAME,
80
80
  METADATA_OUTPUT_STORAGE_CLASS,
81
81
  METADATA_OUTPUT_TEMPLATE,
82
+ PROVENANCE_DATASET_TYPE_NAME,
82
83
  )
83
84
  from .graph import QuantumGraph, QuantumNode
84
85
 
@@ -586,8 +587,8 @@ class TaskSummary(pydantic.BaseModel):
586
587
 
587
588
  Unpack the `QuantumInfo` object, sorting quanta of each status into
588
589
  the correct place in the `TaskSummary`. If looking for error messages
589
- in the `Butler` logs is desired, take special care to catch issues
590
- with missing logs.
590
+ in the `lsst.daf.butler.Butler` logs is desired, take special care to
591
+ catch issues with missing logs.
591
592
 
592
593
  Parameters
593
594
  ----------
@@ -866,7 +867,7 @@ class DatasetTypeSummary(pydantic.BaseModel):
866
867
  class Summary(pydantic.BaseModel):
867
868
  """A summary of the contents of the QuantumProvenanceGraph, including
868
869
  all information on the quanta for each task and the datasets of each
869
- `DatasetType`.
870
+ `~lsst.daf.butler.DatasetType`.
870
871
  """
871
872
 
872
873
  tasks: dict[str, TaskSummary] = pydantic.Field(default_factory=dict)
@@ -885,7 +886,7 @@ class Summary(pydantic.BaseModel):
885
886
 
886
887
  Parameters
887
888
  ----------
888
- summaries : `Sequence[Summary]`
889
+ summaries : `~collections.abc.Sequence` [`Summary`]
889
890
  Sequence of all `Summary` objects to aggregate.
890
891
  """
891
892
  result = cls()
@@ -1245,8 +1246,8 @@ class QuantumProvenanceGraph:
1245
1246
  Returns
1246
1247
  -------
1247
1248
  dataset_info : `DatasetInfo`
1248
- The `TypedDict` with information about the `DatasetType`-dataID
1249
- pair across all runs.
1249
+ The `TypedDict` with information about the
1250
+ `~lsst.daf.butler.DatasetType`-dataID pair across all runs.
1250
1251
  """
1251
1252
  return self._xgraph.nodes[key]
1252
1253
 
@@ -1262,6 +1263,7 @@ class QuantumProvenanceGraph:
1262
1263
  do_store_logs : `bool`
1263
1264
  Store the logs in the summary dictionary.
1264
1265
  n_cores : `int`, optional
1266
+ Number of cores to use.
1265
1267
 
1266
1268
  Returns
1267
1269
  -------
@@ -1513,8 +1515,22 @@ class QuantumProvenanceGraph:
1513
1515
  len(self._datasets.keys()),
1514
1516
  )
1515
1517
  if use_qbb:
1516
- _LOG.verbose("Using quantum-backed butler for metadata loads.")
1517
- self._butler_wrappers[output_run] = _ThreadLocalButlerWrapper.wrap_qbb(butler, qgraph)
1518
+ provenance_graph_ref: DatasetRef | None = None
1519
+ try:
1520
+ provenance_graph_ref = butler.find_dataset(
1521
+ PROVENANCE_DATASET_TYPE_NAME, collections=output_run
1522
+ )
1523
+ except MissingDatasetTypeError:
1524
+ pass
1525
+ if provenance_graph_ref is not None:
1526
+ _LOG.warning(
1527
+ "Cannot use QBB for metadata/log reads after provenance has been ingested; "
1528
+ "falling back to full butler."
1529
+ )
1530
+ self._butler_wrappers[output_run] = _ThreadLocalButlerWrapper.wrap_full(butler)
1531
+ else:
1532
+ _LOG.verbose("Using quantum-backed butler for metadata loads.")
1533
+ self._butler_wrappers[output_run] = _ThreadLocalButlerWrapper.wrap_qbb(butler, qgraph)
1518
1534
  else:
1519
1535
  _LOG.verbose("Using full butler for metadata loads.")
1520
1536
  self._butler_wrappers[output_run] = _ThreadLocalButlerWrapper.wrap_full(butler)
@@ -1777,9 +1793,10 @@ class QuantumProvenanceGraph:
1777
1793
  successes. If "exhaustive", all metadata files will be read. If
1778
1794
  "lazy", only metadata files where at least one predicted output is
1779
1795
  missing will be read.
1780
- butler : `lsst.daf.butler.Butler`
1781
- The Butler used for this report. This should match the Butler
1782
- used for the run associated with the executed quantum graph.
1796
+ executor : `concurrent.futures.Executor`
1797
+ The futures executor to use.
1798
+ futures : `list` [ `concurrent.futures.Future` ]
1799
+ Current list of futures. Will be modified.
1783
1800
  """
1784
1801
  if read_caveats == "lazy" and all(
1785
1802
  self.get_dataset_info(dataset_key)["runs"][output_run].produced
@@ -1994,7 +2011,7 @@ class _ThreadLocalButlerWrapper:
1994
2011
  full_butler : `~lsst.daf.butler.Butler`
1995
2012
  Full butler to draw datastore and dimension configuration from.
1996
2013
  qg : `QuantumGraph`
1997
- Quantum graph,
2014
+ Quantum graph.
1998
2015
 
1999
2016
  Returns
2000
2017
  -------
@@ -40,7 +40,8 @@ from collections.abc import Iterable
40
40
  from typing import Any
41
41
 
42
42
  import lsst.resources
43
- from lsst.daf.butler import Butler
43
+ from lsst.daf.butler import Butler, DatasetRef
44
+ from lsst.daf.butler._rubin.temporary_for_ingest import TemporaryForIngest
44
45
 
45
46
  from ._quantumContext import ExecutionResources
46
47
  from .all_dimensions_quantum_graph_builder import AllDimensionsQuantumGraphBuilder
@@ -78,7 +79,7 @@ class SeparablePipelineExecutor:
78
79
  clobber_output : `bool`, optional
79
80
  If set, the pipeline execution overwrites existing output files.
80
81
  Otherwise, any conflict between existing and new outputs is an error.
81
- skip_existing_in : iterable [`str`], optional
82
+ skip_existing_in : `~collections.abc.Iterable` [`str`], optional
82
83
  If not empty, the pipeline execution searches the listed collections
83
84
  for existing outputs, and skips any quanta that have run to completion
84
85
  (or have no work to do). Otherwise, all tasks are attempted (subject to
@@ -362,6 +363,8 @@ class SeparablePipelineExecutor:
362
363
  fail_fast: bool = False,
363
364
  graph_executor: QuantumGraphExecutor | None = None,
364
365
  num_proc: int = 1,
366
+ *,
367
+ provenance_dataset_ref: DatasetRef | None = None,
365
368
  ) -> None:
366
369
  """Run a pipeline in the form of a prepared quantum graph.
367
370
 
@@ -384,6 +387,14 @@ class SeparablePipelineExecutor:
384
387
  The number of processes that can be used to run the pipeline. The
385
388
  default value ensures that no subprocess is created. Only used with
386
389
  the default graph executor.
390
+ provenance_dataset_ref : `lsst.daf.butler.DatasetRef`, optional
391
+ Dataset that should be used to save provenance. Provenance is only
392
+ supported when running in a single process (at least for the
393
+ default quantum executor), and should not be used with
394
+ ``skip_existing_in=[output_run]`` when retrying a previous
395
+ execution attempt. The caller is responsible for registering the
396
+ dataset type and for ensuring that the dimensions of this dataset
397
+ do not lead to uniqueness conflicts.
387
398
  """
388
399
  if not graph_executor:
389
400
  quantum_executor = SingleQuantumExecutor(
@@ -404,4 +415,9 @@ class SeparablePipelineExecutor:
404
415
  # forked processes.
405
416
  self._butler.registry.resetConnectionPool()
406
417
 
407
- graph_executor.execute(graph)
418
+ if provenance_dataset_ref is not None:
419
+ with TemporaryForIngest(self._butler, provenance_dataset_ref) as temporary:
420
+ graph_executor.execute(graph, provenance_graph_file=temporary.ospath)
421
+ temporary.ingest()
422
+ else:
423
+ graph_executor.execute(graph)