diff --git a/jambo/__init__.py b/jambo/__init__.py index e69de29..fb222f1 100644 --- a/jambo/__init__.py +++ b/jambo/__init__.py @@ -0,0 +1,6 @@ +from .schema_converter import SchemaConverter + + +__all__ = [ + SchemaConverter # Exports the schema converter class for external use +] diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 409aea2..dc44ca4 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -24,7 +24,7 @@ class StringTypeParser(GenericTypeParser): if not isinstance(default_value, str): raise ValueError( f"Default value for {name} must be a string, " - f"but got {type(properties['default'])}." + f"but got <{type(properties['default']).__name__}>." ) if len(default_value) > properties.get("maxLength", float("inf")): diff --git a/jambo/utils/properties_builder/numeric_properties_builder.py b/jambo/utils/properties_builder/numeric_properties_builder.py index 0c52abe..f38dea1 100644 --- a/jambo/utils/properties_builder/numeric_properties_builder.py +++ b/jambo/utils/properties_builder/numeric_properties_builder.py @@ -22,22 +22,22 @@ def numeric_properties_builder(properties): f"Default value must be a number, got {type(default_value).__name__}" ) - if default_value >= properties.get("maximum", float("inf")): + if default_value > properties.get("maximum", float("inf")): raise ValueError( f"Default value exceeds maximum limit of {properties.get('maximum')}" ) - if default_value <= properties.get("minimum", float("-inf")): + if default_value < properties.get("minimum", float("-inf")): raise ValueError( f"Default value is below minimum limit of {properties.get('minimum')}" ) - if default_value > properties.get("exclusiveMaximum", float("inf")): + if default_value >= properties.get("exclusiveMaximum", float("inf")): raise ValueError( f"Default value exceeds exclusive maximum limit of {properties.get('exclusiveMaximum')}" ) - if default_value < properties.get("exclusiveMinimum", float("-inf")): + if default_value <= properties.get("exclusiveMinimum", float("-inf")): raise ValueError( f"Default value is below exclusive minimum limit of {properties.get('exclusiveMinimum')}" ) diff --git a/tests/parser/__init__.py b/tests/parser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/parser/test_array_type_parser.py b/tests/parser/test_array_type_parser.py new file mode 100644 index 0000000..9c06a46 --- /dev/null +++ b/tests/parser/test_array_type_parser.py @@ -0,0 +1,115 @@ +from typing import get_args +from unittest import TestCase + +from jambo.parser import ArrayTypeParser + + +class TestArrayTypeParser(TestCase): + def test_array_parser_no_options(self): + parser = ArrayTypeParser() + + properties = {"items": {"type": "string"}} + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + element_type = get_args(type_parsing)[0] + + self.assertEqual(type_parsing.__origin__, list) + self.assertEqual(element_type, str) + + def test_array_parser_with_options_unique(self): + parser = ArrayTypeParser() + + properties = {"items": {"type": "string"}, "uniqueItems": True} + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing.__origin__, set) + + def test_array_parser_with_options_max_min(self): + parser = ArrayTypeParser() + + properties = {"items": {"type": "string"}, "maxItems": 10, "minItems": 1} + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing.__origin__, list) + self.assertEqual(type_validator["max_length"], 10) + self.assertEqual(type_validator["min_length"], 1) + + def test_array_parser_with_options_default_list(self): + parser = ArrayTypeParser() + + properties = {"items": {"type": "string"}, "default": ["a", "b", "c"]} + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing.__origin__, list) + self.assertEqual(type_validator["default_factory"](), ["a", "b", "c"]) + + def test_array_parse_with_options_default_set(self): + parser = ArrayTypeParser() + + properties = { + "items": {"type": "string"}, + "uniqueItems": True, + "default": ["a", "b", "c"], + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing.__origin__, set) + self.assertEqual(type_validator["default_factory"](), {"a", "b", "c"}) + + def test_array_parser_with_invalid_default_elem_type(self): + parser = ArrayTypeParser() + + properties = {"items": {"type": "string"}, "default": ["a", 1, "c"]} + + with self.assertRaises(ValueError) as context: + 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: + 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: + 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() + + properties = { + "items": {"type": "string"}, + "default": ["a", "b", "c", "d"], + "maxItems": 3, + } + + with self.assertRaises(ValueError) as context: + parser.from_properties("placeholder", properties) + + self.assertEqual( + str(context.exception), "Default list exceeds maxItems limit of 3" + ) diff --git a/tests/parser/test_bool_type_parser.py b/tests/parser/test_bool_type_parser.py new file mode 100644 index 0000000..761ddc0 --- /dev/null +++ b/tests/parser/test_bool_type_parser.py @@ -0,0 +1,28 @@ +from unittest import TestCase + +from jambo.parser import BooleanTypeParser + + +class TestBoolTypeParser(TestCase): + def test_bool_parser_no_options(self): + parser = BooleanTypeParser() + + properties = {"type": "boolean"} + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, bool) + self.assertEqual(type_validator, {}) + + def test_bool_parser_with_default(self): + parser = BooleanTypeParser() + + properties = { + "type": "boolean", + "default": True, + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, bool) + self.assertEqual(type_validator["default"], True) diff --git a/tests/parser/test_float_type_parser.py b/tests/parser/test_float_type_parser.py new file mode 100644 index 0000000..c8e3ad5 --- /dev/null +++ b/tests/parser/test_float_type_parser.py @@ -0,0 +1,165 @@ +from unittest import TestCase + +from jambo.parser import FloatTypeParser + + +class TestFloatTypeParser(TestCase): + def test_float_parser_no_options(self): + parser = FloatTypeParser() + + properties = {"type": "number"} + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, float) + self.assertEqual(type_validator, {}) + + def test_float_parser_with_options(self): + parser = FloatTypeParser() + + properties = { + "type": "number", + "maximum": 10.5, + "minimum": 1.0, + "multipleOf": 0.5, + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, float) + self.assertEqual(type_validator["le"], 10.5) + self.assertEqual(type_validator["ge"], 1.0) + self.assertEqual(type_validator["multiple_of"], 0.5) + + def test_float_parser_with_default(self): + parser = FloatTypeParser() + + properties = { + "type": "number", + "default": 5.0, + "maximum": 10.5, + "minimum": 1.0, + "multipleOf": 0.5, + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, float) + self.assertEqual(type_validator["default"], 5.0) + self.assertEqual(type_validator["le"], 10.5) + self.assertEqual(type_validator["ge"], 1.0) + self.assertEqual(type_validator["multiple_of"], 0.5) + + def test_float_parser_with_default_invalid_type(self): + parser = FloatTypeParser() + + properties = { + "type": "number", + "default": "invalid", # Invalid default value + "maximum": 10.5, + "minimum": 1.0, + "multipleOf": 0.5, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "number", + "default": 15.0, + "maximum": 10.5, + "minimum": 1.0, + "multipleOf": 0.5, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "number", + "default": -5.0, + "maximum": 10.5, + "minimum": 1.0, + "multipleOf": 0.5, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "number", + "default": 10.5, + "exclusiveMaximum": 10.5, + "minimum": 1.0, + "multipleOf": 0.5, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "number", + "default": 1.0, + "maximum": 10.5, + "exclusiveMinimum": 1.0, + "multipleOf": 0.5, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "number", + "default": 5.0, + "maximum": 10.5, + "minimum": 1.0, + "multipleOf": 2.0, + } + + with self.assertRaises(ValueError) as context: + 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 new file mode 100644 index 0000000..e50b340 --- /dev/null +++ b/tests/parser/test_int_type_parser.py @@ -0,0 +1,165 @@ +from unittest import TestCase + +from jambo.parser import IntTypeParser + + +class TestIntTypeParser(TestCase): + def test_int_parser_no_options(self): + parser = IntTypeParser() + + properties = {"type": "integer"} + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, int) + self.assertEqual(type_validator, {}) + + def test_int_parser_with_options(self): + parser = IntTypeParser() + + properties = { + "type": "integer", + "maximum": 10, + "minimum": 1, + "multipleOf": 2, + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, int) + self.assertEqual(type_validator["le"], 10) + self.assertEqual(type_validator["ge"], 1) + self.assertEqual(type_validator["multiple_of"], 2) + + def test_int_parser_with_default(self): + parser = IntTypeParser() + + properties = { + "type": "integer", + "default": 6, + "maximum": 10, + "minimum": 1, + "multipleOf": 2, + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, int) + self.assertEqual(type_validator["default"], 6) + self.assertEqual(type_validator["le"], 10) + self.assertEqual(type_validator["ge"], 1) + self.assertEqual(type_validator["multiple_of"], 2) + + def test_int_parser_with_default_invalid_type(self): + parser = IntTypeParser() + + properties = { + "type": "integer", + "default": "invalid", # Invalid default value + "maximum": 10, + "minimum": 1, + "multipleOf": 2, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "integer", + "default": 15, + "maximum": 10, + "minimum": 1, + "multipleOf": 2, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "integer", + "default": -5, + "maximum": 10, + "minimum": 1, + "multipleOf": 2, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "integer", + "default": 10, + "exclusiveMaximum": 10, + "minimum": 1, + "multipleOf": 2, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "integer", + "default": 1, + "exclusiveMinimum": 1, + "maximum": 10, + "multipleOf": 2, + } + + with self.assertRaises(ValueError) as context: + 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() + + properties = { + "type": "integer", + "default": 5, + "maximum": 10, + "minimum": 1, + "multipleOf": 2, + } + + with self.assertRaises(ValueError) as context: + parser.from_properties("placeholder", properties) + + self.assertEqual( + str(context.exception), + "Default value 5 is not a multiple of 2", + ) diff --git a/tests/parser/test_object_type_parser.py b/tests/parser/test_object_type_parser.py new file mode 100644 index 0000000..8e86d44 --- /dev/null +++ b/tests/parser/test_object_type_parser.py @@ -0,0 +1,23 @@ +from unittest import TestCase + +from jambo.parser import ObjectTypeParser + + +class TestObjectTypeParser(TestCase): + def test_object_type_parser(self): + parser = ObjectTypeParser() + + properties = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + }, + } + + Model, _args = parser.from_properties("placeholder", properties) + + obj = Model(name="name", age=10) + + self.assertEqual(obj.name, "name") + self.assertEqual(obj.age, 10) diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py new file mode 100644 index 0000000..f5d19fe --- /dev/null +++ b/tests/parser/test_string_type_parser.py @@ -0,0 +1,102 @@ +from unittest import TestCase + +from jambo.parser import StringTypeParser + + +class TestStringTypeParser(TestCase): + def test_string_parser_no_options(self): + parser = StringTypeParser() + + properties = {"type": "string"} + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, str) + + def test_string_parser_with_options(self): + parser = StringTypeParser() + + properties = { + "type": "string", + "maxLength": 10, + "minLength": 1, + "pattern": "^[a-zA-Z]+$", + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, str) + self.assertEqual(type_validator["max_length"], 10) + self.assertEqual(type_validator["min_length"], 1) + self.assertEqual(type_validator["pattern"], "^[a-zA-Z]+$") + + def test_string_parser_with_default_value(self): + parser = StringTypeParser() + + properties = { + "type": "string", + "default": "default_value", + "maxLength": 20, + "minLength": 5, + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, str) + self.assertEqual(type_validator["default"], "default_value") + self.assertEqual(type_validator["max_length"], 20) + self.assertEqual(type_validator["min_length"], 5) + + def test_string_parser_with_invalid_default_value_type(self): + parser = StringTypeParser() + + properties = { + "type": "string", + "default": 12345, # Invalid default value + "maxLength": 20, + "minLength": 5, + } + + with self.assertRaises(ValueError) as context: + parser.from_properties("placeholder", properties) + + self.assertEqual( + str(context.exception), + "Default value for placeholder must be a string, but got .", + ) + + def test_string_parser_with_default_invalid_maxlength(self): + parser = StringTypeParser() + + properties = { + "type": "string", + "default": "default_value", + "maxLength": 2, + "minLength": 1, + } + + with self.assertRaises(ValueError) as context: + parser.from_properties("placeholder", properties) + + self.assertEqual( + str(context.exception), + "Default value for placeholder exceeds maxLength limit of 2", + ) + + def test_string_parser_with_default_invalid_minlength(self): + parser = StringTypeParser() + + properties = { + "type": "string", + "default": "a", + "maxLength": 20, + "minLength": 2, + } + + with self.assertRaises(ValueError) as context: + parser.from_properties("placeholder", properties) + + self.assertEqual( + str(context.exception), + "Default value for placeholder is below minLength limit of 2", + ) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 9f8d220..443e0b2 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -1,4 +1,4 @@ -from jambo.schema_converter import SchemaConverter +from jambo import SchemaConverter from pydantic import BaseModel diff --git a/tests/test_type_parser.py b/tests/test_type_parser.py deleted file mode 100644 index cee9818..0000000 --- a/tests/test_type_parser.py +++ /dev/null @@ -1,139 +0,0 @@ -from jambo.parser import ( - ArrayTypeParser, - FloatTypeParser, - GenericTypeParser, - IntTypeParser, - ObjectTypeParser, - StringTypeParser, -) - -import unittest -from typing import get_args - - -class TestTypeParser(unittest.TestCase): - def test_get_impl(self): - self.assertEqual(GenericTypeParser.get_impl("integer"), IntTypeParser) - self.assertEqual(GenericTypeParser.get_impl("string"), StringTypeParser) - self.assertEqual(GenericTypeParser.get_impl("number"), FloatTypeParser) - self.assertEqual(GenericTypeParser.get_impl("object"), ObjectTypeParser) - self.assertEqual(GenericTypeParser.get_impl("array"), ArrayTypeParser) - - def test_int_parser(self): - parser = IntTypeParser() - - type_parsing, type_validator = parser.from_properties( - "placeholder", - { - "type": "integer", - "minimum": 0, - "exclusiveMinimum": 1, - "maximum": 10, - "exclusiveMaximum": 11, - "multipleOf": 2, - }, - ) - - self.assertEqual(type_parsing, int) - self.assertEqual(type_validator["ge"], 0) - self.assertEqual(type_validator["gt"], 1) - self.assertEqual(type_validator["le"], 10) - self.assertEqual(type_validator["lt"], 11) - self.assertEqual(type_validator["multiple_of"], 2) - - def test_float_parser(self): - parser = FloatTypeParser() - - type_parsing, type_validator = parser.from_properties( - "placeholder", - { - "type": "number", - "minimum": 0, - "exclusiveMinimum": 1, - "maximum": 10, - "exclusiveMaximum": 11, - "multipleOf": 2, - }, - ) - - self.assertEqual(type_parsing, float) - self.assertEqual(type_validator["ge"], 0) - self.assertEqual(type_validator["gt"], 1) - self.assertEqual(type_validator["le"], 10) - self.assertEqual(type_validator["lt"], 11) - self.assertEqual(type_validator["multiple_of"], 2) - - def test_string_parser(self): - parser = StringTypeParser() - - type_parsing, type_validator = parser.from_properties( - "placeholder", - { - "type": "string", - "maxLength": 10, - "minLength": 1, - "pattern": "[a-zA-Z0-9]", - }, - ) - - self.assertEqual(type_parsing, str) - self.assertEqual(type_validator["max_length"], 10) - self.assertEqual(type_validator["min_length"], 1) - self.assertEqual(type_validator["pattern"], "[a-zA-Z0-9]") - - def test_object_parser(self): - parser = ObjectTypeParser() - - properties = { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"}, - }, - } - - Model, _args = parser.from_properties("placeholder", properties) - - obj = Model(name="name", age=10) - - self.assertEqual(obj.name, "name") - self.assertEqual(obj.age, 10) - - def test_array_of_string_parser(self): - parser = ArrayTypeParser() - expected_definition = (list[str], {}) - - properties = {"items": {"type": "string"}} - - self.assertEqual( - parser.from_properties("placeholder", properties), expected_definition - ) - - def test_array_of_object_parser(self): - parser = ArrayTypeParser() - - properties = { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"}, - }, - }, - "maxItems": 10, - "minItems": 1, - "uniqueItems": True, - } - - type_parsing, type_validator = parser.from_properties("placeholder", properties) - - self.assertEqual(type_parsing.__origin__, set) - self.assertEqual(type_validator["max_length"], 10) - self.assertEqual(type_validator["min_length"], 1) - - Model = get_args(type_parsing)[0] - obj = Model(name="name", age=10) - - self.assertEqual(obj.name, "name") - self.assertEqual(obj.age, 10)