From 8106d0a24051d5d6f6cb01884f5ff15cb54d59eb Mon Sep 17 00:00:00 2001 From: JeanArhancet Date: Tue, 14 May 2024 19:10:32 +0200 Subject: [PATCH] fix: keyword only non default argument --- src/validators/arguments.rs | 8 +++- tests/validators/test_arguments.py | 77 ++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/validators/arguments.rs b/src/validators/arguments.rs index 457474d65..63ef36d7f 100644 --- a/src/validators/arguments.rs +++ b/src/validators/arguments.rs @@ -51,6 +51,7 @@ impl BuildValidator for ArgumentsValidator { let mut positional_params_count = 0; let mut had_default_arg = false; + let mut had_keyword_only = false; for (arg_index, arg) in arguments_schema.iter().enumerate() { let arg = arg.downcast::()?; @@ -63,11 +64,16 @@ impl BuildValidator for ArgumentsValidator { .map(|py_str| py_str.to_str()) .transpose()? .unwrap_or("positional_or_keyword"); + let positional = mode == "positional_only" || mode == "positional_or_keyword"; if positional { positional_params_count = arg_index + 1; } + if mode == "keyword_only" { + had_keyword_only = true; + } + let mut kw_lookup_key = None; let mut kwarg_key = None; if mode == "keyword_only" || mode == "positional_or_keyword" { @@ -98,7 +104,7 @@ impl BuildValidator for ArgumentsValidator { _ => false, }; - if had_default_arg && !has_default { + if had_default_arg && !has_default && !had_keyword_only { return py_schema_err!("Non-default argument '{}' follows default argument", name); } else if has_default { had_default_arg = true; diff --git a/tests/validators/test_arguments.py b/tests/validators/test_arguments.py index 45ade32e6..915f05878 100644 --- a/tests/validators/test_arguments.py +++ b/tests/validators/test_arguments.py @@ -371,6 +371,83 @@ def test_positional_or_keyword(py_and_json: PyAndJson, input_value, expected): assert v.validate_test(input_value) == expected +@pytest.mark.parametrize( + 'input_value,expected,arguments_schema', + [ + ( + {'a': 1, 'b': 2, 'e': 3.14}, + ((), {'a': 1, 'b': 2, 'c': 5, 'd': 'default', 'e': 3.14}), + [ + {'name': 'a', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}}, + {'name': 'b', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}}, + { + 'name': 'c', + 'mode': 'keyword_only', + 'schema': {'type': 'default', 'schema': {'type': 'int'}, 'default': 5}, + }, + { + 'name': 'd', + 'mode': 'keyword_only', + 'schema': {'type': 'default', 'schema': {'type': 'str'}, 'default': 'default'}, + }, + {'name': 'e', 'mode': 'keyword_only', 'schema': {'type': 'float'}}, + ], + ), + ( + {'y': 'test'}, + ((), {'x': 1, 'y': 'test'}), + [ + { + 'name': 'x', + 'mode': 'keyword_only', + 'schema': {'type': 'default', 'schema': {'type': 'int'}, 'default': 1}, + }, + {'name': 'y', 'mode': 'keyword_only', 'schema': {'type': 'str'}}, + ], + ), + ( + {'a': 1, 'd': 3.14}, + ((), {'a': 1, 'b': 10, 'c': 'hello', 'd': 3.14}), + [ + {'name': 'a', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}}, + { + 'name': 'b', + 'mode': 'positional_or_keyword', + 'schema': {'type': 'default', 'schema': {'type': 'int'}, 'default': 10}, + }, + { + 'name': 'c', + 'mode': 'keyword_only', + 'schema': {'type': 'default', 'schema': {'type': 'str'}, 'default': 'hello'}, + }, + {'name': 'd', 'mode': 'keyword_only', 'schema': {'type': 'float'}}, + ], + ), + ( + {'x': 3, 'y': 'custom', 'z': 4}, + ((), {'x': 3, 'y': 'custom', 'z': 4}), + [ + {'name': 'x', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}}, + { + 'name': 'y', + 'mode': 'keyword_only', + 'schema': {'type': 'default', 'schema': {'type': 'str'}, 'default': 'default'}, + }, + {'name': 'z', 'mode': 'keyword_only', 'schema': {'type': 'int'}}, + ], + ), + ], +) +def test_keyword_only_non_default(py_and_json: PyAndJson, input_value, expected, arguments_schema): + v = py_and_json( + { + 'type': 'arguments', + 'arguments_schema': arguments_schema, + } + ) + assert v.validate_test(input_value) == expected + + @pytest.mark.parametrize('input_value,expected', [[(1,), ((1,), {})], [(), ((42,), {})]], ids=repr) def test_positional_optional(py_and_json: PyAndJson, input_value, expected): v = py_and_json(