lsst-pipe-base 29.2025.3900__py3-none-any.whl → 29.2025.4100__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 (40) hide show
  1. lsst/pipe/base/_task_metadata.py +15 -0
  2. lsst/pipe/base/dot_tools.py +14 -152
  3. lsst/pipe/base/exec_fixup_data_id.py +17 -44
  4. lsst/pipe/base/execution_graph_fixup.py +49 -18
  5. lsst/pipe/base/graph/_versionDeserializers.py +6 -5
  6. lsst/pipe/base/graph/graph.py +30 -10
  7. lsst/pipe/base/graph/graphSummary.py +30 -0
  8. lsst/pipe/base/graph_walker.py +119 -0
  9. lsst/pipe/base/log_capture.py +5 -2
  10. lsst/pipe/base/mermaid_tools.py +11 -64
  11. lsst/pipe/base/mp_graph_executor.py +298 -236
  12. lsst/pipe/base/pipeline_graph/io.py +1 -1
  13. lsst/pipe/base/quantum_graph/__init__.py +32 -0
  14. lsst/pipe/base/quantum_graph/_common.py +632 -0
  15. lsst/pipe/base/quantum_graph/_multiblock.py +808 -0
  16. lsst/pipe/base/quantum_graph/_predicted.py +1950 -0
  17. lsst/pipe/base/quantum_graph/visualization.py +302 -0
  18. lsst/pipe/base/quantum_graph_builder.py +292 -34
  19. lsst/pipe/base/quantum_graph_executor.py +2 -1
  20. lsst/pipe/base/quantum_provenance_graph.py +16 -7
  21. lsst/pipe/base/quantum_reports.py +45 -0
  22. lsst/pipe/base/separable_pipeline_executor.py +126 -15
  23. lsst/pipe/base/simple_pipeline_executor.py +44 -43
  24. lsst/pipe/base/single_quantum_executor.py +1 -40
  25. lsst/pipe/base/tests/mocks/__init__.py +1 -1
  26. lsst/pipe/base/tests/mocks/_pipeline_task.py +16 -1
  27. lsst/pipe/base/tests/mocks/{_in_memory_repo.py → _repo.py} +324 -45
  28. lsst/pipe/base/tests/mocks/_storage_class.py +51 -0
  29. lsst/pipe/base/tests/simpleQGraph.py +11 -5
  30. lsst/pipe/base/version.py +1 -1
  31. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/METADATA +2 -1
  32. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/RECORD +40 -34
  33. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/WHEEL +0 -0
  34. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/entry_points.txt +0 -0
  35. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/licenses/COPYRIGHT +0 -0
  36. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/licenses/LICENSE +0 -0
  37. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/licenses/bsd_license.txt +0 -0
  38. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/licenses/gpl-v3.0.txt +0 -0
  39. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/top_level.txt +0 -0
  40. {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/zip-safe +0 -0
@@ -47,6 +47,7 @@ from .all_dimensions_quantum_graph_builder import AllDimensionsQuantumGraphBuild
47
47
  from .graph import QuantumGraph
48
48
  from .mp_graph_executor import MPGraphExecutor
49
49
  from .pipeline import Pipeline
50
+ from .quantum_graph import PredictedQuantumGraph
50
51
  from .quantum_graph_builder import QuantumGraphBuilder
51
52
  from .quantum_graph_executor import QuantumGraphExecutor
52
53
  from .single_quantum_executor import SingleQuantumExecutor
@@ -120,7 +121,7 @@ class SeparablePipelineExecutor:
120
121
 
121
122
  def pre_execute_qgraph(
122
123
  self,
123
- graph: QuantumGraph,
124
+ graph: QuantumGraph | PredictedQuantumGraph,
124
125
  register_dataset_types: bool = False,
125
126
  save_init_outputs: bool = True,
126
127
  save_versions: bool = True,
@@ -133,7 +134,7 @@ class SeparablePipelineExecutor:
133
134
 
134
135
  Parameters
135
136
  ----------
136
- graph : `.QuantumGraph`
137
+ graph : `.QuantumGraph` or `.quantum_graph.PredictedQuantumGraph`
137
138
  The quantum graph defining the pipeline and datasets to
138
139
  be initialized.
139
140
  register_dataset_types : `bool`, optional
@@ -169,6 +170,55 @@ class SeparablePipelineExecutor:
169
170
  """
170
171
  return Pipeline.from_uri(pipeline_uri)
171
172
 
173
+ def make_quantum_graph_builder(
174
+ self,
175
+ pipeline: Pipeline,
176
+ where: str = "",
177
+ *,
178
+ builder_class: type[QuantumGraphBuilder] = AllDimensionsQuantumGraphBuilder,
179
+ **kwargs: Any,
180
+ ) -> QuantumGraphBuilder:
181
+ """Initialize a quantum graph builder from a pipeline and input
182
+ datasets.
183
+
184
+ Parameters
185
+ ----------
186
+ pipeline : `.Pipeline`
187
+ The pipeline for which to generate a quantum graph.
188
+ where : `str`, optional
189
+ A data ID query that constrains the quanta generated. Must not be
190
+ provided if a custom ``builder_class`` is given and that class does
191
+ not accept ``where`` as a construction argument.
192
+ builder_class : `type` [ \
193
+ `.quantum_graph_builder.QuantumGraphBuilder` ], optional
194
+ Quantum graph builder implementation. Ignored if ``builder`` is
195
+ provided.
196
+ **kwargs
197
+ Additional keyword arguments are forwarded to ``builder_class``
198
+ when a quantum graph builder instance is constructed. All
199
+ arguments accepted by the
200
+ `~.quantum_graph_builder.QuantumGraphBuilder` base
201
+ class are provided automatically (from explicit arguments to this
202
+ method and executor attributes) and do not need to be included
203
+ as keyword arguments.
204
+
205
+ Returns
206
+ -------
207
+ builder : `.quantum_graph_builder.QuantumGraphBuilder`
208
+ A quantum graph builder.
209
+ """
210
+ if where:
211
+ # Only pass 'where' if it's actually provided, since some
212
+ # QuantumGraphBuilder subclasses may not accept it.
213
+ kwargs["where"] = where
214
+ return builder_class(
215
+ pipeline.to_graph(),
216
+ self._butler,
217
+ skip_existing_in=self._skip_existing_in,
218
+ clobber=self._clobber_output,
219
+ **kwargs,
220
+ )
221
+
172
222
  def make_quantum_graph(
173
223
  self,
174
224
  pipeline: Pipeline,
@@ -180,6 +230,10 @@ class SeparablePipelineExecutor:
180
230
  ) -> QuantumGraph:
181
231
  """Build a quantum graph from a pipeline and input datasets.
182
232
 
233
+ This returns an instance of the old `.QuantumGraph` class. Use
234
+ `build_quantum_graph` to construct a
235
+ `.quantum_graph.PredictedQuantumGraph`.
236
+
183
237
  Parameters
184
238
  ----------
185
239
  pipeline : `.Pipeline`
@@ -225,17 +279,7 @@ class SeparablePipelineExecutor:
225
279
  "user": getpass.getuser(),
226
280
  "time": str(datetime.datetime.now()),
227
281
  }
228
- if where:
229
- # Only pass 'where' if it's actually provided, since some
230
- # QuantumGraphBuilder subclasses may not accept it.
231
- kwargs["where"] = where
232
- qg_builder = builder_class(
233
- pipeline.to_graph(),
234
- self._butler,
235
- skip_existing_in=self._skip_existing_in,
236
- clobber=self._clobber_output,
237
- **kwargs,
238
- )
282
+ qg_builder = self.make_quantum_graph_builder(pipeline, where, builder_class=builder_class, **kwargs)
239
283
  graph = qg_builder.build(metadata=metadata, attach_datastore_records=attach_datastore_records)
240
284
  _LOG.info(
241
285
  "QuantumGraph contains %d quanta for %d tasks, graph ID: %r",
@@ -245,9 +289,76 @@ class SeparablePipelineExecutor:
245
289
  )
246
290
  return graph
247
291
 
292
+ def build_quantum_graph(
293
+ self,
294
+ pipeline: Pipeline,
295
+ where: str = "",
296
+ *,
297
+ builder_class: type[QuantumGraphBuilder] = AllDimensionsQuantumGraphBuilder,
298
+ attach_datastore_records: bool = False,
299
+ **kwargs: Any,
300
+ ) -> PredictedQuantumGraph:
301
+ """Build a quantum graph from a pipeline and input datasets.
302
+
303
+ This returns an instance of the new
304
+ `.quantum_graph.PredictedQuantumGraph` class. Use `make_quantum_graph`
305
+ to construct a `.QuantumGraph`.
306
+
307
+ Parameters
308
+ ----------
309
+ pipeline : `.Pipeline`
310
+ The pipeline for which to generate a quantum graph.
311
+ where : `str`, optional
312
+ A data ID query that constrains the quanta generated. Must not be
313
+ provided if a custom ``builder_class`` is given and that class does
314
+ not accept ``where`` as a construction argument.
315
+ builder_class : `type` [ \
316
+ `.quantum_graph_builder.QuantumGraphBuilder` ], optional
317
+ Quantum graph builder implementation. Ignored if ``builder`` is
318
+ provided.
319
+ attach_datastore_records : `bool`, optional
320
+ Whether to attach datastore records. These are currently used only
321
+ by `lsst.daf.butler.QuantumBackedButler`, which is not used by
322
+ `SeparablePipelineExecutor` for execution.
323
+ **kwargs
324
+ Additional keyword arguments are forwarded to ``builder_class``
325
+ when a quantum graph builder instance is constructed. All
326
+ arguments accepted by the
327
+ `~.quantum_graph_builder.QuantumGraphBuilder` base
328
+ class are provided automatically (from explicit arguments to this
329
+ method and executor attributes) and do not need to be included
330
+ as keyword arguments.
331
+
332
+ Returns
333
+ -------
334
+ graph : `.QuantumGraph`
335
+ The quantum graph for ``.Pipeline`` as run on the datasets
336
+ identified by ``where``.
337
+
338
+ Notes
339
+ -----
340
+ This method does no special handling of empty quantum graphs. If
341
+ needed, clients can use `len` to test if the returned graph is empty.
342
+ """
343
+ metadata = {
344
+ "skip_existing_in": self._skip_existing_in,
345
+ "skip_existing": bool(self._skip_existing_in),
346
+ "data_query": where,
347
+ }
348
+ qg_builder = self.make_quantum_graph_builder(pipeline, where, builder_class=builder_class, **kwargs)
349
+ graph = qg_builder.finish(
350
+ metadata=metadata, attach_datastore_records=attach_datastore_records
351
+ ).assemble()
352
+ _LOG.info(
353
+ "PredictedQuantumGraph contains %d quanta for %d tasks.",
354
+ len(graph),
355
+ len(graph.quanta_by_task),
356
+ )
357
+ return graph
358
+
248
359
  def run_pipeline(
249
360
  self,
250
- graph: QuantumGraph,
361
+ graph: QuantumGraph | PredictedQuantumGraph,
251
362
  fail_fast: bool = False,
252
363
  graph_executor: QuantumGraphExecutor | None = None,
253
364
  num_proc: int = 1,
@@ -259,7 +370,7 @@ class SeparablePipelineExecutor:
259
370
 
260
371
  Parameters
261
372
  ----------
262
- graph : `.QuantumGraph`
373
+ graph : `.QuantumGraph` or `.quantum_graph.PredictedQuantumGraph`
263
374
  The pipeline and datasets to execute.
264
375
  fail_fast : `bool`, optional
265
376
  If `True`, abort all execution if any task fails when
@@ -29,20 +29,15 @@ from __future__ import annotations
29
29
 
30
30
  __all__ = ("SimplePipelineExecutor",)
31
31
 
32
- import datetime
33
- import getpass
34
- import itertools
35
32
  import os
36
33
  from collections.abc import Iterable, Iterator, Mapping
37
- from typing import Any, cast
34
+ from typing import Any
38
35
 
39
36
  from lsst.daf.butler import (
40
37
  Butler,
41
38
  CollectionType,
42
39
  DataCoordinate,
43
40
  DatasetRef,
44
- DimensionDataExtractor,
45
- DimensionGroup,
46
41
  Quantum,
47
42
  )
48
43
  from lsst.pex.config import Config
@@ -54,6 +49,7 @@ from .graph import QuantumGraph
54
49
  from .pipeline import Pipeline
55
50
  from .pipeline_graph import PipelineGraph
56
51
  from .pipelineTask import PipelineTask
52
+ from .quantum_graph import PredictedQuantumGraph
57
53
  from .single_quantum_executor import SingleQuantumExecutor
58
54
  from .taskFactory import TaskFactory
59
55
 
@@ -95,12 +91,19 @@ class SimplePipelineExecutor:
95
91
 
96
92
  def __init__(
97
93
  self,
98
- quantum_graph: QuantumGraph,
94
+ quantum_graph: QuantumGraph | PredictedQuantumGraph,
99
95
  butler: Butler,
100
96
  resources: ExecutionResources | None = None,
101
97
  raise_on_partial_outputs: bool = True,
102
98
  ):
103
- self.quantum_graph = quantum_graph
99
+ from .graph import QuantumGraph
100
+
101
+ self._quantum_graph: QuantumGraph | None = None
102
+ if isinstance(quantum_graph, QuantumGraph):
103
+ self._quantum_graph = quantum_graph
104
+ self.predicted = PredictedQuantumGraph.from_old_quantum_graph(self._quantum_graph)
105
+ else:
106
+ self.predicted = quantum_graph
104
107
  self.butler = butler
105
108
  self.resources = resources
106
109
  self.raise_on_partial_outputs = raise_on_partial_outputs
@@ -442,25 +445,29 @@ class SimplePipelineExecutor:
442
445
  pipeline_graph, butler, where=where, bind=bind, output_run=output_run
443
446
  )
444
447
  metadata = {
445
- "input": list(butler.collections.defaults),
446
- "output": output,
447
- "output_run": output_run,
448
448
  "skip_existing_in": [],
449
449
  "skip_existing": False,
450
450
  "data_query": where,
451
- "user": getpass.getuser(),
452
- "time": str(datetime.datetime.now()),
453
451
  }
454
- quantum_graph = quantum_graph_builder.build(
455
- metadata=metadata, attach_datastore_records=attach_datastore_records
456
- )
452
+ predicted = quantum_graph_builder.finish(
453
+ output=output,
454
+ metadata=metadata,
455
+ attach_datastore_records=attach_datastore_records,
456
+ ).assemble()
457
457
  return cls(
458
- quantum_graph=quantum_graph,
458
+ predicted,
459
459
  butler=butler,
460
460
  resources=resources,
461
461
  raise_on_partial_outputs=raise_on_partial_outputs,
462
462
  )
463
463
 
464
+ @property
465
+ def quantum_graph(self) -> QuantumGraph:
466
+ """The quantum graph run by this executor."""
467
+ if self._quantum_graph is None:
468
+ self._quantum_graph = self.predicted.to_old_quantum_graph()
469
+ return self._quantum_graph
470
+
464
471
  def use_local_butler(
465
472
  self, root: str, register_dataset_types: bool = True, transfer_dimensions: bool = True
466
473
  ) -> Butler:
@@ -503,9 +510,9 @@ class SimplePipelineExecutor:
503
510
  Butler.makeRepo(root)
504
511
  out_butler = Butler.from_config(root, writeable=True)
505
512
 
506
- output_run = self.quantum_graph.metadata["output_run"]
513
+ output_run = self.predicted.header.output_run
507
514
  out_butler.collections.register(output_run, CollectionType.RUN)
508
- output = self.quantum_graph.metadata["output"]
515
+ output = self.predicted.header.output
509
516
  inputs: str | None = None
510
517
  if output is not None:
511
518
  inputs = f"{output}/inputs"
@@ -525,12 +532,12 @@ class SimplePipelineExecutor:
525
532
  # into a TAGGED collection.
526
533
  refs: set[DatasetRef] = set()
527
534
  to_tag_by_type: dict[str, dict[DataCoordinate, DatasetRef | None]] = {}
528
- pipeline_graph = self.quantum_graph.pipeline_graph
535
+ pipeline_graph = self.predicted.pipeline_graph
529
536
  for name, dataset_type_node in pipeline_graph.iter_overall_inputs():
530
537
  assert dataset_type_node is not None, "PipelineGraph should be resolved."
531
538
  to_tag_for_type = to_tag_by_type.setdefault(name, {})
532
539
  for task_node in pipeline_graph.consumers_of(name):
533
- for quantum in self.quantum_graph.get_task_quanta(task_node.label).values():
540
+ for quantum in self.predicted.build_execution_quanta(task_label=task_node.label).values():
534
541
  for ref in quantum.inputs[name]:
535
542
  ref = dataset_type_node.generalize_ref(ref)
536
543
  refs.add(ref)
@@ -563,7 +570,7 @@ class SimplePipelineExecutor:
563
570
  return self.butler
564
571
 
565
572
  def run(self, register_dataset_types: bool = False, save_versions: bool = True) -> list[Quantum]:
566
- """Run all the quanta in the `.QuantumGraph` in topological order.
573
+ """Run all the quanta in the quantum graph in topological order.
567
574
 
568
575
  Use this method to run all quanta in the graph. Use
569
576
  `as_generator` to get a generator to run the quanta one at
@@ -594,7 +601,7 @@ class SimplePipelineExecutor:
594
601
  def as_generator(
595
602
  self, register_dataset_types: bool = False, save_versions: bool = True
596
603
  ) -> Iterator[Quantum]:
597
- """Yield quanta in the `.QuantumGraph` in topological order.
604
+ """Yield quanta in the quantum graph in topological order.
598
605
 
599
606
  These quanta will be run as the returned generator is iterated
600
607
  over. Use this method to run the quanta one at a time.
@@ -623,11 +630,11 @@ class SimplePipelineExecutor:
623
630
  guarantees are made about the order in which quanta are processed.
624
631
  """
625
632
  if register_dataset_types:
626
- self.quantum_graph.pipeline_graph.register_dataset_types(self.butler)
627
- self.quantum_graph.write_configs(self.butler, compare_existing=False)
628
- self.quantum_graph.write_init_outputs(self.butler, skip_existing=False)
633
+ self.predicted.pipeline_graph.register_dataset_types(self.butler)
634
+ self.predicted.write_configs(self.butler, compare_existing=False)
635
+ self.predicted.write_init_outputs(self.butler, skip_existing=False)
629
636
  if save_versions:
630
- self.quantum_graph.write_packages(self.butler, compare_existing=False)
637
+ self.predicted.write_packages(self.butler, compare_existing=False)
631
638
  task_factory = TaskFactory()
632
639
  single_quantum_executor = SingleQuantumExecutor(
633
640
  butler=self.butler,
@@ -635,14 +642,20 @@ class SimplePipelineExecutor:
635
642
  resources=self.resources,
636
643
  raise_on_partial_outputs=self.raise_on_partial_outputs,
637
644
  )
645
+ self.predicted.build_execution_quanta()
646
+ nodes_map = self.predicted.quantum_only_xgraph.nodes
638
647
  # Important that this returns a generator expression rather than being
639
648
  # a generator itself; that is what makes the init stuff above happen
640
649
  # immediately instead of when the first quanta is executed, which might
641
650
  # be useful for callers who want to check the state of the repo in
642
651
  # between.
643
652
  return (
644
- single_quantum_executor.execute(qnode.task_node, qnode.quantum, qnode.nodeId)[0]
645
- for qnode in self.quantum_graph
653
+ single_quantum_executor.execute(
654
+ nodes_map[quantum_id]["pipeline_node"],
655
+ nodes_map[quantum_id]["quantum"],
656
+ quantum_id,
657
+ )[0]
658
+ for quantum_id in self.predicted
646
659
  )
647
660
 
648
661
  def _transfer_qg_dimension_records(self, out_butler: Butler) -> None:
@@ -653,20 +666,8 @@ class SimplePipelineExecutor:
653
666
  out_butler : `lsst.daf.butler.Butler`
654
667
  Butler to transfer records to.
655
668
  """
656
- pipeline_graph = self.quantum_graph.pipeline_graph
657
- all_dimensions = DimensionGroup.union(
658
- *pipeline_graph.group_by_dimensions(prerequisites=True).keys(),
659
- universe=self.butler.dimensions,
660
- )
661
- dimension_data_extractor = DimensionDataExtractor.from_dimension_group(all_dimensions)
662
- for task_node in pipeline_graph.tasks.values():
663
- task_quanta = self.quantum_graph.get_task_quanta(task_node.label)
664
- for quantum in task_quanta.values():
665
- dimension_data_extractor.update([cast(DataCoordinate, quantum.dataId)])
666
- for refs in itertools.chain(quantum.inputs.values(), quantum.outputs.values()):
667
- dimension_data_extractor.update(ref.dataId for ref in refs)
668
- for element_name in all_dimensions.elements:
669
- record_set = dimension_data_extractor.records.get(element_name)
669
+ assert self.predicted.dimension_data is not None, "Dimension data must be present for execution."
670
+ for record_set in self.predicted.dimension_data.records.values():
670
671
  if record_set and record_set.element.has_own_table:
671
672
  out_butler.registry.insertDimensionData(
672
673
  record_set.element,
@@ -33,7 +33,7 @@ import uuid
33
33
  from collections import defaultdict
34
34
  from collections.abc import Callable, Mapping
35
35
  from itertools import chain
36
- from typing import Any, cast
36
+ from typing import Any
37
37
 
38
38
  from lsst.daf.butler import (
39
39
  Butler,
@@ -46,7 +46,6 @@ from lsst.daf.butler import (
46
46
  )
47
47
  from lsst.utils.timer import logInfo
48
48
 
49
- from ._instrument import Instrument
50
49
  from ._quantumContext import ExecutionResources, QuantumContext
51
50
  from ._status import AnnotatedPartialOutputsError, InvalidQuantumError, NoWorkFound, QuantumSuccessCaveats
52
51
  from .connections import AdjustQuantumHelper
@@ -238,9 +237,6 @@ class SingleQuantumExecutor(QuantumExecutor):
238
237
  except ImportError:
239
238
  _LOG.warning("No 'debug' module found.")
240
239
 
241
- # initialize global state
242
- self._init_globals(quantum)
243
-
244
240
  # Ensure that we are executing a frozen config
245
241
  task_node.config.freeze()
246
242
  logInfo(None, "init", metadata=quantumMetadata) # type: ignore[arg-type]
@@ -569,41 +565,6 @@ class SingleQuantumExecutor(QuantumExecutor):
569
565
  ) from exc
570
566
  limited_butler.put(metadata, ref)
571
567
 
572
- def _init_globals(self, quantum: Quantum) -> None:
573
- """Initialize global state needed for task execution.
574
-
575
- Parameters
576
- ----------
577
- quantum : `~lsst.daf.butler.Quantum`
578
- Single Quantum instance.
579
-
580
- Notes
581
- -----
582
- There is an issue with initializing filters singleton which is done
583
- by instrument, to avoid requiring tasks to do it in runQuantum()
584
- we do it here when any dataId has an instrument dimension. Also for
585
- now we only allow single instrument, verify that all instrument
586
- names in all dataIds are identical.
587
-
588
- This will need revision when filter singleton disappears.
589
- """
590
- # can only work for full butler
591
- if self._butler is None:
592
- return
593
- oneInstrument = None
594
- for datasetRefs in chain(quantum.inputs.values(), quantum.outputs.values()):
595
- for datasetRef in datasetRefs:
596
- dataId = datasetRef.dataId
597
- instrument = cast(str, dataId.get("instrument"))
598
- if instrument is not None:
599
- if oneInstrument is not None:
600
- assert ( # type: ignore
601
- instrument == oneInstrument
602
- ), "Currently require that only one instrument is used per graph"
603
- else:
604
- oneInstrument = instrument
605
- Instrument.fromName(instrument, self._butler.registry)
606
-
607
568
  def _should_assume_exists(self, quantum: Quantum, ref: DatasetRef) -> bool | None:
608
569
  """Report whether the given dataset can be assumed to exist because
609
570
  some previous check reported that it did.
@@ -32,6 +32,6 @@ See :ref:`testing-pipelines-with-mocks` for details.
32
32
  """
33
33
 
34
34
  from ._data_id_match import *
35
- from ._in_memory_repo import *
35
+ from ._repo import *
36
36
  from ._pipeline_task import *
37
37
  from ._storage_class import *
@@ -56,6 +56,7 @@ from lsst.utils.iteration import ensure_iterable
56
56
 
57
57
  from ... import connectionTypes as cT
58
58
  from ..._status import AlgorithmError, AnnotatedPartialOutputsError
59
+ from ...automatic_connection_constants import METADATA_OUTPUT_CONNECTION_NAME, METADATA_OUTPUT_STORAGE_CLASS
59
60
  from ...config import PipelineTaskConfig
60
61
  from ...connections import InputQuantizedConnection, OutputQuantizedConnection, PipelineTaskConnections
61
62
  from ...pipeline_graph import PipelineGraph
@@ -202,6 +203,13 @@ class BaseTestPipelineTaskConfig(PipelineTaskConfig, pipelineConnections=BaseTes
202
203
  doc="Time to sleep (seconds) before mock execution reading inputs or failing.",
203
204
  )
204
205
 
206
+ int_value = Field[int](
207
+ "Arbitrary integer value to write into mock output datasets", dtype=int, optional=True, default=None
208
+ )
209
+ str_value = Field[str](
210
+ "Arbitrary string value to write into mock output datasets", dtype=str, optional=True, default=None
211
+ )
212
+
205
213
  def data_id_match(self) -> DataIdMatch | None:
206
214
  if not self.fail_condition:
207
215
  return None
@@ -294,6 +302,8 @@ class BaseTestPipelineTask(PipelineTask):
294
302
  run=None, # task also has no way to get this
295
303
  quantum=mock_dataset_quantum,
296
304
  output_connection_name=connection_name,
305
+ int_value=self.config.int_value,
306
+ str_value=self.config.str_value,
297
307
  )
298
308
  setattr(self, connection_name, output_dataset)
299
309
 
@@ -373,6 +383,8 @@ class BaseTestPipelineTask(PipelineTask):
373
383
  run=ref.run,
374
384
  quantum=mock_dataset_quantum,
375
385
  output_connection_name=name,
386
+ int_value=self.config.int_value,
387
+ str_value=self.config.str_value,
376
388
  )
377
389
  butlerQC.put(output, ref)
378
390
 
@@ -476,7 +488,10 @@ class MockPipelineTaskConnections(BaseTestPipelineTaskConnections, dimensions=()
476
488
  raise ValueError(
477
489
  f"Unmocked dataset type {connection.name!r} cannot be used as an init-output."
478
490
  )
479
- elif connection.name.endswith("_metadata") and connection.storageClass == "TaskMetadata":
491
+ elif (
492
+ connection.name.endswith(METADATA_OUTPUT_CONNECTION_NAME)
493
+ and connection.storageClass == METADATA_OUTPUT_STORAGE_CLASS
494
+ ):
480
495
  # Task metadata does not use a mock storage class, because it's
481
496
  # written by the system, but it does end up with the _mock_*
482
497
  # prefix because the task label does.