Skip to content

Commit

Permalink
Use signature for argument conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD committed Jun 29, 2022
1 parent 02ea5c0 commit a01b66c
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 83 deletions.
4 changes: 4 additions & 0 deletions hypothesis-python/RELEASE.rst
@@ -0,0 +1,4 @@
RELEASE_TYPE: patch

This patch tidies up some internal introspection logic, which will improve
support for positional-only arguments in a future release (:issue:`2706`).
87 changes: 17 additions & 70 deletions hypothesis-python/src/hypothesis/internal/reflection.py
Expand Up @@ -176,44 +176,9 @@ def convert_keyword_arguments(function, args, kwargs):
passed as positional and keyword args to the function. Unless function has
kwonlyargs or **kwargs the dictionary will always be empty.
"""
argspec = getfullargspec_except_self(function)
new_args = []
kwargs = dict(kwargs)

defaults = dict(argspec.kwonlydefaults or {})

if argspec.defaults:
for name, value in zip(
argspec.args[-len(argspec.defaults) :], argspec.defaults
):
defaults[name] = value

n = max(len(args), len(argspec.args))

for i in range(n):
if i < len(args):
new_args.append(args[i])
else:
arg_name = argspec.args[i]
if arg_name in kwargs:
new_args.append(kwargs.pop(arg_name))
elif arg_name in defaults:
new_args.append(defaults[arg_name])
else:
raise TypeError(f"No value provided for argument {arg_name!r}")

if kwargs and not (argspec.varkw or argspec.kwonlyargs):
if len(kwargs) > 1:
raise TypeError(
"%s() got unexpected keyword arguments %s"
% (function.__name__, ", ".join(map(repr, kwargs)))
)
else:
bad_kwarg = next(iter(kwargs))
raise TypeError(
f"{function.__name__}() got an unexpected keyword argument {bad_kwarg!r}"
)
return tuple(new_args), kwargs
sig = inspect.signature(function, follow_wrapped=False)
bound = sig.bind(*args, **kwargs)
return bound.args, bound.kwargs


def convert_positional_arguments(function, args, kwargs):
Expand All @@ -222,38 +187,20 @@ def convert_positional_arguments(function, args, kwargs):
new_args will only be non-empty if function has a variadic argument.
"""
argspec = getfullargspec_except_self(function)
new_kwargs = dict(argspec.kwonlydefaults or {})
new_kwargs.update(kwargs)
if not argspec.varkw:
for k in new_kwargs.keys():
if k not in argspec.args and k not in argspec.kwonlyargs:
raise TypeError(
f"{function.__name__}() got an unexpected keyword argument {k!r}"
)
if len(args) < len(argspec.args):
for i in range(len(args), len(argspec.args) - len(argspec.defaults or ())):
if argspec.args[i] not in kwargs:
raise TypeError(f"No value provided for argument {argspec.args[i]}")
for kw in argspec.kwonlyargs:
if kw not in new_kwargs:
raise TypeError(f"No value provided for argument {kw}")

if len(args) > len(argspec.args) and not argspec.varargs:
raise TypeError(
f"{function.__name__}() takes at most {len(argspec.args)} "
f"positional arguments ({len(args)} given)"
)

for arg, name in zip(args, argspec.args):
if name in new_kwargs:
raise TypeError(
f"{function.__name__}() got multiple values for keyword argument {name!r}"
)
else:
new_kwargs[name] = arg

return (tuple(args[len(argspec.args) :]), new_kwargs)
sig = inspect.signature(function, follow_wrapped=False)
bound = sig.bind(*args, **kwargs)
new_args = []
new_kwargs = dict(bound.arguments)
for p in sig.parameters.values():
if p.name in new_kwargs:
if p.kind is p.POSITIONAL_ONLY:
new_args.append(new_kwargs.pop(p.name))
elif p.kind is p.VAR_POSITIONAL:
new_args.extend(new_kwargs.pop(p.name))
elif p.kind is p.VAR_KEYWORD:
assert set(new_kwargs[p.name]).isdisjoint(set(new_kwargs) - {p.name})
new_kwargs.update(new_kwargs.pop(p.name))
return tuple(new_args), new_kwargs


def ast_arguments_matches_signature(args, sig):
Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/tests/cover/test_annotations.py
Expand Up @@ -78,7 +78,7 @@ def f(*, a, b=2):
pass

out = convert_positional_arguments(f, (), {"a": 1})
assert out == ((), {"a": 1, "b": 2})
assert out == ((), {"a": 1})


def test_converter_notices_missing_kwonly_args():
Expand Down
14 changes: 2 additions & 12 deletions hypothesis-python/tests/cover/test_reflection.py
Expand Up @@ -59,16 +59,6 @@ def foo(a, b, c):
do_conversion_test(foo, (1,), {"c": 2, "b": "foo"})


def test_populates_defaults():
def bar(x=[], y=1):
pass

assert convert_keyword_arguments(bar, (), {}) == (([], 1), {})
assert convert_keyword_arguments(bar, (), {"y": 42}) == (([], 42), {})
do_conversion_test(bar, (), {})
do_conversion_test(bar, (1,), {})


def test_leaves_unknown_kwargs_in_dict():
def bar(x, **kwargs):
pass
Expand Down Expand Up @@ -130,7 +120,7 @@ def test_positional_errors_if_too_many_args():
def foo(a):
pass

with raises(TypeError, match="2 given"):
with raises(TypeError, match="too many positional arguments"):
convert_positional_arguments(foo, (1, 2), {})


Expand All @@ -153,7 +143,7 @@ def test_positional_errors_if_given_bad_kwargs():
def foo(a):
pass

with raises(TypeError, match="unexpected keyword argument"):
with raises(TypeError, match="missing a required argument: 'a'"):
convert_positional_arguments(foo, (), {"b": 1})


Expand Down

0 comments on commit a01b66c

Please sign in to comment.