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.
Files changed (146) hide show
  1. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/PKG-INFO +1 -1
  2. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/pyproject.toml +14 -3
  3. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/main.py +99 -154
  4. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/wizards.py +3 -5
  5. lbaplocal-0.10.2/src/LbAPLocal/handlers/__init__.py +63 -0
  6. lbaplocal-0.10.2/src/LbAPLocal/handlers/anaprod.py +100 -0
  7. lbaplocal-0.10.2/src/LbAPLocal/handlers/mc_request.py +166 -0
  8. lbaplocal-0.10.2/src/LbAPLocal/models.py +21 -0
  9. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/PKG-INFO +1 -1
  10. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/SOURCES.txt +5 -0
  11. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_cwl_cli.py +124 -137
  12. lbaplocal-0.10.2/tests/test_handlers.py +238 -0
  13. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.coveragerc +0 -0
  14. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.gitattributes +0 -0
  15. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.gitignore +0 -0
  16. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.gitlab-ci.yml +0 -0
  17. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/.pre-commit-config.yaml +0 -0
  18. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/CONTRIBUTING.md +0 -0
  19. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/LICENSE +0 -0
  20. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/README.md +0 -0
  21. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/example.json +0 -0
  22. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/setup.cfg +0 -0
  23. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/__init__.py +0 -0
  24. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/__init__.py +0 -0
  25. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/generate.py +0 -0
  26. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/legacy.py +0 -0
  27. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cli/textual_ui.py +0 -0
  28. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cwl/__init__.py +0 -0
  29. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cwl/generate_replica_map.py +0 -0
  30. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/cwl/rewrite.py +0 -0
  31. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/job_slice.py +0 -0
  32. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/log_parsing.py +0 -0
  33. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/testing.py +0 -0
  34. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal/utils.py +0 -0
  35. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/dependency_links.txt +0 -0
  36. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/entry_points.txt +0 -0
  37. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/requires.txt +0 -0
  38. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/src/LbAPLocal.egg-info/top_level.txt +0 -0
  39. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/capture_fixtures.py +0 -0
  40. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK-2444].yaml +0 -0
  41. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK_noautoconf-2444].yaml +0 -0
  42. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK-2393].yaml +0 -0
  43. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK_noautoconf-2393].yaml +0 -0
  44. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/conftest.py +0 -0
  45. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/.gitignore +0 -0
  46. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/.gitlab-ci.yml +0 -0
  47. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/AnalysisProductions.xenv +0 -0
  48. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bc2Bspi2JpsiPhi.py +0 -0
  49. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar.py +0 -0
  50. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar_stripping.py +0 -0
  51. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2DsPi.py +0 -0
  52. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi.py +0 -0
  53. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt.py +0 -0
  54. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt_PVMixer.py +0 -0
  55. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Swave.py +0 -0
  56. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus.py +0 -0
  57. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus_stripping.py +0 -0
  58. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/LbJpsipK.py +0 -0
  59. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/README.md +0 -0
  60. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/__init__.py +0 -0
  61. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/stripping.py +0 -0
  62. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/tuple_maker.py +0 -0
  63. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/info.yaml +0 -0
  64. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/BsToJpsiPhi/sequence_setup.py +0 -0
  65. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/COPYING +0 -0
  66. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/README.md +0 -0
  67. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/Run1_Signal.py +0 -0
  68. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/Run2_Signal.py +0 -0
  69. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/helpers/__init__.py +0 -0
  70. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/helpers/signal_tuple_maker.py +0 -0
  71. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/RDs/info.yaml +0 -0
  72. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/README.md +0 -0
  73. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/__init__.py +0 -0
  74. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2011_datatype.py +0 -0
  75. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2012_datatype.py +0 -0
  76. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2015_datatype.py +0 -0
  77. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2016_datatype.py +0 -0
  78. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2017_datatype.py +0 -0
  79. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/2018_datatype.py +0 -0
  80. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/B0ToK0ee.py +0 -0
  81. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/B0ToK0mumu.py +0 -0
  82. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/BpToKee.py +0 -0
  83. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/BpToKmumu.py +0 -0
  84. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0ee.py +0 -0
  85. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0eeSS.py +0 -0
  86. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0emu.py +0 -0
  87. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0emuSS.py +0 -0
  88. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0mumu.py +0 -0
  89. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/LbToL0mumuSS.py +0 -0
  90. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/README.md +0 -0
  91. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/__init__.py +0 -0
  92. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/general_options.py +0 -0
  93. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/MC_tuple_maker.py +0 -0
  94. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/__init__.py +0 -0
  95. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/decay_descriptors.py +0 -0
  96. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/locations.py +0 -0
  97. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/related_info.py +0 -0
  98. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/stripping.py +0 -0
  99. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/tags.py +0 -0
  100. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/helpers/tuple_maker.py +0 -0
  101. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/info.yaml +0 -0
  102. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_general_options_DST.py +0 -0
  103. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FDST.py +0 -0
  104. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FMDST.py +0 -0
  105. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_general_options_MDST.py +0 -0
  106. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_ee.py +0 -0
  107. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_mm.py +0 -0
  108. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksee.py +0 -0
  109. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksmm.py +0 -0
  110. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_ee.py +0 -0
  111. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_mm.py +0 -0
  112. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_ee.py +0 -0
  113. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_mm.py +0 -0
  114. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_ee.py +0 -0
  115. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_mm.py +0 -0
  116. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lee.py +0 -0
  117. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lem.py +0 -0
  118. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lmm.py +0 -0
  119. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_ee.py +0 -0
  120. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_mm.py +0 -0
  121. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/sequence_setup.py +0 -0
  122. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/cbtesting/stripping_seq.py +0 -0
  123. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/executabletests/2016_MagDown_PromptMC_D02KK_autoconf.py +0 -0
  124. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/executabletests/2016_MagUp_PromptMC_D02KK_autoconf.py +0 -0
  125. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/executabletests/info.yaml +0 -0
  126. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/data-pkg-repo/executabletests/ntuple_options.py +0 -0
  127. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-failed-to-read-file.log +0 -0
  128. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-illegal-instruction.log +0 -0
  129. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-missing-shared-library.log +0 -0
  130. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-platform-unsupported.log +0 -0
  131. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/error-related-info-missing.log +0 -0
  132. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/good-DaVinci_00110296_00000038_1.log +0 -0
  133. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/good-DaVinci_00110296_00000194_1.log +0 -0
  134. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/data/example-logs/good-Gauss_00104988_00000011_1.log +0 -0
  135. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/fixture_loader.py +0 -0
  136. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/fixtures/executabletests/2016_MagDown_PromptMC_D02KK/query_result.json +0 -0
  137. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/fixtures/executabletests/2016_MagUp_PromptMC_D02KK/query_result.json +0 -0
  138. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/fixtures/executabletests/manifest.yaml +0 -0
  139. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/reslice_fixture.py +0 -0
  140. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test-input-dst/00070767_00000711_7.AllStreams_slice.dst +0 -0
  141. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test-input-dst/00070793_00000685_7.AllStreams_slice.dst +0 -0
  142. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_cli.py +0 -0
  143. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_execute.py +0 -0
  144. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_generate.py +0 -0
  145. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_local_datapkg.py +0 -0
  146. {lbaplocal-0.10.0 → lbaplocal-0.10.2}/tests/test_log_parse.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: LbAPLocal
3
- Version: 0.10.0
3
+ Version: 0.10.2
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
@@ -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
- inside_ap_datapkg(state.repo_config.xenv_file)
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
- try:
1671
- check_production(production_name)
1672
- except Exception as e:
1673
- err_console.print(f"[bold red]✗ Production not found:[/bold red] {e}")
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
- # Parse info.yaml
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 job_data:
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 job_data:
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 = list(available_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
- info_yaml_path = Path(folder) / "info.yaml"
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
- info_yaml_path = Path(folder) / "info.yaml"
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 info.yaml")
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
- inside_ap_datapkg(state.repo_config.xenv_file)
1842
- productions_list = list(available_productions())
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
- inside_ap_datapkg(state.repo_config.xenv_file)
1857
- check_production(production_name)
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
- inside_ap_datapkg(state.repo_config.xenv_file)
3496
- productions_list = list(available_productions())
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
- inside_ap_datapkg(state.repo_config.xenv_file)
3690
- productions_list = list(available_productions())
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
- inside_ap_datapkg(state.repo_config.xenv_file)
3708
- check_production(production_name)
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 using LbAPCommon
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
- # Run LbAPCommon main to generate production request
3788
- # Use --only-include to generate just the job we want
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]✓ Saved DIRAC production request:[/green] {prod_yaml_path.name}"
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 to check if CMT is needed
4084
- application_name, application_version = jobs_data[job_name]["application"].rsplit(
4085
- "/", 1
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
- # Note: prod_key may be a grouped name like "job1,job2" while job_name is just "job1"
4125
- for filename, content in (
4126
- metadata["productions"][prod_key].get("dynamic_files", {}).items()
4127
- ):
4128
- file_path = dynamic_dir / filename
4129
- file_path.parent.mkdir(parents=True, exist_ok=True)
4130
- file_path.write_text(content)
4131
- console.print(f"[green]✓ Created dynamic file:[/green] {file_path}")
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.upper() in [k.upper() for k in known_working_groups]
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
- ).upper()
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