Move Aux Function to the GenericTypeParser Class

This commit is contained in:
2025-04-19 16:45:32 -03:00
parent d74e700233
commit 20e4a69968
15 changed files with 89 additions and 164 deletions

View File

@@ -13,11 +13,12 @@ class GenericTypeParser(ABC, Generic[T]):
json_schema_type: str = None
@staticmethod
@abstractmethod
def from_properties(
name: str, properties: dict[str, any], required: bool = False
) -> tuple[T, dict]: ...
default_mappings = {
"default": "default",
"description": "description",
}
type_mappings: dict[str, str] = None
@classmethod
def get_impl(cls, type_name: str) -> Self:
@@ -30,7 +31,24 @@ class GenericTypeParser(ABC, Generic[T]):
raise ValueError(f"Unknown type: {type_name}")
@staticmethod
def validate_default(field_type: type, field_prop: dict, value):
@abstractmethod
def from_properties(
self, name: str, properties: dict[str, any], required: bool = False
) -> tuple[T, dict]: ...
def mappings_properties_builder(self, properties, required=False) -> dict[str, any]:
if self.type_mappings is None:
raise NotImplementedError("Type mappings not defined")
if not required:
properties["default"] = properties.get("default", None)
mappings = self.default_mappings | self.type_mappings
return {
mappings[key]: value for key, value in properties.items() if key in mappings
}
def validate_default(self, field_type: type, field_prop: dict, value) -> None:
field = Annotated[field_type, Field(**field_prop)]
TypeAdapter(field).validate_python(value)

View File

@@ -6,8 +6,7 @@ class AllOfTypeParser(GenericTypeParser):
json_schema_type = "allOf"
@staticmethod
def from_properties(name, properties, required=False):
def from_properties(self, name, properties, required=False):
subProperties = properties.get("allOf")
if not subProperties:
raise ValueError("Invalid JSON Schema: 'allOf' is not specified.")
@@ -28,16 +27,13 @@ class AllOfTypeParser(GenericTypeParser):
# If a sub-property has not defined a type, we need to set it to the top-level type
subProperty["type"] = _mapped_type
combined_properties = AllOfTypeParser._rebuild_properties_from_subproperties(
subProperties
)
combined_properties = self._rebuild_properties_from_subproperties(subProperties)
return GenericTypeParser.get_impl(_mapped_type).from_properties(
name, combined_properties
)
@staticmethod
def _rebuild_properties_from_subproperties(subProperties):
def _rebuild_properties_from_subproperties(self, subProperties):
properties = {}
for subProperty in subProperties:
for name, prop in subProperty.items():

View File

@@ -11,8 +11,7 @@ class AnyOfTypeParser(GenericTypeParser):
json_schema_type = "anyOf"
@staticmethod
def from_properties(name, properties, required=False):
def from_properties(self, name, properties, required=False):
if "anyOf" not in properties:
raise ValueError(f"Invalid JSON Schema: {properties}")
@@ -34,9 +33,7 @@ class AnyOfTypeParser(GenericTypeParser):
if default_value is not None:
for sub_type, sub_property in sub_types:
try:
GenericTypeParser.validate_default(
sub_type, sub_property, default_value
)
self.validate_default(sub_type, sub_property, default_value)
break
except ValueError:
continue

View File

@@ -1,7 +1,4 @@
from jambo.parser._type_parser import GenericTypeParser
from jambo.utils.properties_builder.mappings_properties_builder import (
mappings_properties_builder,
)
import copy
from typing import TypeVar
@@ -15,30 +12,29 @@ class ArrayTypeParser(GenericTypeParser):
json_schema_type = "array"
@staticmethod
def from_properties(name, properties, required=False):
_item_type, _item_args = GenericTypeParser.get_impl(
properties["items"]["type"]
).from_properties(name, properties["items"], required=True)
default_mappings = {"description": "description"}
_mappings = {
type_mappings = {
"maxItems": "max_length",
"minItems": "min_length",
}
def from_properties(self, name, properties, required=False):
_item_type, _item_args = GenericTypeParser.get_impl(
properties["items"]["type"]
).from_properties(name, properties["items"], required=True)
wrapper_type = set if properties.get("uniqueItems", False) else list
field_type = wrapper_type[_item_type]
mapped_properties = mappings_properties_builder(
mapped_properties = self.mappings_properties_builder(
properties,
_mappings,
required=required,
default_mappings={"description": "description"},
)
default_list = properties.pop("default", None)
if default_list is not None:
ArrayTypeParser.validate_default(
self.validate_default(
field_type,
mapped_properties,
default_list,

View File

@@ -1,7 +1,4 @@
from jambo.parser._type_parser import GenericTypeParser
from jambo.utils.properties_builder.mappings_properties_builder import (
mappings_properties_builder,
)
class BooleanTypeParser(GenericTypeParser):
@@ -9,13 +6,12 @@ class BooleanTypeParser(GenericTypeParser):
json_schema_type = "boolean"
@staticmethod
def from_properties(name, properties, required=False):
_mappings = {
type_mappings = {
"default": "default",
}
mapped_properties = mappings_properties_builder(properties, _mappings, required)
def from_properties(self, name, properties, required=False):
mapped_properties = self.mappings_properties_builder(properties, required)
default_value = properties.get("default")
if default_value is not None and not isinstance(default_value, bool):

View File

@@ -1,7 +1,4 @@
from jambo.parser._type_parser import GenericTypeParser
from jambo.utils.properties_builder.mappings_properties_builder import (
mappings_properties_builder,
)
class FloatTypeParser(GenericTypeParser):
@@ -9,9 +6,7 @@ class FloatTypeParser(GenericTypeParser):
json_schema_type = "number"
@staticmethod
def from_properties(name, properties, required=False):
_mappings = {
type_mappings = {
"minimum": "ge",
"exclusiveMinimum": "gt",
"maximum": "le",
@@ -19,10 +14,12 @@ class FloatTypeParser(GenericTypeParser):
"multipleOf": "multiple_of",
"default": "default",
}
mapped_properties = mappings_properties_builder(properties, _mappings, required)
def from_properties(self, name, properties, required=False):
mapped_properties = self.mappings_properties_builder(properties, required)
default_value = mapped_properties.get("default")
if default_value is not None:
FloatTypeParser.validate_default(float, mapped_properties, default_value)
self.validate_default(float, mapped_properties, default_value)
return float, mapped_properties

View File

@@ -1,7 +1,4 @@
from jambo.parser._type_parser import GenericTypeParser
from jambo.utils.properties_builder.mappings_properties_builder import (
mappings_properties_builder,
)
class IntTypeParser(GenericTypeParser):
@@ -9,9 +6,7 @@ class IntTypeParser(GenericTypeParser):
json_schema_type = "integer"
@staticmethod
def from_properties(name, properties, required=False):
_mappings = {
type_mappings = {
"minimum": "ge",
"exclusiveMinimum": "gt",
"maximum": "le",
@@ -19,10 +14,12 @@ class IntTypeParser(GenericTypeParser):
"multipleOf": "multiple_of",
"default": "default",
}
mapped_properties = mappings_properties_builder(properties, _mappings, required)
def from_properties(self, name, properties, required=False):
mapped_properties = self.mappings_properties_builder(properties, required)
default_value = mapped_properties.get("default")
if default_value is not None:
IntTypeParser.validate_default(int, mapped_properties, default_value)
self.validate_default(int, mapped_properties, default_value)
return int, mapped_properties

View File

@@ -1,7 +1,4 @@
from jambo.parser._type_parser import GenericTypeParser
from jambo.utils.properties_builder.mappings_properties_builder import (
mappings_properties_builder,
)
class StringTypeParser(GenericTypeParser):
@@ -9,18 +6,17 @@ class StringTypeParser(GenericTypeParser):
json_schema_type = "string"
@staticmethod
def from_properties(name, properties, required=False):
_mappings = {
type_mappings = {
"maxLength": "max_length",
"minLength": "min_length",
"pattern": "pattern",
}
mapped_properties = mappings_properties_builder(properties, _mappings, required)
def from_properties(self, name, properties, required=False):
mapped_properties = self.mappings_properties_builder(properties, required)
default_value = properties.get("default")
if default_value is not None:
StringTypeParser.validate_default(str, mapped_properties, default_value)
self.validate_default(str, mapped_properties, default_value)
return str, mapped_properties

View File

@@ -1,16 +0,0 @@
def mappings_properties_builder(
properties, mappings, required=False, default_mappings=None
):
if not required:
properties["default"] = properties.get("default", None)
default_mappings = default_mappings or {
"default": "default",
"description": "description",
}
mappings = default_mappings | mappings
return {
mappings[key]: value for key, value in properties.items() if key in mappings
}

View File

@@ -1,52 +0,0 @@
from jambo.utils.properties_builder.mappings_properties_builder import (
mappings_properties_builder,
)
def numeric_properties_builder(properties, required=False):
_mappings = {
"minimum": "ge",
"exclusiveMinimum": "gt",
"maximum": "le",
"exclusiveMaximum": "lt",
"multipleOf": "multiple_of",
"default": "default",
}
mapped_properties = mappings_properties_builder(properties, _mappings, required)
default_value = properties.get("default")
if default_value is not None:
default_value = properties["default"]
if not isinstance(default_value, (int, float)):
raise ValueError(
f"Default value must be a number, got {type(default_value).__name__}"
)
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")):
raise ValueError(
f"Default value is below minimum limit of {properties.get('minimum')}"
)
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")):
raise ValueError(
f"Default value is below exclusive minimum limit of {properties.get('exclusiveMinimum')}"
)
if "multipleOf" in properties:
if default_value % properties["multipleOf"] != 0:
raise ValueError(
f"Default value {default_value} is not a multiple of {properties['multipleOf']}"
)
return mapped_properties

View File

@@ -38,7 +38,7 @@ class TestAllOfTypeParser(TestCase):
],
}
type_parsing, type_validator = AllOfTypeParser.from_properties(
type_parsing, type_validator = AllOfTypeParser().from_properties(
"placeholder", properties
)
@@ -83,7 +83,7 @@ class TestAllOfTypeParser(TestCase):
],
}
type_parsing, type_validator = AllOfTypeParser.from_properties(
type_parsing, type_validator = AllOfTypeParser().from_properties(
"placeholder", properties
)
@@ -112,7 +112,7 @@ class TestAllOfTypeParser(TestCase):
],
}
type_parsing, type_validator = AllOfTypeParser.from_properties(
type_parsing, type_validator = AllOfTypeParser().from_properties(
"placeholder", properties
)
@@ -133,7 +133,7 @@ class TestAllOfTypeParser(TestCase):
]
}
type_parsing, type_validator = AllOfTypeParser.from_properties(
type_parsing, type_validator = AllOfTypeParser().from_properties(
"placeholder", properties
)
@@ -155,7 +155,7 @@ class TestAllOfTypeParser(TestCase):
}
with self.assertRaises(ValueError):
AllOfTypeParser.from_properties("placeholder", properties)
AllOfTypeParser().from_properties("placeholder", properties)
def test_all_of_invalid_type_not_present(self):
properties = {
@@ -168,7 +168,7 @@ class TestAllOfTypeParser(TestCase):
}
with self.assertRaises(ValueError):
AllOfTypeParser.from_properties("placeholder", properties)
AllOfTypeParser().from_properties("placeholder", properties)
def test_all_of_invalid_type_in_fields(self):
properties = {
@@ -181,7 +181,7 @@ class TestAllOfTypeParser(TestCase):
}
with self.assertRaises(ValueError):
AllOfTypeParser.from_properties("placeholder", properties)
AllOfTypeParser().from_properties("placeholder", properties)
def test_all_of_description_field(self):
"""
@@ -218,7 +218,7 @@ class TestAllOfTypeParser(TestCase):
],
}
type_parsing, _ = AllOfTypeParser.from_properties("placeholder", properties)
type_parsing, _ = AllOfTypeParser().from_properties("placeholder", properties)
self.assertEqual(
type_parsing.schema()["properties"]["name"]["description"],
@@ -256,7 +256,7 @@ class TestAllOfTypeParser(TestCase):
],
}
type_parsing, _ = AllOfTypeParser.from_properties("placeholder", properties)
type_parsing, _ = AllOfTypeParser().from_properties("placeholder", properties)
obj = type_parsing()
self.assertEqual(obj.name, "John")
self.assertEqual(obj.age, 30)
@@ -289,4 +289,4 @@ class TestAllOfTypeParser(TestCase):
}
with self.assertRaises(ValueError):
AllOfTypeParser.from_properties("placeholder", properties)
AllOfTypeParser().from_properties("placeholder", properties)

View File

@@ -19,7 +19,7 @@ class TestAnyOfTypeParser(TestCase):
],
}
type_parsing, _ = AnyOfTypeParser.from_properties("placeholder", properties)
type_parsing, _ = AnyOfTypeParser().from_properties("placeholder", properties)
# check union type has string and int
self.assertEqual(get_origin(type_parsing), Union)
@@ -45,7 +45,7 @@ class TestAnyOfTypeParser(TestCase):
"default": 42,
}
type_parsing, type_validator = AnyOfTypeParser.from_properties(
type_parsing, type_validator = AnyOfTypeParser().from_properties(
"placeholder", properties
)