LbAPLocal 0.10.2__tar.gz → 0.11.0__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.
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/PKG-INFO +4 -3
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/pyproject.toml +6 -2
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/main.py +196 -79
- lbaplocal-0.11.0/src/LbAPLocal/cwl/app_runner.py +507 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cwl/generate_replica_map.py +142 -95
- lbaplocal-0.11.0/src/LbAPLocal/cwl/quickjs_engine.py +111 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cwl/rewrite.py +57 -25
- lbaplocal-0.11.0/src/LbAPLocal/cwl/runner.py +139 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/handlers/mc_request.py +9 -4
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/job_slice.py +8 -4
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/PKG-INFO +4 -3
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/SOURCES.txt +10 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/entry_points.txt +2 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/requires.txt +3 -2
- lbaplocal-0.11.0/src/LbAPLocal.egg-info/scm_file_list.json +150 -0
- lbaplocal-0.11.0/src/LbAPLocal.egg-info/scm_version.json +8 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/conftest.py +78 -73
- lbaplocal-0.11.0/tests/test_app_runner.py +101 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_cwl_cli.py +132 -26
- lbaplocal-0.11.0/tests/test_cwl_runner.py +240 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_execute.py +72 -1
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_handlers.py +31 -2
- lbaplocal-0.11.0/tests/test_input_resolution.py +205 -0
- lbaplocal-0.11.0/tests/test_no_dirac_cwl.py +45 -0
- lbaplocal-0.11.0/tests/test_quickjs_engine.py +117 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.coveragerc +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.gitattributes +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.gitignore +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.gitlab-ci.yml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.pre-commit-config.yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/CONTRIBUTING.md +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/LICENSE +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/README.md +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/example.json +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/setup.cfg +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/__init__.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/__init__.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/generate.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/legacy.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/textual_ui.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/wizards.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cwl/__init__.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/handlers/__init__.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/handlers/anaprod.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/log_parsing.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/models.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/testing.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/utils.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/dependency_links.txt +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/top_level.txt +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/capture_fixtures.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK-2444].yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK_noautoconf-2444].yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK-2393].yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK_noautoconf-2393].yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/.gitignore +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/.gitlab-ci.yml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/AnalysisProductions.xenv +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bc2Bspi2JpsiPhi.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar_stripping.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2DsPi.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt_PVMixer.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Swave.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus_stripping.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/LbJpsipK.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/README.md +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/__init__.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/stripping.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/tuple_maker.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/info.yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/sequence_setup.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/COPYING +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/README.md +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/Run1_Signal.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/Run2_Signal.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/helpers/__init__.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/helpers/signal_tuple_maker.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/info.yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/README.md +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/__init__.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2011_datatype.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2012_datatype.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2015_datatype.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2016_datatype.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2017_datatype.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2018_datatype.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/B0ToK0ee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/B0ToK0mumu.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/BpToKee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/BpToKmumu.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0ee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0eeSS.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0emu.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0emuSS.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0mumu.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0mumuSS.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/README.md +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/__init__.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/general_options.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/MC_tuple_maker.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/__init__.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/decay_descriptors.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/locations.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/related_info.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/stripping.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/tags.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/tuple_maker.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/info.yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_DST.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FDST.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FMDST.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_MDST.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_ee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_mm.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksmm.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_ee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_mm.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_ee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_mm.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_ee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_mm.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lem.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lmm.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_ee.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_mm.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/sequence_setup.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/stripping_seq.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/executabletests/2016_MagDown_PromptMC_D02KK_autoconf.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/executabletests/2016_MagUp_PromptMC_D02KK_autoconf.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/executabletests/info.yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/executabletests/ntuple_options.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-failed-to-read-file.log +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-illegal-instruction.log +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-missing-shared-library.log +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-platform-unsupported.log +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-related-info-missing.log +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/good-DaVinci_00110296_00000038_1.log +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/good-DaVinci_00110296_00000194_1.log +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/good-Gauss_00104988_00000011_1.log +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/fixture_loader.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/fixtures/executabletests/2016_MagDown_PromptMC_D02KK/query_result.json +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/fixtures/executabletests/2016_MagUp_PromptMC_D02KK/query_result.json +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/fixtures/executabletests/manifest.yaml +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/reslice_fixture.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test-input-dst/00070767_00000711_7.AllStreams_slice.dst +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test-input-dst/00070793_00000685_7.AllStreams_slice.dst +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_cli.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_generate.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_local_datapkg.py +0 -0
- {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_log_parse.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: LbAPLocal
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: Tool to locally run tests for AnalysisProductions
|
|
5
5
|
Author: LHCb
|
|
6
6
|
Project-URL: Homepage, https://gitlab.cern.ch/lhcb-dpa/analysis-productions/lbaplocal
|
|
@@ -13,9 +13,10 @@ Classifier: Programming Language :: Python :: 3
|
|
|
13
13
|
Requires-Python: >=3.12
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: LbAPCommon>=0.
|
|
16
|
+
Requires-Dist: LbAPCommon>=0.19.0
|
|
17
17
|
Requires-Dist: LbProdRun>1.12.2
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: cwltool>=3.1.20260108
|
|
19
|
+
Requires-Dist: cwl-utils>=0.41
|
|
19
20
|
Requires-Dist: LbDiracWrappers
|
|
20
21
|
Requires-Dist: LbEnv
|
|
21
22
|
Requires-Dist: apd>=0.6.0
|
|
@@ -19,9 +19,11 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
requires-python = ">=3.12"
|
|
21
21
|
dependencies = [
|
|
22
|
-
"LbAPCommon>=0.
|
|
22
|
+
"LbAPCommon>=0.19.0",
|
|
23
23
|
"LbProdRun>1.12.2",
|
|
24
|
-
|
|
24
|
+
# floor = oldest verified build that routes JS eval through cwl-utils (set_js_engine)
|
|
25
|
+
"cwltool>=3.1.20260108",
|
|
26
|
+
"cwl-utils>=0.41",
|
|
25
27
|
"LbDiracWrappers",
|
|
26
28
|
"LbEnv",
|
|
27
29
|
"apd>=0.6.0",
|
|
@@ -57,6 +59,8 @@ testing = [
|
|
|
57
59
|
[project.scripts]
|
|
58
60
|
lb-ap = "LbAPLocal.cli.main:app"
|
|
59
61
|
lb-ap-legacy = "LbAPLocal.cli.legacy:main"
|
|
62
|
+
lb-ap-run-app = "LbAPLocal.cwl.app_runner:main"
|
|
63
|
+
lb-ap-cwl-run = "LbAPLocal.cwl.runner:app"
|
|
60
64
|
|
|
61
65
|
[tool.black]
|
|
62
66
|
exclude = '''
|
|
@@ -29,6 +29,7 @@ from dataclasses import dataclass, field
|
|
|
29
29
|
from enum import Enum
|
|
30
30
|
from pathlib import Path
|
|
31
31
|
from typing import Annotated, Optional
|
|
32
|
+
from urllib.parse import unquote, urlparse
|
|
32
33
|
|
|
33
34
|
import click
|
|
34
35
|
import typer
|
|
@@ -599,7 +600,7 @@ def version_callback(value: bool):
|
|
|
599
600
|
"LbAPCommon",
|
|
600
601
|
"LbEnv",
|
|
601
602
|
"LbDiracWrappers",
|
|
602
|
-
"
|
|
603
|
+
"cwltool",
|
|
603
604
|
]:
|
|
604
605
|
try:
|
|
605
606
|
version = importlib.metadata.version(package)
|
|
@@ -3699,9 +3700,7 @@ def cwl_test(
|
|
|
3699
3700
|
lb-ap cwl test MyAnalysis MyJob --pick-smallest-lfn # Use smallest LFN
|
|
3700
3701
|
lb-ap cwl test MyAnalysis MyJob --pretty # Split-view real-time logs
|
|
3701
3702
|
lb-ap cwl test MyAnalysis MyJob -o ./output
|
|
3702
|
-
lb-ap cwl test MyAnalysis MyJob --executor toil-cwl-runner
|
|
3703
3703
|
"""
|
|
3704
|
-
executor = "dirac-cwl-run" # Default executor
|
|
3705
3704
|
handler = get_handler(state.repo_type)
|
|
3706
3705
|
# Get available productions for wizard
|
|
3707
3706
|
productions_list = None
|
|
@@ -3741,6 +3740,12 @@ def cwl_test(
|
|
|
3741
3740
|
)
|
|
3742
3741
|
raise typer.Exit(code=1)
|
|
3743
3742
|
|
|
3743
|
+
# Fail fast if the CWL runner is missing — before any slow work
|
|
3744
|
+
# (YAML rendering, BK queries, replica resolution over the network).
|
|
3745
|
+
if not shutil.which("lb-ap-cwl-run"):
|
|
3746
|
+
err_console.print("[bold red]✗ lb-ap-cwl-run not found on PATH![/bold red]\n")
|
|
3747
|
+
raise typer.Exit(code=1)
|
|
3748
|
+
|
|
3744
3749
|
# Set up output directory with timestamp for easy identification
|
|
3745
3750
|
import datetime
|
|
3746
3751
|
|
|
@@ -3757,7 +3762,6 @@ def cwl_test(
|
|
|
3757
3762
|
f"[cyan]Job:[/cyan] {job_name}\n"
|
|
3758
3763
|
f"[cyan]Events:[/cyan] {n_events or 'default'}\n"
|
|
3759
3764
|
f"[cyan]Input:[/cyan] {dependent_input or 'default'}\n"
|
|
3760
|
-
f"[cyan]Executor:[/cyan] {executor}\n"
|
|
3761
3765
|
f"[cyan]Output:[/cyan] {output_dir}",
|
|
3762
3766
|
title="🧪 CWL Test Configuration",
|
|
3763
3767
|
border_style="cyan",
|
|
@@ -3932,79 +3936,165 @@ def cwl_test(
|
|
|
3932
3936
|
# Replace dependent_input with the sliced local file
|
|
3933
3937
|
dependent_input = str(sliced_path)
|
|
3934
3938
|
|
|
3935
|
-
# Step 2b:
|
|
3939
|
+
# Step 2b: Resolve/rewrite input data for execution.
|
|
3940
|
+
#
|
|
3941
|
+
# Case A (no -i): resolve inputs directly from the workflow's
|
|
3942
|
+
# dirac:MetadataQuery via a BK query -- writes a plain inputs.yml,
|
|
3943
|
+
# no CWL rewriting needed.
|
|
3944
|
+
# Case B (-i <local path>): local file, no resolution needed -- rewrite
|
|
3945
|
+
# the CWL directly with the given path (skips the producing steps).
|
|
3946
|
+
# Case C (-i LFN:<lfn>): resolve the LFN to a PFN first (BK query for a
|
|
3947
|
+
# specific LFN), then rewrite the CWL with the resolved PFN string --
|
|
3948
|
+
# never pass an unresolved "LFN:..." string to
|
|
3949
|
+
# rewrite_cwl_for_custom_input, which raises on that.
|
|
3936
3950
|
cwl_inputs_path = None
|
|
3951
|
+
# NOTE: replica_map_path is a user-facing debug artifact (and, in the
|
|
3952
|
+
# LFN case, the resolve_lfn_to_pfn input); unlike the retired DIRAC-based
|
|
3953
|
+
# CWL runner's --replica-map flag, the runner itself never consumes it —
|
|
3954
|
+
# resolved PFNs flow through the inputs YAML instead.
|
|
3937
3955
|
replica_map_path = None
|
|
3938
|
-
|
|
3956
|
+
|
|
3957
|
+
def rewrite_for_custom_input(input_value):
|
|
3958
|
+
"""Rewrite the CWL for a resolved custom input, exiting on failure."""
|
|
3939
3959
|
from LbAPLocal.cwl.rewrite import rewrite_cwl_for_custom_input
|
|
3940
3960
|
|
|
3941
3961
|
console.print(
|
|
3942
|
-
f"\n[bold cyan]Rewriting CWL for custom input:[/bold cyan] {
|
|
3962
|
+
f"\n[bold cyan]Rewriting CWL for custom input:[/bold cyan] {input_value}"
|
|
3943
3963
|
)
|
|
3944
3964
|
try:
|
|
3945
|
-
|
|
3946
|
-
cwl_workflow_path, [
|
|
3965
|
+
workflow_path, inputs_path = rewrite_cwl_for_custom_input(
|
|
3966
|
+
cwl_workflow_path, [input_value], job_name
|
|
3947
3967
|
)
|
|
3948
|
-
console.print(
|
|
3949
|
-
f"[green]✓ Modified CWL workflow:[/green] {cwl_workflow_path}"
|
|
3950
|
-
)
|
|
3951
|
-
console.print(f"[green]✓ Custom inputs file:[/green] {cwl_inputs_path}")
|
|
3952
3968
|
except Exception as e:
|
|
3953
3969
|
err_console.print(f"[bold red]✗ CWL rewriting failed:[/bold red] {e}")
|
|
3954
3970
|
import traceback
|
|
3955
3971
|
|
|
3956
3972
|
err_console.print(traceback.format_exc())
|
|
3957
3973
|
raise typer.Exit(code=1)
|
|
3974
|
+
console.print(f"[green]✓ Modified CWL workflow:[/green] {workflow_path}")
|
|
3975
|
+
console.print(f"[green]✓ Custom inputs file:[/green] {inputs_path}")
|
|
3976
|
+
return workflow_path, inputs_path
|
|
3977
|
+
|
|
3978
|
+
if dependent_input and dependent_input.startswith("LFN:"):
|
|
3979
|
+
console.print(
|
|
3980
|
+
"\n[bold cyan]Generating replica map for custom input LFN[/bold cyan]"
|
|
3981
|
+
)
|
|
3982
|
+
import importlib.util
|
|
3983
|
+
|
|
3984
|
+
spec = importlib.util.find_spec("LbAPLocal.cwl.generate_replica_map")
|
|
3985
|
+
if spec is None or spec.origin is None:
|
|
3986
|
+
err_console.print(
|
|
3987
|
+
"[bold red]✗ Could not find LbAPLocal.cwl.generate_replica_map module[/bold red]"
|
|
3988
|
+
)
|
|
3989
|
+
raise typer.Exit(code=1)
|
|
3990
|
+
|
|
3991
|
+
map_path = output_dir / f"{cwl_workflow_path.stem}-replica-map.json"
|
|
3992
|
+
# Strip the LFN: prefix — DIRAC APIs expect bare paths
|
|
3993
|
+
lfn_path = dependent_input.removeprefix("LFN:")
|
|
3994
|
+
cmd_map = [
|
|
3995
|
+
"lb-dirac",
|
|
3996
|
+
"python",
|
|
3997
|
+
spec.origin,
|
|
3998
|
+
"--lfns",
|
|
3999
|
+
lfn_path,
|
|
4000
|
+
"--output-map",
|
|
4001
|
+
str(map_path),
|
|
4002
|
+
]
|
|
4003
|
+
if download:
|
|
4004
|
+
cmd_map.append("--download")
|
|
4005
|
+
result = subprocess.run(cmd_map, capture_output=True, text=True)
|
|
4006
|
+
if result.returncode != 0:
|
|
4007
|
+
# generate_replica_map prints errors to stdout via rich Console
|
|
4008
|
+
error_output = result.stderr or result.stdout
|
|
4009
|
+
err_console.print(
|
|
4010
|
+
f"[bold red]✗ Failed to generate replica map:[/bold red]\n{error_output}"
|
|
4011
|
+
)
|
|
4012
|
+
raise typer.Exit(code=1)
|
|
4013
|
+
replica_map_path = map_path
|
|
4014
|
+
console.print(f"[green]✓ Generated replica map:[/green] {replica_map_path}")
|
|
4015
|
+
|
|
4016
|
+
from LbAPLocal.cwl.generate_replica_map import resolve_lfn_to_pfn
|
|
4017
|
+
|
|
4018
|
+
lfn_pfn_map = resolve_lfn_to_pfn(map_path)
|
|
4019
|
+
if not lfn_pfn_map:
|
|
4020
|
+
err_console.print(
|
|
4021
|
+
f"[bold red]✗ Replica map contains no entries:[/bold red] {map_path}"
|
|
4022
|
+
)
|
|
4023
|
+
raise typer.Exit(code=1)
|
|
4024
|
+
resolved_input = next(iter(lfn_pfn_map.values()))
|
|
4025
|
+
console.print(
|
|
4026
|
+
f"[green]✓ Resolved custom input to PFN:[/green] {resolved_input}"
|
|
4027
|
+
)
|
|
3958
4028
|
|
|
3959
|
-
|
|
3960
|
-
|
|
4029
|
+
cwl_workflow_path, cwl_inputs_path = rewrite_for_custom_input(resolved_input)
|
|
4030
|
+
|
|
4031
|
+
elif dependent_input:
|
|
4032
|
+
cwl_workflow_path, cwl_inputs_path = rewrite_for_custom_input(dependent_input)
|
|
4033
|
+
|
|
4034
|
+
else:
|
|
4035
|
+
# Resolve input data for the default (no -i) case: BK query from the
|
|
4036
|
+
# dirac:MetadataQuery on the workflow's input-data parameter.
|
|
4037
|
+
from LbAPLocal.cwl.generate_replica_map import has_metadata_query
|
|
4038
|
+
|
|
4039
|
+
if not has_metadata_query(cwl_workflow_path):
|
|
4040
|
+
# Simulation workflows whose first step is Gauss have no
|
|
4041
|
+
# input-data parameter at all -- there is nothing to resolve
|
|
4042
|
+
# from Bookkeeping. Leave cwl_inputs_path as None so the
|
|
4043
|
+
# runner is invoked without an inputs file; workflow defaults
|
|
4044
|
+
# (event-type/run-number/number-of-events) apply.
|
|
3961
4045
|
console.print(
|
|
3962
|
-
"
|
|
4046
|
+
"[dim]No dirac:MetadataQuery on this workflow (e.g. Gauss-first "
|
|
4047
|
+
"simulation) - skipping input resolution[/dim]"
|
|
3963
4048
|
)
|
|
4049
|
+
else:
|
|
3964
4050
|
import importlib.util
|
|
3965
4051
|
|
|
3966
4052
|
spec = importlib.util.find_spec("LbAPLocal.cwl.generate_replica_map")
|
|
3967
4053
|
if spec is None or spec.origin is None:
|
|
3968
4054
|
err_console.print(
|
|
3969
|
-
"[bold red]✗ Could not find
|
|
4055
|
+
"[bold red]✗ Could not find generate_replica_map module[/bold red]"
|
|
3970
4056
|
)
|
|
3971
4057
|
raise typer.Exit(code=1)
|
|
3972
4058
|
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
lfn_path = dependent_input.removeprefix("LFN:")
|
|
4059
|
+
cwl_inputs_path = output_dir / f"{cwl_workflow_path.stem}-inputs.yml"
|
|
4060
|
+
replica_map_path = output_dir / f"{cwl_workflow_path.stem}-replica-map.json"
|
|
3976
4061
|
cmd_map = [
|
|
3977
4062
|
"lb-dirac",
|
|
3978
4063
|
"python",
|
|
3979
4064
|
spec.origin,
|
|
3980
|
-
|
|
3981
|
-
|
|
4065
|
+
str(cwl_workflow_path),
|
|
4066
|
+
"--output-yaml",
|
|
4067
|
+
str(cwl_inputs_path),
|
|
3982
4068
|
"--output-map",
|
|
3983
|
-
str(
|
|
4069
|
+
str(replica_map_path),
|
|
3984
4070
|
]
|
|
3985
|
-
|
|
4071
|
+
if n_lfns is not None:
|
|
4072
|
+
cmd_map.extend(["--n-lfns", str(n_lfns)])
|
|
4073
|
+
if pick_smallest:
|
|
4074
|
+
cmd_map.append("--pick-smallest-lfn")
|
|
4075
|
+
if download:
|
|
4076
|
+
cmd_map.append("--download")
|
|
4077
|
+
|
|
4078
|
+
with Progress(
|
|
4079
|
+
SpinnerColumn(),
|
|
4080
|
+
TextColumn("[progress.description]{task.description}"),
|
|
4081
|
+
console=console,
|
|
4082
|
+
) as progress:
|
|
4083
|
+
progress.add_task(
|
|
4084
|
+
"Resolving input data from Bookkeeping...", total=None
|
|
4085
|
+
)
|
|
4086
|
+
result = subprocess.run(cmd_map, capture_output=True, text=True)
|
|
3986
4087
|
if result.returncode != 0:
|
|
3987
|
-
# generate_replica_map prints errors to stdout via rich Console
|
|
3988
4088
|
error_output = result.stderr or result.stdout
|
|
3989
4089
|
err_console.print(
|
|
3990
|
-
f"[bold red]✗
|
|
4090
|
+
f"[bold red]✗ Input resolution failed:[/bold red]\n{error_output}"
|
|
3991
4091
|
)
|
|
3992
4092
|
raise typer.Exit(code=1)
|
|
3993
|
-
|
|
3994
|
-
console.print(f"[green]✓ Generated replica map:[/green] {replica_map_path}")
|
|
4093
|
+
console.print(f"[green]✓ Resolved inputs:[/green] {cwl_inputs_path}")
|
|
3995
4094
|
|
|
3996
4095
|
# Step 3: Execute CWL workflow
|
|
3997
4096
|
console.print("\n[bold cyan]Step 3/3: Executing CWL workflow[/bold cyan]")
|
|
3998
4097
|
|
|
3999
|
-
# Check if executor is available
|
|
4000
|
-
executor_check = shutil.which(executor)
|
|
4001
|
-
if not executor_check:
|
|
4002
|
-
err_console.print(
|
|
4003
|
-
f"[bold red]✗ CWL executor '{executor}' not found![/bold red]\n"
|
|
4004
|
-
f"Install with: pip install cwltool (or your preferred CWL runner)"
|
|
4005
|
-
)
|
|
4006
|
-
raise typer.Exit(code=1)
|
|
4007
|
-
|
|
4008
4098
|
# Set up lb-run environment for local AnalysisProductions testing
|
|
4009
4099
|
# This creates a fake siteroot so lb-run can find AnalysisProductions.v999999999999 locally
|
|
4010
4100
|
import asyncio
|
|
@@ -4084,49 +4174,22 @@ def cwl_test(
|
|
|
4084
4174
|
cwl_output_dir = output_dir / "cwl_output"
|
|
4085
4175
|
cwl_output_dir.mkdir(parents=True, exist_ok=True)
|
|
4086
4176
|
|
|
4087
|
-
# Use absolute paths to avoid path resolution issues
|
|
4177
|
+
# Use absolute paths to avoid path resolution issues. The whole curated
|
|
4178
|
+
# custom_env is passed straight through to the subprocess below; the
|
|
4179
|
+
# runner itself preserves the entire environment (see cwl/runner.py), so
|
|
4180
|
+
# no --preserve-environment allowlisting is needed here.
|
|
4181
|
+
summary_path = output_dir / "summary.json"
|
|
4088
4182
|
cmd = [
|
|
4089
|
-
|
|
4183
|
+
"lb-ap-cwl-run",
|
|
4090
4184
|
"--outdir",
|
|
4091
4185
|
str(cwl_output_dir.resolve()),
|
|
4186
|
+
"--leave-tmpdir",
|
|
4187
|
+
"--tmpdir-prefix",
|
|
4188
|
+
f"{output_dir}/tmp_step_",
|
|
4189
|
+
"--write-summary",
|
|
4190
|
+
str(summary_path),
|
|
4092
4191
|
]
|
|
4093
|
-
|
|
4094
|
-
# Add dirac-cwl-run specific options for input generation
|
|
4095
|
-
if executor == "dirac-cwl-run":
|
|
4096
|
-
if not cwl_inputs_path:
|
|
4097
|
-
# Only add BK query options when not using custom input
|
|
4098
|
-
cmd.extend(["--n-lfns", str(n_lfns)])
|
|
4099
|
-
if pick_smallest:
|
|
4100
|
-
cmd.append("--pick-smallest-lfn")
|
|
4101
|
-
if download:
|
|
4102
|
-
cmd.append("--download")
|
|
4103
|
-
# Force regeneration to avoid interactive prompts
|
|
4104
|
-
cmd.append("--force-regenerate")
|
|
4105
|
-
# Keep temp directories for debugging
|
|
4106
|
-
cmd.append("--leave-tmpdir")
|
|
4107
|
-
# set a specific prefix for temp directories to easily identify them
|
|
4108
|
-
cmd.extend(["--tmpdir-prefix", f"{output_dir}/tmp_step_"])
|
|
4109
|
-
# Enable verbose logging to see what's happening
|
|
4110
|
-
cmd.append("--verbose")
|
|
4111
|
-
cmd.extend(["--preserve-environment", "CMAKE_PREFIX_PATH"])
|
|
4112
|
-
cmd.extend(["--preserve-environment", "ANALYSIS_PRODUCTIONS_DYNAMIC"])
|
|
4113
|
-
# Preserve X509/XRootD authentication variables so that grid
|
|
4114
|
-
# authentication works
|
|
4115
|
-
from LbDiracWrappers import (
|
|
4116
|
-
ENV_VAR_WHITELIST,
|
|
4117
|
-
) # compiled RE to decide env vars to keep
|
|
4118
|
-
|
|
4119
|
-
for var in sorted(os.environ):
|
|
4120
|
-
if ENV_VAR_WHITELIST.match(var):
|
|
4121
|
-
cmd.extend(["--preserve-environment", var])
|
|
4122
|
-
|
|
4123
|
-
if replica_map_path:
|
|
4124
|
-
cmd.extend(["--replica-map", str(replica_map_path)])
|
|
4125
|
-
|
|
4126
|
-
# Add workflow file
|
|
4127
4192
|
cmd.append(str(cwl_workflow_path.resolve()))
|
|
4128
|
-
|
|
4129
|
-
# Add inputs file if custom input was provided
|
|
4130
4193
|
if cwl_inputs_path:
|
|
4131
4194
|
cmd.append(str(cwl_inputs_path.resolve()))
|
|
4132
4195
|
|
|
@@ -4159,8 +4222,17 @@ def cwl_test(
|
|
|
4159
4222
|
current_run_tmpdirs = [] # Track temp directories used in this run
|
|
4160
4223
|
|
|
4161
4224
|
def find_log_files(include_temp_dirs=False):
|
|
4162
|
-
"""Find and return log files, optionally including temp directories
|
|
4163
|
-
|
|
4225
|
+
"""Find and return log files, optionally including temp directories.
|
|
4226
|
+
|
|
4227
|
+
Merges the .log File entries recorded in the runner's
|
|
4228
|
+
--write-summary output object with filesystem globbing (the two
|
|
4229
|
+
sources are deduplicated). On a workflow failure the summary only
|
|
4230
|
+
covers steps that completed, so the failing step's log may exist
|
|
4231
|
+
only in a --leave-tmpdir directory — never let a partial summary
|
|
4232
|
+
short-circuit the temp-dir search.
|
|
4233
|
+
"""
|
|
4234
|
+
log_files = _logs_from_summary(summary_path)
|
|
4235
|
+
log_files.extend(cwl_output_dir.glob("**/*.log"))
|
|
4164
4236
|
|
|
4165
4237
|
if include_temp_dirs and current_run_tmpdirs:
|
|
4166
4238
|
# Only search in temp directories from this specific run
|
|
@@ -4293,8 +4365,9 @@ def cwl_test(
|
|
|
4293
4365
|
|
|
4294
4366
|
raise typer.Exit(code=1)
|
|
4295
4367
|
|
|
4296
|
-
# Success! Find and list log files
|
|
4297
|
-
|
|
4368
|
+
# Success! Find and list log files (from the summary, falling back to
|
|
4369
|
+
# a directory glob).
|
|
4370
|
+
log_files = find_log_files()
|
|
4298
4371
|
|
|
4299
4372
|
log_files_str = ""
|
|
4300
4373
|
if log_files:
|
|
@@ -4363,6 +4436,50 @@ def cwl_test(
|
|
|
4363
4436
|
_show_log_analysis_for_files(log_files, console)
|
|
4364
4437
|
|
|
4365
4438
|
|
|
4439
|
+
def _logs_from_summary(summary_file: Path) -> list[Path]:
|
|
4440
|
+
"""Extract .log File paths from the CWL output object.
|
|
4441
|
+
|
|
4442
|
+
``summary_file`` is the raw Factory output object written by
|
|
4443
|
+
``lb-ap-cwl-run --write-summary``: File/Directory entries are keyed by
|
|
4444
|
+
``location`` file-URIs only (no ``path`` key), and the object may
|
|
4445
|
+
contain cwltool-namespaced keys such as
|
|
4446
|
+
``"http://commonwl.org/cwltool#generation"`` alongside ordinary CWL
|
|
4447
|
+
output names — treat every dict as a plain, URI-keyed bag to walk.
|
|
4448
|
+
|
|
4449
|
+
Success/failure of the run is intentionally NOT considered here: the
|
|
4450
|
+
runner writes this file both on a clean success and on a workflow
|
|
4451
|
+
failure with partial output (see cwl/runner.py); it is only absent for
|
|
4452
|
+
a load error that happened before any job ran. Callers should key off
|
|
4453
|
+
this file's existence, not the runner's exit code, to decide whether
|
|
4454
|
+
there is anything to parse.
|
|
4455
|
+
"""
|
|
4456
|
+
if not summary_file.is_file():
|
|
4457
|
+
return []
|
|
4458
|
+
try:
|
|
4459
|
+
output_object = json.loads(summary_file.read_text())
|
|
4460
|
+
except json.JSONDecodeError:
|
|
4461
|
+
return []
|
|
4462
|
+
logs: list[Path] = []
|
|
4463
|
+
|
|
4464
|
+
def walk(node):
|
|
4465
|
+
if isinstance(node, dict):
|
|
4466
|
+
location = str(node.get("location", ""))
|
|
4467
|
+
if (
|
|
4468
|
+
node.get("class") == "File"
|
|
4469
|
+
and location.startswith("file://")
|
|
4470
|
+
and location.endswith(".log")
|
|
4471
|
+
):
|
|
4472
|
+
logs.append(Path(unquote(urlparse(location).path)))
|
|
4473
|
+
for v in node.values():
|
|
4474
|
+
walk(v)
|
|
4475
|
+
elif isinstance(node, list):
|
|
4476
|
+
for v in node:
|
|
4477
|
+
walk(v)
|
|
4478
|
+
|
|
4479
|
+
walk(output_object)
|
|
4480
|
+
return sorted(set(logs), key=lambda p: p.name)
|
|
4481
|
+
|
|
4482
|
+
|
|
4366
4483
|
def _show_log_analysis_for_files(log_files: list[Path], console) -> None:
|
|
4367
4484
|
"""Parse Gaudi log files and render analysis summaries."""
|
|
4368
4485
|
if not log_files:
|