LbAPCommon 0.19.2__tar.gz → 0.19.4__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 (98) hide show
  1. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/PKG-INFO +1 -1
  2. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/models/__init__.py +7 -2
  3. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/models/recipes.py +10 -0
  4. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/models/validators.py +10 -0
  5. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/transforms/yaml-transform.js +4 -2
  6. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon.egg-info/PKG-INFO +1 -1
  7. lbapcommon-0.19.4/src/LbAPCommon.egg-info/scm_version.json +8 -0
  8. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_good_parsing.py +22 -0
  9. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_transforms.py +40 -0
  10. lbapcommon-0.19.2/src/LbAPCommon.egg-info/scm_version.json +0 -8
  11. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/.gitignore +0 -0
  12. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/.gitlab-ci.yml +0 -0
  13. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/.pre-commit-config.yaml +0 -0
  14. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/LICENSE +0 -0
  15. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/README.md +0 -0
  16. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/environment.yaml +0 -0
  17. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/pixi.lock +0 -0
  18. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/pixi.toml +0 -0
  19. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/pyproject.toml +0 -0
  20. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/setup.cfg +0 -0
  21. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/__init__.py +0 -0
  22. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/__main__.py +0 -0
  23. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/app_catalog.py +0 -0
  24. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/cern_sso.py +0 -0
  25. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/config.py +0 -0
  26. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/data/__init__.py +0 -0
  27. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/data/ap_merger.py +0 -0
  28. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/data/query_dirac.py +0 -0
  29. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/data/templates/configure-legacy-application.py.j2 +0 -0
  30. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/dirac_conversion.py +0 -0
  31. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/hacks.py +0 -0
  32. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/linting/__init__.py +0 -0
  33. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/linting/bk_paths.py +0 -0
  34. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/options_parsing/__init__.py +0 -0
  35. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/options_parsing/gaudi_pickle_to_json.py +0 -0
  36. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/parsing.py +0 -0
  37. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/prod_request_to_cwl/__init__.py +0 -0
  38. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/prod_request_to_cwl/__main__.py +0 -0
  39. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/prod_request_to_cwl/anaprod.py +0 -0
  40. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/prod_request_to_cwl/common.py +0 -0
  41. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/prod_request_to_cwl/simulation.py +0 -0
  42. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/transforms/__init__.py +0 -0
  43. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/transforms/runner.py +0 -0
  44. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/transforms/vendor/LICENSE-yaml +0 -0
  45. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/transforms/vendor/yaml.bundle.js +0 -0
  46. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/validators/__init__.py +0 -0
  47. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/validators/bookkeeping_xml.py +0 -0
  48. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/validators/check_gen_fsr.py +0 -0
  49. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/validators/counters.py +0 -0
  50. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/validators/logs/__init__.py +0 -0
  51. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/validators/logs/data/known_messages.yaml +0 -0
  52. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon/workflow.py +0 -0
  53. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon.egg-info/SOURCES.txt +0 -0
  54. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon.egg-info/dependency_links.txt +0 -0
  55. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon.egg-info/entry_points.txt +0 -0
  56. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon.egg-info/requires.txt +0 -0
  57. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon.egg-info/scm_file_list.json +0 -0
  58. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/src/LbAPCommon.egg-info/top_level.txt +0 -0
  59. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/conftest.py +0 -0
  60. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/error-failed-to-read-file.log +0 -0
  61. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/error-fill-dependent-scale-factor.log +0 -0
  62. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/error-illegal-instruction.log +0 -0
  63. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/error-missing-shared-library.log +0 -0
  64. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/error-platform-unsupported.log +0 -0
  65. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/error-related-info-missing.log +0 -0
  66. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/error-rootioalg.log +0 -0
  67. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/error-unpack-raw-event.log +0 -0
  68. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/good-DaVinci_00110296_00000038_1.log +0 -0
  69. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/good-DaVinci_00110296_00000194_1.log +0 -0
  70. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example-logs/good-Gauss_00104988_00000011_1.log +0 -0
  71. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_filtering/AnaProd_example_workflows_job_tuple,split,filter_B0.cwl +0 -0
  72. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_filtering.in.yaml +0 -0
  73. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_filtering.out.yaml +0 -0
  74. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_filtering_two_groups/AnaProd_example_workflows_job2_tuple,split,filter_B0,S0.cwl +0 -0
  75. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_filtering_two_groups/AnaProd_example_workflows_job_tuple,split,filter_B0,S0.cwl +0 -0
  76. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_filtering_two_groups.in.yaml +0 -0
  77. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_filtering_two_groups.out.yaml +0 -0
  78. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_layers_of_splitting/AnaProd_example_workflows_xicpststtoxicppipi_data_2018_magdown_split,_xicpstst,0st,pststwspmm.cwl +0 -0
  79. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_layers_of_splitting.in.yaml +0 -0
  80. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/complex_workflow_with_layers_of_splitting.out.yaml +0 -0
  81. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/mcreweight_and_pidgen2/AnaProd_example_workflows_noPID_block5_b0_dstdx_e_SPRUCE,DV,mva_rank,cmatch,PIDGen,mcreweight.cwl +0 -0
  82. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/mcreweight_and_pidgen2.in.yaml +0 -0
  83. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/example_workflows/mcreweight_and_pidgen2.out.yaml +0 -0
  84. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/linting/test_bk_paths.py +0 -0
  85. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/linting/test_processing_pass.py +0 -0
  86. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_app_catalog.py +0 -0
  87. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_auto_configure.py +0 -0
  88. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_auto_configure_dstformat.py +0 -0
  89. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_auto_configure_streams.py +0 -0
  90. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_autoconfig_logging.py +0 -0
  91. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_cwl_contract.py +0 -0
  92. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_dirac_conversion.py +0 -0
  93. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_hacks.py +0 -0
  94. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_log_parsing.py +0 -0
  95. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_log_splitting.py +0 -0
  96. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_performance/example1.yaml +0 -0
  97. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_performance.py +0 -0
  98. {lbapcommon-0.19.2 → lbapcommon-0.19.4}/tests/test_platform_selection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: LbAPCommon
3
- Version: 0.19.2
3
+ Version: 0.19.4
4
4
  Summary: Common utilities used by LHCb DPA WP2 related software
5
5
  Author: LHCb
6
6
  Project-URL: Homepage, https://gitlab.cern.ch/lhcb-dpa/analysis-productions/LbAPCommon
@@ -33,7 +33,10 @@ from pydantic import (
33
33
 
34
34
  from LbAPCommon import config as config
35
35
  from LbAPCommon.linting.bk_paths import InvalidBKQueryError, validate_bk_query
36
- from LbAPCommon.models.validators import output_filetype_validator
36
+ from LbAPCommon.models.validators import (
37
+ output_filetype_validator,
38
+ unique_output_filetypes_validator,
39
+ )
37
40
 
38
41
  from .recipes import AllRecipes
39
42
 
@@ -477,7 +480,9 @@ class APJob(BaseModel):
477
480
  discriminator=Discriminator(input_discriminator),
478
481
  )
479
482
 
480
- output: list[OutputFileType] = Field(
483
+ output: Annotated[
484
+ list[OutputFileType], AfterValidator(unique_output_filetypes_validator)
485
+ ] = Field(
481
486
  title="Output files",
482
487
  description=(
483
488
  "List of output file types that will be produced by this job. "
@@ -44,6 +44,16 @@ class SplittingRecipe(BaseModel):
44
44
 
45
45
  split: list[SplitHow]
46
46
 
47
+ allow_missing: bool = Field(
48
+ default=False,
49
+ title="Allow missing trees",
50
+ description=(
51
+ "If true, objects in the input file that match none of the 'split' key patterns "
52
+ "are silently dropped instead of failing the job. Use this to keep only a subset "
53
+ "of the input trees."
54
+ ),
55
+ )
56
+
47
57
 
48
58
  class FilteringRecipe(BaseModel):
49
59
  """Recipe for filtering ROOT files using a custom entrypoint."""
@@ -25,3 +25,13 @@ def output_filetype_validator(v: str) -> str:
25
25
  "It appears the data taking year is included. Please remove it!"
26
26
  )
27
27
  return v
28
+
29
+
30
+ def unique_output_filetypes_validator(v: list[str]) -> list[str]:
31
+ duplicates = sorted({filetype for filetype in v if v.count(filetype) > 1})
32
+ if duplicates:
33
+ raise ValueError(
34
+ f"Duplicate output filetype(s): {', '.join(duplicates)}. "
35
+ "Each output filetype may only be listed once per job."
36
+ )
37
+ return v
@@ -348,7 +348,9 @@ function expandSplitTrees(recipe, baseJob) {
348
348
  const into = String(split.into).toLowerCase().replace(/\.root$/i, "");
349
349
  return "--write=" + into + "=" + split.key;
350
350
  });
351
- const outputs = splits.map((split) => split.into);
351
+ // Multiple keys may be written into the same file; only declare it once
352
+ const outputs = [...new Set(splits.map((split) => split.into))];
353
+ const flags = recipe.allow_missing ? ["--allow-missing"] : [];
352
354
 
353
355
  return [
354
356
  {
@@ -361,7 +363,7 @@ function expandSplitTrees(recipe, baseJob) {
361
363
  optimise_baskets: false,
362
364
  },
363
365
  },
364
- extra_args: ["--", ...writeArgs],
366
+ extra_args: ["--", ...flags, ...writeArgs],
365
367
  },
366
368
  output: outputs,
367
369
  },
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: LbAPCommon
3
- Version: 0.19.2
3
+ Version: 0.19.4
4
4
  Summary: Common utilities used by LHCb DPA WP2 related software
5
5
  Author: LHCb
6
6
  Project-URL: Homepage, https://gitlab.cern.ch/lhcb-dpa/analysis-productions/LbAPCommon
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.19.4",
3
+ "distance": 0,
4
+ "node": "g73f997884d784d4aa417d6d89a726219ea90edd8",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-28"
8
+ }
@@ -167,6 +167,28 @@ def test_bad_end_run():
167
167
  parse_yaml(rendered_yaml)
168
168
 
169
169
 
170
+ def test_bad_duplicate_output_filetypes():
171
+ rendered_yaml = dedent("""\
172
+ job_1:
173
+ application: DaVinci/v45r3
174
+ input:
175
+ bk_query: /MC/2018/Beam6500GeV-2018-MagDown/Sim09g/Trig0x617d18a4/Reco18/24142001/ALLSTREAMS.DST
176
+ output:
177
+ - TREE.ROOT
178
+ - tree.root
179
+ options:
180
+ - options.py
181
+ wg: Charm
182
+ inform: cernuser
183
+ priority: 1a
184
+ """)
185
+ with pytest.raises(
186
+ ValueError,
187
+ match=r"Configuration validation failed(.|\n)*❌ job_1 -> output(.|\n)*[Dd]uplicate",
188
+ ):
189
+ parse_yaml(rendered_yaml)
190
+
191
+
170
192
  @pytest.mark.parametrize(
171
193
  "value,expected",
172
194
  [
@@ -346,6 +346,25 @@ class TestRecipeJobs:
346
346
  "--write=output1=tree1",
347
347
  ]
348
348
 
349
+ def test_split_trees_allow_missing(self):
350
+ input_yaml = textwrap.dedent("""
351
+ job1:
352
+ recipe:
353
+ name: split-trees
354
+ allow_missing: true
355
+ split:
356
+ - key: tree1
357
+ into: OUTPUT1.ROOT
358
+ """).strip()
359
+
360
+ result = yaml.safe_load(transform_yaml(input_yaml))
361
+
362
+ assert result["job1"]["options"]["extra_args"] == [
363
+ "--",
364
+ "--allow-missing",
365
+ "--write=output1=tree1",
366
+ ]
367
+
349
368
  def test_split_trees_generates_output(self):
350
369
  input_yaml = textwrap.dedent("""
351
370
  job1:
@@ -362,6 +381,27 @@ class TestRecipeJobs:
362
381
 
363
382
  assert result["job1"]["output"] == ["OUTPUT1.ROOT", "OUTPUT2.ROOT"]
364
383
 
384
+ def test_split_trees_deduplicates_shared_output(self):
385
+ input_yaml = textwrap.dedent("""
386
+ job1:
387
+ recipe:
388
+ name: split-trees
389
+ split:
390
+ - key: tree1$
391
+ into: TREE.ROOT
392
+ - key: tree2$
393
+ into: TREE.ROOT
394
+ """).strip()
395
+
396
+ result = yaml.safe_load(transform_yaml(input_yaml))
397
+
398
+ assert result["job1"]["output"] == ["TREE.ROOT"]
399
+ assert result["job1"]["options"]["extra_args"] == [
400
+ "--",
401
+ "--write=tree=tree1$",
402
+ "--write=tree=tree2$",
403
+ ]
404
+
365
405
  def test_filter_trees_sets_application(self):
366
406
  input_yaml = textwrap.dedent("""
367
407
  job1:
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.19.2",
3
- "distance": 0,
4
- "node": "gfaff33efef68eed53bc8e8c0430dadb4416a0ae2",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-14"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes