LbAPLocal 0.10.0__tar.gz → 0.10.2__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.0 → lbaplocal-0.10.2}/PKG-INFO +1 -1
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/pyproject.toml +14 -3
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/main.py +99 -154
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/wizards.py +3 -5
- lbaplocal-0.10.2/src/LbAPLocal/handlers/__init__.py +63 -0
- lbaplocal-0.10.2/src/LbAPLocal/handlers/anaprod.py +100 -0
- lbaplocal-0.10.2/src/LbAPLocal/handlers/mc_request.py +166 -0
- lbaplocal-0.10.2/src/LbAPLocal/models.py +21 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/PKG-INFO +1 -1
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/SOURCES.txt +5 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_cwl_cli.py +124 -137
- lbaplocal-0.10.2/tests/test_handlers.py +238 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.coveragerc +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.gitattributes +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.gitignore +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.gitlab-ci.yml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.pre-commit-config.yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/CONTRIBUTING.md +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/LICENSE +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/README.md +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/example.json +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/setup.cfg +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/__init__.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/__init__.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/generate.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/legacy.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/textual_ui.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cwl/__init__.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cwl/generate_replica_map.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cwl/rewrite.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/job_slice.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/log_parsing.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/testing.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/utils.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/dependency_links.txt +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/entry_points.txt +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/requires.txt +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/top_level.txt +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/capture_fixtures.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK-2444].yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK_noautoconf-2444].yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK-2393].yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK_noautoconf-2393].yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/conftest.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/.gitignore +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/.gitlab-ci.yml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/AnalysisProductions.xenv +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bc2Bspi2JpsiPhi.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar_stripping.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2DsPi.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt_PVMixer.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Swave.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus_stripping.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/LbJpsipK.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/README.md +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/__init__.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/stripping.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/tuple_maker.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/info.yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/sequence_setup.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/COPYING +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/README.md +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/Run1_Signal.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/Run2_Signal.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/helpers/__init__.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/helpers/signal_tuple_maker.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/info.yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/README.md +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/__init__.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2011_datatype.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2012_datatype.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2015_datatype.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2016_datatype.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2017_datatype.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2018_datatype.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/B0ToK0ee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/B0ToK0mumu.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/BpToKee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/BpToKmumu.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0ee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0eeSS.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0emu.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0emuSS.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0mumu.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0mumuSS.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/README.md +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/__init__.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/general_options.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/MC_tuple_maker.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/__init__.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/decay_descriptors.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/locations.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/related_info.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/stripping.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/tags.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/tuple_maker.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/info.yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_general_options_DST.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FDST.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FMDST.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_general_options_MDST.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_ee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_mm.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksmm.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_ee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_mm.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_ee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_mm.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_ee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_mm.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lem.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lmm.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_ee.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_mm.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/sequence_setup.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/stripping_seq.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/executabletests/2016_MagDown_PromptMC_D02KK_autoconf.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/executabletests/2016_MagUp_PromptMC_D02KK_autoconf.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/executabletests/info.yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/executabletests/ntuple_options.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-failed-to-read-file.log +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-illegal-instruction.log +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-missing-shared-library.log +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-platform-unsupported.log +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-related-info-missing.log +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/good-DaVinci_00110296_00000038_1.log +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/good-DaVinci_00110296_00000194_1.log +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/good-Gauss_00104988_00000011_1.log +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/fixture_loader.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/fixtures/executabletests/2016_MagDown_PromptMC_D02KK/query_result.json +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/fixtures/executabletests/2016_MagUp_PromptMC_D02KK/query_result.json +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/fixtures/executabletests/manifest.yaml +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/reslice_fixture.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test-input-dst/00070767_00000711_7.AllStreams_slice.dst +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test-input-dst/00070793_00000685_7.AllStreams_slice.dst +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_cli.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_execute.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_generate.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_local_datapkg.py +0 -0
- {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_log_parse.py +0 -0
|
@@ -99,17 +99,19 @@ src_paths = ["src", "tests"]
|
|
|
99
99
|
|
|
100
100
|
[tool.pixi.workspace]
|
|
101
101
|
channels = ["conda-forge"]
|
|
102
|
-
platforms = ["linux-64"]
|
|
102
|
+
platforms = ["linux-64", "osx-arm64"]
|
|
103
103
|
|
|
104
104
|
[tool.pixi.dependencies]
|
|
105
105
|
python = ">=3.12,<3.13"
|
|
106
106
|
pip = "*"
|
|
107
|
-
python-gfal2 = "*"
|
|
108
107
|
textual-plotext = "*"
|
|
109
108
|
nodejs = "*"
|
|
109
|
+
quickjs = "*"
|
|
110
|
+
python-gfal2 = "*"
|
|
110
111
|
m2crypto = ">=0.38.0"
|
|
112
|
+
|
|
113
|
+
[tool.pixi.target.linux-64.dependencies]
|
|
111
114
|
prmon = "*"
|
|
112
|
-
quickjs = "*"
|
|
113
115
|
|
|
114
116
|
[tool.pixi.pypi-dependencies]
|
|
115
117
|
LbAPLocal = { path = ".", editable = true }
|
|
@@ -129,6 +131,14 @@ test = "pytest"
|
|
|
129
131
|
lint = "pre-commit run --all-files"
|
|
130
132
|
pre-commit-install = "pre-commit install"
|
|
131
133
|
|
|
134
|
+
[tool.pixi.feature.lint.dependencies]
|
|
135
|
+
python = ">=3.12,<3.13"
|
|
136
|
+
pre-commit = "*"
|
|
137
|
+
ruff = "*"
|
|
138
|
+
|
|
139
|
+
[tool.pixi.feature.lint.tasks]
|
|
140
|
+
lint = "pre-commit run --all-files"
|
|
141
|
+
|
|
132
142
|
[tool.pixi.tasks]
|
|
133
143
|
lb-ap = "lb-ap"
|
|
134
144
|
slice-dst = "lb-run LHCb/v58r2 lbexec GaudiConf.mergeDST:dst"
|
|
@@ -138,3 +148,4 @@ reslice-fixture = "python tests/reslice_fixture.py"
|
|
|
138
148
|
[tool.pixi.environments]
|
|
139
149
|
default = { solve-group = "default" }
|
|
140
150
|
testing = { features = ["testing"], solve-group = "default" }
|
|
151
|
+
lint = { features = ["lint"], no-default-feature = true }
|
|
@@ -20,6 +20,7 @@ import importlib.metadata
|
|
|
20
20
|
import json
|
|
21
21
|
import os
|
|
22
22
|
import random
|
|
23
|
+
import re
|
|
23
24
|
import shlex
|
|
24
25
|
import shutil
|
|
25
26
|
import subprocess
|
|
@@ -32,7 +33,6 @@ from typing import Annotated, Optional
|
|
|
32
33
|
import click
|
|
33
34
|
import typer
|
|
34
35
|
import yaml
|
|
35
|
-
from LbAPCommon import parse_yaml, render_yaml
|
|
36
36
|
from packaging.version import InvalidVersion, Version
|
|
37
37
|
from rich.console import Console
|
|
38
38
|
from rich.live import Live
|
|
@@ -44,11 +44,11 @@ from rich.text import Text
|
|
|
44
44
|
from rich.tree import Tree
|
|
45
45
|
|
|
46
46
|
from LbAPLocal.job_slice import run_minimal_reproducer
|
|
47
|
+
from LbAPLocal.models import RepoType
|
|
47
48
|
|
|
49
|
+
from ..handlers import get_handler
|
|
48
50
|
from ..utils import (
|
|
49
51
|
LBAPI_HOST,
|
|
50
|
-
available_productions,
|
|
51
|
-
check_production,
|
|
52
52
|
inside_ap_datapkg,
|
|
53
53
|
logging_subprocess_run,
|
|
54
54
|
production_to_versions,
|
|
@@ -412,14 +412,6 @@ class CloneType(str, Enum):
|
|
|
412
412
|
KRB5 = "krb5"
|
|
413
413
|
|
|
414
414
|
|
|
415
|
-
class RepoType(str, Enum):
|
|
416
|
-
"""Repository types supported by lb-ap"""
|
|
417
|
-
|
|
418
|
-
ANA_PROD = "ana-prod"
|
|
419
|
-
MC = "mc"
|
|
420
|
-
RAD = "rad"
|
|
421
|
-
|
|
422
|
-
|
|
423
415
|
@dataclass(frozen=True)
|
|
424
416
|
class RepoConfig:
|
|
425
417
|
"""Configuration for a datapkg repository"""
|
|
@@ -1368,6 +1360,41 @@ def clone(
|
|
|
1368
1360
|
new_branch = params["branch_name"]
|
|
1369
1361
|
clone_type = CloneType(params["clone_type"])
|
|
1370
1362
|
|
|
1363
|
+
# Validate argument count: only 0, 1, or 4 positional args are valid
|
|
1364
|
+
n_args = sum(
|
|
1365
|
+
x is not None for x in [branch_or_wg, production_name, version, new_branch]
|
|
1366
|
+
)
|
|
1367
|
+
if n_args == 2:
|
|
1368
|
+
err_console.print(
|
|
1369
|
+
"[bold red]✗ Invalid arguments:[/bold red] clone expects either "
|
|
1370
|
+
"1 argument (branch mode) or 4 arguments (production mode).\n\n"
|
|
1371
|
+
"[cyan]Usage:[/cyan]\n"
|
|
1372
|
+
" lb-ap clone my-branch # Branch mode\n"
|
|
1373
|
+
" lb-ap clone <wg> <production> <version> <branch> # Production mode\n"
|
|
1374
|
+
" lb-ap clone # Interactive wizard"
|
|
1375
|
+
)
|
|
1376
|
+
raise typer.Exit(code=1)
|
|
1377
|
+
if n_args == 3:
|
|
1378
|
+
version_re = re.compile(r"v\d+r\d+(?:p\d+)?$")
|
|
1379
|
+
if version_re.match(branch_or_wg):
|
|
1380
|
+
err_console.print(
|
|
1381
|
+
"[bold red]✗ Invalid arguments:[/bold red] it looks like the "
|
|
1382
|
+
"arguments may be in the wrong order.\n\n"
|
|
1383
|
+
"[cyan]Expected order:[/cyan]\n"
|
|
1384
|
+
" lb-ap clone <working-group> <production> <version> <new-branch>\n\n"
|
|
1385
|
+
f"[cyan]Did you mean:[/cyan]\n"
|
|
1386
|
+
f" lb-ap clone {production_name} {version} {branch_or_wg} <new-branch>"
|
|
1387
|
+
)
|
|
1388
|
+
else:
|
|
1389
|
+
err_console.print(
|
|
1390
|
+
"[bold red]✗ Invalid arguments:[/bold red] production mode requires "
|
|
1391
|
+
"4 arguments but only 3 were given.\n\n"
|
|
1392
|
+
"[cyan]Usage:[/cyan]\n"
|
|
1393
|
+
f" lb-ap clone {branch_or_wg} {production_name} {version} <new-branch>\n\n"
|
|
1394
|
+
"The 4th argument is the name of the new branch to create."
|
|
1395
|
+
)
|
|
1396
|
+
raise typer.Exit(code=1)
|
|
1397
|
+
|
|
1371
1398
|
# Determine mode: branch clone vs production checkout
|
|
1372
1399
|
is_production_mode = all([production_name, version, new_branch])
|
|
1373
1400
|
|
|
@@ -1657,8 +1684,10 @@ def list_productions(
|
|
|
1657
1684
|
lb-ap list MyAnalysis
|
|
1658
1685
|
lb-ap list MyAnalysis --tree
|
|
1659
1686
|
"""
|
|
1687
|
+
handler = get_handler(state.repo_type)
|
|
1688
|
+
|
|
1660
1689
|
try:
|
|
1661
|
-
|
|
1690
|
+
handler.check_directory()
|
|
1662
1691
|
except Exception as e:
|
|
1663
1692
|
err_console.print(
|
|
1664
1693
|
f"[bold red]✗ Not in {state.repo_config.name} directory:[/bold red] {e}"
|
|
@@ -1667,21 +1696,18 @@ def list_productions(
|
|
|
1667
1696
|
|
|
1668
1697
|
if production_name:
|
|
1669
1698
|
# List jobs for specific production
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1699
|
+
productions = handler.list_productions()
|
|
1700
|
+
if production_name not in productions:
|
|
1701
|
+
err_console.print(
|
|
1702
|
+
f"[bold red]✗ Production not found:[/bold red] {production_name}"
|
|
1703
|
+
)
|
|
1674
1704
|
raise typer.Exit(code=1)
|
|
1675
1705
|
|
|
1676
|
-
|
|
1677
|
-
info_yaml_path = Path(production_name) / "info.yaml"
|
|
1678
|
-
with open(info_yaml_path, "rt") as fp:
|
|
1679
|
-
raw_yaml = fp.read()
|
|
1680
|
-
job_data = parse_yaml(render_yaml(raw_yaml))
|
|
1706
|
+
job_names = handler.list_jobs(production_name)
|
|
1681
1707
|
|
|
1682
1708
|
if show_tree:
|
|
1683
1709
|
tree = Tree(f"[bold cyan]{production_name}[/bold cyan]")
|
|
1684
|
-
for job_name in
|
|
1710
|
+
for job_name in job_names:
|
|
1685
1711
|
tree.add(f"[green]{job_name}[/green]")
|
|
1686
1712
|
console.print(tree)
|
|
1687
1713
|
else:
|
|
@@ -1693,24 +1719,19 @@ def list_productions(
|
|
|
1693
1719
|
table.add_column("Job Name", style="green", no_wrap=True)
|
|
1694
1720
|
table.add_column("Status", style="yellow")
|
|
1695
1721
|
|
|
1696
|
-
for job_name in
|
|
1722
|
+
for job_name in job_names:
|
|
1697
1723
|
table.add_row(job_name, "✓ Available")
|
|
1698
1724
|
|
|
1699
1725
|
console.print(table)
|
|
1700
1726
|
else:
|
|
1701
1727
|
# List all productions
|
|
1702
|
-
productions =
|
|
1728
|
+
productions = handler.list_productions()
|
|
1703
1729
|
|
|
1704
1730
|
if show_tree:
|
|
1705
1731
|
tree = Tree(f"[bold cyan]📂 {state.repo_config.name}[/bold cyan]")
|
|
1706
1732
|
for folder in productions:
|
|
1707
|
-
# Count jobs for tree view
|
|
1708
1733
|
try:
|
|
1709
|
-
|
|
1710
|
-
with open(info_yaml_path, "rt") as fp:
|
|
1711
|
-
raw_yaml = fp.read()
|
|
1712
|
-
job_data = parse_yaml(render_yaml(raw_yaml))
|
|
1713
|
-
job_count = len(job_data)
|
|
1734
|
+
job_count = len(handler.list_jobs(folder))
|
|
1714
1735
|
tree.add(f"[green]{folder}[/green] [dim]({job_count} jobs)[/dim]")
|
|
1715
1736
|
except Exception:
|
|
1716
1737
|
tree.add(f"[green]{folder}[/green] [dim](? jobs)[/dim]")
|
|
@@ -1726,16 +1747,11 @@ def list_productions(
|
|
|
1726
1747
|
table.add_column("Status", style="yellow")
|
|
1727
1748
|
|
|
1728
1749
|
for folder in productions:
|
|
1729
|
-
# Count jobs in each production
|
|
1730
1750
|
try:
|
|
1731
|
-
|
|
1732
|
-
with open(info_yaml_path, "rt") as fp:
|
|
1733
|
-
raw_yaml = fp.read()
|
|
1734
|
-
job_data = parse_yaml(render_yaml(raw_yaml))
|
|
1735
|
-
job_count = len(job_data)
|
|
1751
|
+
job_count = len(handler.list_jobs(folder))
|
|
1736
1752
|
table.add_row(folder, str(job_count), "✓ Available")
|
|
1737
1753
|
except Exception:
|
|
1738
|
-
table.add_row(folder, "?", "⚠ Error reading
|
|
1754
|
+
table.add_row(folder, "?", "⚠ Error reading jobs")
|
|
1739
1755
|
|
|
1740
1756
|
console.print(table)
|
|
1741
1757
|
|
|
@@ -1835,11 +1851,12 @@ def test(
|
|
|
1835
1851
|
"""
|
|
1836
1852
|
# Handle dry-run mode first with custom logic
|
|
1837
1853
|
if dry_run:
|
|
1854
|
+
handler = get_handler(state.repo_type)
|
|
1838
1855
|
# Get available productions for wizard
|
|
1839
1856
|
productions_list = None
|
|
1840
1857
|
try:
|
|
1841
|
-
|
|
1842
|
-
productions_list =
|
|
1858
|
+
handler.check_directory()
|
|
1859
|
+
productions_list = handler.list_productions()
|
|
1843
1860
|
except Exception:
|
|
1844
1861
|
pass
|
|
1845
1862
|
|
|
@@ -1853,8 +1870,9 @@ def test(
|
|
|
1853
1870
|
job_name = params["job_name"]
|
|
1854
1871
|
|
|
1855
1872
|
try:
|
|
1856
|
-
|
|
1857
|
-
|
|
1873
|
+
handler.check_directory()
|
|
1874
|
+
if production_name not in handler.list_productions():
|
|
1875
|
+
raise RuntimeError(f"Can't find production {production_name}")
|
|
1858
1876
|
except Exception as e:
|
|
1859
1877
|
err_console.print(f"[bold red]✗ Error:[/bold red] {e}")
|
|
1860
1878
|
raise typer.Exit(code=1)
|
|
@@ -3489,11 +3507,12 @@ def cwl_generate(
|
|
|
3489
3507
|
lb-ap cwl generate MyAnalysis MyJob
|
|
3490
3508
|
lb-ap cwl generate MyAnalysis MyJob -o ./cwl_output
|
|
3491
3509
|
"""
|
|
3510
|
+
handler = get_handler(state.repo_type)
|
|
3492
3511
|
# Get available productions for wizard
|
|
3493
3512
|
productions_list = None
|
|
3494
3513
|
try:
|
|
3495
|
-
|
|
3496
|
-
productions_list =
|
|
3514
|
+
handler.check_directory()
|
|
3515
|
+
productions_list = handler.list_productions()
|
|
3497
3516
|
except Exception:
|
|
3498
3517
|
pass
|
|
3499
3518
|
|
|
@@ -3683,11 +3702,12 @@ def cwl_test(
|
|
|
3683
3702
|
lb-ap cwl test MyAnalysis MyJob --executor toil-cwl-runner
|
|
3684
3703
|
"""
|
|
3685
3704
|
executor = "dirac-cwl-run" # Default executor
|
|
3705
|
+
handler = get_handler(state.repo_type)
|
|
3686
3706
|
# Get available productions for wizard
|
|
3687
3707
|
productions_list = None
|
|
3688
3708
|
try:
|
|
3689
|
-
|
|
3690
|
-
productions_list =
|
|
3709
|
+
handler.check_directory()
|
|
3710
|
+
productions_list = handler.list_productions()
|
|
3691
3711
|
except Exception:
|
|
3692
3712
|
pass
|
|
3693
3713
|
|
|
@@ -3704,8 +3724,9 @@ def cwl_test(
|
|
|
3704
3724
|
skip_validation = params.get("skip_validation", skip_validation)
|
|
3705
3725
|
|
|
3706
3726
|
try:
|
|
3707
|
-
|
|
3708
|
-
|
|
3727
|
+
handler.check_directory()
|
|
3728
|
+
if production_name not in handler.list_productions():
|
|
3729
|
+
raise RuntimeError(f"Can't find production {production_name}")
|
|
3709
3730
|
except Exception as e:
|
|
3710
3731
|
err_console.print(f"[bold red]✗ Error:[/bold red] {e}")
|
|
3711
3732
|
raise typer.Exit(code=1)
|
|
@@ -3743,108 +3764,18 @@ def cwl_test(
|
|
|
3743
3764
|
)
|
|
3744
3765
|
)
|
|
3745
3766
|
|
|
3746
|
-
# Step 1: Generate DIRAC production request YAML
|
|
3767
|
+
# Step 1: Generate DIRAC production request YAML
|
|
3747
3768
|
console.print(
|
|
3748
3769
|
"\n[bold cyan]Step 1/3: Generating DIRAC production request YAML[/bold cyan]"
|
|
3749
3770
|
)
|
|
3750
3771
|
|
|
3751
|
-
# Read and render the info.yaml to check job exists
|
|
3752
|
-
info_yaml_path = Path(production_name) / "info.yaml"
|
|
3753
|
-
if not info_yaml_path.exists():
|
|
3754
|
-
err_console.print(
|
|
3755
|
-
f"[bold red]✗ info.yaml not found:[/bold red] {info_yaml_path}"
|
|
3756
|
-
)
|
|
3757
|
-
raise typer.Exit(code=1)
|
|
3758
|
-
|
|
3759
|
-
# Quick check that job exists
|
|
3760
|
-
from LbAPCommon import parse_yaml, render_yaml
|
|
3761
|
-
|
|
3762
|
-
raw_yaml = info_yaml_path.read_text()
|
|
3763
|
-
rendered = render_yaml(raw_yaml)
|
|
3764
|
-
jobs_data = parse_yaml(rendered, production_name, None)
|
|
3765
|
-
|
|
3766
|
-
if job_name not in jobs_data:
|
|
3767
|
-
available_jobs = list(jobs_data.keys())
|
|
3768
|
-
err_console.print(
|
|
3769
|
-
f"[bold red]✗ Job '{job_name}' not found in production '{production_name}'[/bold red]\n"
|
|
3770
|
-
f"Available jobs: {', '.join(available_jobs)}"
|
|
3771
|
-
)
|
|
3772
|
-
raise typer.Exit(code=1)
|
|
3773
|
-
|
|
3774
|
-
# Use LbAPCommon's main function to generate proper DIRAC production request
|
|
3775
|
-
# This queries DIRAC for input metadata and creates the full production structure
|
|
3776
|
-
import asyncio
|
|
3777
|
-
import json
|
|
3778
|
-
|
|
3779
|
-
from LbAPCommon.__main__ import main as lbapcommon_main
|
|
3780
|
-
|
|
3781
|
-
console.print("[cyan]Querying DIRAC for job metadata...[/cyan]")
|
|
3782
|
-
|
|
3783
|
-
# Store metadata in output directory for inspection
|
|
3784
|
-
metadata_path = output_dir / "dirac_metadata.json"
|
|
3785
|
-
|
|
3786
3772
|
try:
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
asyncio.run(
|
|
3790
|
-
lbapcommon_main(
|
|
3791
|
-
production_name=production_name,
|
|
3792
|
-
ap_pkg_version="v999999999999", # Test version
|
|
3793
|
-
input_file=info_yaml_path,
|
|
3794
|
-
output_file=metadata_path,
|
|
3795
|
-
server_credentials=None,
|
|
3796
|
-
only_include=job_name,
|
|
3797
|
-
dump_requests=True, # This creates the YAML file we need
|
|
3798
|
-
)
|
|
3799
|
-
)
|
|
3800
|
-
|
|
3801
|
-
# Read the generated metadata
|
|
3802
|
-
with open(metadata_path, "r") as f:
|
|
3803
|
-
metadata = json.load(f)
|
|
3804
|
-
|
|
3805
|
-
# Find the production request YAML that was dumped
|
|
3806
|
-
# dump_requests creates files named after the production
|
|
3807
|
-
productions = metadata.get("productions", {})
|
|
3808
|
-
if not productions:
|
|
3809
|
-
err_console.print("[bold red]✗ No productions generated[/bold red]")
|
|
3810
|
-
raise typer.Exit(code=1)
|
|
3811
|
-
|
|
3812
|
-
# Get the first (and should be only) production
|
|
3813
|
-
prod_key = list(productions.keys())[0]
|
|
3814
|
-
production_data = productions[prod_key]
|
|
3815
|
-
|
|
3816
|
-
console.print(f"[green]✓ Generated production:[/green] {prod_key}")
|
|
3817
|
-
console.print(
|
|
3818
|
-
f"[dim]Request chain: {' → '.join([s['name'].split('#')[-1] for s in production_data['request']])}[/dim]"
|
|
3773
|
+
prod_yaml_path = handler.generate_request_yaml(
|
|
3774
|
+
production_name, job_name, output_dir
|
|
3819
3775
|
)
|
|
3820
|
-
|
|
3821
|
-
# The dump_requests option creates a YAML file with the request
|
|
3822
|
-
# Look for it in the current directory (where metadata was written)
|
|
3823
|
-
request_yaml_path = metadata_path.with_name(prod_key).with_suffix(".yaml")
|
|
3824
|
-
|
|
3825
|
-
if not request_yaml_path.exists():
|
|
3826
|
-
# If dump wasn't created, save it ourselves
|
|
3827
|
-
request_yaml_path = output_dir / f"{prod_key}.yaml"
|
|
3828
|
-
with open(request_yaml_path, "w") as f:
|
|
3829
|
-
yaml.dump(
|
|
3830
|
-
production_data["request"],
|
|
3831
|
-
f,
|
|
3832
|
-
default_flow_style=False,
|
|
3833
|
-
sort_keys=False,
|
|
3834
|
-
)
|
|
3835
|
-
else:
|
|
3836
|
-
# Move the dumped file to output directory if needed
|
|
3837
|
-
if request_yaml_path.parent != output_dir:
|
|
3838
|
-
final_yaml_path = output_dir / f"{prod_key}.yaml"
|
|
3839
|
-
shutil.move(str(request_yaml_path), str(final_yaml_path))
|
|
3840
|
-
request_yaml_path = final_yaml_path
|
|
3841
|
-
|
|
3842
|
-
prod_yaml_path = request_yaml_path
|
|
3843
3776
|
console.print(
|
|
3844
|
-
f"[green]✓
|
|
3777
|
+
f"[green]✓ Generated production request:[/green] {prod_yaml_path.name}"
|
|
3845
3778
|
)
|
|
3846
|
-
console.print(f"[green]✓ Saved metadata:[/green] {metadata_path.name}")
|
|
3847
|
-
|
|
3848
3779
|
except Exception as e:
|
|
3849
3780
|
err_console.print(
|
|
3850
3781
|
f"[bold red]✗ DIRAC production request generation failed:[/bold red] {e}"
|
|
@@ -4076,14 +4007,24 @@ def cwl_test(
|
|
|
4076
4007
|
|
|
4077
4008
|
# Set up lb-run environment for local AnalysisProductions testing
|
|
4078
4009
|
# This creates a fake siteroot so lb-run can find AnalysisProductions.v999999999999 locally
|
|
4010
|
+
import asyncio
|
|
4079
4011
|
import os
|
|
4080
4012
|
|
|
4081
4013
|
from LbAPCommon.hacks import project_uses_cmt, setup_lbrun_environment
|
|
4082
4014
|
|
|
4083
|
-
# Parse application info
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
)
|
|
4015
|
+
# Parse application info from the generated production request YAML
|
|
4016
|
+
with open(prod_yaml_path) as _f:
|
|
4017
|
+
_prod_request = yaml.safe_load(_f)
|
|
4018
|
+
if isinstance(_prod_request, list):
|
|
4019
|
+
_prod_request = _prod_request[0]
|
|
4020
|
+
# For AP: "application" key in request steps; for Simulation: first step's application
|
|
4021
|
+
if "steps" in _prod_request:
|
|
4022
|
+
_app = _prod_request["steps"][0]["application"]
|
|
4023
|
+
application_name = _app["name"]
|
|
4024
|
+
application_version = _app["version"]
|
|
4025
|
+
else:
|
|
4026
|
+
_app_str = _prod_request.get("application", "Unknown/v1")
|
|
4027
|
+
application_name, application_version = _app_str.rsplit("/", 1)
|
|
4087
4028
|
if "@" in application_version:
|
|
4088
4029
|
application_version = application_version.split("@", 1)[0]
|
|
4089
4030
|
require_cmt = project_uses_cmt(application_name, application_version)
|
|
@@ -4120,15 +4061,19 @@ def cwl_test(
|
|
|
4120
4061
|
ap_dynamic_dir.unlink()
|
|
4121
4062
|
ap_dynamic_dir.symlink_to(dynamic_dir.resolve())
|
|
4122
4063
|
|
|
4123
|
-
# Create files in dynamic
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4064
|
+
# Create files in dynamic (AP-specific: from dirac_metadata.json if present)
|
|
4065
|
+
metadata_path = output_dir / "dirac_metadata.json"
|
|
4066
|
+
if metadata_path.exists():
|
|
4067
|
+
import json
|
|
4068
|
+
|
|
4069
|
+
with open(metadata_path) as _mf:
|
|
4070
|
+
_metadata = json.load(_mf)
|
|
4071
|
+
for _pk, _pv in _metadata.get("productions", {}).items():
|
|
4072
|
+
for filename, content in _pv.get("dynamic_files", {}).items():
|
|
4073
|
+
file_path = dynamic_dir / filename
|
|
4074
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
4075
|
+
file_path.write_text(content)
|
|
4076
|
+
console.print(f"[green]✓ Created dynamic file:[/green] {file_path}")
|
|
4132
4077
|
|
|
4133
4078
|
# Create a custom environment dict with the lb-run setup to pass to subprocess
|
|
4134
4079
|
# This ensures CMAKE_PREFIX_PATH and other variables are set for the CWL executor
|
|
@@ -614,18 +614,16 @@ def generate_wizard(seen_welcome: bool = False) -> dict:
|
|
|
614
614
|
)
|
|
615
615
|
|
|
616
616
|
wg_validator = prompt_toolkit.validation.Validator.from_callable(
|
|
617
|
-
lambda wg: wg
|
|
618
|
-
)
|
|
619
|
-
wg_completer = prompt_toolkit.completion.WordCompleter(
|
|
620
|
-
known_working_groups, ignore_case=True
|
|
617
|
+
lambda wg: wg in known_working_groups
|
|
621
618
|
)
|
|
619
|
+
wg_completer = prompt_toolkit.completion.FuzzyWordCompleter(known_working_groups)
|
|
622
620
|
|
|
623
621
|
wg = prompt_toolkit.prompt(
|
|
624
622
|
"Enter working group: ",
|
|
625
623
|
default=detected_wg,
|
|
626
624
|
completer=wg_completer,
|
|
627
625
|
validator=wg_validator,
|
|
628
|
-
)
|
|
626
|
+
)
|
|
629
627
|
|
|
630
628
|
console.print(f"\n[bold cyan]Step {step}: Review[/bold cyan]")
|
|
631
629
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
# (c) Copyright 2026 CERN for the benefit of the LHCb Collaboration #
|
|
3
|
+
# #
|
|
4
|
+
# This software is distributed under the terms of the GNU General Public #
|
|
5
|
+
# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". #
|
|
6
|
+
# #
|
|
7
|
+
# In applying this licence, CERN does not waive the privileges and immunities #
|
|
8
|
+
# granted to it by virtue of its status as an Intergovernmental Organization #
|
|
9
|
+
# or submit itself to any jurisdiction. #
|
|
10
|
+
###############################################################################
|
|
11
|
+
"""Production handler protocol and registry.
|
|
12
|
+
|
|
13
|
+
Different production types (AnalysisProductions, MC Requests, RadProductions)
|
|
14
|
+
have different repository layouts, config formats, and parsing pipelines.
|
|
15
|
+
Handlers encapsulate these differences behind a common interface so the CLI
|
|
16
|
+
can work with any production type.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Protocol
|
|
23
|
+
|
|
24
|
+
from LbAPLocal.models import RepoType
|
|
25
|
+
|
|
26
|
+
from .anaprod import APHandler
|
|
27
|
+
from .mc_request import MCHandler
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProductionHandler(Protocol):
|
|
31
|
+
"""Interface for production-type-specific logic."""
|
|
32
|
+
|
|
33
|
+
def check_directory(self) -> None:
|
|
34
|
+
"""Verify CWD is the correct repo type. Raises on failure."""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def list_productions(self) -> list[str]:
|
|
38
|
+
"""Return available production/directory names."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
def list_jobs(self, production: str) -> list[str]:
|
|
42
|
+
"""Return job identifiers within a production."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def generate_request_yaml(
|
|
46
|
+
self, production: str, job: str, output_dir: Path
|
|
47
|
+
) -> Path:
|
|
48
|
+
"""Generate a Stage 6 DIRAC production request YAML.
|
|
49
|
+
|
|
50
|
+
Returns the path to the generated file in output_dir.
|
|
51
|
+
"""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_HANDLERS: dict[RepoType, ProductionHandler] = {
|
|
56
|
+
RepoType.ANA_PROD: APHandler(),
|
|
57
|
+
RepoType.MC: MCHandler(),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_handler(repo_type: RepoType) -> ProductionHandler:
|
|
62
|
+
"""Return the handler for the given repo type."""
|
|
63
|
+
return _HANDLERS[repo_type]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
# (c) Copyright 2026 CERN for the benefit of the LHCb Collaboration #
|
|
3
|
+
# #
|
|
4
|
+
# This software is distributed under the terms of the GNU General Public #
|
|
5
|
+
# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". #
|
|
6
|
+
# #
|
|
7
|
+
# In applying this licence, CERN does not waive the privileges and immunities #
|
|
8
|
+
# granted to it by virtue of its status as an Intergovernmental Organization #
|
|
9
|
+
# or submit itself to any jurisdiction. #
|
|
10
|
+
###############################################################################
|
|
11
|
+
"""AnalysisProductions handler.
|
|
12
|
+
|
|
13
|
+
Extracts AP-specific logic from cli/main.py: directory validation, production
|
|
14
|
+
discovery via info.yaml, job listing via LbAPCommon parse_yaml, and DIRAC
|
|
15
|
+
production request generation via LbAPCommon.__main__.main().
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
import glob
|
|
22
|
+
import json
|
|
23
|
+
import shutil
|
|
24
|
+
from os.path import dirname
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
import yaml
|
|
28
|
+
|
|
29
|
+
from LbAPLocal.utils import inside_ap_datapkg
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class APHandler:
|
|
33
|
+
"""Handler for AnalysisProductions repositories."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, xenv_file: str = "AnalysisProductions.xenv"):
|
|
36
|
+
self.xenv_file = xenv_file
|
|
37
|
+
|
|
38
|
+
def check_directory(self) -> None:
|
|
39
|
+
inside_ap_datapkg(self.xenv_file)
|
|
40
|
+
|
|
41
|
+
def list_productions(self) -> list[str]:
|
|
42
|
+
folders = glob.glob("*/info.yaml")
|
|
43
|
+
return [dirname(f) for f in folders]
|
|
44
|
+
|
|
45
|
+
def list_jobs(self, production: str) -> list[str]:
|
|
46
|
+
from LbAPCommon import parse_yaml, render_yaml
|
|
47
|
+
|
|
48
|
+
info_yaml = Path(production) / "info.yaml"
|
|
49
|
+
raw = info_yaml.read_text()
|
|
50
|
+
rendered = render_yaml(raw)
|
|
51
|
+
jobs = parse_yaml(rendered, production, None)
|
|
52
|
+
return list(jobs.keys())
|
|
53
|
+
|
|
54
|
+
def generate_request_yaml(
|
|
55
|
+
self, production: str, job: str, output_dir: Path
|
|
56
|
+
) -> Path:
|
|
57
|
+
from LbAPCommon.__main__ import main as lbapcommon_main
|
|
58
|
+
|
|
59
|
+
info_yaml_path = Path(production) / "info.yaml"
|
|
60
|
+
metadata_path = output_dir / "dirac_metadata.json"
|
|
61
|
+
|
|
62
|
+
asyncio.run(
|
|
63
|
+
lbapcommon_main(
|
|
64
|
+
production_name=production,
|
|
65
|
+
ap_pkg_version="v999999999999",
|
|
66
|
+
input_file=info_yaml_path,
|
|
67
|
+
output_file=metadata_path,
|
|
68
|
+
server_credentials=None,
|
|
69
|
+
only_include=job,
|
|
70
|
+
dump_requests=True,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
with open(metadata_path) as f:
|
|
75
|
+
metadata = json.load(f)
|
|
76
|
+
|
|
77
|
+
productions = metadata.get("productions", {})
|
|
78
|
+
if not productions:
|
|
79
|
+
raise RuntimeError("No productions generated")
|
|
80
|
+
|
|
81
|
+
prod_key = list(productions.keys())[0]
|
|
82
|
+
production_data = productions[prod_key]
|
|
83
|
+
|
|
84
|
+
request_yaml_path = metadata_path.with_name(prod_key).with_suffix(".yaml")
|
|
85
|
+
|
|
86
|
+
if not request_yaml_path.exists():
|
|
87
|
+
request_yaml_path = output_dir / f"{prod_key}.yaml"
|
|
88
|
+
with open(request_yaml_path, "w") as f:
|
|
89
|
+
yaml.dump(
|
|
90
|
+
production_data["request"],
|
|
91
|
+
f,
|
|
92
|
+
default_flow_style=False,
|
|
93
|
+
sort_keys=False,
|
|
94
|
+
)
|
|
95
|
+
elif request_yaml_path.parent != output_dir:
|
|
96
|
+
final_path = output_dir / f"{prod_key}.yaml"
|
|
97
|
+
shutil.move(str(request_yaml_path), str(final_path))
|
|
98
|
+
request_yaml_path = final_path
|
|
99
|
+
|
|
100
|
+
return request_yaml_path
|