lsst-pipe-base 29.2025.3900__tar.gz → 29.2025.4100__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.4100}/PKG-INFO +2 -1
  2. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/index.rst +4 -0
  3. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/pyproject.toml +2 -0
  4. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/_task_metadata.py +15 -0
  5. lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/dot_tools.py +130 -0
  6. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/exec_fixup_data_id.py +17 -44
  7. lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/execution_graph_fixup.py +100 -0
  8. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/graph/_versionDeserializers.py +6 -5
  9. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/graph/graph.py +30 -10
  10. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/graph/graphSummary.py +30 -0
  11. lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/graph_walker.py +119 -0
  12. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/log_capture.py +5 -2
  13. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/mermaid_tools.py +11 -64
  14. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/mp_graph_executor.py +298 -236
  15. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/io.py +1 -1
  16. lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/quantum_graph/__init__.py +32 -0
  17. lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/quantum_graph/_common.py +632 -0
  18. lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/quantum_graph/_multiblock.py +808 -0
  19. lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/quantum_graph/_predicted.py +1950 -0
  20. lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/quantum_graph/visualization.py +302 -0
  21. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/quantum_graph_builder.py +292 -34
  22. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/quantum_graph_executor.py +2 -1
  23. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/quantum_provenance_graph.py +16 -7
  24. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/quantum_reports.py +45 -0
  25. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/separable_pipeline_executor.py +126 -15
  26. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/simple_pipeline_executor.py +44 -43
  27. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/single_quantum_executor.py +1 -40
  28. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/mocks/__init__.py +1 -1
  29. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/mocks/_pipeline_task.py +16 -1
  30. lsst_pipe_base-29.2025.3900/python/lsst/pipe/base/tests/mocks/_in_memory_repo.py → lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/tests/mocks/_repo.py +324 -45
  31. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/mocks/_storage_class.py +51 -0
  32. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/simpleQGraph.py +11 -5
  33. lsst_pipe_base-29.2025.4100/python/lsst/pipe/base/version.py +2 -0
  34. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100/python/lsst_pipe_base.egg-info}/PKG-INFO +2 -1
  35. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst_pipe_base.egg-info/SOURCES.txt +9 -1
  36. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst_pipe_base.egg-info/requires.txt +1 -0
  37. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_adjust_all_quanta.py +5 -2
  38. lsst_pipe_base-29.2025.4100/tests/test_graph_walker.py +142 -0
  39. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_init_output_run.py +15 -19
  40. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_instrument.py +5 -11
  41. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_mp_graph_executor.py +51 -30
  42. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_pipeline_graph.py +5 -0
  43. lsst_pipe_base-29.2025.4100/tests/test_predicted_qg.py +607 -0
  44. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_qg_builder_dimensions.py +20 -20
  45. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_separable_pipeline_executor.py +465 -25
  46. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_single_quantum_executor.py +0 -2
  47. lsst_pipe_base-29.2025.3900/python/lsst/pipe/base/dot_tools.py +0 -268
  48. lsst_pipe_base-29.2025.3900/python/lsst/pipe/base/execution_graph_fixup.py +0 -69
  49. lsst_pipe_base-29.2025.3900/python/lsst/pipe/base/version.py +0 -2
  50. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/COPYRIGHT +0 -0
  51. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/LICENSE +0 -0
  52. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/MANIFEST.in +0 -0
  53. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/README.md +0 -0
  54. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/bsd_license.txt +0 -0
  55. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/CHANGES.rst +0 -0
  56. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/creating-a-pipeline.rst +0 -0
  57. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/creating-a-pipelinetask.rst +0 -0
  58. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/creating-a-task.rst +0 -0
  59. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/task-framework-overview.rst +0 -0
  60. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/task-retargeting-howto.rst +0 -0
  61. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/testing-a-pipeline-task.rst +0 -0
  62. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/testing-pipelines-with-mocks.rst +0 -0
  63. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/doc/lsst.pipe.base/working-with-pipeline-graphs.rst +0 -0
  64. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/gpl-v3.0.txt +0 -0
  65. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/__init__.py +0 -0
  66. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/__init__.py +0 -0
  67. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/__init__.py +0 -0
  68. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/_datasetQueryConstraints.py +0 -0
  69. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/_dataset_handle.py +0 -0
  70. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/_instrument.py +0 -0
  71. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/_observation_dimension_packer.py +0 -0
  72. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/_quantumContext.py +0 -0
  73. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/_status.py +0 -0
  74. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/all_dimensions_quantum_graph_builder.py +0 -0
  75. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/automatic_connection_constants.py +0 -0
  76. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/caching_limited_butler.py +0 -0
  77. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/cli/__init__.py +0 -0
  78. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/cli/_get_cli_subcommands.py +0 -0
  79. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/cli/cmd/__init__.py +0 -0
  80. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/cli/cmd/commands.py +0 -0
  81. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/cli/opt/__init__.py +0 -0
  82. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/cli/opt/arguments.py +0 -0
  83. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/cli/opt/options.py +0 -0
  84. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/config.py +0 -0
  85. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/configOverrides.py +0 -0
  86. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/connectionTypes.py +0 -0
  87. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/connections.py +0 -0
  88. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/execution_reports.py +0 -0
  89. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/formatters/__init__.py +0 -0
  90. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/formatters/pexConfig.py +0 -0
  91. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/graph/__init__.py +0 -0
  92. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/graph/_implDetails.py +0 -0
  93. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/graph/_loadHelpers.py +0 -0
  94. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/graph/quantumNode.py +0 -0
  95. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline.py +0 -0
  96. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipelineIR.py +0 -0
  97. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipelineTask.py +0 -0
  98. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/__init__.py +0 -0
  99. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/__main__.py +0 -0
  100. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/_dataset_types.py +0 -0
  101. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/_edges.py +0 -0
  102. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/_exceptions.py +0 -0
  103. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/_mapping_views.py +0 -0
  104. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/_nodes.py +0 -0
  105. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/_pipeline_graph.py +0 -0
  106. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/_task_subsets.py +0 -0
  107. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/_tasks.py +0 -0
  108. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/expressions.py +0 -0
  109. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/__init__.py +0 -0
  110. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/_dot.py +0 -0
  111. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/_formatting.py +0 -0
  112. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/_layout.py +0 -0
  113. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/_merge.py +0 -0
  114. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/_mermaid.py +0 -0
  115. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/_options.py +0 -0
  116. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/_printer.py +0 -0
  117. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/_show.py +0 -0
  118. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/pipeline_graph/visualization/_status_annotator.py +0 -0
  119. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/prerequisite_helpers.py +0 -0
  120. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/py.typed +0 -0
  121. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/quantum_graph_skeleton.py +0 -0
  122. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/script/__init__.py +0 -0
  123. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/script/register_instrument.py +0 -0
  124. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/script/retrieve_artifacts_for_quanta.py +0 -0
  125. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/script/transfer_from_graph.py +0 -0
  126. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/script/utils.py +0 -0
  127. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/script/zip_from_graph.py +0 -0
  128. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/struct.py +0 -0
  129. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/task.py +0 -0
  130. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/taskFactory.py +0 -0
  131. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/testUtils.py +0 -0
  132. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/__init__.py +0 -0
  133. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/in_memory_limited_butler.py +0 -0
  134. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/mocks/_data_id_match.py +0 -0
  135. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/no_dimensions.py +0 -0
  136. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/pipelineStepTester.py +0 -0
  137. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/tests/util.py +0 -0
  138. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst/pipe/base/utils.py +0 -0
  139. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst_pipe_base.egg-info/dependency_links.txt +0 -0
  140. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst_pipe_base.egg-info/entry_points.txt +0 -0
  141. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst_pipe_base.egg-info/top_level.txt +0 -0
  142. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/python/lsst_pipe_base.egg-info/zip-safe +0 -0
  143. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/setup.cfg +0 -0
  144. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_caching_limited_butler.py +0 -0
  145. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_cliCmdRegisterInstrument.py +0 -0
  146. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_configOverrides.py +0 -0
  147. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_config_formatter.py +0 -0
  148. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_connections.py +0 -0
  149. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_dataid_match.py +0 -0
  150. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_dataset_handle.py +0 -0
  151. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_dot_tools.py +0 -0
  152. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_dynamic_connections.py +0 -0
  153. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_execution_reports.py +0 -0
  154. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_execution_storage_class_conversion.py +0 -0
  155. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_graphBuilder.py +0 -0
  156. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_mermaid.py +0 -0
  157. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_pipeline.py +0 -0
  158. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_pipelineIR.py +0 -0
  159. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_pipelineLoadSubset.py +0 -0
  160. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_pipelineTask.py +0 -0
  161. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_pipeline_graph_expressions.py +0 -0
  162. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_quantumGraph.py +0 -0
  163. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_quantum_provenance_graph.py +0 -0
  164. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_quantum_reports.py +0 -0
  165. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_quantum_success_caveats.py +0 -0
  166. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_script_utils.py +0 -0
  167. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_simple_pipeline_executor.py +0 -0
  168. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_struct.py +0 -0
  169. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_task.py +0 -0
  170. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_task_factory.py +0 -0
  171. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_taskmetadata.py +0 -0
  172. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/tests/test_testUtils.py +0 -0
  173. {lsst_pipe_base-29.2025.3900 → lsst_pipe_base-29.2025.4100}/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.4100
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]
@@ -686,6 +686,21 @@ class TaskMetadata(BaseModel):
686
686
  """See `pydantic.BaseModel.model_json_schema`."""
687
687
  return super().model_json_schema(*args, **kwargs)
688
688
 
689
+ @classmethod
690
+ def model_validate(cls, *args: Any, **kwargs: Any) -> Any:
691
+ """See `pydantic.BaseModel.model_validate`."""
692
+ return super().model_validate(*args, **kwargs)
693
+
694
+ @classmethod
695
+ def model_validate_json(cls, *args: Any, **kwargs: Any) -> Any:
696
+ """See `pydantic.BaseModel.model_validate_json`."""
697
+ return super().model_validate_json(*args, **kwargs)
698
+
699
+ @classmethod
700
+ def model_validate_strings(cls, *args: Any, **kwargs: Any) -> Any:
701
+ """See `pydantic.BaseModel.model_validate_strings`."""
702
+ return super().model_validate_strings(*args, **kwargs)
703
+
689
704
 
690
705
  # Needed because a TaskMetadata can contain a TaskMetadata.
691
706
  TaskMetadata.model_rebuild()
@@ -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()
@@ -38,7 +38,7 @@ from collections import defaultdict
38
38
  from collections.abc import Callable
39
39
  from dataclasses import dataclass
40
40
  from types import SimpleNamespace
41
- from typing import TYPE_CHECKING, ClassVar, cast
41
+ from typing import TYPE_CHECKING, ClassVar
42
42
 
43
43
  import networkx as nx
44
44
 
@@ -50,6 +50,7 @@ from lsst.daf.butler import (
50
50
  Quantum,
51
51
  SerializedDimensionRecord,
52
52
  )
53
+ from lsst.daf.butler._rubin import generate_uuidv7
53
54
  from lsst.utils import doImportType
54
55
 
55
56
  from ..config import PipelineTaskConfig
@@ -242,7 +243,7 @@ class DeserializerV1(DeserializerBase):
242
243
 
243
244
  # reconstruct node
244
245
  qNode = pickle.loads(dump)
245
- object.__setattr__(qNode, "nodeId", uuid.uuid4())
246
+ object.__setattr__(qNode, "nodeId", generate_uuidv7())
246
247
 
247
248
  # read the saved node, name. If it has been loaded, attach it, if
248
249
  # not read in the taskDef first, and then load it
@@ -376,7 +377,7 @@ class DeserializerV2(DeserializerBase):
376
377
 
377
378
  # reconstruct node
378
379
  qNode = pickle.loads(dump)
379
- object.__setattr__(qNode, "nodeId", uuid.uuid4())
380
+ object.__setattr__(qNode, "nodeId", generate_uuidv7())
380
381
 
381
382
  # read the saved node, name. If it has been loaded, attach it, if
382
383
  # not read in the taskDef first, and then load it
@@ -599,11 +600,11 @@ class DeserializerV3(DeserializerBase):
599
600
  # initInputRefs and initOutputRefs are optional
600
601
  if (refs := taskDefDump.get("initInputRefs")) is not None:
601
602
  initInputRefs[recreatedTaskDef.label] = [
602
- cast(DatasetRef, DatasetRef.from_json(ref, universe=universe)) for ref in refs
603
+ DatasetRef.from_json(ref, universe=universe) for ref in refs
603
604
  ]
604
605
  if (refs := taskDefDump.get("initOutputRefs")) is not None:
605
606
  initOutputRefs[recreatedTaskDef.label] = [
606
- cast(DatasetRef, DatasetRef.from_json(ref, universe=universe)) for ref in refs
607
+ DatasetRef.from_json(ref, universe=universe) for ref in refs
607
608
  ]
608
609
 
609
610
  # rebuild the mappings that associate dataset type names with
@@ -59,6 +59,7 @@ from lsst.daf.butler import (
59
59
  Quantum,
60
60
  QuantumBackedButler,
61
61
  )
62
+ from lsst.daf.butler._rubin import generate_uuidv7
62
63
  from lsst.daf.butler.datastore.record_data import DatastoreRecordData
63
64
  from lsst.daf.butler.persistence_context import PersistenceContextVars
64
65
  from lsst.daf.butler.registry import ConflictingDefinitionError
@@ -246,7 +247,7 @@ class QuantumGraph:
246
247
  "associated value in the mapping"
247
248
  )
248
249
  else:
249
- nodeId = uuid.uuid4()
250
+ nodeId = generate_uuidv7()
250
251
 
251
252
  inits = quantum.initInputs.values()
252
253
  inputs = quantum.inputs.values()
@@ -806,11 +807,18 @@ class QuantumGraph:
806
807
  uri : convertible to `~lsst.resources.ResourcePath`
807
808
  URI to where the graph should be saved.
808
809
  """
809
- buffer = self._buildSaveObject()
810
810
  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
811
+ match path.getExtension():
812
+ case ".qgraph":
813
+ buffer = self._buildSaveObject()
814
+ path.write(buffer) # type: ignore # Ignore because bytearray is safe to use in place of bytes
815
+ case ".qg":
816
+ from ..quantum_graph import PredictedQuantumGraphComponents
817
+
818
+ pqg = PredictedQuantumGraphComponents.from_old_quantum_graph(self)
819
+ pqg.write(path)
820
+ case ext:
821
+ raise TypeError(f"Can currently only save a graph in .qgraph or .qg format, not {ext!r}.")
814
822
 
815
823
  @property
816
824
  def metadata(self) -> MappingProxyType[str, Any]:
@@ -962,11 +970,23 @@ class QuantumGraph:
962
970
  the graph.
963
971
  """
964
972
  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}")
973
+ match uri.getExtension():
974
+ case ".qgraph":
975
+ with LoadHelper(uri, minimumVersion, fullRead=(nodes is None)) as loader:
976
+ qgraph = loader.load(universe, nodes, graphID)
977
+ case ".qg":
978
+ from ..quantum_graph import PredictedQuantumGraphReader
979
+
980
+ with PredictedQuantumGraphReader.open(uri, page_size=100000) as qgr:
981
+ quantum_ids = (
982
+ [uuid.UUID(q) if not isinstance(q, uuid.UUID) else q for q in nodes]
983
+ if nodes is not None
984
+ else None
985
+ )
986
+ qgr.read_execution_quanta(quantum_ids)
987
+ qgraph = qgr.finish().to_old_quantum_graph()
988
+ case _:
989
+ raise ValueError(f"Only know how to handle files saved as `.qgraph`, not {uri}")
970
990
  if not isinstance(qgraph, QuantumGraph):
971
991
  raise TypeError(f"QuantumGraph file {uri} contains unexpected object type: {type(qgraph)}")
972
992
  return qgraph
@@ -75,6 +75,21 @@ class QgraphTaskSummary(pydantic.BaseModel):
75
75
  """See `pydantic.BaseModel.model_json_schema`."""
76
76
  return super().model_json_schema(*args, **kwargs)
77
77
 
78
+ @classmethod
79
+ def model_validate(cls, *args: Any, **kwargs: Any) -> Any:
80
+ """See `pydantic.BaseModel.model_validate`."""
81
+ return super().model_validate(*args, **kwargs)
82
+
83
+ @classmethod
84
+ def model_validate_json(cls, *args: Any, **kwargs: Any) -> Any:
85
+ """See `pydantic.BaseModel.model_validate_json`."""
86
+ return super().model_validate_json(*args, **kwargs)
87
+
88
+ @classmethod
89
+ def model_validate_strings(cls, *args: Any, **kwargs: Any) -> Any:
90
+ """See `pydantic.BaseModel.model_validate_strings`."""
91
+ return super().model_validate_strings(*args, **kwargs)
92
+
78
93
 
79
94
  class QgraphSummary(pydantic.BaseModel):
80
95
  """Report for the QuantumGraph creation or reading."""
@@ -129,3 +144,18 @@ class QgraphSummary(pydantic.BaseModel):
129
144
  def model_json_schema(cls, *args: Any, **kwargs: Any) -> Any:
130
145
  """See `pydantic.BaseModel.model_json_schema`."""
131
146
  return super().model_json_schema(*args, **kwargs)
147
+
148
+ @classmethod
149
+ def model_validate(cls, *args: Any, **kwargs: Any) -> Any:
150
+ """See `pydantic.BaseModel.model_validate`."""
151
+ return super().model_validate(*args, **kwargs)
152
+
153
+ @classmethod
154
+ def model_validate_json(cls, *args: Any, **kwargs: Any) -> Any:
155
+ """See `pydantic.BaseModel.model_validate_json`."""
156
+ return super().model_validate_json(*args, **kwargs)
157
+
158
+ @classmethod
159
+ def model_validate_strings(cls, *args: Any, **kwargs: Any) -> Any:
160
+ """See `pydantic.BaseModel.model_validate_strings`."""
161
+ return super().model_validate_strings(*args, **kwargs)
@@ -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