lsst-pipe-base 29.2025.3000__tar.gz → 29.2025.3100__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.
- {lsst_pipe_base-29.2025.3000/python/lsst_pipe_base.egg-info → lsst_pipe_base-29.2025.3100}/PKG-INFO +1 -1
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/index.rst +15 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/_datasetQueryConstraints.py +1 -1
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/all_dimensions_quantum_graph_builder.py +6 -4
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/connectionTypes.py +19 -19
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/connections.py +2 -2
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/exec_fixup_data_id.py +131 -0
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/execution_graph_fixup.py +69 -0
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/log_capture.py +227 -0
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/mp_graph_executor.py +774 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/quantum_graph_builder.py +43 -42
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/quantum_graph_executor.py +125 -0
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/quantum_reports.py +334 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/script/transfer_from_graph.py +4 -1
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/separable_pipeline_executor.py +296 -0
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/simple_pipeline_executor.py +674 -0
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/single_quantum_executor.py +636 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/taskFactory.py +18 -12
- lsst_pipe_base-29.2025.3100/python/lsst/pipe/base/version.py +2 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100/python/lsst_pipe_base.egg-info}/PKG-INFO +1 -1
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst_pipe_base.egg-info/SOURCES.txt +15 -0
- lsst_pipe_base-29.2025.3100/tests/test_execution_storage_class_conversion.py +332 -0
- lsst_pipe_base-29.2025.3100/tests/test_executors.py +808 -0
- lsst_pipe_base-29.2025.3100/tests/test_quantum_reports.py +133 -0
- lsst_pipe_base-29.2025.3100/tests/test_separable_pipeline_executor.py +630 -0
- lsst_pipe_base-29.2025.3100/tests/test_simple_pipeline_executor.py +555 -0
- lsst_pipe_base-29.2025.3100/tests/test_task_factory.py +139 -0
- lsst_pipe_base-29.2025.3000/python/lsst/pipe/base/version.py +0 -2
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/COPYRIGHT +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/LICENSE +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/MANIFEST.in +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/README.md +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/bsd_license.txt +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/CHANGES.rst +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/creating-a-pipeline.rst +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/creating-a-pipelinetask.rst +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/creating-a-task.rst +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/task-framework-overview.rst +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/task-retargeting-howto.rst +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/testing-a-pipeline-task.rst +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/testing-pipelines-with-mocks.rst +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/doc/lsst.pipe.base/working-with-pipeline-graphs.rst +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/gpl-v3.0.txt +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/pyproject.toml +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/_dataset_handle.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/_instrument.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/_observation_dimension_packer.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/_quantumContext.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/_status.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/_task_metadata.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/automatic_connection_constants.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/caching_limited_butler.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/cli/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/cli/_get_cli_subcommands.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/cli/cmd/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/cli/cmd/commands.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/cli/opt/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/cli/opt/arguments.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/cli/opt/options.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/config.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/configOverrides.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/dot_tools.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/executionButlerBuilder.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/execution_reports.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/formatters/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/formatters/pexConfig.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/graph/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/graph/_implDetails.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/graph/_loadHelpers.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/graph/_versionDeserializers.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/graph/graph.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/graph/graphSummary.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/graph/quantumNode.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/mermaid_tools.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipelineIR.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipelineTask.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/__main__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/_dataset_types.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/_edges.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/_exceptions.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/_mapping_views.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/_nodes.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/_pipeline_graph.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/_task_subsets.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/_tasks.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/expressions.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/io.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/_dot.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/_formatting.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/_layout.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/_merge.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/_mermaid.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/_options.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/_printer.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/_show.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/pipeline_graph/visualization/_status_annotator.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/prerequisite_helpers.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/py.typed +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/quantum_graph_skeleton.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/quantum_provenance_graph.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/script/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/script/register_instrument.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/script/retrieve_artifacts_for_quanta.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/script/utils.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/script/zip_from_graph.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/struct.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/task.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/testUtils.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/tests/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/tests/mocks/__init__.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/tests/mocks/_data_id_match.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/tests/mocks/_pipeline_task.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/tests/mocks/_storage_class.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/tests/no_dimensions.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/tests/pipelineStepTester.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/tests/simpleQGraph.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/tests/util.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/utils.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst_pipe_base.egg-info/dependency_links.txt +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst_pipe_base.egg-info/entry_points.txt +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst_pipe_base.egg-info/requires.txt +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst_pipe_base.egg-info/top_level.txt +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst_pipe_base.egg-info/zip-safe +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/setup.cfg +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_adjust_all_quanta.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_caching_limited_butler.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_cliCmdRegisterInstrument.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_configOverrides.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_config_formatter.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_connections.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_dataid_match.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_dataset_handle.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_dot_tools.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_dynamic_connections.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_executionButler.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_execution_reports.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_graphBuilder.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_init_output_run.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_instrument.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_mermaid.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_pipeline.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_pipelineIR.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_pipelineLoadSubset.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_pipelineTask.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_pipeline_graph.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_pipeline_graph_expressions.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_qg_builder_dimensions.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_quantumGraph.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_quantum_provenance_graph.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_quantum_success_caveats.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_script_utils.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_struct.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_task.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_taskmetadata.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_testUtils.py +0 -0
- {lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/tests/test_utils.py +0 -0
{lsst_pipe_base-29.2025.3000/python/lsst_pipe_base.egg-info → lsst_pipe_base-29.2025.3100}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-pipe-base
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.3100
|
|
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
|
|
@@ -96,6 +96,10 @@ Python API reference
|
|
|
96
96
|
.. automodapi:: lsst.pipe.base.execution_reports
|
|
97
97
|
:no-main-docstr:
|
|
98
98
|
|
|
99
|
+
.. automodapi:: lsst.pipe.base.simple_pipeline_executor
|
|
100
|
+
|
|
101
|
+
.. automodapi:: lsst.pipe.base.separable_pipeline_executor
|
|
102
|
+
|
|
99
103
|
QuantumGraph generation API reference
|
|
100
104
|
-------------------------------------
|
|
101
105
|
|
|
@@ -105,3 +109,14 @@ QuantumGraph generation API reference
|
|
|
105
109
|
.. automodapi:: lsst.pipe.base.all_dimensions_quantum_graph_builder
|
|
106
110
|
:skip: DatasetQueryConstraintVariant
|
|
107
111
|
.. automodapi:: lsst.pipe.base._datasetQueryConstraints
|
|
112
|
+
|
|
113
|
+
Execution system API reference
|
|
114
|
+
------------------------------
|
|
115
|
+
|
|
116
|
+
.. automodapi:: lsst.pipe.base.exec_fixup_data_id
|
|
117
|
+
.. automodapi:: lsst.pipe.base.execution_graph_fixup
|
|
118
|
+
.. automodapi:: lsst.pipe.base.log_capture
|
|
119
|
+
.. automodapi:: lsst.pipe.base.mp_graph_executor
|
|
120
|
+
.. automodapi:: lsst.pipe.base.quantum_graph_executor
|
|
121
|
+
.. automodapi:: lsst.pipe.base.quantum_reports
|
|
122
|
+
.. automodapi:: lsst.pipe.base.single_quantum_executor
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
27
27
|
|
|
28
28
|
"""Symbols defined in this package should be imported from
|
|
29
|
-
|
|
29
|
+
`.all_dimensions_quantum_graph_builder` instead; it only appears in the docs
|
|
30
30
|
due to limitations in Sphinx.
|
|
31
31
|
"""
|
|
32
32
|
|
|
@@ -65,13 +65,14 @@ if TYPE_CHECKING:
|
|
|
65
65
|
|
|
66
66
|
@final
|
|
67
67
|
class AllDimensionsQuantumGraphBuilder(QuantumGraphBuilder):
|
|
68
|
-
"""An implementation of
|
|
69
|
-
query for data IDs covering all dimensions in the
|
|
68
|
+
"""An implementation of `.quantum_graph_builder.QuantumGraphBuilder` that
|
|
69
|
+
uses a single large query for data IDs covering all dimensions in the
|
|
70
|
+
pipeline.
|
|
70
71
|
|
|
71
72
|
Parameters
|
|
72
73
|
----------
|
|
73
74
|
pipeline_graph : `.pipeline_graph.PipelineGraph`
|
|
74
|
-
Pipeline to build a
|
|
75
|
+
Pipeline to build a `.QuantumGraph` from, as a graph. Will be resolved
|
|
75
76
|
in-place with the given butler (any existing resolution is ignored).
|
|
76
77
|
butler : `lsst.daf.butler.Butler`
|
|
77
78
|
Client for the data repository. Should be read-only.
|
|
@@ -92,7 +93,8 @@ class AllDimensionsQuantumGraphBuilder(QuantumGraphBuilder):
|
|
|
92
93
|
are constrained by the ``where`` argument or pipeline data ID will be
|
|
93
94
|
filled in automatically.
|
|
94
95
|
**kwargs
|
|
95
|
-
Additional keyword arguments forwarded to
|
|
96
|
+
Additional keyword arguments forwarded to
|
|
97
|
+
`.quantum_graph_builder.QuantumGraphBuilder`.
|
|
96
98
|
|
|
97
99
|
Notes
|
|
98
100
|
-----
|
{lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/connectionTypes.py
RENAMED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
27
27
|
|
|
28
28
|
"""Module defining connection types to be used within a
|
|
29
|
-
|
|
29
|
+
`.PipelineTaskConnections` class.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
__all__ = ["BaseConnection", "InitInput", "InitOutput", "Input", "Output", "PrerequisiteInput"]
|
|
@@ -53,7 +53,7 @@ class BaseConnection:
|
|
|
53
53
|
Indicates if this connection should expect to contain multiple objects
|
|
54
54
|
of the given dataset type. Tasks with more than one connection with
|
|
55
55
|
``multiple=True`` with the same dimensions may want to implement
|
|
56
|
-
|
|
56
|
+
`.PipelineTaskConnections.adjustQuantum` to ensure those datasets are
|
|
57
57
|
consistent (i.e. zip-iterable) in `PipelineTask.runQuantum()` and
|
|
58
58
|
notify the execution system as early as possible of outputs that will
|
|
59
59
|
not be produced because the corresponding input is missing.
|
|
@@ -121,7 +121,7 @@ class DimensionedConnection(BaseConnection):
|
|
|
121
121
|
Indicates if this connection should expect to contain multiple objects
|
|
122
122
|
of the given dataset type. Tasks with more than one connection with
|
|
123
123
|
``multiple=True`` with the same dimensions may want to implement
|
|
124
|
-
|
|
124
|
+
`.PipelineTaskConnections.adjustQuantum` to ensure those datasets are
|
|
125
125
|
consistent (i.e. zip-iterable) in `PipelineTask.runQuantum` and notify
|
|
126
126
|
the execution system as early as possible of outputs that will not be
|
|
127
127
|
produced because the corresponding input is missing.
|
|
@@ -161,7 +161,7 @@ class BaseInput(DimensionedConnection):
|
|
|
161
161
|
Indicates if this connection should expect to contain multiple objects
|
|
162
162
|
of the given dataset type. Tasks with more than one connection with
|
|
163
163
|
``multiple=True`` with the same dimensions may want to implement
|
|
164
|
-
|
|
164
|
+
`.PipelineTaskConnections.adjustQuantum` to ensure those datasets are
|
|
165
165
|
consistent (i.e. zip-iterable) in `PipelineTask.runQuantum` and notify
|
|
166
166
|
the execution system as early as possible of outputs that will not be
|
|
167
167
|
produced because the corresponding input is missing.
|
|
@@ -175,14 +175,14 @@ class BaseInput(DimensionedConnection):
|
|
|
175
175
|
minimum : `bool`
|
|
176
176
|
Minimum number of datasets required for this connection, per quantum.
|
|
177
177
|
This is checked in the base implementation of
|
|
178
|
-
|
|
178
|
+
`.PipelineTaskConnections.adjustQuantum`, which raises `NoWorkFound` if
|
|
179
179
|
the minimum is not met for `Input` connections (causing the quantum to
|
|
180
180
|
be pruned, skipped, or never created, depending on the context), and
|
|
181
181
|
`FileNotFoundError` for `PrerequisiteInput` connections (causing
|
|
182
182
|
QuantumGraph generation to fail). `PipelineTask` implementations may
|
|
183
|
-
provide custom
|
|
184
|
-
for more fine-grained or configuration-driven
|
|
185
|
-
they are compatible with this minium.
|
|
183
|
+
provide custom `~.PipelineTaskConnections.adjustQuantum`
|
|
184
|
+
implementations for more fine-grained or configuration-driven
|
|
185
|
+
constraints, as long as they are compatible with this minium.
|
|
186
186
|
|
|
187
187
|
Raises
|
|
188
188
|
------
|
|
@@ -216,7 +216,7 @@ class Input(BaseInput):
|
|
|
216
216
|
Indicates if this connection should expect to contain multiple objects
|
|
217
217
|
of the given dataset type. Tasks with more than one connection with
|
|
218
218
|
``multiple=True`` with the same dimensions may want to implement
|
|
219
|
-
|
|
219
|
+
`.PipelineTaskConnections.adjustQuantum` to ensure those datasets are
|
|
220
220
|
consistent (i.e. zip-iterable) in `PipelineTask.runQuantum` and notify
|
|
221
221
|
the execution system as early as possible of outputs that will not be
|
|
222
222
|
produced because the corresponding input is missing.
|
|
@@ -230,14 +230,14 @@ class Input(BaseInput):
|
|
|
230
230
|
minimum : `bool`
|
|
231
231
|
Minimum number of datasets required for this connection, per quantum.
|
|
232
232
|
This is checked in the base implementation of
|
|
233
|
-
|
|
233
|
+
`.PipelineTaskConnections.adjustQuantum`, which raises `NoWorkFound` if
|
|
234
234
|
the minimum is not met for `Input` connections (causing the quantum to
|
|
235
235
|
be pruned, skipped, or never created, depending on the context), and
|
|
236
236
|
`FileNotFoundError` for `PrerequisiteInput` connections (causing
|
|
237
237
|
QuantumGraph generation to fail). `PipelineTask` implementations may
|
|
238
|
-
provide custom
|
|
239
|
-
for more fine-grained or configuration-driven
|
|
240
|
-
they are compatible with this minium.
|
|
238
|
+
provide custom `~.PipelineTaskConnections.adjustQuantum`
|
|
239
|
+
implementations for more fine-grained or configuration-driven
|
|
240
|
+
constraints, as long as they are compatible with this minium.
|
|
241
241
|
deferGraphConstraint : `bool`, optional
|
|
242
242
|
If `True`, do not include this dataset type's existence in the initial
|
|
243
243
|
query that starts the QuantumGraph generation process. This can be
|
|
@@ -286,7 +286,7 @@ class PrerequisiteInput(BaseInput):
|
|
|
286
286
|
Indicates if this connection should expect to contain multiple objects
|
|
287
287
|
of the given dataset type. Tasks with more than one connection with
|
|
288
288
|
``multiple=True`` with the same dimensions may want to implement
|
|
289
|
-
|
|
289
|
+
`.PipelineTaskConnections.adjustQuantum` to ensure those datasets are
|
|
290
290
|
consistent (i.e. zip-iterable) in `PipelineTask.runQuantum` and notify
|
|
291
291
|
the execution system as early as possible of outputs that will not be
|
|
292
292
|
produced because the corresponding input is missing.
|
|
@@ -296,12 +296,12 @@ class PrerequisiteInput(BaseInput):
|
|
|
296
296
|
minimum : `bool`
|
|
297
297
|
Minimum number of datasets required for this connection, per quantum.
|
|
298
298
|
This is checked in the base implementation of
|
|
299
|
-
|
|
299
|
+
`.PipelineTaskConnections.adjustQuantum`, which raises
|
|
300
300
|
`FileNotFoundError` (causing QuantumGraph generation to fail).
|
|
301
|
-
`PipelineTask` implementations may
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
`PipelineTask` implementations may provide custom
|
|
302
|
+
`~.PipelineTaskConnections.adjustQuantum` implementations for more
|
|
303
|
+
fine-grained or configuration-driven constraints, as long as they are
|
|
304
|
+
compatible with this minium.
|
|
305
305
|
lookupFunction : `typing.Callable`, optional
|
|
306
306
|
An optional callable function that will look up PrerequisiteInputs
|
|
307
307
|
using the DatasetType, registry, quantum dataId, and input collections
|
{lsst_pipe_base-29.2025.3000 → lsst_pipe_base-29.2025.3100}/python/lsst/pipe/base/connections.py
RENAMED
|
@@ -1063,8 +1063,8 @@ def iterConnections(
|
|
|
1063
1063
|
class AdjustQuantumHelper:
|
|
1064
1064
|
"""Helper class for calling `PipelineTaskConnections.adjustQuantum`.
|
|
1065
1065
|
|
|
1066
|
-
This class holds `
|
|
1067
|
-
`Quantum` and execution harness code, i.e. with
|
|
1066
|
+
This class holds `inputs` and `outputs` mappings in the form used by
|
|
1067
|
+
`lsst.daf.butler.Quantum` and execution harness code, i.e. with
|
|
1068
1068
|
`~lsst.daf.butler.DatasetType` keys, translating them to and from the
|
|
1069
1069
|
connection-oriented mappings used inside `PipelineTaskConnections`.
|
|
1070
1070
|
"""
|
|
@@ -0,0 +1,131 @@
|
|
|
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 contextlib
|
|
31
|
+
import itertools
|
|
32
|
+
from collections import defaultdict
|
|
33
|
+
from collections.abc import Sequence
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
import networkx as nx
|
|
37
|
+
|
|
38
|
+
from .execution_graph_fixup import ExecutionGraphFixup
|
|
39
|
+
from .graph import QuantumGraph, QuantumNode
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ExecFixupDataId(ExecutionGraphFixup):
|
|
43
|
+
"""Implementation of ExecutionGraphFixup for ordering of tasks based
|
|
44
|
+
on DataId values.
|
|
45
|
+
|
|
46
|
+
This class is a trivial implementation mostly useful as an example,
|
|
47
|
+
though it can be used to make actual fixup instances by defining
|
|
48
|
+
a method that instantiates it, e.g.::
|
|
49
|
+
|
|
50
|
+
# lsst/ap/verify/ci_fixup.py
|
|
51
|
+
|
|
52
|
+
from lsst.pipe.base.exec_fixup_data_id import ExecFixupDataId
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def assoc_fixup():
|
|
56
|
+
return ExecFixupDataId(
|
|
57
|
+
taskLabel="ap_assoc", dimensions=("visit", "detector")
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
and then executing pipetask::
|
|
61
|
+
|
|
62
|
+
pipetask run --graph-fixup=lsst.ap.verify.ci_fixup.assoc_fixup ...
|
|
63
|
+
|
|
64
|
+
This will add new dependencies between quanta executed by the task with
|
|
65
|
+
label "ap_assoc". Quanta with higher visit number will depend on quanta
|
|
66
|
+
with lower visit number and their execution will wait until lower visit
|
|
67
|
+
number finishes.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
taskLabel : `str`
|
|
72
|
+
The label of the task for which to add dependencies.
|
|
73
|
+
dimensions : `str` or sequence [`str`]
|
|
74
|
+
One or more dimension names, quanta execution will be ordered
|
|
75
|
+
according to values of these dimensions.
|
|
76
|
+
reverse : `bool`, optional
|
|
77
|
+
If `False` (default) then quanta with higher values of dimensions
|
|
78
|
+
will be executed after quanta with lower values, otherwise the order
|
|
79
|
+
is reversed.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, taskLabel: str, dimensions: str | Sequence[str], reverse: bool = False):
|
|
83
|
+
self.taskLabel = taskLabel
|
|
84
|
+
self.dimensions = dimensions
|
|
85
|
+
self.reverse = reverse
|
|
86
|
+
if isinstance(self.dimensions, str):
|
|
87
|
+
self.dimensions = (self.dimensions,)
|
|
88
|
+
else:
|
|
89
|
+
self.dimensions = tuple(self.dimensions)
|
|
90
|
+
|
|
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
|
+
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
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
from abc import ABC, abstractmethod
|
|
31
|
+
|
|
32
|
+
from .graph import QuantumGraph
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ExecutionGraphFixup(ABC):
|
|
36
|
+
"""Interface for classes which update quantum graphs before execution.
|
|
37
|
+
|
|
38
|
+
Primary goal of this class is to modify quanta dependencies which may not
|
|
39
|
+
be possible to reflect in a quantum graph using standard tools. One known
|
|
40
|
+
use case for that is to guarantee particular execution order of visits in
|
|
41
|
+
CI jobs for cases when outcome depends on the processing order of visits
|
|
42
|
+
(e.g. AP association pipeline).
|
|
43
|
+
|
|
44
|
+
Instances of this class receive pre-ordered sequence of quanta
|
|
45
|
+
(`.QuantumGraph` instances) and they are allowed to modify quanta data in
|
|
46
|
+
place, for example update ``dependencies`` field to add additional
|
|
47
|
+
dependencies. Returned list of quanta will be re-ordered once again by the
|
|
48
|
+
graph executor to reflect new dependencies.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def fixupQuanta(self, graph: QuantumGraph) -> QuantumGraph:
|
|
53
|
+
"""Update quanta in a graph.
|
|
54
|
+
|
|
55
|
+
Potentially anything in the graph could be changed if it does not
|
|
56
|
+
break executor assumptions. If modifications result in a dependency
|
|
57
|
+
cycle the executor will raise an exception.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
graph : `.QuantumGraph`
|
|
62
|
+
Quantum Graph that will be executed by the executor.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
graph : `.QuantumGraph`
|
|
67
|
+
Modified graph.
|
|
68
|
+
"""
|
|
69
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,227 @@
|
|
|
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__ = ["LogCapture"]
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
import os
|
|
34
|
+
import shutil
|
|
35
|
+
import tempfile
|
|
36
|
+
from collections.abc import Iterator
|
|
37
|
+
from contextlib import contextmanager, suppress
|
|
38
|
+
from logging import FileHandler
|
|
39
|
+
|
|
40
|
+
from lsst.daf.butler import Butler, FileDataset, LimitedButler, Quantum
|
|
41
|
+
from lsst.daf.butler.logging import ButlerLogRecordHandler, ButlerLogRecords, ButlerMDC, JsonLogFormatter
|
|
42
|
+
|
|
43
|
+
from ._status import InvalidQuantumError
|
|
44
|
+
from .pipeline_graph import TaskNode
|
|
45
|
+
|
|
46
|
+
_LOG = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _LogCaptureFlag:
|
|
50
|
+
"""Simple flag to enable/disable log-to-butler saving."""
|
|
51
|
+
|
|
52
|
+
store: bool = True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class LogCapture:
|
|
56
|
+
"""Class handling capture of logging messages and their export to butler.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
butler : `~lsst.daf.butler.LimitedButler`
|
|
61
|
+
Data butler with limited API.
|
|
62
|
+
full_butler : `~lsst.daf.butler.Butler` or `None`
|
|
63
|
+
Data butler with full API, or `None` if full Butler is not available.
|
|
64
|
+
If not none, then this must be the same instance as ``butler``.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
stream_json_logs = True
|
|
68
|
+
"""If True each log record is written to a temporary file and ingested
|
|
69
|
+
when quantum completes. If False the records are accumulated in memory
|
|
70
|
+
and stored in butler on quantum completion. If full butler is not available
|
|
71
|
+
then temporary file is not used."""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
butler: LimitedButler,
|
|
76
|
+
full_butler: Butler | None,
|
|
77
|
+
):
|
|
78
|
+
self.butler = butler
|
|
79
|
+
self.full_butler = full_butler
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_limited(cls, butler: LimitedButler) -> LogCapture:
|
|
83
|
+
return cls(butler, None)
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_full(cls, butler: Butler) -> LogCapture:
|
|
87
|
+
return cls(butler, butler)
|
|
88
|
+
|
|
89
|
+
@contextmanager
|
|
90
|
+
def capture_logging(self, task_node: TaskNode, /, quantum: Quantum) -> Iterator[_LogCaptureFlag]:
|
|
91
|
+
"""Configure logging system to capture logs for execution of this task.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
task_node : `~lsst.pipe.base.pipeline_graph.TaskNode`
|
|
96
|
+
The task definition.
|
|
97
|
+
quantum : `~lsst.daf.butler.Quantum`
|
|
98
|
+
Single Quantum instance.
|
|
99
|
+
|
|
100
|
+
Notes
|
|
101
|
+
-----
|
|
102
|
+
Expected to be used as a context manager to ensure that logging
|
|
103
|
+
records are inserted into the butler once the quantum has been
|
|
104
|
+
executed:
|
|
105
|
+
|
|
106
|
+
.. code-block:: py
|
|
107
|
+
|
|
108
|
+
with self.capture_logging(task_node, quantum):
|
|
109
|
+
# Run quantum and capture logs.
|
|
110
|
+
|
|
111
|
+
Ths method can also setup logging to attach task- or
|
|
112
|
+
quantum-specific information to log messages. Potentially this can
|
|
113
|
+
take into account some info from task configuration as well.
|
|
114
|
+
"""
|
|
115
|
+
# include quantum dataId and task label into MDC
|
|
116
|
+
mdc = {"LABEL": task_node.label, "RUN": ""}
|
|
117
|
+
if quantum.dataId:
|
|
118
|
+
mdc["LABEL"] += f":{quantum.dataId}"
|
|
119
|
+
if self.full_butler is not None:
|
|
120
|
+
mdc["RUN"] = self.full_butler.run or ""
|
|
121
|
+
ctx = _LogCaptureFlag()
|
|
122
|
+
log_dataset_name = (
|
|
123
|
+
task_node.log_output.dataset_type_name if task_node.log_output is not None else None
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Add a handler to the root logger to capture execution log output.
|
|
127
|
+
if log_dataset_name is not None:
|
|
128
|
+
# Either accumulate into ButlerLogRecords or stream JSON records to
|
|
129
|
+
# file and ingest that (ingest is possible only with full butler).
|
|
130
|
+
if self.stream_json_logs and self.full_butler is not None:
|
|
131
|
+
# Create the log file in a temporary directory rather than
|
|
132
|
+
# creating a temporary file. This is necessary because
|
|
133
|
+
# temporary files are created with restrictive permissions
|
|
134
|
+
# and during file ingest these permissions persist in the
|
|
135
|
+
# datastore. Using a temp directory allows us to create
|
|
136
|
+
# a file with umask default permissions.
|
|
137
|
+
tmpdir = tempfile.mkdtemp(prefix="butler-temp-logs-")
|
|
138
|
+
|
|
139
|
+
# Construct a file to receive the log records and "touch" it.
|
|
140
|
+
log_file = os.path.join(tmpdir, f"butler-log-{task_node.label}.json")
|
|
141
|
+
with open(log_file, "w"):
|
|
142
|
+
pass
|
|
143
|
+
log_handler_file = FileHandler(log_file)
|
|
144
|
+
log_handler_file.setFormatter(JsonLogFormatter())
|
|
145
|
+
logging.getLogger().addHandler(log_handler_file)
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
with ButlerMDC.set_mdc(mdc):
|
|
149
|
+
yield ctx
|
|
150
|
+
finally:
|
|
151
|
+
# Ensure that the logs are stored in butler.
|
|
152
|
+
logging.getLogger().removeHandler(log_handler_file)
|
|
153
|
+
log_handler_file.close()
|
|
154
|
+
if ctx.store:
|
|
155
|
+
self._ingest_log_records(quantum, log_dataset_name, log_file)
|
|
156
|
+
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
157
|
+
|
|
158
|
+
else:
|
|
159
|
+
log_handler_memory = ButlerLogRecordHandler()
|
|
160
|
+
logging.getLogger().addHandler(log_handler_memory)
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
with ButlerMDC.set_mdc(mdc):
|
|
164
|
+
yield ctx
|
|
165
|
+
finally:
|
|
166
|
+
# Ensure that the logs are stored in butler.
|
|
167
|
+
logging.getLogger().removeHandler(log_handler_memory)
|
|
168
|
+
if ctx.store:
|
|
169
|
+
self._store_log_records(quantum, log_dataset_name, log_handler_memory)
|
|
170
|
+
log_handler_memory.records.clear()
|
|
171
|
+
|
|
172
|
+
else:
|
|
173
|
+
with ButlerMDC.set_mdc(mdc):
|
|
174
|
+
yield ctx
|
|
175
|
+
|
|
176
|
+
def _store_log_records(
|
|
177
|
+
self, quantum: Quantum, dataset_type: str, log_handler: ButlerLogRecordHandler
|
|
178
|
+
) -> None:
|
|
179
|
+
# DatasetRef has to be in the Quantum outputs, can lookup by name.
|
|
180
|
+
try:
|
|
181
|
+
[ref] = quantum.outputs[dataset_type]
|
|
182
|
+
except LookupError as exc:
|
|
183
|
+
raise InvalidQuantumError(
|
|
184
|
+
f"Quantum outputs is missing log output dataset type {dataset_type};"
|
|
185
|
+
" this could happen due to inconsistent options between QuantumGraph generation"
|
|
186
|
+
" and execution"
|
|
187
|
+
) from exc
|
|
188
|
+
|
|
189
|
+
self.butler.put(log_handler.records, ref)
|
|
190
|
+
|
|
191
|
+
def _ingest_log_records(self, quantum: Quantum, dataset_type: str, filename: str) -> None:
|
|
192
|
+
# If we are logging to an external file we must always try to
|
|
193
|
+
# close it.
|
|
194
|
+
assert self.full_butler is not None, "Expected to have full butler for ingest"
|
|
195
|
+
ingested = False
|
|
196
|
+
try:
|
|
197
|
+
# DatasetRef has to be in the Quantum outputs, can lookup by name.
|
|
198
|
+
try:
|
|
199
|
+
[ref] = quantum.outputs[dataset_type]
|
|
200
|
+
except LookupError as exc:
|
|
201
|
+
raise InvalidQuantumError(
|
|
202
|
+
f"Quantum outputs is missing log output dataset type {dataset_type};"
|
|
203
|
+
" this could happen due to inconsistent options between QuantumGraph generation"
|
|
204
|
+
" and execution"
|
|
205
|
+
) from exc
|
|
206
|
+
|
|
207
|
+
# Need to ingest this file directly into butler.
|
|
208
|
+
dataset = FileDataset(path=filename, refs=ref)
|
|
209
|
+
try:
|
|
210
|
+
self.full_butler.ingest(dataset, transfer="move")
|
|
211
|
+
ingested = True
|
|
212
|
+
except NotImplementedError:
|
|
213
|
+
# Some datastores can't receive files (e.g. in-memory datastore
|
|
214
|
+
# when testing), we store empty list for those just to have a
|
|
215
|
+
# dataset. Alternative is to read the file as a
|
|
216
|
+
# ButlerLogRecords object and put it.
|
|
217
|
+
_LOG.info(
|
|
218
|
+
"Log records could not be stored in this butler because the"
|
|
219
|
+
" datastore can not ingest files, empty record list is stored instead."
|
|
220
|
+
)
|
|
221
|
+
records = ButlerLogRecords.from_records([])
|
|
222
|
+
self.full_butler.put(records, ref)
|
|
223
|
+
finally:
|
|
224
|
+
# remove file if it is not ingested
|
|
225
|
+
if not ingested:
|
|
226
|
+
with suppress(OSError):
|
|
227
|
+
os.remove(filename)
|