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.
Files changed (156) hide show
  1. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/PKG-INFO +4 -3
  2. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/pyproject.toml +6 -2
  3. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/main.py +196 -79
  4. lbaplocal-0.11.0/src/LbAPLocal/cwl/app_runner.py +507 -0
  5. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cwl/generate_replica_map.py +142 -95
  6. lbaplocal-0.11.0/src/LbAPLocal/cwl/quickjs_engine.py +111 -0
  7. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cwl/rewrite.py +57 -25
  8. lbaplocal-0.11.0/src/LbAPLocal/cwl/runner.py +139 -0
  9. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/handlers/mc_request.py +9 -4
  10. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/job_slice.py +8 -4
  11. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/PKG-INFO +4 -3
  12. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/SOURCES.txt +10 -0
  13. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/entry_points.txt +2 -0
  14. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/requires.txt +3 -2
  15. lbaplocal-0.11.0/src/LbAPLocal.egg-info/scm_file_list.json +150 -0
  16. lbaplocal-0.11.0/src/LbAPLocal.egg-info/scm_version.json +8 -0
  17. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/conftest.py +78 -73
  18. lbaplocal-0.11.0/tests/test_app_runner.py +101 -0
  19. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_cwl_cli.py +132 -26
  20. lbaplocal-0.11.0/tests/test_cwl_runner.py +240 -0
  21. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_execute.py +72 -1
  22. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_handlers.py +31 -2
  23. lbaplocal-0.11.0/tests/test_input_resolution.py +205 -0
  24. lbaplocal-0.11.0/tests/test_no_dirac_cwl.py +45 -0
  25. lbaplocal-0.11.0/tests/test_quickjs_engine.py +117 -0
  26. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.coveragerc +0 -0
  27. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.gitattributes +0 -0
  28. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.gitignore +0 -0
  29. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.gitlab-ci.yml +0 -0
  30. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/.pre-commit-config.yaml +0 -0
  31. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/CONTRIBUTING.md +0 -0
  32. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/LICENSE +0 -0
  33. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/README.md +0 -0
  34. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/example.json +0 -0
  35. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/setup.cfg +0 -0
  36. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/__init__.py +0 -0
  37. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/__init__.py +0 -0
  38. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/generate.py +0 -0
  39. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/legacy.py +0 -0
  40. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/textual_ui.py +0 -0
  41. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cli/wizards.py +0 -0
  42. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/cwl/__init__.py +0 -0
  43. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/handlers/__init__.py +0 -0
  44. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/handlers/anaprod.py +0 -0
  45. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/log_parsing.py +0 -0
  46. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/models.py +0 -0
  47. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/testing.py +0 -0
  48. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal/utils.py +0 -0
  49. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/dependency_links.txt +0 -0
  50. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/src/LbAPLocal.egg-info/top_level.txt +0 -0
  51. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/capture_fixtures.py +0 -0
  52. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK-2444].yaml +0 -0
  53. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK_noautoconf-2444].yaml +0 -0
  54. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK-2393].yaml +0 -0
  55. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK_noautoconf-2393].yaml +0 -0
  56. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/.gitignore +0 -0
  57. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/.gitlab-ci.yml +0 -0
  58. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/AnalysisProductions.xenv +0 -0
  59. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bc2Bspi2JpsiPhi.py +0 -0
  60. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar.py +0 -0
  61. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar_stripping.py +0 -0
  62. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2DsPi.py +0 -0
  63. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi.py +0 -0
  64. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt.py +0 -0
  65. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt_PVMixer.py +0 -0
  66. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Swave.py +0 -0
  67. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus.py +0 -0
  68. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus_stripping.py +0 -0
  69. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/LbJpsipK.py +0 -0
  70. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/README.md +0 -0
  71. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/__init__.py +0 -0
  72. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/stripping.py +0 -0
  73. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/tuple_maker.py +0 -0
  74. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/info.yaml +0 -0
  75. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/BsToJpsiPhi/sequence_setup.py +0 -0
  76. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/COPYING +0 -0
  77. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/README.md +0 -0
  78. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/Run1_Signal.py +0 -0
  79. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/Run2_Signal.py +0 -0
  80. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/helpers/__init__.py +0 -0
  81. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/helpers/signal_tuple_maker.py +0 -0
  82. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/RDs/info.yaml +0 -0
  83. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/README.md +0 -0
  84. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/__init__.py +0 -0
  85. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2011_datatype.py +0 -0
  86. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2012_datatype.py +0 -0
  87. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2015_datatype.py +0 -0
  88. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2016_datatype.py +0 -0
  89. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2017_datatype.py +0 -0
  90. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/2018_datatype.py +0 -0
  91. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/B0ToK0ee.py +0 -0
  92. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/B0ToK0mumu.py +0 -0
  93. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/BpToKee.py +0 -0
  94. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/BpToKmumu.py +0 -0
  95. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0ee.py +0 -0
  96. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0eeSS.py +0 -0
  97. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0emu.py +0 -0
  98. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0emuSS.py +0 -0
  99. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0mumu.py +0 -0
  100. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/LbToL0mumuSS.py +0 -0
  101. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/README.md +0 -0
  102. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/__init__.py +0 -0
  103. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/general_options.py +0 -0
  104. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/MC_tuple_maker.py +0 -0
  105. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/__init__.py +0 -0
  106. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/decay_descriptors.py +0 -0
  107. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/locations.py +0 -0
  108. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/related_info.py +0 -0
  109. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/stripping.py +0 -0
  110. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/tags.py +0 -0
  111. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/helpers/tuple_maker.py +0 -0
  112. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/info.yaml +0 -0
  113. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_DST.py +0 -0
  114. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FDST.py +0 -0
  115. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FMDST.py +0 -0
  116. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_MDST.py +0 -0
  117. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_ee.py +0 -0
  118. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_mm.py +0 -0
  119. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksee.py +0 -0
  120. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksmm.py +0 -0
  121. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_ee.py +0 -0
  122. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_mm.py +0 -0
  123. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_ee.py +0 -0
  124. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_mm.py +0 -0
  125. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_ee.py +0 -0
  126. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_mm.py +0 -0
  127. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lee.py +0 -0
  128. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lem.py +0 -0
  129. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lmm.py +0 -0
  130. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_ee.py +0 -0
  131. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_mm.py +0 -0
  132. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/sequence_setup.py +0 -0
  133. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/cbtesting/stripping_seq.py +0 -0
  134. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/executabletests/2016_MagDown_PromptMC_D02KK_autoconf.py +0 -0
  135. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/executabletests/2016_MagUp_PromptMC_D02KK_autoconf.py +0 -0
  136. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/executabletests/info.yaml +0 -0
  137. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/data-pkg-repo/executabletests/ntuple_options.py +0 -0
  138. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-failed-to-read-file.log +0 -0
  139. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-illegal-instruction.log +0 -0
  140. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-missing-shared-library.log +0 -0
  141. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-platform-unsupported.log +0 -0
  142. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/error-related-info-missing.log +0 -0
  143. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/good-DaVinci_00110296_00000038_1.log +0 -0
  144. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/good-DaVinci_00110296_00000194_1.log +0 -0
  145. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/data/example-logs/good-Gauss_00104988_00000011_1.log +0 -0
  146. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/fixture_loader.py +0 -0
  147. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/fixtures/executabletests/2016_MagDown_PromptMC_D02KK/query_result.json +0 -0
  148. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/fixtures/executabletests/2016_MagUp_PromptMC_D02KK/query_result.json +0 -0
  149. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/fixtures/executabletests/manifest.yaml +0 -0
  150. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/reslice_fixture.py +0 -0
  151. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test-input-dst/00070767_00000711_7.AllStreams_slice.dst +0 -0
  152. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test-input-dst/00070793_00000685_7.AllStreams_slice.dst +0 -0
  153. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_cli.py +0 -0
  154. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_generate.py +0 -0
  155. {lbaplocal-0.10.2 → lbaplocal-0.11.0}/tests/test_local_datapkg.py +0 -0
  156. {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.10.2
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.15.10
16
+ Requires-Dist: LbAPCommon>=0.19.0
17
17
  Requires-Dist: LbProdRun>1.12.2
18
- Requires-Dist: dirac-cwl>=1.1.3
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.15.10",
22
+ "LbAPCommon>=0.19.0",
23
23
  "LbProdRun>1.12.2",
24
- "dirac-cwl>=1.1.3",
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
- "dirac-cwl",
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: Rewrite CWL for custom input (if provided)
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
- if dependent_input:
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] {dependent_input}"
3962
+ f"\n[bold cyan]Rewriting CWL for custom input:[/bold cyan] {input_value}"
3943
3963
  )
3944
3964
  try:
3945
- cwl_workflow_path, cwl_inputs_path = rewrite_cwl_for_custom_input(
3946
- cwl_workflow_path, [dependent_input], job_name
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
- # Generate replica map for LFN custom inputs so the executor can resolve them
3960
- if dependent_input.startswith("LFN:"):
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
- "\n[bold cyan]Generating replica map for custom input LFN[/bold cyan]"
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 LbAPLocal.cwl.generate_replica_map module[/bold red]"
4055
+ "[bold red]✗ Could not find generate_replica_map module[/bold red]"
3970
4056
  )
3971
4057
  raise typer.Exit(code=1)
3972
4058
 
3973
- map_path = output_dir / f"{cwl_workflow_path.stem}-replica-map.json"
3974
- # Strip the LFN: prefix — DIRAC APIs expect bare paths
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
- "--lfns",
3981
- lfn_path,
4065
+ str(cwl_workflow_path),
4066
+ "--output-yaml",
4067
+ str(cwl_inputs_path),
3982
4068
  "--output-map",
3983
- str(map_path),
4069
+ str(replica_map_path),
3984
4070
  ]
3985
- result = subprocess.run(cmd_map, capture_output=True, text=True)
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]✗ Failed to generate replica map:[/bold red]\n{error_output}"
4090
+ f"[bold red]✗ Input resolution failed:[/bold red]\n{error_output}"
3991
4091
  )
3992
4092
  raise typer.Exit(code=1)
3993
- replica_map_path = map_path
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
- executor,
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
- log_files = list(cwl_output_dir.glob("**/*.log"))
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 in cwl_output directory only
4297
- log_files = list(cwl_output_dir.glob("**/*.log"))
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: