From: Vasek Sraier Date: Thu, 15 Apr 2021 11:51:48 +0000 (+0200) Subject: manager: parser validator: support for booleans and more hand specified type coercions X-Git-Tag: v6.0.0a1~172 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0f84b7059eb37d4531004ef08e0ca0b26313e77c;p=thirdparty%2Fknot-resolver.git manager: parser validator: support for booleans and more hand specified type coercions --- diff --git a/manager/knot_resolver_manager/utils/dataclasses_parservalidator.py b/manager/knot_resolver_manager/utils/dataclasses_parservalidator.py index 3f565e51a..40ff6b702 100644 --- a/manager/knot_resolver_manager/utils/dataclasses_parservalidator.py +++ b/manager/knot_resolver_manager/utils/dataclasses_parservalidator.py @@ -22,7 +22,10 @@ class ValidationException(Exception): def _from_dictlike_obj(cls: Any, obj: Any, default: Any, use_default: bool) -> Any: - # pylint: disable=too-many-branches,too-many-locals + # Disabling these checks, because I think it's much more readable as a single function + # and it's not that large at this point. If it got larger, then we should definitely split + # it + # pylint: disable=too-many-branches,too-many-locals,too-many-statements # default values if obj is None and use_default: @@ -49,20 +52,43 @@ def _from_dictlike_obj(cls: Any, obj: Any, default: Any, use_default: bool) -> A elif obj is None: raise ValidationException(f"Unexpected None value for type {cls}") - # floats and ints - elif cls in (int, float): - # special case checking, that we won't cast a string or any other object into a number - if isinstance(obj, (int, float)): - return cls(obj) + # int + elif cls == int: + # we don't want to make an int out of anything else than other int + if isinstance(obj, int): + return int(obj) else: - raise ValidationException(f"Expected {cls}, found {type(obj)}") - + raise ValidationException(f"Expected int, found {type(obj)}") + # str elif cls == str: # we are willing to cast any primitive value to string, but no compound values are allowed - if not isinstance(obj, (str, float, int)): - raise ValidationException(f"Expected str (or number that would be cast to string), but found type {type(obj)}") - return str(obj) + if isinstance(obj, (str, float, int)): + return str(obj) + elif isinstance(obj, bool): + raise ValidationException( + "Expected str, found bool. Be careful, that YAML parsers consider even" + ' "no" and "yes" as a bool. Search for the Norway Problem for more' + " details. And please use quotes explicitly." + ) + else: + raise ValidationException( + f"Expected str (or number that would be cast to string), but found type {type(obj)}" + ) + + # bool + elif cls == bool: + if isinstance(obj, bool): + return obj + else: + raise ValidationException(f"Expected bool, found {type(obj)}") + + # float + elif cls == float: + raise NotImplementedError( + "Floating point values are not supported in the parser validator." + " Please implement them and be careful with type coercions" + ) # Literal[T] elif is_literal(cls): diff --git a/manager/tests/utils/test_dataclasses_parservalidator.py b/manager/tests/utils/test_dataclasses_parservalidator.py index f85f883c7..fc5627e2f 100644 --- a/manager/tests/utils/test_dataclasses_parservalidator.py +++ b/manager/tests/utils/test_dataclasses_parservalidator.py @@ -9,20 +9,18 @@ def test_parsing_primitive(): class TestClass(DataclassParserValidatorMixin): i: int s: str - f: float def validate(self): pass yaml = """i: 5 s: "test" -f: 3.14""" +""" obj = TestClass.from_yaml(yaml) assert obj.i == 5 assert obj.s == "test" - assert obj.f == 3.14 def test_parsing_nested():