diff --git a/GaudiConf/python/GaudiConf/LbExec/__init__.py b/GaudiConf/python/GaudiConf/LbExec/__init__.py index fc3181391edcaa9c64448dec029e1e24a5c93c37..519d54208ec1111d7488a03a57cbe636604932be 100644 --- a/GaudiConf/python/GaudiConf/LbExec/__init__.py +++ b/GaudiConf/python/GaudiConf/LbExec/__init__.py @@ -9,7 +9,7 @@ # or submit itself to any jurisdiction. # ############################################################################### __all__ = ("InputProcessTypes", "DataTypeEnum", "HltSourceID", "FileFormats", - "EventStores", "Options", "main") + "EventStores", "Options", "TestOptions", "TestOptionsBase", "main") import os import sys @@ -17,7 +17,7 @@ import sys import click from .configurables import config2opts, do_export -from .options import InputProcessTypes, HltSourceID, DataTypeEnum, FileFormats, EventStores, Options +from .options import InputProcessTypes, HltSourceID, DataTypeEnum, FileFormats, EventStores, Options, TestOptions, TestOptionsBase def main(function, diff --git a/GaudiConf/python/GaudiConf/LbExec/__main__.py b/GaudiConf/python/GaudiConf/LbExec/__main__.py index 9ba5b725a9867a7f1425cff30017d99a7daa2b8b..42cef328a00b2dec21f83bdc4418f2c4f5e14f16 100644 --- a/GaudiConf/python/GaudiConf/LbExec/__main__.py +++ b/GaudiConf/python/GaudiConf/LbExec/__main__.py @@ -43,6 +43,12 @@ def parse_args(): action="store_true", help="Include options set to default values (for use with --export)", ) + parser.add_argument( + "--override-option-class", + help= + "Allows to override the option class specified in the function signature." + "Given in the form 'my_module:ClassName'.", + ) parser.add_argument( "--app-type", default="Gaudi::Application", @@ -50,7 +56,8 @@ def parse_args(): # "Name of the Gaudi application to use, primarily needed for Online." ) kwargs = vars(parser.parse_args()) - kwargs["options"] = OptionsLoader(kwargs["function"], kwargs["options"]) + kwargs["options"] = OptionsLoader(kwargs["function"], kwargs["options"], + kwargs.pop("override_option_class")) return main(**kwargs) diff --git a/GaudiConf/python/GaudiConf/LbExec/cli_utils.py b/GaudiConf/python/GaudiConf/LbExec/cli_utils.py index a5eef4fe7b8f6a0f7260466aad5bb488649a5c1a..eb7e089ceb43a5d8f4788f67a7b2bd6a18ea2cec 100644 --- a/GaudiConf/python/GaudiConf/LbExec/cli_utils.py +++ b/GaudiConf/python/GaudiConf/LbExec/cli_utils.py @@ -162,7 +162,9 @@ class FunctionLoader: return OptionsClass -def OptionsLoader(function: FunctionLoader, options_spec: str) -> OptionsBase: +def OptionsLoader(function: FunctionLoader, + options_spec: str, + options_class: Optional[str] = None) -> OptionsBase: """Convert a '+' separated list of paths to an Application.Options object.""" if options_spec.endswith((".yaml", ".yml", ".json")): # Load and merge the various input YAML files @@ -195,8 +197,17 @@ def OptionsLoader(function: FunctionLoader, options_spec: str) -> OptionsBase: _config_from_fsr(options) # Parse the merged YAML + if options_class: + options_class = FunctionLoader(options_class)._function + if not issubclass(options_class, function.OptionsClass): + log_error( + f"Override OptionsClass {options_class!r} should be subclass of {function.OptionsClass!r}" + ) + sys.exit(1) + else: + options_class = function.OptionsClass try: - return function.OptionsClass.parse_obj(options) + return options_class.parse_obj(options) except pydantic.ValidationError as e: errors = e.errors() log_error(f"Failed to validate options! Found {len(errors)} errors:") diff --git a/GaudiConf/python/GaudiConf/LbExec/options.py b/GaudiConf/python/GaudiConf/LbExec/options.py index 20bc58cfad94c9d3f558c0e409a8ac4133707e6d..2119697218fd7896b8926619504897a934c8299f 100644 --- a/GaudiConf/python/GaudiConf/LbExec/options.py +++ b/GaudiConf/python/GaudiConf/LbExec/options.py @@ -331,3 +331,53 @@ def _expand_braces_impl(text, seen, substitutions): replaced[start:stop] = f"\uE000{replacement}\uE000" yield from _expand_braces_impl("".join(replaced), seen, substitutions) + + +class TestOptionsBase(BaseModel): + """Specialized Options class only to be inherited when building an Option class dedicated to tests + + Essentially allows to use the TestFileDB for inputs, setting a number + of options (input files, geometry and condition version, ...) from the + given entry in TestFileDB + """ + testfiledb_key: str + + @root_validator(pre=True) + def validate_input(cls, data): + from PRConfig import TestFileDB + if not isinstance(data, dict): + return data + if "input_files" in data: + raise ValueError( + "Cannot set input_files directly, set testfiledb_key instead") + if "testfiledb_key" not in data: + raise ValueError("testfiledb_key is missing") + if data["testfiledb_key"] not in TestFileDB.test_file_db: + raise ValueError( + f"Unknown entry to TestFileDB : {data['testfiledb_key']}") + tfdb_entry = TestFileDB.test_file_db.get(data["testfiledb_key"]) + qualifiers = tfdb_entry.qualifiers + data["input_files"] = tfdb_entry.filenames + # for all other fields, allow overriding by the original yaml + # so only set the value to the TestFileDB version is not already present + if "input_type" not in data: + file_format = qualifiers['Format'] + data["input_type"] = 'ROOT' if file_format != 'MDF' else 'RAW' + if "data_type" not in data: + data["data_type"] = qualifiers['DataType'] + if "simulation" not in data: + data["simulation"] = qualifiers['Simulation'] + if "dddb_tag" not in data: + data["dddb_tag"] = qualifiers['DDDB'] + if "conddb_tag" not in data: + data["conddb_tag"] = qualifiers['CondDB'] + if "GeometryVersion" in qualifiers and "geometry_version" not in data: + data["geometry_version"] = qualifiers["GeometryVersion"] + if "ConditionsVersion" in qualifiers and "conditions_version" not in data: + data["conditions_version"] = qualifiers["ConditionsVersion"] + return data + + +class TestOptions(Options, TestOptionsBase): + """Specialized Options class for LHCb Tests""" + pass diff --git a/GaudiConf/python/GaudiConf/QMTest/LHCbTest.py b/GaudiConf/python/GaudiConf/QMTest/LHCbTest.py index bcec204a319bb76f8bb892acf988b9dc62f8251c..7eabb9f7d4cf3401fffda0a29aedff91a8063c99 100644 --- a/GaudiConf/python/GaudiConf/QMTest/LHCbTest.py +++ b/GaudiConf/python/GaudiConf/QMTest/LHCbTest.py @@ -605,7 +605,6 @@ class LHCbTest(GaudiTesting.QMTTest.QMTTest): def __init__(self, *args, **kwargs): """Adds support for creating lbexec's yaml files for qmt tests""" self.prerequisites = "" - self.test_file_db_options_yaml = "" self.options_yaml_fn = "" self.extra_options_yaml = "" super().__init__(*args, **kwargs) @@ -633,12 +632,6 @@ class LHCbTest(GaudiTesting.QMTTest.QMTTest): lbexec_options.update(yaml.safe_load(options_yaml)) if self.extra_options_yaml: lbexec_options.update(yaml.safe_load(self.extra_options_yaml)) - if self.test_file_db_options_yaml: - from PRConfig.TestFileDB import test_file_db - - tfdb_entry = test_file_db[self.test_file_db_options_yaml] - lbexec_options = tfdb_entry.make_lbexec_options( - **lbexec_options).dict(exclude_unset=True) return lbexec_options def run(self): diff --git a/GaudiConf/python/GaudiConf/QMTest/tests/lbexec_testfiledb.qmt b/GaudiConf/python/GaudiConf/QMTest/tests/lbexec_testfiledb.qmt index 71da5b9adfc079bf42bd1a1a1f6eef41e11c37ec..15a45ce3f5a6cdcbf83e1458c05dcafa23527fbe 100644 --- a/GaudiConf/python/GaudiConf/QMTest/tests/lbexec_testfiledb.qmt +++ b/GaudiConf/python/GaudiConf/QMTest/tests/lbexec_testfiledb.qmt @@ -11,13 +11,12 @@ --> <extension class="GaudiTest.GaudiExeTest" kind="test"> <argument name="program"><text>lbexec</text></argument> - <argument name="test_file_db_options_yaml"><text>Upgrade_Bd2KstarMuMu</text></argument> <argument name="extra_options_yaml"><text> - simulation: true - data_type: Upgrade + testfiledb_key: "Upgrade_Bd2KstarMuMu" </text></argument> <argument name="args"><set> <text>lbexec_example:check_input_files</text> + <text>--override-option-class=GaudiConf.LbExec:TestOptions</text> <text>--dry-run</text> </set></argument> </extension>