def parse_args_to_config(argv: Sequence[str]) -> Config:
"""Construct an argument parser and parse string arguments to a config.
Args:
argv: Sequence of string arguments.
Returns:
Configuration.
"""
parser = argparse.ArgumentParser(
description="""\
Task Performance Suite (TaPS) CLI.
Application benchmarks can be configured via CLI options, a TOML
configuration file, or a mix of both. CLI options take precedence
over configuration files.
The default behavior of -h/--help is to show only the minimally
relevant set of options. For example, only the process-pool
executor options will be shown if --engine.executor process-pool
is specified; the options for other executors will be suppressed.
This behavior applies to all plugin types.
""",
prog='python -m taps.run',
formatter_class=_ArgparseFormatter,
)
parser.add_argument(
'--config',
'-c',
default=argparse.SUPPRESS,
nargs='+',
help=(
'Base toml configuration files to load '
'(file are parsed in order).'
),
)
app_group = parser.add_argument_group('app options')
app_group.add_argument(
'--app',
choices=list(get_app_configs().keys()),
default=argparse.SUPPRESS,
dest='app.name',
metavar='APP',
help='App choice {%(choices)s}. (required)',
)
engine_group = parser.add_argument_group(
'engine options',
description='Engine configuration.',
)
engine_group.add_argument(
'--engine.executor',
'--executor',
choices=sorted(get_executor_configs().keys()),
default=argparse.SUPPRESS,
dest='engine.executor.name',
metavar='EXECUTOR',
help='Executor choice {%(choices)s}. (default: process-pool)',
)
engine_group.add_argument(
'--engine.filter',
'--filter',
choices=sorted([*get_filter_configs().keys(), 'none']),
default=argparse.SUPPRESS,
dest='engine.filter.name',
metavar='FILTER',
help='Filter choice {%(choices)s}. (default: none)',
)
engine_group.add_argument(
'--engine.transformer',
'--transformer',
choices=sorted([*get_transformer_configs().keys(), 'none']),
default=argparse.SUPPRESS,
dest='engine.transformer.name',
metavar='TRANSFORMER',
help='Transformer choice {%(choices)s}. (default: none)',
)
if len(argv) == 0 or argv[0] in ['-h', '--help']:
# Shortcut to print help output if no args or just -h/--help
# are provided.
parser.parse_args(['--help']) # pragma: no cover
argv = list(argv)
# Strip --help from argv so we can quickly parse the base options
# to figure out which config types we will need to use. --help
# will be parsed again by CliSettingsSource.
_argv = list(filter(lambda v: v not in ['-h', '--help'], argv))
base_options = vars(parser.parse_known_args(_argv)[0])
base_options = {k: v for k, v in base_options.items() if v is not None}
config_files = base_options.pop('config', [])
toml_options: dict[str, Any] = {}
for config_file in config_files:
toml_options.update(flatten_mapping(_parse_toml_options(config_file)))
# base_options takes precedence over toml_options if there are
# matching keys.
base_options = {**toml_options, **base_options}
if 'app.name' not in base_options or base_options['app.name'] is None:
raise ValueError(
'App name option is required. Either pass --app {APP} via the'
'CLI arguments or add the app.name attribute to the config '
'file with --config {PATH}.',
)
app_group.description = (
f'App configuration (selected: {base_options["app.name"]}).'
)
settings_cls = _make_config_cls(base_options)
base_namespace = argparse.Namespace(**base_options)
cli_settings: CliSettingsSource[Config] = CliSettingsSource(
settings_cls,
cli_avoid_json=True,
# cli_parse_args is annotated as:
# "bool | list[str] | tuple[str, ...] | None"
# which is suitable for argv with is Sequence[str].
cli_parse_args=argv,
cli_parse_none_str='none',
cli_use_class_docs_for_groups=False,
root_parser=parser,
add_argument_method=_add_argument,
add_argument_group_method=_add_argument_group,
parse_args_method=functools.partial(
_parse_args,
namespace=base_namespace,
),
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
return settings_cls(_cli_settings_source=cli_settings)