mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
fix(decorator): align var args + keywords behaviour with native python (#2252)
* fix(decorator): align kwargs behaviour with native python Previously, validate_arguments would strip the value if the keyword was the same as the target kwargs argument. Align with Python's behaviour. * fix: also prevent overwriting variable args Introduces tests for both, which compare against the native python implementation Co-authored-by: Eric Jolibois <em.jolibois@gmail.com> * mention about 'args' in the changelog too * simplify var_kwargs pop Co-authored-by: Eric Jolibois <em.jolibois@gmail.com> Co-authored-by: Eric Jolibois <em.jolibois@gmail.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
Fix `validate_decorator` so `**kwargs` doesn't exclude values when the keyword
|
||||
has the same name as the `*args` or `**kwargs` names.
|
||||
@@ -161,8 +161,9 @@ class ValidatedFunction:
|
||||
|
||||
var_kwargs = {}
|
||||
wrong_positional_args = []
|
||||
non_var_fields = set(self.model.__fields__) - {self.v_args_name, self.v_kwargs_name}
|
||||
for k, v in kwargs.items():
|
||||
if k in self.model.__fields__:
|
||||
if k in non_var_fields:
|
||||
if k in self.positional_only_args:
|
||||
wrong_positional_args.append(k)
|
||||
values[k] = v
|
||||
@@ -177,9 +178,7 @@ class ValidatedFunction:
|
||||
|
||||
def execute(self, m: BaseModel) -> Any:
|
||||
d = {k: v for k, v in m._iter() if k in m.__fields_set__}
|
||||
kwargs = d.pop(self.v_kwargs_name, None)
|
||||
if kwargs:
|
||||
d.update(kwargs)
|
||||
var_kwargs = d.pop(self.v_kwargs_name, {})
|
||||
|
||||
if self.v_args_name in d:
|
||||
args_: List[Any] = []
|
||||
@@ -193,7 +192,7 @@ class ValidatedFunction:
|
||||
in_kwargs = True
|
||||
else:
|
||||
args_.append(value)
|
||||
return self.raw_function(*args_, **kwargs)
|
||||
return self.raw_function(*args_, **kwargs, **var_kwargs)
|
||||
elif self.positional_only_args:
|
||||
args_ = []
|
||||
kwargs = {}
|
||||
@@ -202,9 +201,9 @@ class ValidatedFunction:
|
||||
args_.append(value)
|
||||
else:
|
||||
kwargs[name] = value
|
||||
return self.raw_function(*args_, **kwargs)
|
||||
return self.raw_function(*args_, **kwargs, **var_kwargs)
|
||||
else:
|
||||
return self.raw_function(**d)
|
||||
return self.raw_function(**d, **var_kwargs)
|
||||
|
||||
def create_model(self, fields: Dict[str, Any], takes_args: bool, takes_kwargs: bool, config: 'ConfigType') -> None:
|
||||
pos_args = len(self.arg_mapping)
|
||||
|
||||
@@ -110,15 +110,22 @@ def test_untyped():
|
||||
assert foo(1, {'x': 2}, c='3', d='4') == "1, {'x': 2}, 3, 4"
|
||||
|
||||
|
||||
def test_var_args_kwargs():
|
||||
@validate_arguments
|
||||
@pytest.mark.parametrize('validated', (True, False))
|
||||
def test_var_args_kwargs(validated):
|
||||
def foo(a, b, *args, d=3, **kwargs):
|
||||
return f'a={a!r}, b={b!r}, args={args!r}, d={d!r}, kwargs={kwargs!r}'
|
||||
|
||||
if validated:
|
||||
foo = validate_arguments(foo)
|
||||
|
||||
assert foo(1, 2) == 'a=1, b=2, args=(), d=3, kwargs={}'
|
||||
assert foo(1, 2, 3, d=4) == 'a=1, b=2, args=(3,), d=4, kwargs={}'
|
||||
assert foo(*[1, 2, 3], d=4) == 'a=1, b=2, args=(3,), d=4, kwargs={}'
|
||||
assert foo(1, 2, args=(10, 11)) == "a=1, b=2, args=(), d=3, kwargs={'args': (10, 11)}"
|
||||
assert foo(1, 2, 3, args=(10, 11)) == "a=1, b=2, args=(3,), d=3, kwargs={'args': (10, 11)}"
|
||||
assert foo(1, 2, 3, e=10) == "a=1, b=2, args=(3,), d=3, kwargs={'e': 10}"
|
||||
assert foo(1, 2, kwargs=4) == "a=1, b=2, args=(), d=3, kwargs={'kwargs': 4}"
|
||||
assert foo(1, 2, kwargs=4, e=5) == "a=1, b=2, args=(), d=3, kwargs={'kwargs': 4, 'e': 5}"
|
||||
|
||||
|
||||
@skip_pre_38
|
||||
|
||||
Reference in New Issue
Block a user