From c6a37dab7492950cb41d23647dcc3781fb091c89 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sat, 19 Apr 2025 15:44:27 -0300 Subject: [PATCH] Better Defaults Validation Implementation --- jambo/parser/anyof_type_parser.py | 4 +-- jambo/parser/array_type_parser.py | 27 +++++------------ jambo/parser/boolean_type_parser.py | 9 +++++- jambo/parser/float_type_parser.py | 20 ++++++++++-- jambo/parser/int_type_parser.py | 20 ++++++++++-- tests/parser/test_array_type_parser.py | 25 +++------------ tests/parser/test_float_type_parser.py | 42 ++++---------------------- tests/parser/test_int_type_parser.py | 42 ++++---------------------- 8 files changed, 67 insertions(+), 122 deletions(-) diff --git a/jambo/parser/anyof_type_parser.py b/jambo/parser/anyof_type_parser.py index 49408be..76b1a32 100644 --- a/jambo/parser/anyof_type_parser.py +++ b/jambo/parser/anyof_type_parser.py @@ -50,8 +50,8 @@ class AnyOfTypeParser(GenericTypeParser): if not required: mapped_properties["default"] = mapped_properties.get("default") - # By defining the type as Union, we can use the Field validator to enforce - # the constraints on the union type. + # By defining the type as Union of Annotated type we can use the Field validator + # to enforce the constraints of each union type when needed. # We use Annotated to attach the Field validators to the type. field_types = [Annotated[t, Field(**v)] if v else t for t, v in sub_types] diff --git a/jambo/parser/array_type_parser.py b/jambo/parser/array_type_parser.py index d2d6e30..b96a399 100644 --- a/jambo/parser/array_type_parser.py +++ b/jambo/parser/array_type_parser.py @@ -27,6 +27,7 @@ class ArrayTypeParser(GenericTypeParser): } wrapper_type = set if properties.get("uniqueItems", False) else list + field_type = wrapper_type[_item_type] mapped_properties = mappings_properties_builder( properties, @@ -37,25 +38,11 @@ class ArrayTypeParser(GenericTypeParser): default_list = properties.get("default") if default_list is not None: - if not isinstance(default_list, list): - raise ValueError( - f"Default value must be a list, got {type(default_list).__name__}" - ) - - if len(default_list) > properties.get("maxItems", float("inf")): - raise ValueError( - f"Default list exceeds maxItems limit of {properties.get('maxItems')}" - ) - - if len(default_list) < properties.get("minItems", 0): - raise ValueError( - f"Default list is below minItems limit of {properties.get('minItems')}" - ) - - if not all(isinstance(item, _item_type) for item in default_list): - raise ValueError( - f"All items in the default list must be of type {_item_type.__name__}" - ) + ArrayTypeParser.validate_default( + field_type, + mapped_properties, + default_list, + ) if wrapper_type is list: mapped_properties["default_factory"] = lambda: copy.deepcopy( @@ -69,4 +56,4 @@ class ArrayTypeParser(GenericTypeParser): if "default_factory" in mapped_properties and "default" in mapped_properties: del mapped_properties["default"] - return wrapper_type[_item_type], mapped_properties + return field_type, mapped_properties diff --git a/jambo/parser/boolean_type_parser.py b/jambo/parser/boolean_type_parser.py index 4b21cf8..e9f0ab0 100644 --- a/jambo/parser/boolean_type_parser.py +++ b/jambo/parser/boolean_type_parser.py @@ -14,4 +14,11 @@ class BooleanTypeParser(GenericTypeParser): _mappings = { "default": "default", } - return bool, mappings_properties_builder(properties, _mappings) + + mapped_properties = mappings_properties_builder(properties, _mappings, required) + + default_value = properties.get("default") + if default_value is not None and not isinstance(default_value, bool): + raise ValueError(f"Default value for {name} must be a boolean.") + + return bool, mapped_properties diff --git a/jambo/parser/float_type_parser.py b/jambo/parser/float_type_parser.py index 5326c08..4e1b075 100644 --- a/jambo/parser/float_type_parser.py +++ b/jambo/parser/float_type_parser.py @@ -1,6 +1,6 @@ from jambo.parser._type_parser import GenericTypeParser -from jambo.utils.properties_builder.numeric_properties_builder import ( - numeric_properties_builder, +from jambo.utils.properties_builder.mappings_properties_builder import ( + mappings_properties_builder, ) @@ -11,4 +11,18 @@ class FloatTypeParser(GenericTypeParser): @staticmethod def from_properties(name, properties, required=False): - return float, numeric_properties_builder(properties, required) + _mappings = { + "minimum": "ge", + "exclusiveMinimum": "gt", + "maximum": "le", + "exclusiveMaximum": "lt", + "multipleOf": "multiple_of", + "default": "default", + } + mapped_properties = mappings_properties_builder(properties, _mappings, required) + + default_value = mapped_properties.get("default") + if default_value is not None: + FloatTypeParser.validate_default(float, mapped_properties, default_value) + + return float, mapped_properties diff --git a/jambo/parser/int_type_parser.py b/jambo/parser/int_type_parser.py index 82bbfb9..3e0f92e 100644 --- a/jambo/parser/int_type_parser.py +++ b/jambo/parser/int_type_parser.py @@ -1,6 +1,6 @@ from jambo.parser._type_parser import GenericTypeParser -from jambo.utils.properties_builder.numeric_properties_builder import ( - numeric_properties_builder, +from jambo.utils.properties_builder.mappings_properties_builder import ( + mappings_properties_builder, ) @@ -11,4 +11,18 @@ class IntTypeParser(GenericTypeParser): @staticmethod def from_properties(name, properties, required=False): - return int, numeric_properties_builder(properties, required) + _mappings = { + "minimum": "ge", + "exclusiveMinimum": "gt", + "maximum": "le", + "exclusiveMaximum": "lt", + "multipleOf": "multiple_of", + "default": "default", + } + mapped_properties = mappings_properties_builder(properties, _mappings, required) + + default_value = mapped_properties.get("default") + if default_value is not None: + IntTypeParser.validate_default(int, mapped_properties, default_value) + + return int, mapped_properties diff --git a/tests/parser/test_array_type_parser.py b/tests/parser/test_array_type_parser.py index 4177cce..172b98f 100644 --- a/tests/parser/test_array_type_parser.py +++ b/tests/parser/test_array_type_parser.py @@ -66,38 +66,25 @@ class TestArrayTypeParser(TestCase): properties = {"items": {"type": "string"}, "default": ["a", 1, "c"]} - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "All items in the default list must be of type str", - ) - def test_array_parser_with_invalid_default_type(self): parser = ArrayTypeParser() properties = {"items": {"type": "string"}, "default": "not_a_list"} - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), "Default value must be a list, got str" - ) - def test_array_parser_with_invalid_default_min(self): parser = ArrayTypeParser() properties = {"items": {"type": "string"}, "default": ["a"], "minItems": 2} - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), "Default list is below minItems limit of 2" - ) - def test_array_parser_with_invalid_default_max(self): parser = ArrayTypeParser() @@ -107,9 +94,5 @@ class TestArrayTypeParser(TestCase): "maxItems": 3, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - - self.assertEqual( - str(context.exception), "Default list exceeds maxItems limit of 3" - ) diff --git a/tests/parser/test_float_type_parser.py b/tests/parser/test_float_type_parser.py index 66a29f0..c462d64 100644 --- a/tests/parser/test_float_type_parser.py +++ b/tests/parser/test_float_type_parser.py @@ -61,14 +61,9 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value must be a number, got str", - ) - def test_float_parser_with_default_invalid_maximum(self): parser = FloatTypeParser() @@ -80,14 +75,9 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value exceeds maximum limit of 10.5", - ) - def test_float_parser_with_default_invalid_minimum(self): parser = FloatTypeParser() @@ -99,14 +89,9 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value is below minimum limit of 1.0", - ) - def test_float_parser_with_default_invalid_exclusive_maximum(self): parser = FloatTypeParser() @@ -118,14 +103,9 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value exceeds exclusive maximum limit of 10.5", - ) - def test_float_parser_with_default_invalid_exclusive_minimum(self): parser = FloatTypeParser() @@ -137,14 +117,9 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value is below exclusive minimum limit of 1.0", - ) - def test_float_parser_with_default_invalid_multiple(self): parser = FloatTypeParser() @@ -156,10 +131,5 @@ class TestFloatTypeParser(TestCase): "multipleOf": 2.0, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - - self.assertEqual( - str(context.exception), - "Default value 5.0 is not a multiple of 2.0", - ) diff --git a/tests/parser/test_int_type_parser.py b/tests/parser/test_int_type_parser.py index 84c9e17..5cfeed5 100644 --- a/tests/parser/test_int_type_parser.py +++ b/tests/parser/test_int_type_parser.py @@ -61,14 +61,9 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value must be a number, got str", - ) - def test_int_parser_with_default_invalid_maximum(self): parser = IntTypeParser() @@ -80,14 +75,9 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value exceeds maximum limit of 10", - ) - def test_int_parser_with_default_invalid_minimum(self): parser = IntTypeParser() @@ -99,14 +89,9 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value is below minimum limit of 1", - ) - def test_int_parser_with_default_invalid_exclusive_maximum(self): parser = IntTypeParser() @@ -118,14 +103,9 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value exceeds exclusive maximum limit of 10", - ) - def test_int_parser_with_default_invalid_exclusive_minimum(self): parser = IntTypeParser() @@ -137,14 +117,9 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - self.assertEqual( - str(context.exception), - "Default value is below exclusive minimum limit of 1", - ) - def test_int_parser_with_default_invalid_multipleOf(self): parser = IntTypeParser() @@ -156,10 +131,5 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError) as context: + with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) - - self.assertEqual( - str(context.exception), - "Default value 5 is not a multiple of 2", - )