lsst-pipe-base 29.2025.3900__tar.gz → 29.2025.4000__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 (173) hide show
  1. {lsst_pipe_base-29.2025.3900/python/lsst_pipe_base.egg-info → lsst_pipe_base-29.2025.4000}/PKG-INFO +2 -1
  2. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/index.rst +4 -0
  3. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/pyproject.toml +2 -0
  4. lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/dot_tools.py +130 -0
  5. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/exec_fixup_data_id.py +17 -44
  6. lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/execution_graph_fixup.py +100 -0
  7. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/graph/graph.py +28 -9
  8. lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/graph_walker.py +119 -0
  9. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/log_capture.py +5 -2
  10. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/mermaid_tools.py +11 -64
  11. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/mp_graph_executor.py +298 -236
  12. lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/quantum_graph/__init__.py +32 -0
  13. lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/quantum_graph/_common.py +610 -0
  14. lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/quantum_graph/_multiblock.py +737 -0
  15. lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/quantum_graph/_predicted.py +1874 -0
  16. lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/quantum_graph/visualization.py +302 -0
  17. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/quantum_graph_builder.py +292 -34
  18. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/quantum_graph_executor.py +2 -1
  19. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/quantum_provenance_graph.py +16 -7
  20. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/separable_pipeline_executor.py +126 -15
  21. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/simple_pipeline_executor.py +44 -43
  22. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/single_quantum_executor.py +1 -40
  23. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/mocks/__init__.py +1 -1
  24. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/mocks/_pipeline_task.py +16 -1
  25. lsst_pipe_base-29.2025.3900/python/lsst/pipe/base/tests/mocks/_in_memory_repo.py → lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/tests/mocks/_repo.py +324 -45
  26. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/mocks/_storage_class.py +6 -0
  27. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/simpleQGraph.py +11 -5
  28. lsst_pipe_base-29.2025.4000/python/lsst/pipe/base/version.py +2 -0
  29. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000/python/lsst_pipe_base.egg-info}/PKG-INFO +2 -1
  30. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst_pipe_base.egg-info/SOURCES.txt +9 -1
  31. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst_pipe_base.egg-info/requires.txt +1 -0
  32. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_adjust_all_quanta.py +5 -2
  33. lsst_pipe_base-29.2025.4000/tests/test_graph_walker.py +142 -0
  34. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_init_output_run.py +15 -19
  35. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_instrument.py +5 -11
  36. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_mp_graph_executor.py +51 -30
  37. lsst_pipe_base-29.2025.4000/tests/test_predicted_qg.py +607 -0
  38. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_qg_builder_dimensions.py +20 -20
  39. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_separable_pipeline_executor.py +465 -25
  40. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_single_quantum_executor.py +0 -2
  41. lsst_pipe_base-29.2025.3900/python/lsst/pipe/base/dot_tools.py +0 -268
  42. lsst_pipe_base-29.2025.3900/python/lsst/pipe/base/execution_graph_fixup.py +0 -69
  43. lsst_pipe_base-29.2025.3900/python/lsst/pipe/base/version.py +0 -2
  44. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/COPYRIGHT +0 -0
  45. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/LICENSE +0 -0
  46. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/MANIFEST.in +0 -0
  47. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/README.md +0 -0
  48. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/bsd_license.txt +0 -0
  49. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/CHANGES.rst +0 -0
  50. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/creating-a-pipeline.rst +0 -0
  51. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/creating-a-pipelinetask.rst +0 -0
  52. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/creating-a-task.rst +0 -0
  53. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/task-framework-overview.rst +0 -0
  54. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/task-retargeting-howto.rst +0 -0
  55. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/testing-a-pipeline-task.rst +0 -0
  56. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/testing-pipelines-with-mocks.rst +0 -0
  57. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/doc/lsst.pipe.base/working-with-pipeline-graphs.rst +0 -0
  58. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/gpl-v3.0.txt +0 -0
  59. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/__init__.py +0 -0
  60. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/__init__.py +0 -0
  61. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/__init__.py +0 -0
  62. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/_datasetQueryConstraints.py +0 -0
  63. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/_dataset_handle.py +0 -0
  64. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/_instrument.py +0 -0
  65. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/_observation_dimension_packer.py +0 -0
  66. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/_quantumContext.py +0 -0
  67. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/_status.py +0 -0
  68. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/_task_metadata.py +0 -0
  69. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/all_dimensions_quantum_graph_builder.py +0 -0
  70. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/automatic_connection_constants.py +0 -0
  71. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/caching_limited_butler.py +0 -0
  72. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/cli/__init__.py +0 -0
  73. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/cli/_get_cli_subcommands.py +0 -0
  74. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/cli/cmd/__init__.py +0 -0
  75. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/cli/cmd/commands.py +0 -0
  76. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/cli/opt/__init__.py +0 -0
  77. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/cli/opt/arguments.py +0 -0
  78. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/cli/opt/options.py +0 -0
  79. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/config.py +0 -0
  80. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/configOverrides.py +0 -0
  81. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/connectionTypes.py +0 -0
  82. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/connections.py +0 -0
  83. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/execution_reports.py +0 -0
  84. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/formatters/__init__.py +0 -0
  85. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/formatters/pexConfig.py +0 -0
  86. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/graph/__init__.py +0 -0
  87. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/graph/_implDetails.py +0 -0
  88. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/graph/_loadHelpers.py +0 -0
  89. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/graph/_versionDeserializers.py +0 -0
  90. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/graph/graphSummary.py +0 -0
  91. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/graph/quantumNode.py +0 -0
  92. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline.py +0 -0
  93. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipelineIR.py +0 -0
  94. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipelineTask.py +0 -0
  95. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/__init__.py +0 -0
  96. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/__main__.py +0 -0
  97. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/_dataset_types.py +0 -0
  98. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/_edges.py +0 -0
  99. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/_exceptions.py +0 -0
  100. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/_mapping_views.py +0 -0
  101. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/_nodes.py +0 -0
  102. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/_pipeline_graph.py +0 -0
  103. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/_task_subsets.py +0 -0
  104. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/_tasks.py +0 -0
  105. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/expressions.py +0 -0
  106. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/io.py +0 -0
  107. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/__init__.py +0 -0
  108. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/_dot.py +0 -0
  109. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/_formatting.py +0 -0
  110. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/_layout.py +0 -0
  111. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/_merge.py +0 -0
  112. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/_mermaid.py +0 -0
  113. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/_options.py +0 -0
  114. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/_printer.py +0 -0
  115. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/_show.py +0 -0
  116. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/pipeline_graph/visualization/_status_annotator.py +0 -0
  117. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/prerequisite_helpers.py +0 -0
  118. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/py.typed +0 -0
  119. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/quantum_graph_skeleton.py +0 -0
  120. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/quantum_reports.py +0 -0
  121. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/script/__init__.py +0 -0
  122. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/script/register_instrument.py +0 -0
  123. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/script/retrieve_artifacts_for_quanta.py +0 -0
  124. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/script/transfer_from_graph.py +0 -0
  125. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/script/utils.py +0 -0
  126. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/script/zip_from_graph.py +0 -0
  127. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/struct.py +0 -0
  128. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/task.py +0 -0
  129. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/taskFactory.py +0 -0
  130. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/testUtils.py +0 -0
  131. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/__init__.py +0 -0
  132. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/in_memory_limited_butler.py +0 -0
  133. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/mocks/_data_id_match.py +0 -0
  134. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/no_dimensions.py +0 -0
  135. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/pipelineStepTester.py +0 -0
  136. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/tests/util.py +0 -0
  137. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst/pipe/base/utils.py +0 -0
  138. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst_pipe_base.egg-info/dependency_links.txt +0 -0
  139. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst_pipe_base.egg-info/entry_points.txt +0 -0
  140. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst_pipe_base.egg-info/top_level.txt +0 -0
  141. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/python/lsst_pipe_base.egg-info/zip-safe +0 -0
  142. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/setup.cfg +0 -0
  143. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_caching_limited_butler.py +0 -0
  144. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_cliCmdRegisterInstrument.py +0 -0
  145. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_configOverrides.py +0 -0
  146. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_config_formatter.py +0 -0
  147. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_connections.py +0 -0
  148. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_dataid_match.py +0 -0
  149. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_dataset_handle.py +0 -0
  150. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_dot_tools.py +0 -0
  151. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_dynamic_connections.py +0 -0
  152. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_execution_reports.py +0 -0
  153. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_execution_storage_class_conversion.py +0 -0
  154. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_graphBuilder.py +0 -0
  155. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_mermaid.py +0 -0
  156. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_pipeline.py +0 -0
  157. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_pipelineIR.py +0 -0
  158. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_pipelineLoadSubset.py +0 -0
  159. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_pipelineTask.py +0 -0
  160. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_pipeline_graph.py +0 -0
  161. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_pipeline_graph_expressions.py +0 -0
  162. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_quantumGraph.py +0 -0
  163. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_quantum_provenance_graph.py +0 -0
  164. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_quantum_reports.py +0 -0
  165. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_quantum_success_caveats.py +0 -0
  166. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_script_utils.py +0 -0
  167. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_simple_pipeline_executor.py +0 -0
  168. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_struct.py +0 -0
  169. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_task.py +0 -0
  170. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_task_factory.py +0 -0
  171. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_taskmetadata.py +0 -0
  172. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_testUtils.py +0 -0
  173. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4000}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-pipe-base
3
- Version: 29.2025.3900
3
+ Version: 29.2025.4000
4
4
  Summary: Pipeline infrastructure for the Rubin Science Pipelines.
5
5
  Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
6
6
  License: BSD 3-Clause License
@@ -31,6 +31,7 @@ Requires-Dist: wcwidth
31
31
  Requires-Dist: pyyaml>=5.1
32
32
  Requires-Dist: numpy>=1.17
33
33
  Requires-Dist: frozendict
34
+ Requires-Dist: zstandard<0.24,>=0.23.0
34
35
  Provides-Extra: test
35
36
  Requires-Dist: pytest>=3.2; extra == "test"
36
37
  Provides-Extra: mermaid
@@ -100,6 +100,10 @@ Python API reference
100
100
 
101
101
  .. automodapi:: lsst.pipe.base.separable_pipeline_executor
102
102
 
103
+ .. automodapi:: lsst.pipe.base.quantum_graph
104
+
105
+ .. automodapi:: lsst.pipe.base.quantum_graph.visualization
106
+
103
107
  QuantumGraph generation API reference
104
108
  -------------------------------------
105
109
 
@@ -34,6 +34,7 @@ dependencies = [
34
34
  "pyyaml >= 5.1",
35
35
  "numpy >= 1.17",
36
36
  "frozendict",
37
+ "zstandard >=0.23.0,<0.24",
37
38
  ]
38
39
 
39
40
  dynamic = ["version"]
@@ -122,6 +123,7 @@ exclude_lines = [
122
123
  "raise NotImplementedError",
123
124
  "if __name__ == .__main__.:",
124
125
  "if TYPE_CHECKING:",
126
+ 'if "sphinx" in sys.modules:',
125
127
  ]
126
128
 
127
129
  [tool.pydocstyle]
@@ -0,0 +1,130 @@
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
+
28
+ """Module defining few methods to generate GraphViz diagrams from pipelines
29
+ or quantum graphs.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ __all__ = ["graph2dot", "pipeline2dot"]
35
+
36
+ from collections.abc import Iterable
37
+ from typing import TYPE_CHECKING, Any
38
+
39
+ from .pipeline import Pipeline
40
+
41
+ if TYPE_CHECKING:
42
+ from .graph import QuantumGraph
43
+ from .pipeline import TaskDef
44
+ from .quantum_graph import PredictedQuantumGraph
45
+
46
+
47
+ def graph2dot(qgraph: QuantumGraph | PredictedQuantumGraph, file: Any) -> None:
48
+ """Convert QuantumGraph into GraphViz digraph.
49
+
50
+ This method is mostly for documentation/presentation purposes.
51
+
52
+ Parameters
53
+ ----------
54
+ qgraph : `lsst.pipe.base.QuantumGraph` or \
55
+ `lsst.pipe.base.quantum_graph.PredictedQuantumGraph`
56
+ Quantum graph object.
57
+ file : `str` or file object
58
+ File where GraphViz graph (DOT language) is written, can be a file name
59
+ or file object.
60
+
61
+ Raises
62
+ ------
63
+ OSError
64
+ Raised if the output file cannot be opened.
65
+ ImportError
66
+ Raised if the task class cannot be imported.
67
+ """
68
+ from .quantum_graph import PredictedQuantumGraph, visualization
69
+
70
+ if not isinstance(qgraph, PredictedQuantumGraph):
71
+ qgraph = PredictedQuantumGraph.from_old_quantum_graph(qgraph)
72
+
73
+ # open a file if needed
74
+ close = False
75
+ if not hasattr(file, "write"):
76
+ file = open(file, "w")
77
+ close = True
78
+
79
+ v = visualization.QuantumGraphDotVisualizer()
80
+ v.write_bipartite(qgraph, file)
81
+
82
+ if close:
83
+ file.close()
84
+
85
+
86
+ def pipeline2dot(pipeline: Pipeline | Iterable[TaskDef], file: Any) -> None:
87
+ """Convert `~lsst.pipe.base.Pipeline` into GraphViz digraph.
88
+
89
+ This method is mostly for documentation/presentation purposes.
90
+ Unlike other methods this method does not validate graph consistency.
91
+
92
+ Parameters
93
+ ----------
94
+ pipeline : `.Pipeline` or `~collections.abc.Iterable` [ `.TaskDef` ]
95
+ Pipeline description.
96
+ file : `str` or file object
97
+ File where GraphViz graph (DOT language) is written, can be a file name
98
+ or file object.
99
+
100
+ Raises
101
+ ------
102
+ OSError
103
+ Raised if the output file cannot be opened.
104
+ ImportError
105
+ Raised if the task class cannot be imported.
106
+ """
107
+ from .pipeline_graph import PipelineGraph, visualization
108
+
109
+ # open a file if needed
110
+ close = False
111
+ if not hasattr(file, "write"):
112
+ file = open(file, "w")
113
+ close = True
114
+
115
+ if isinstance(pipeline, Pipeline):
116
+ pg = pipeline.to_graph(visualization_only=True)
117
+ else:
118
+ pg = PipelineGraph()
119
+ for task_def in pipeline:
120
+ pg.add_task(
121
+ task_def.label,
122
+ task_class=task_def.taskClass,
123
+ config=task_def.config,
124
+ connections=task_def.connections,
125
+ )
126
+ pg.resolve(visualization_only=True)
127
+ visualization.show_dot(pg, stream=file, dataset_types=True)
128
+
129
+ if close:
130
+ file.close()
@@ -27,16 +27,17 @@
27
27
 
28
28
  __all__ = ["ExecutionGraphFixup"]
29
29
 
30
- import contextlib
31
30
  import itertools
31
+ import uuid
32
32
  from collections import defaultdict
33
- from collections.abc import Sequence
34
- from typing import Any
33
+ from collections.abc import Mapping, Sequence
35
34
 
36
35
  import networkx as nx
37
36
 
37
+ from lsst.daf.butler import DataCoordinate, DataIdValue
38
+
38
39
  from .execution_graph_fixup import ExecutionGraphFixup
39
- from .graph import QuantumGraph, QuantumNode
40
+ from .graph import QuantumGraph
40
41
 
41
42
 
42
43
  class ExecFixupDataId(ExecutionGraphFixup):
@@ -88,44 +89,16 @@ class ExecFixupDataId(ExecutionGraphFixup):
88
89
  else:
89
90
  self.dimensions = tuple(self.dimensions)
90
91
 
91
- def _key(self, qnode: QuantumNode) -> tuple[Any, ...]:
92
- """Produce comparison key for quantum data.
93
-
94
- Parameters
95
- ----------
96
- qnode : `QuantumNode`
97
- An individual node in a `~lsst.pipe.base.QuantumGraph`
98
-
99
- Returns
100
- -------
101
- key : `tuple`
102
- """
103
- dataId = qnode.quantum.dataId
104
- assert dataId is not None, "Quantum DataId cannot be None"
105
- key = tuple(dataId[dim] for dim in self.dimensions)
106
- return key
107
-
108
92
  def fixupQuanta(self, graph: QuantumGraph) -> QuantumGraph:
109
- taskDef = graph.findTaskDefByLabel(self.taskLabel)
110
- if taskDef is None:
111
- raise ValueError(f"Cannot find task with label {self.taskLabel}")
112
- quanta = list(graph.getNodesForTask(taskDef))
113
- keyQuanta = defaultdict(list)
114
- for q in quanta:
115
- key = self._key(q)
116
- keyQuanta[key].append(q)
117
- keys = sorted(keyQuanta.keys(), reverse=self.reverse)
118
- networkGraph = graph.graph
119
-
120
- for prev_key, key in itertools.pairwise(keys):
121
- for prev_node in keyQuanta[prev_key]:
122
- for node in keyQuanta[key]:
123
- # remove any existing edges between the two nodes, but
124
- # don't fail if there are not any. Both directions need
125
- # tried because in a directed graph, order maters
126
- for edge in ((node, prev_node), (prev_node, node)):
127
- with contextlib.suppress(nx.NetworkXException):
128
- networkGraph.remove_edge(*edge)
129
-
130
- networkGraph.add_edge(prev_node, node)
131
- return graph
93
+ raise NotImplementedError()
94
+
95
+ def fixup_graph(
96
+ self, xgraph: nx.DiGraph, quanta_by_task: Mapping[str, Mapping[DataCoordinate, uuid.UUID]]
97
+ ) -> None:
98
+ quanta_by_sort_key: defaultdict[tuple[DataIdValue, ...], list[uuid.UUID]] = defaultdict(list)
99
+ for data_id, quantum_id in quanta_by_task[self.taskLabel].items():
100
+ key = tuple(data_id[dim] for dim in self.dimensions)
101
+ quanta_by_sort_key[key].append(quantum_id)
102
+ sorted_keys = sorted(quanta_by_sort_key.keys(), reverse=self.reverse)
103
+ for prev_key, key in itertools.pairwise(sorted_keys):
104
+ xgraph.add_edges_from(itertools.product(quanta_by_sort_key[prev_key], quanta_by_sort_key[key]))
@@ -0,0 +1,100 @@
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
+
28
+ __all__ = ["ExecutionGraphFixup"]
29
+
30
+ import uuid
31
+ from abc import ABC
32
+ from collections.abc import Mapping
33
+
34
+ import networkx
35
+
36
+ from lsst.daf.butler import DataCoordinate
37
+
38
+ from .graph import QuantumGraph
39
+
40
+
41
+ class ExecutionGraphFixup(ABC):
42
+ """Interface for classes which update quantum graphs before execution.
43
+
44
+ Notes
45
+ -----
46
+ The primary goal of this class is to modify quanta dependencies which may
47
+ not be possible to reflect in a quantum graph using standard tools. One
48
+ known use case for that is to guarantee particular execution order of
49
+ visits in CI jobs for cases when outcome depends on the processing order of
50
+ visits (e.g. AP association pipeline).
51
+
52
+ Instances of this class receive a preliminary graph and are allowed to
53
+ add edges, as long as those edges do not result in a cycle. Edges and
54
+ nodes may not be removed.
55
+
56
+ New subclasses should implement only `fixup_graph`, which will always be
57
+ called first. `fixupQuanta` is only called if `fixup_graph` raises
58
+ `NotImplementedError`.
59
+ """
60
+
61
+ def fixupQuanta(self, graph: QuantumGraph) -> QuantumGraph:
62
+ """Update quanta in a graph.
63
+
64
+ Parameters
65
+ ----------
66
+ graph : `.QuantumGraph`
67
+ Quantum Graph that will be executed by the executor.
68
+
69
+ Returns
70
+ -------
71
+ graph : `.QuantumGraph`
72
+ Modified graph.
73
+
74
+ Notes
75
+ -----
76
+ This hook is provided for backwards compatibility only.
77
+ """
78
+ raise NotImplementedError()
79
+
80
+ def fixup_graph(
81
+ self, xgraph: networkx.DiGraph, quanta_by_task: Mapping[str, Mapping[DataCoordinate, uuid.UUID]]
82
+ ) -> None:
83
+ """Update a networkx graph of quanta in place by adding edges to
84
+ further constrain the ordering.
85
+
86
+ Parameters
87
+ ----------
88
+ xgraph : `networkx.DiGraph`
89
+ A directed acyclic graph of quanta to modify in place. Node keys
90
+ are quantum UUIDs, and attributes include ``task_label`` (`str`)
91
+ and ``data_id`` (a full `lsst.daf.butler.DataCoordinate`, without
92
+ dimension records attached). Edges may be added, but not removed.
93
+ Nodes may not be modified.
94
+ quanta_by_task : `~collections.abc.Mapping` [ `str`,\
95
+ `~collections.abc.Mapping` [ `lsst.daf.butler.DataCoordinate`,\
96
+ `uuid.UUID` ] ]
97
+ All quanta in the graph, grouped first by task label and then by
98
+ data ID.
99
+ """
100
+ raise NotImplementedError()
@@ -806,11 +806,18 @@ class QuantumGraph:
806
806
  uri : convertible to `~lsst.resources.ResourcePath`
807
807
  URI to where the graph should be saved.
808
808
  """
809
- buffer = self._buildSaveObject()
810
809
  path = ResourcePath(uri)
811
- if path.getExtension() not in (".qgraph"):
812
- raise TypeError(f"Can currently only save a graph in qgraph format not {uri}")
813
- path.write(buffer) # type: ignore # Ignore because bytearray is safe to use in place of bytes
810
+ match path.getExtension():
811
+ case ".qgraph":
812
+ buffer = self._buildSaveObject()
813
+ path.write(buffer) # type: ignore # Ignore because bytearray is safe to use in place of bytes
814
+ case ".qg":
815
+ from ..quantum_graph import PredictedQuantumGraphComponents
816
+
817
+ pqg = PredictedQuantumGraphComponents.from_old_quantum_graph(self)
818
+ pqg.write(path)
819
+ case ext:
820
+ raise TypeError(f"Can currently only save a graph in .qgraph or .qg format, not {ext!r}.")
814
821
 
815
822
  @property
816
823
  def metadata(self) -> MappingProxyType[str, Any]:
@@ -962,11 +969,23 @@ class QuantumGraph:
962
969
  the graph.
963
970
  """
964
971
  uri = ResourcePath(uri)
965
- if uri.getExtension() in {".qgraph"}:
966
- with LoadHelper(uri, minimumVersion, fullRead=(nodes is None)) as loader:
967
- qgraph = loader.load(universe, nodes, graphID)
968
- else:
969
- raise ValueError(f"Only know how to handle files saved as `.qgraph`, not {uri}")
972
+ match uri.getExtension():
973
+ case ".qgraph":
974
+ with LoadHelper(uri, minimumVersion, fullRead=(nodes is None)) as loader:
975
+ qgraph = loader.load(universe, nodes, graphID)
976
+ case ".qg":
977
+ from ..quantum_graph import PredictedQuantumGraphReader
978
+
979
+ with PredictedQuantumGraphReader.open(uri, page_size=100000) as qgr:
980
+ quantum_ids = (
981
+ [uuid.UUID(q) if not isinstance(q, uuid.UUID) else q for q in nodes]
982
+ if nodes is not None
983
+ else None
984
+ )
985
+ qgr.read_execution_quanta(quantum_ids)
986
+ qgraph = qgr.finish().to_old_quantum_graph()
987
+ case _:
988
+ raise ValueError(f"Only know how to handle files saved as `.qgraph`, not {uri}")
970
989
  if not isinstance(qgraph, QuantumGraph):
971
990
  raise TypeError(f"QuantumGraph file {uri} contains unexpected object type: {type(qgraph)}")
972
991
  return qgraph
@@ -0,0 +1,119 @@
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
+
28
+ from __future__ import annotations
29
+
30
+ __all__ = ("GraphWalker",)
31
+
32
+ from typing import Generic, Self, TypeVar
33
+
34
+ import networkx
35
+
36
+ _T = TypeVar("_T")
37
+
38
+
39
+ class GraphWalker(Generic[_T]):
40
+ """A helper for traversing directed acyclic graphs.
41
+
42
+ Parameters
43
+ ----------
44
+ xgraph : `networkx.DiGraph` or `networkx.MultiDiGraph`
45
+ Networkx graph to process. Will be consumed during iteration, so this
46
+ should often be a copy.
47
+
48
+ Notes
49
+ -----
50
+ Each iteration yields a `frozenset` of nodes, which may be empty if there
51
+ are no nodes ready for processing. A node is only considered ready if all
52
+ of its predecessor nodes have been marked as complete with `finish`.
53
+ Iteration only completes when all nodes have been finished or failed.
54
+
55
+ `GraphWalker` is not thread-safe; calling one `GraphWalker` method while
56
+ another is in progress is undefined behavior. It is designed to be used
57
+ in the management thread or process in a parallel traversal.
58
+ """
59
+
60
+ def __init__(self, xgraph: networkx.DiGraph | networkx.MultiDiGraph):
61
+ self._xgraph = xgraph
62
+ self._ready: set[_T] = set(next(iter(networkx.dag.topological_generations(self._xgraph)), []))
63
+ self._active: set[_T] = set()
64
+ self._incomplete: set[_T] = set(self._xgraph)
65
+
66
+ def __iter__(self) -> Self:
67
+ return self
68
+
69
+ def __next__(self) -> frozenset[_T]:
70
+ if not self._incomplete:
71
+ raise StopIteration()
72
+ new_active = frozenset(self._ready)
73
+ self._active.update(new_active)
74
+ self._ready.clear()
75
+ return new_active
76
+
77
+ def finish(self, key: _T) -> None:
78
+ """Mark a node as successfully processed, unblocking (at least in part)
79
+ iteration over successor nodes.
80
+
81
+ Parameters
82
+ ----------
83
+ key : unspecified
84
+ NetworkX key of the node to mark finished.
85
+ """
86
+ self._active.remove(key)
87
+ self._incomplete.remove(key)
88
+ successors = list(self._xgraph.successors(key))
89
+ for successor in successors:
90
+ assert successor not in self._active, (
91
+ "A node downstream of an active one should not have been yielded yet."
92
+ )
93
+ if all(
94
+ predecessor not in self._incomplete for predecessor in self._xgraph.predecessors(successor)
95
+ ):
96
+ self._ready.add(successor)
97
+
98
+ def fail(self, key: _T) -> list[_T]:
99
+ """Mark a node as unsuccessfully processed, permanently blocking all
100
+ recursive descendants.
101
+
102
+ Parameters
103
+ ----------
104
+ key : unspecified
105
+ NetworkX key of the node to mark as a failure.
106
+
107
+ Returns
108
+ -------
109
+ blocked : `list`
110
+ NetworkX keys of nodes that were recursive descendants of the
111
+ failed node, and will hence never be yielded by the iterator.
112
+ """
113
+ self._active.remove(key)
114
+ self._incomplete.remove(key)
115
+ descendants = list(networkx.dag.descendants(self._xgraph, key))
116
+ self._xgraph.remove_node(key)
117
+ self._xgraph.remove_nodes_from(descendants)
118
+ self._incomplete.difference_update(descendants)
119
+ return descendants
@@ -41,6 +41,7 @@ from lsst.daf.butler import Butler, FileDataset, LimitedButler, Quantum
41
41
  from lsst.daf.butler.logging import ButlerLogRecordHandler, ButlerLogRecords, ButlerMDC, JsonLogFormatter
42
42
 
43
43
  from ._status import InvalidQuantumError
44
+ from .automatic_connection_constants import METADATA_OUTPUT_TEMPLATE
44
45
  from .pipeline_graph import TaskNode
45
46
 
46
47
  _LOG = logging.getLogger(__name__)
@@ -116,8 +117,10 @@ class LogCapture:
116
117
  mdc = {"LABEL": task_node.label, "RUN": ""}
117
118
  if quantum.dataId:
118
119
  mdc["LABEL"] += f":{quantum.dataId}"
119
- if self.full_butler is not None:
120
- mdc["RUN"] = self.full_butler.run or ""
120
+
121
+ metadata_ref = quantum.outputs[METADATA_OUTPUT_TEMPLATE.format(label=task_node.label)][0]
122
+ mdc["RUN"] = metadata_ref.run
123
+
121
124
  ctx = _LogCaptureFlag()
122
125
  log_dataset_name = (
123
126
  task_node.log_output.dataset_type_name if task_node.log_output is not None else None
@@ -39,39 +39,12 @@ from typing import TYPE_CHECKING, Any, Literal
39
39
  from .pipeline import Pipeline
40
40
 
41
41
  if TYPE_CHECKING:
42
- from lsst.daf.butler import DatasetRef
43
- from lsst.pipe.base import QuantumGraph, TaskDef
42
+ from .graph import QuantumGraph
43
+ from .pipeline import TaskDef
44
+ from .quantum_graph import PredictedQuantumGraph
44
45
 
45
46
 
46
- def _datasetRefId(dsRef: DatasetRef) -> str:
47
- """Make a unique identifier string for a dataset ref based on its name and
48
- dataId.
49
- """
50
- dsIdParts = [dsRef.datasetType.name]
51
- dsIdParts.extend(f"{key}_{dsRef.dataId[key]}" for key in sorted(dsRef.dataId.required.keys()))
52
- return "_".join(dsIdParts)
53
-
54
-
55
- def _makeDatasetNode(dsRef: DatasetRef, allDatasetRefs: dict[str, str], file: Any) -> str:
56
- """Create a Mermaid node for a dataset if it doesn't exist, and return its
57
- node ID.
58
- """
59
- dsId = _datasetRefId(dsRef)
60
- nodeName = allDatasetRefs.get(dsId)
61
- if nodeName is None:
62
- nodeName = f"DATASET_{len(allDatasetRefs)}"
63
- allDatasetRefs[dsId] = nodeName
64
- # Simple label: datasetType name and run.
65
- label_lines = [f"**{dsRef.datasetType.name}**", f"run: {dsRef.run}"]
66
- # Add dataId info.
67
- for k in sorted(dsRef.dataId.required.keys()):
68
- label_lines.append(f"{k}={dsRef.dataId[k]}")
69
- label = "<br>".join(label_lines)
70
- print(f'{nodeName}["{label}"]', file=file)
71
- return nodeName
72
-
73
-
74
- def graph2mermaid(qgraph: QuantumGraph, file: Any) -> None:
47
+ def graph2mermaid(qgraph: QuantumGraph | PredictedQuantumGraph, file: Any) -> None:
75
48
  """Convert QuantumGraph into a Mermaid flowchart (top-down).
76
49
 
77
50
  This method is mostly for documentation/presentation purposes.
@@ -91,45 +64,19 @@ def graph2mermaid(qgraph: QuantumGraph, file: Any) -> None:
91
64
  ImportError
92
65
  Raised if the task class cannot be imported.
93
66
  """
67
+ from .quantum_graph import PredictedQuantumGraph, visualization
68
+
69
+ if not isinstance(qgraph, PredictedQuantumGraph):
70
+ qgraph = PredictedQuantumGraph.from_old_quantum_graph(qgraph)
71
+
94
72
  # Open a file if needed.
95
73
  close = False
96
74
  if not hasattr(file, "write"):
97
75
  file = open(file, "w")
98
76
  close = True
99
77
 
100
- # Start Mermaid code block with flowchart.
101
- print("flowchart TD", file=file)
102
-
103
- # To avoid duplicating dataset nodes, we track them.
104
- allDatasetRefs: dict[str, str] = {}
105
-
106
- # Process each task/quantum.
107
- for taskId, taskDef in enumerate(qgraph.taskGraph):
108
- quanta = qgraph.getNodesForTask(taskDef)
109
- for qId, quantumNode in enumerate(quanta):
110
- # Create quantum node.
111
- taskNodeName = f"TASK_{taskId}_{qId}"
112
- taskLabelLines = [f"**{taskDef.label}**", f"Node ID: {quantumNode.nodeId}"]
113
- dataId = quantumNode.quantum.dataId
114
- if dataId is not None:
115
- for k in sorted(dataId.required.keys()):
116
- taskLabelLines.append(f"{k}={dataId[k]}")
117
- else:
118
- raise ValueError("Quantum DataId cannot be None")
119
- taskLabel = "<br>".join(taskLabelLines)
120
- print(f'{taskNodeName}["{taskLabel}"]', file=file)
121
-
122
- # Quantum inputs: datasets --> tasks
123
- for dsRefs in quantumNode.quantum.inputs.values():
124
- for dsRef in dsRefs:
125
- dsNode = _makeDatasetNode(dsRef, allDatasetRefs, file)
126
- print(f"{dsNode} --> {taskNodeName}", file=file)
127
-
128
- # Quantum outputs: tasks --> datasets
129
- for dsRefs in quantumNode.quantum.outputs.values():
130
- for dsRef in dsRefs:
131
- dsNode = _makeDatasetNode(dsRef, allDatasetRefs, file)
132
- print(f"{taskNodeName} --> {dsNode}", file=file)
78
+ v = visualization.QuantumGraphMermaidVisualizer()
79
+ v.write_bipartite(qgraph, file)
133
80
 
134
81
  if close:
135
82
  file.close()