Skip to content

Commit

Permalink
MAINT: consolidate validation of options from build frontend
Browse files Browse the repository at this point in the history
PEP 517 specifies that the options are received as a dictionary with
string keys and string values, thus there is no need to verify that.
  • Loading branch information
dnicolodi committed Feb 9, 2023
1 parent da3ed13 commit e0363cc
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 52 deletions.
94 changes: 44 additions & 50 deletions mesonpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import argparse
import collections
import contextlib
import difflib
import functools
import importlib.machinery
import io
Expand Down Expand Up @@ -689,6 +690,44 @@ def _strings(value: Any, name: str) -> List[str]:
return scheme(table, 'tool.meson-python')


def _validate_config_settings(config_settings: Dict[str, Any]) -> Dict[str, Any]:
"""Validate options received from build frontend."""

def _string(value: Any, name: str) -> str:
if not isinstance(value, str):
raise ConfigError(f'only one value for "{name}" can be specified')
return value

def _bool(value: Any, name: str) -> bool:
return True

def _string_or_strings(value: Any, name: str) -> List[str]:
return list([value,] if isinstance(value, str) else value)

options = {
'builddir': _string,
'editable-verbose': _bool,
'dist-args': _string_or_strings,
'setup-args': _string_or_strings,
'compile-args': _string_or_strings,
'install-args': _string_or_strings,
}
assert all(f'{name}-args' in options for name in _MESON_ARGS_KEYS)

config = {}
for key, value in config_settings.items():
parser = options.get(key)
if parser is None:
matches = difflib.get_close_matches(key, options.keys(), n=2)
if matches:
alternatives = ' or '.join(f'"{match}"' for match in matches)
raise ConfigError(f'unknown option "{key}". did you mean {alternatives}?')
else:
raise ConfigError(f'unknown option "{key}"')
config[key] = parser(value, key)
return config


class Project():
"""Meson project wrapper to generate Python artifacts."""

Expand Down Expand Up @@ -1093,59 +1132,14 @@ def editable(self, directory: Path) -> pathlib.Path:
@contextlib.contextmanager
def _project(config_settings: Optional[Dict[Any, Any]]) -> Iterator[Project]:
"""Create the project given the given config settings."""
if config_settings is None:
config_settings = {}

# expand all string values to single element tuples and convert collections to tuple
config_settings = {
key: tuple(value) if isinstance(value, Collection) and not isinstance(value, str) else (value,)
for key, value in config_settings.items()
}

builddir_value = config_settings.get('builddir', {})
if len(builddir_value) > 0:
if len(builddir_value) != 1:
raise ConfigError('Only one value for configuration entry "builddir" can be specified')
builddir = builddir_value[0]
if not isinstance(builddir, str):
raise ConfigError(f'Configuration entry "builddir" should be a string not {type(builddir)}')
else:
builddir = None

def _validate_string_collection(key: str) -> None:
assert isinstance(config_settings, Mapping)
problematic_items: Sequence[Any] = list(filter(None, (
item if not isinstance(item, str) else None
for item in config_settings.get(key, ())
)))
if problematic_items:
s = ', '.join(f'"{item}" ({type(item)})' for item in problematic_items)
raise ConfigError(f'Configuration entries for "{key}" must be strings but contain: {s}')

meson_args_keys = _MESON_ARGS_KEYS
meson_args_cli_keys = tuple(f'{key}-args' for key in meson_args_keys)

for key in config_settings:
known_keys = ('builddir', 'editable-verbose', *meson_args_cli_keys)
if key not in known_keys:
import difflib
matches = difflib.get_close_matches(key, known_keys, n=3)
if len(matches):
alternatives = ' or '.join(f'"{match}"' for match in matches)
raise ConfigError(f'Unknown configuration entry "{key}". Did you mean {alternatives}?')
else:
raise ConfigError(f'Unknown configuration entry "{key}"')

for key in meson_args_cli_keys:
_validate_string_collection(key)
settings = _validate_config_settings(config_settings or {})
meson_args = {name: settings.get(f'{name}-args', []) for name in _MESON_ARGS_KEYS}

with Project.with_temp_working_dir(
build_dir=builddir,
meson_args=typing.cast(MesonArgs, {
key: config_settings.get(f'{key}-args', ())
for key in meson_args_keys
}),
editable_verbose=bool(config_settings.get('editable-verbose'))
build_dir=settings.get('builddir'),
meson_args=typing.cast(MesonArgs, meson_args),
editable_verbose=bool(settings.get('editable-verbose'))
) as project:
yield project

Expand Down
4 changes: 2 additions & 2 deletions tests/test_pep517.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def test_invalid_config_settings(capsys, package_pure, tmp_path_session):
method(tmp_path_session, {'invalid': ()})
out, err = capsys.readouterr()
assert out.splitlines()[-1].endswith(
'Unknown configuration entry "invalid"')
'unknown option "invalid"')


def test_invalid_config_settings_suggest(capsys, package_pure, tmp_path_session):
Expand All @@ -73,4 +73,4 @@ def test_invalid_config_settings_suggest(capsys, package_pure, tmp_path_session)
method(tmp_path_session, {'setup_args': ()})
out, err = capsys.readouterr()
assert out.splitlines()[-1].endswith(
'Unknown configuration entry "setup_args". Did you mean "setup-args" or "dist-args"?')
'unknown option "setup_args". did you mean "setup-args" or "dist-args"?')

0 comments on commit e0363cc

Please sign in to comment.