Skip to content

Commit 7fa450d

Browse files
author
Andrés Sandoval
authored
pass extra argument in arguments validator (#1094)
1 parent f323e74 commit 7fa450d

File tree

2 files changed

+71
-62
lines changed

2 files changed

+71
-62
lines changed

src/validators/arguments.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use pyo3::types::{PyDict, PyList, PyString, PyTuple};
55
use ahash::AHashSet;
66

77
use crate::build_tools::py_schema_err;
8-
use crate::build_tools::schema_or_config_same;
8+
use crate::build_tools::{schema_or_config_same, ExtraBehavior};
99
use crate::errors::{AsLocItem, ErrorTypeDefaults, ValError, ValLineError, ValResult};
1010
use crate::input::{GenericArguments, Input, ValidationMatch};
1111
use crate::lookup_key::LookupKey;
@@ -31,6 +31,7 @@ pub struct ArgumentsValidator {
3131
var_args_validator: Option<Box<CombinedValidator>>,
3232
var_kwargs_validator: Option<Box<CombinedValidator>>,
3333
loc_by_alias: bool,
34+
extra: ExtraBehavior,
3435
}
3536

3637
impl BuildValidator for ArgumentsValidator {
@@ -119,6 +120,7 @@ impl BuildValidator for ArgumentsValidator {
119120
None => None,
120121
},
121122
loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true),
123+
extra: ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Forbid)?,
122124
}
123125
.into())
124126
}
@@ -307,15 +309,16 @@ impl Validator for ArgumentsValidator {
307309
Err(err) => return Err(err),
308310
},
309311
None => {
310-
errors.push(ValLineError::new_with_loc(
311-
ErrorTypeDefaults::UnexpectedKeywordArgument,
312-
value,
313-
raw_key.as_loc_item(),
314-
));
312+
if let ExtraBehavior::Forbid = self.extra {
313+
errors.push(ValLineError::new_with_loc(
314+
ErrorTypeDefaults::UnexpectedKeywordArgument,
315+
value,
316+
raw_key.as_loc_item(),
317+
));
318+
}
315319
}
316320
}
317-
}
318-
}
321+
}}
319322
}
320323
}
321324
}};

tests/validators/test_arguments.py

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -775,57 +775,57 @@ def test_alias_populate_by_name(py_and_json: PyAndJson, input_value, expected):
775775
assert v.validate_test(input_value) == expected
776776

777777

778-
def validate(function):
779-
"""
780-
a demo validation decorator to test arguments
781-
"""
782-
parameters = signature(function).parameters
783-
784-
type_hints = get_type_hints(function)
785-
mode_lookup = {
786-
Parameter.POSITIONAL_ONLY: 'positional_only',
787-
Parameter.POSITIONAL_OR_KEYWORD: 'positional_or_keyword',
788-
Parameter.KEYWORD_ONLY: 'keyword_only',
789-
}
790-
791-
arguments_schema = []
792-
schema = {'type': 'arguments', 'arguments_schema': arguments_schema}
793-
for i, (name, p) in enumerate(parameters.items()):
794-
if p.annotation is p.empty:
795-
annotation = Any
796-
else:
797-
annotation = type_hints[name]
798-
799-
assert annotation in (bool, int, float, str, Any), f'schema for {annotation} not implemented'
800-
if annotation in (bool, int, float, str):
801-
arg_schema = {'type': annotation.__name__}
802-
else:
803-
assert annotation is Any
804-
arg_schema = {'type': 'any'}
805-
806-
if p.kind in mode_lookup:
807-
if p.default is not p.empty:
808-
arg_schema = {'type': 'default', 'schema': arg_schema, 'default': p.default}
809-
s = {'name': name, 'mode': mode_lookup[p.kind], 'schema': arg_schema}
810-
arguments_schema.append(s)
811-
elif p.kind == Parameter.VAR_POSITIONAL:
812-
schema['var_args_schema'] = arg_schema
813-
else:
814-
assert p.kind == Parameter.VAR_KEYWORD, p.kind
815-
schema['var_kwargs_schema'] = arg_schema
816-
817-
validator = SchemaValidator(schema)
818-
819-
@wraps(function)
820-
def wrapper(*args, **kwargs):
821-
validated_args, validated_kwargs = validator.validate_python(ArgsKwargs(args, kwargs))
822-
return function(*validated_args, **validated_kwargs)
823-
824-
return wrapper
778+
def validate(config=None):
779+
def decorator(function):
780+
parameters = signature(function).parameters
781+
type_hints = get_type_hints(function)
782+
mode_lookup = {
783+
Parameter.POSITIONAL_ONLY: 'positional_only',
784+
Parameter.POSITIONAL_OR_KEYWORD: 'positional_or_keyword',
785+
Parameter.KEYWORD_ONLY: 'keyword_only',
786+
}
787+
788+
arguments_schema = []
789+
schema = {'type': 'arguments', 'arguments_schema': arguments_schema}
790+
for i, (name, p) in enumerate(parameters.items()):
791+
if p.annotation is p.empty:
792+
annotation = Any
793+
else:
794+
annotation = type_hints[name]
795+
796+
assert annotation in (bool, int, float, str, Any), f'schema for {annotation} not implemented'
797+
if annotation in (bool, int, float, str):
798+
arg_schema = {'type': annotation.__name__}
799+
else:
800+
assert annotation is Any
801+
arg_schema = {'type': 'any'}
802+
803+
if p.kind in mode_lookup:
804+
if p.default is not p.empty:
805+
arg_schema = {'type': 'default', 'schema': arg_schema, 'default': p.default}
806+
s = {'name': name, 'mode': mode_lookup[p.kind], 'schema': arg_schema}
807+
arguments_schema.append(s)
808+
elif p.kind == Parameter.VAR_POSITIONAL:
809+
schema['var_args_schema'] = arg_schema
810+
else:
811+
assert p.kind == Parameter.VAR_KEYWORD, p.kind
812+
schema['var_kwargs_schema'] = arg_schema
813+
814+
validator = SchemaValidator(schema, config=config)
815+
816+
@wraps(function)
817+
def wrapper(*args, **kwargs):
818+
# Validate arguments using the original schema
819+
validated_args, validated_kwargs = validator.validate_python(ArgsKwargs(args, kwargs))
820+
return function(*validated_args, **validated_kwargs)
821+
822+
return wrapper
823+
824+
return decorator
825825

826826

827827
def test_function_any():
828-
@validate
828+
@validate()
829829
def foobar(a, b, c):
830830
return a, b, c
831831

@@ -842,7 +842,7 @@ def foobar(a, b, c):
842842

843843

844844
def test_function_types():
845-
@validate
845+
@validate()
846846
def foobar(a: int, b: int, *, c: int):
847847
return a, b, c
848848

@@ -894,8 +894,8 @@ def test_function_positional_only(import_execute):
894894
# language=Python
895895
m = import_execute(
896896
"""
897-
def create_function(validate):
898-
@validate
897+
def create_function(validate, config = None):
898+
@validate(config = config)
899899
def foobar(a: int, b: int, /, c: int):
900900
return a, b, c
901901
return foobar
@@ -915,6 +915,12 @@ def foobar(a: int, b: int, /, c: int):
915915
},
916916
{'type': 'unexpected_keyword_argument', 'loc': ('b',), 'msg': 'Unexpected keyword argument', 'input': 2},
917917
]
918+
# Allowing extras using the config
919+
foobar = m.create_function(validate, config={'title': 'func', 'extra_fields_behavior': 'allow'})
920+
assert foobar('1', '2', c=3, d=4) == (1, 2, 3)
921+
# Ignore works similar than allow
922+
foobar = m.create_function(validate, config={'title': 'func', 'extra_fields_behavior': 'ignore'})
923+
assert foobar('1', '2', c=3, d=4) == (1, 2, 3)
918924

919925

920926
@pytest.mark.skipif(sys.version_info < (3, 10), reason='requires python3.10 or higher')
@@ -923,7 +929,7 @@ def test_function_positional_only_default(import_execute):
923929
m = import_execute(
924930
"""
925931
def create_function(validate):
926-
@validate
932+
@validate()
927933
def foobar(a: int, b: int = 42, /):
928934
return a, b
929935
return foobar
@@ -940,7 +946,7 @@ def test_function_positional_kwargs(import_execute):
940946
m = import_execute(
941947
"""
942948
def create_function(validate):
943-
@validate
949+
@validate()
944950
def foobar(a: int, b: int, /, **kwargs: bool):
945951
return a, b, kwargs
946952
return foobar
@@ -953,7 +959,7 @@ def foobar(a: int, b: int, /, **kwargs: bool):
953959

954960

955961
def test_function_args_kwargs():
956-
@validate
962+
@validate()
957963
def foobar(*args, **kwargs):
958964
return args, kwargs
959965

0 commit comments

Comments
 (0)