Coverage for python/lsst/pipe/base/tests/mocks/_repo.py: 20%

145 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-13 01:39 -0800

1# This file is part of pipe_base. 

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 

28from __future__ import annotations 

29 

30__all__ = ("DirectButlerRepo", "InMemoryRepo", "MockRepo") 

31 

32import tempfile 

33from abc import ABC, abstractmethod 

34from collections.abc import Iterable, Iterator, Mapping 

35from contextlib import contextmanager 

36from typing import Any 

37 

38from lsst.daf.butler import ( 

39 Butler, 

40 CollectionType, 

41 DataCoordinate, 

42 DatasetRef, 

43 DatasetType, 

44 DimensionConfig, 

45 LimitedButler, 

46 RegistryConfig, 

47) 

48from lsst.daf.butler.tests.utils import create_populated_sqlite_registry 

49from lsst.resources import ResourcePath, ResourcePathExpression 

50from lsst.sphgeom import RangeSet 

51 

52from ...all_dimensions_quantum_graph_builder import AllDimensionsQuantumGraphBuilder 

53from ...pipeline_graph import PipelineGraph 

54from ...quantum_graph import PredictedQuantumGraph 

55from ...single_quantum_executor import SingleQuantumExecutor 

56from ..in_memory_limited_butler import InMemoryLimitedButler 

57from ._pipeline_task import ( 

58 DynamicConnectionConfig, 

59 DynamicTestPipelineTask, 

60 DynamicTestPipelineTaskConfig, 

61) 

62from ._storage_class import MockDataset, is_mock_name 

63 

64 

65class MockRepo(ABC): 

66 """A test helper that populates a butler repository for task execution. 

67 

68 Parameters 

69 ---------- 

70 butler : `lsst.daf.butler.Butler` 

71 Butler to use for at least quantum graph building. Must be writeable. 

72 input_run : `str`, optional 

73 Name of a `~lsst.daf.butler.CollectionType.RUN` collection that will be 

74 used as an input to quantum graph generation. Input datasets created 

75 by the helper are added to this collection. 

76 input_chain : `str`, optional 

77 Name of a `~lsst.daf.butler.CollectionType.CHAINED` collection that 

78 will be the direct input to quantum graph generation. This always 

79 includes ``input_run``. 

80 input_children : `str` or `~collections.abc.Iterable` [ `str`], optional 

81 Additional collections to include in ``input_chain``. 

82 """ 

83 

84 def __init__( 

85 self, 

86 butler: Butler, 

87 input_run: str = "input_run", 

88 input_chain: str = "input_chain", 

89 input_children: Iterable[str] = (), 

90 ): 

91 self.butler = butler 

92 input_chain_definition = [input_run] 

93 input_chain_definition.extend(input_children) 

94 self.input_run = input_run 

95 self.input_chain = input_chain 

96 self.butler.collections.register(self.input_run) 

97 self.butler.collections.register(self.input_chain, CollectionType.CHAINED) 

98 self.butler.collections.redefine_chain(self.input_chain, input_chain_definition) 

99 self.pipeline_graph = PipelineGraph() 

100 self.last_auto_dataset_type_index = 0 

101 self.last_auto_task_index = 0 

102 

103 def add_task( 

104 self, 

105 label: str | None = None, 

106 *, 

107 task_class: type[DynamicTestPipelineTask] = DynamicTestPipelineTask, 

108 config: DynamicTestPipelineTaskConfig | None = None, 

109 dimensions: Iterable[str] | None = None, 

110 inputs: Mapping[str, DynamicConnectionConfig] | None = None, 

111 outputs: Mapping[str, DynamicConnectionConfig] | None = None, 

112 prerequisite_inputs: Mapping[str, DynamicConnectionConfig] | None = None, 

113 init_inputs: Mapping[str, DynamicConnectionConfig] | None = None, 

114 init_outputs: Mapping[str, DynamicConnectionConfig] | None = None, 

115 ) -> None: 

116 """Add a task to the helper's pipeline graph. 

117 

118 Parameters 

119 ---------- 

120 label : `str`, optional 

121 Label for the task. If not provided, the task name will be 

122 ``task_auto{self.last_auto_task_index}``, with that variable 

123 incremented. 

124 task_class : `type`, optional 

125 Subclass of `DynamicTestPipelineTask` to use. 

126 config : `DynamicTestPipelineTaskConfig`, optional 

127 Task configuration to use. Note that the dimensions are always 

128 overridden by the ``dimensions`` argument and ``inputs`` and 

129 ``outputs`` are updated by those arguments unless they are 

130 explicitly set to empty dictionaries. 

131 dimensions : `~collections.abc.Iterable` [ `str` ], optional 

132 Dimensions of the task and any automatically-added input or output 

133 connection. 

134 inputs : `~collections.abc.Mapping` [ `str`, \ 

135 `DynamicConnectionConfig` ], optional 

136 Input connections to add. If not provided, a single connection is 

137 added with the same dimensions as the task and dataset type name 

138 ``dataset_auto{self.last_auto_dataset_type_index}``. 

139 outputs : `~collections.abc.Mapping` [ `str`, \ 

140 `DynamicConnectionConfig` ], optional 

141 Output connections to add. If not provided, a single connection is 

142 added with the same dimensions as the task and dataset type name 

143 ``dataset_auto{self.last_auto_dataset_type_index}``, with that 

144 variable incremented first. 

145 prerequisite_inputs : `~collections.abc.Mapping` [ `str`, \ 

146 `DynamicConnectionConfig` ], optional 

147 Prerequisite input connections to add. Defaults to an empty 

148 mapping. 

149 init_inputs : `~collections.abc.Mapping` [ `str`, \ 

150 `DynamicConnectionConfig` ], optional 

151 Init input connections to add. Defaults to an empty mapping. 

152 init_outputs : `~collections.abc.Mapping` [ `str`, \ 

153 `DynamicConnectionConfig` ], optional 

154 Init output connections to add. Defaults to an empty mapping. 

155 

156 Notes 

157 ----- 

158 The defaults for this method's arguments are designed to allow it to be 

159 called in succession to create a sequence of "one-to-one" tasks in 

160 which each consumes the output of the last. 

161 """ 

162 if config is None: 

163 config = DynamicTestPipelineTaskConfig() 

164 if dimensions is not None: 

165 config.dimensions = list(dimensions) 

166 if inputs is not None: 

167 config.inputs.update(inputs) 

168 else: 

169 config.inputs["input_connection"] = DynamicConnectionConfig( 

170 dataset_type_name=f"dataset_auto{self.last_auto_dataset_type_index}", 

171 dimensions=list(config.dimensions), 

172 ) 

173 if outputs is not None: 

174 config.outputs.update(outputs) 

175 else: 

176 self.last_auto_dataset_type_index += 1 

177 config.outputs["output_connection"] = DynamicConnectionConfig( 

178 dataset_type_name=f"dataset_auto{self.last_auto_dataset_type_index}", 

179 dimensions=list(config.dimensions), 

180 ) 

181 if prerequisite_inputs is not None: 

182 config.prerequisite_inputs.update(prerequisite_inputs) 

183 if init_inputs is not None: 

184 config.init_inputs.update(init_inputs) 

185 if init_outputs is not None: 

186 config.init_outputs.update(init_outputs) 

187 if label is None: 

188 self.last_auto_task_index += 1 

189 label = f"task_auto{self.last_auto_task_index}" 

190 self.pipeline_graph.add_task(label, task_class=task_class, config=config) 

191 

192 def make_quantum_graph( 

193 self, 

194 *, 

195 output: str | None = None, 

196 output_run: str = "output_run", 

197 insert_mocked_inputs: bool = True, 

198 register_output_dataset_types: bool = True, 

199 ) -> PredictedQuantumGraph: 

200 """Make a quantum graph from the pipeline task and internal data 

201 repository. 

202 

203 Parameters 

204 ---------- 

205 output : `str` or `None`, optional 

206 Name of the output chained collection to embed within the quantum 

207 graph. Note that this does not actually create this collection. 

208 output_run : `str`, optional 

209 Name of the `~lsst.daf.butler.CollectionType.RUN` collection for 

210 execution outputs. Note that this does not actually create this 

211 collection. 

212 insert_mocked_inputs : `bool`, optional 

213 Whether to automatically insert datasets for all overall inputs to 

214 the pipeline graph whose dataset types have not already been 

215 registered. If set to `False`, inputs must be provided by imported 

216 YAML files or explicit calls to `insert_datasets`, which provides 

217 more fine-grained control over the data IDs of the datasets. 

218 register_output_dataset_types : `bool`, optional 

219 If `True`, register all output dataset types. 

220 

221 Returns 

222 ------- 

223 qg : `..quantum_graph.PredictedQuantumGraph` 

224 Quantum graph. Datastore records will not be attached, since the 

225 test helper does not actually have a datastore. 

226 """ 

227 return ( 

228 self.make_quantum_graph_builder( 

229 insert_mocked_inputs=insert_mocked_inputs, 

230 register_output_dataset_types=register_output_dataset_types, 

231 output_run=output_run, 

232 ) 

233 .finish(output=output, attach_datastore_records=False) 

234 .assemble() 

235 ) 

236 

237 def make_quantum_graph_builder( 

238 self, 

239 *, 

240 output_run: str = "output_run", 

241 insert_mocked_inputs: bool = True, 

242 register_output_dataset_types: bool = True, 

243 ) -> AllDimensionsQuantumGraphBuilder: 

244 """Make a quantum graph builder from the pipeline task and internal 

245 data repository. 

246 

247 Parameters 

248 ---------- 

249 output_run : `str`, optional 

250 Name of the `~lsst.daf.butler.CollectionType.RUN` collection for 

251 execution outputs. Note that this does not actually create this 

252 collection. 

253 insert_mocked_inputs : `bool`, optional 

254 Whether to automatically insert datasets for all overall inputs to 

255 the pipeline graph whose dataset types have not already been 

256 registered. If set to `False`, inputs must be provided by imported 

257 YAML files or explicit calls to `insert_datasets`, which provides 

258 more fine-grained control over the data IDs of the datasets. 

259 register_output_dataset_types : `bool`, optional 

260 If `True`, register all output dataset types. 

261 

262 Returns 

263 ------- 

264 builder : \ 

265 `..all_dimensions_quantum_graph_builder.AllDimensionsQuantumGraphBuilder` 

266 Quantum graph builder. Note that 

267 ``attach_datastore_records=False`` must be passed to `build`, since 

268 the helper's butler does not have a datastore. 

269 """ 

270 if insert_mocked_inputs: 

271 self.pipeline_graph.resolve(self.butler.registry) 

272 for _, dataset_type_node in self.pipeline_graph.iter_overall_inputs(): 

273 assert dataset_type_node is not None, "pipeline graph is resolved." 

274 if self.butler.registry.registerDatasetType(dataset_type_node.dataset_type): 

275 self.insert_datasets(dataset_type_node.dataset_type, register=False) 

276 builder = AllDimensionsQuantumGraphBuilder( 

277 self.pipeline_graph, 

278 self.butler, 

279 input_collections=[self.input_chain], 

280 output_run=output_run, 

281 ) 

282 if register_output_dataset_types: 

283 self.pipeline_graph.register_dataset_types(self.butler) 

284 return builder 

285 

286 def insert_datasets( 

287 self, dataset_type: DatasetType | str, register: bool = True, *args: Any, **kwargs: Any 

288 ) -> list[DatasetRef]: 

289 """Insert input datasets into the test repository. 

290 

291 Parameters 

292 ---------- 

293 dataset_type : `~lsst.daf.butler.DatasetType` or `str` 

294 Dataset type or name. If a name, it must be included in the 

295 pipeline graph. 

296 register : `bool`, optional 

297 Whether to register the dataset type. If `False`, the dataset type 

298 must already be registered. 

299 *args : `object` 

300 Forwarded to `~lsst.daf.butler.query_data_ids`. 

301 **kwargs : `object` 

302 Forwarded to `~lsst.daf.butler.query_data_ids`. 

303 

304 Returns 

305 ------- 

306 refs : `list` [ `lsst.daf.butler.DatasetRef` ] 

307 References to the inserted datasets. 

308 

309 Notes 

310 ----- 

311 For dataset types with dimensions that are queryable, this queries for 

312 all data IDs in the repository (forwarding ``*args`` and ``**kwargs`` 

313 for e.g. ``where`` strings). For skypix dimensions, this queries for 

314 both patches and visit-detector regions (forwarding `*args`` and 

315 ``**kwargs`` to both) and uses all overlapping sky pixels. Dataset 

316 types with a mix of skypix and queryable dimensions are not supported. 

317 """ 

318 if isinstance(dataset_type, str): 

319 self.pipeline_graph.resolve(self.butler.registry) 

320 dataset_type = self.pipeline_graph.dataset_types[dataset_type].dataset_type 

321 if register: 

322 self.butler.registry.registerDatasetType(dataset_type) 

323 dimensions = dataset_type.dimensions 

324 if dataset_type.dimensions.skypix: 

325 if len(dimensions) == 1: 

326 (skypix_name,) = dimensions.skypix 

327 pixelization = dimensions.universe.skypix_dimensions[skypix_name].pixelization 

328 ranges = RangeSet() 

329 for patch_record in self.butler.query_dimension_records( 

330 "patch", *args, **kwargs, explain=False 

331 ): 

332 ranges |= pixelization.envelope(patch_record.region) 

333 for vdr_record in self.butler.query_dimension_records( 

334 "visit_detector_region", *args, **kwargs, explain=False 

335 ): 

336 ranges |= pixelization.envelope(vdr_record.region) 

337 data_ids = [] 

338 for begin, end in ranges: 

339 for index in range(begin, end): 

340 data_ids.append(DataCoordinate.from_required_values(dimensions, (index,))) 

341 else: 

342 raise NotImplementedError( 

343 "Can only generate data IDs for queryable dimensions and isolated skypix." 

344 ) 

345 else: 

346 data_ids = self.butler.query_data_ids(dimensions, *args, **kwargs, explain=False) 

347 return self._insert_datasets_impl(dataset_type, data_ids) 

348 

349 @abstractmethod 

350 def _insert_datasets_impl( 

351 self, dataset_type: DatasetType, data_ids: list[DataCoordinate] 

352 ) -> list[DatasetRef]: 

353 """Insert datasets after their data IDs have been generated. 

354 

355 Parameters 

356 ---------- 

357 dataset_type : `lsst.daf.butler.DatasetType` 

358 Type of the datasets. 

359 data_ids : `list` [ `lsst.daf.butler.DataCoordinate` ] 

360 Data IDs of all datasets. 

361 

362 Returns 

363 ------- 

364 refs : `list` [ `lsst.daf.butler.DatasetRef` ] 

365 References to the new datasets. 

366 """ 

367 raise NotImplementedError() 

368 

369 @abstractmethod 

370 def make_single_quantum_executor( 

371 self, qg: PredictedQuantumGraph 

372 ) -> tuple[SingleQuantumExecutor, LimitedButler]: 

373 """Make a single-quantum executor. 

374 

375 Parameters 

376 ---------- 

377 qg : `..quantum_graph.PredictedQuantumGraph` 

378 Graph whose quanta the executor must be capable of executing. 

379 

380 Returns 

381 ------- 

382 executor : `..single_quantum_executor.SingleQuantumExecutor` 

383 An executor for a single quantum. 

384 butler : `lsst.daf.butler.LimitedButler` 

385 The butler that the executor will write to. 

386 """ 

387 raise NotImplementedError() 

388 

389 

390class InMemoryRepo(MockRepo): 

391 """A test helper that simulates a butler repository for task execution 

392 without any disk I/O. 

393 

394 Parameters 

395 ---------- 

396 *args : `str` or `lsst.resources.ResourcePath` 

397 Butler YAML import files to load into the test repository. 

398 registry_config : `lsst.daf.butler.RegistryConfig`, optional 

399 Registry configuration for the repository. 

400 dimension_config : `lsst.daf.butler.DimensionConfig`, optional 

401 Dimension universe configuration for the repository. 

402 input_run : `str`, optional 

403 Name of a `~lsst.daf.butler.CollectionType.RUN` collection that will be 

404 used as an input to quantum graph generation. Input datasets created 

405 by the helper are added to this collection. 

406 input_chain : `str`, optional 

407 Name of a `~lsst.daf.butler.CollectionType.CHAINED` collection that 

408 will be the direct input to quantum graph generation. This always 

409 includes ``input_run``. 

410 use_import_collections_as_input : `bool`, `str`, or \ 

411 `~collections.abc.Iterable` [ `str`], optional 

412 Additional collections from YAML import files to include in 

413 ``input_chain``, or `True` to include all such collections (in 

414 chain-flattened lexicographical order). 

415 data_root : convertible to `lsst.resources.ResourcePath`, optional 

416 Root directory to join to each element in ``*args``. Defaults to 

417 the `lsst.daf.butler.tests.registry_data` package. 

418 

419 Notes 

420 ----- 

421 This helper maintains an `..pipeline_graph.PipelineGraph` and a 

422 no-datastore butler backed by an in-memory SQLite database for use in 

423 quantum graph generation. It creates a separate in-memory limited butler 

424 for execution as needed. 

425 """ 

426 

427 def __init__( 

428 self, 

429 *args: str | ResourcePath, 

430 registry_config: RegistryConfig | None = None, 

431 dimension_config: DimensionConfig | None = None, 

432 input_run: str = "input_run", 

433 input_chain: str = "input_chain", 

434 use_import_collections_as_input: bool | str | Iterable[str] = True, 

435 data_root: ResourcePathExpression | None = "resource://lsst.daf.butler/tests/registry_data", 

436 ): 

437 if data_root is not None: 

438 data_root = ResourcePath(data_root, forceDirectory=True) 

439 args = tuple(data_root.join(arg) for arg in args) 

440 butler = create_populated_sqlite_registry( 

441 *args, registry_config=registry_config, dimension_config=dimension_config 

442 ) 

443 if use_import_collections_as_input: 

444 if use_import_collections_as_input is True: 

445 use_import_collections_as_input = sorted(butler.collections.query("*", flatten_chains=True)) 

446 else: 

447 use_import_collections_as_input = () 

448 super().__init__( 

449 butler, 

450 input_run=input_run, 

451 input_chain=input_chain, 

452 input_children=list(use_import_collections_as_input), 

453 ) 

454 

455 def _insert_datasets_impl( 

456 self, dataset_type: DatasetType, data_ids: list[DataCoordinate] 

457 ) -> list[DatasetRef]: 

458 return self.butler.registry.insertDatasets(dataset_type, data_ids, run=self.input_run) 

459 

460 def make_limited_butler(self) -> InMemoryLimitedButler: 

461 """Make a test limited butler for execution. 

462 

463 Returns 

464 ------- 

465 limited_butler : `.InMemoryLimitedButler` 

466 A limited butler that can be used for task execution. 

467 

468 Notes 

469 ----- 

470 This queries the database-only butler used for quantum-graph generation 

471 for all datasets in the ``input_chain`` collection, and populates the 

472 limited butler with those that have a mock storage class. Other 

473 datasets are ignored, so they will appear as though they were present 

474 during quantum graph generation but absent during execution. 

475 """ 

476 butler = InMemoryLimitedButler(self.butler.dimensions, self.butler.registry.queryDatasetTypes()) 

477 for ref in self.butler.query_all_datasets(self.input_chain): 

478 if is_mock_name(ref.datasetType.storageClass_name): 

479 butler.put( 

480 MockDataset( 

481 dataset_id=ref.id, 

482 dataset_type=ref.datasetType.to_simple(), 

483 data_id=dict(ref.dataId.mapping), 

484 run=ref.run, 

485 ), 

486 ref, 

487 ) 

488 return butler 

489 

490 def make_single_quantum_executor( 

491 self, qg: PredictedQuantumGraph | None = None 

492 ) -> tuple[SingleQuantumExecutor, InMemoryLimitedButler]: 

493 """Make a single-quantum executor backed by a new limited butler. 

494 

495 Parameters 

496 ---------- 

497 qg : `..quantum_graph.PredictedQuantumGraph` 

498 Ignored by this implementation. 

499 

500 Returns 

501 ------- 

502 executor : `..single_quantum_executor.SingleQuantumExecutor` 

503 An executor for a single quantum. 

504 butler : `.InMemoryLimitedButler` 

505 The butler that the executor will write to. 

506 """ 

507 butler = self.make_limited_butler() 

508 return SingleQuantumExecutor(limited_butler_factory=butler.factory), butler 

509 

510 

511class DirectButlerRepo(MockRepo): 

512 """A test helper for task execution backed by a local direct butler. 

513 

514 Parameters 

515 ---------- 

516 butler : `lsst.daf.butler.direct_butler.DirectButler` 

517 Butler to write to. 

518 *args : `str` or `lsst.resources.ResourcePath` 

519 Butler YAML import files to load into the test repository. 

520 input_run : `str`, optional 

521 Name of a `~lsst.daf.butler.CollectionType.RUN` collection that will be 

522 used as an input to quantum graph generation. Input datasets created 

523 by the helper are added to this collection. 

524 input_chain : `str`, optional 

525 Name of a `~lsst.daf.butler.CollectionType.CHAINED` collection that 

526 will be the direct input to quantum graph generation. This always 

527 includes ``input_run``. 

528 use_import_collections_as_input : `bool`, `str`, or \ 

529 `~collections.abc.Iterable` [ `str`], optional 

530 Additional collections from YAML import files to include in 

531 ``input_chain``, or `True` to include all such collections (in 

532 chain-flattened lexicographical order). 

533 data_root : convertible to `lsst.resources.ResourcePath`, optional 

534 Root directory to join to each element in ``*args``. Defaults to 

535 the `lsst.daf.butler.tests.registry_data` package. 

536 

537 Notes 

538 ----- 

539 This helper maintains an `..pipeline_graph.PipelineGraph` and a 

540 no-datastore butler backed by an in-memory SQLite database for use in 

541 quantum graph generation. It creates a separate in-memory limited butler 

542 for execution as needed. 

543 """ 

544 

545 def __init__( 

546 self, 

547 butler: Butler, 

548 *args: str | ResourcePath, 

549 input_run: str = "input_run", 

550 input_chain: str = "input_chain", 

551 use_import_collections_as_input: bool | str | Iterable[str] = True, 

552 data_root: ResourcePathExpression | None = "resource://lsst.daf.butler/tests/registry_data", 

553 ): 

554 if data_root is not None: 

555 data_root = ResourcePath(data_root, forceDirectory=True) 

556 args = tuple(data_root.join(arg) for arg in args) 

557 for arg in args: 

558 butler.import_(filename=arg) 

559 if use_import_collections_as_input: 

560 if use_import_collections_as_input is True: 

561 use_import_collections_as_input = sorted(butler.collections.query("*", flatten_chains=True)) 

562 else: 

563 use_import_collections_as_input = () 

564 super().__init__( 

565 butler, 

566 input_run=input_run, 

567 input_chain=input_chain, 

568 input_children=list(use_import_collections_as_input), 

569 ) 

570 

571 @classmethod 

572 @contextmanager 

573 def make_temporary( 

574 cls, 

575 *args: str | ResourcePath, 

576 input_run: str = "input_run", 

577 input_chain: str = "input_chain", 

578 use_import_collections_as_input: bool | str | Iterable[str] = True, 

579 data_root: ResourcePathExpression | None = "resource://lsst.daf.butler/tests/registry_data", 

580 **kwargs: Any, 

581 ) -> Iterator[tuple[DirectButlerRepo, str]]: 

582 with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as root: 

583 config = Butler.makeRepo(root, **kwargs) 

584 butler = Butler.from_config(config, writeable=True) 

585 yield ( 

586 cls( 

587 butler, 

588 *args, 

589 input_run=input_run, 

590 input_chain=input_chain, 

591 use_import_collections_as_input=use_import_collections_as_input, 

592 data_root=data_root, 

593 ), 

594 root, 

595 ) 

596 

597 def _insert_datasets_impl( 

598 self, dataset_type: DatasetType, data_ids: list[DataCoordinate] 

599 ) -> list[DatasetRef]: 

600 if is_mock_name(dataset_type.storageClass_name): 

601 refs: list[DatasetRef] = [] 

602 for data_id in data_ids: 

603 data_id = self.butler.registry.expandDataId(data_id) 

604 ref = DatasetRef(dataset_type, data_id, run=self.input_run) 

605 self.butler.put( 

606 MockDataset( 

607 dataset_id=ref.id, 

608 dataset_type=ref.datasetType.to_simple(), 

609 data_id=dict(ref.dataId.mapping), 

610 run=ref.run, 

611 ), 

612 ref, 

613 ) 

614 refs.append(ref) 

615 return refs 

616 else: 

617 return self.butler.registry.insertDatasets(dataset_type, data_ids, run=self.input_run) 

618 

619 def make_single_quantum_executor( 

620 self, qg: PredictedQuantumGraph | None = None 

621 ) -> tuple[SingleQuantumExecutor, Butler]: 

622 """Make a single-quantum executor backed by a new limited butler. 

623 

624 Parameters 

625 ---------- 

626 qg : `..quantum_graph.PredictedQuantumGraph` 

627 Ignored by this implementation. 

628 

629 Returns 

630 ------- 

631 executor : `..single_quantum_executor.SingleQuantumExecutor` 

632 An executor for a single quantum. 

633 butler : `lsst.daf.butler.Butler` 

634 The butler that the executor will write to. 

635 """ 

636 return SingleQuantumExecutor(limited_butler_factory=None, butler=self.butler), self.butler