Skip to content

Commit

Permalink
interpreterbase: Add deprecated_values and since_values to KwargInfo
Browse files Browse the repository at this point in the history
This allows checking specific values that are added or deprecated, which
we do a surprising amount of. This works with both containers and scalar
values
  • Loading branch information
dcbaker committed Jun 14, 2021
1 parent 6490b13 commit 5bb75dc
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 0 deletions.
30 changes: 30 additions & 0 deletions mesonbuild/interpreterbase/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,22 +290,30 @@ class KwargInfo(T.Generic[_T]):
different type. This is intended for cases such as the meson DSL using a
string, but the implementation using an Enum. This should not do
validation, just converstion.
:param deprecated_values: a dictionary mapping a value to the version of
meson it was deprecated in.
:param since: a dictionary mapping a value to the version of meson it was
added in.
"""

def __init__(self, name: str, types: T.Union[T.Type[_T], T.Tuple[T.Type[_T], ...], ContainerTypeInfo],
*, required: bool = False, listify: bool = False,
default: T.Optional[_T] = None,
since: T.Optional[str] = None,
since_values: T.Optional[T.Dict[str, str]] = None,
deprecated: T.Optional[str] = None,
deprecated_values: T.Optional[T.Dict[str, str]] = None,
validator: T.Optional[T.Callable[[_T], T.Optional[str]]] = None,
convertor: T.Optional[T.Callable[[_T], TYPE_nvar]] = None):
self.name = name
self.types = types
self.required = required
self.listify = listify
self.default = default
self.since_values = since_values
self.since = since
self.deprecated = deprecated
self.deprecated_values = deprecated_values
self.validator = validator
self.convertor = convertor

Expand Down Expand Up @@ -368,6 +376,28 @@ def wrapper(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
msg = info.validator(value)
if msg is not None:
raise InvalidArguments(f'{name} keyword argument "{info.name}" {msg}')

warn: bool
if info.deprecated_values is not None:
for n, version in info.deprecated_values.items():
if isinstance(value, (dict, list)):
warn = n in value
else:
warn = n == value

if warn:
FeatureDeprecated.single_use(f'"{name}" keyword argument "{info.name}" value "{n}"', version, subproject)

if info.since_values is not None:
for n, version in info.since_values.items():
if isinstance(value, (dict, list)):
warn = n in value
else:
warn = n == value

if warn:
FeatureNew.single_use(f'"{name}" keyword argument "{info.name}" value "{n}"', version, subproject)

elif info.required:
raise InvalidArguments(f'{name} is missing required keyword argument "{info.name}"')
else:
Expand Down
39 changes: 39 additions & 0 deletions run_unittests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,45 @@ def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, MachineChoice]) -> None:

_(None, mock.Mock(), tuple(), dict(native=True))

@mock.patch.dict(mesonbuild.mesonlib.project_meson_versions, {'': '1.0'})
def test_typed_kwarg_since_values(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', ContainerTypeInfo(list, str), listify=True, default=[], deprecated_values={'foo': '0.9'}, since_values={'bar': '1.1'}),
KwargInfo('output', ContainerTypeInfo(dict, str), default={}, deprecated_values={'foo': '0.9'}, since_values={'bar': '1.1'}),
KwargInfo(
'mode', str,
validator=lambda x: 'Should be one of "clean", "build", "rebuild"' if x not in {'clean', 'build', 'rebuild', 'deprecated', 'since'} else None,
deprecated_values={'deprecated': '1.0'},
since_values={'since': '1.1'}),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
pass

with mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'input': ['foo']})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*deprecated since '0.9': "testfunc" keyword argument "input" value "foo".*""")

with mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'input': ['bar']})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "input" value "bar".*""")

with mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'output': {'foo': 'a'}})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*deprecated since '0.9': "testfunc" keyword argument "output" value "foo".*""")

with mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'output': {'bar': 'b'}})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "output" value "bar".*""")

with mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'mode': 'deprecated'})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*deprecated since '1.0': "testfunc" keyword argument "mode" value "deprecated".*""")

with mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'mode': 'since'})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "mode" value "since".*""")


@unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release')
class DataTests(unittest.TestCase):
Expand Down

0 comments on commit 5bb75dc

Please sign in to comment.