Initial allOf Implementation
This commit is contained in:
@@ -8,3 +8,4 @@ from .string_type_parser import StringTypeParser as StringTypeParser
|
|||||||
from .array_type_parser import ArrayTypeParser as ArrayTypeParser
|
from .array_type_parser import ArrayTypeParser as ArrayTypeParser
|
||||||
from .boolean_type_parser import BooleanTypeParser as BooleanTypeParser
|
from .boolean_type_parser import BooleanTypeParser as BooleanTypeParser
|
||||||
from .float_type_parser import FloatTypeParser as FloatTypeParser
|
from .float_type_parser import FloatTypeParser as FloatTypeParser
|
||||||
|
from .allof_type_parser import AllOfTypeParser as AllOfTypeParser
|
||||||
|
|||||||
76
jambo/parser/allof_type_parser.py
Normal file
76
jambo/parser/allof_type_parser.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from jambo.parser._type_parser import GenericTypeParser
|
||||||
|
|
||||||
|
|
||||||
|
class AllOfTypeParser(GenericTypeParser):
|
||||||
|
mapped_type = any
|
||||||
|
|
||||||
|
json_schema_type = "allOf"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_properties(name, properties):
|
||||||
|
subProperties = properties.get("allOf")
|
||||||
|
if not subProperties:
|
||||||
|
raise ValueError("Invalid JSON Schema: 'allOf' is not specified.")
|
||||||
|
|
||||||
|
_mapped_type = properties.get("type")
|
||||||
|
if _mapped_type is None:
|
||||||
|
_mapped_type = subProperties[0].get("type")
|
||||||
|
|
||||||
|
if _mapped_type is None:
|
||||||
|
raise ValueError("Invalid JSON Schema: 'type' is not specified.")
|
||||||
|
|
||||||
|
if not all(prop.get("type") == _mapped_type for prop in subProperties):
|
||||||
|
raise ValueError("Invalid JSON Schema: allOf types do not match.")
|
||||||
|
|
||||||
|
combined_properties = AllOfTypeParser._rebuild_properties_from_subproperties(
|
||||||
|
subProperties
|
||||||
|
)
|
||||||
|
|
||||||
|
return GenericTypeParser.get_impl(_mapped_type).from_properties(
|
||||||
|
name, combined_properties
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _rebuild_properties_from_subproperties(subProperties):
|
||||||
|
properties = {}
|
||||||
|
for subProperty in subProperties:
|
||||||
|
for name, prop in subProperty.items():
|
||||||
|
if name not in properties:
|
||||||
|
properties[name] = prop
|
||||||
|
else:
|
||||||
|
# Merge properties if they exist in both sub-properties
|
||||||
|
properties[name] = AllOfTypeParser._validate_prop(
|
||||||
|
name, properties[name], prop
|
||||||
|
)
|
||||||
|
return properties
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_prop(prop_name, old_value, new_value):
|
||||||
|
if prop_name == "type":
|
||||||
|
if old_value != new_value:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid JSON Schema: conflicting types for '{prop_name}'"
|
||||||
|
)
|
||||||
|
return old_value
|
||||||
|
|
||||||
|
if prop_name == "description":
|
||||||
|
return f"{old_value} | {new_value}"
|
||||||
|
|
||||||
|
if prop_name == "default":
|
||||||
|
if old_value != new_value:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid JSON Schema: conflicting defaults for '{prop_name}'"
|
||||||
|
)
|
||||||
|
return old_value
|
||||||
|
|
||||||
|
if prop_name == "required":
|
||||||
|
return old_value + new_value
|
||||||
|
|
||||||
|
if prop_name in ("maxLength", "maximum", "exclusiveMaximum"):
|
||||||
|
return old_value if old_value > new_value else new_value
|
||||||
|
|
||||||
|
if prop_name in ("minLength", "minimum", "exclusiveMinimum"):
|
||||||
|
return old_value if old_value < new_value else new_value
|
||||||
|
|
||||||
|
# Handle other properties by just returning the first valued
|
||||||
|
return old_value
|
||||||
@@ -79,8 +79,20 @@ class SchemaConverter:
|
|||||||
def _build_field(
|
def _build_field(
|
||||||
name, properties: dict, required_keys: list[str]
|
name, properties: dict, required_keys: list[str]
|
||||||
) -> tuple[type, dict]:
|
) -> tuple[type, dict]:
|
||||||
|
match properties:
|
||||||
|
case {"anyOf": _}:
|
||||||
|
_field_type = "anyOf"
|
||||||
|
case {"allOf": _}:
|
||||||
|
_field_type = "allOf"
|
||||||
|
case {"oneOf": _}:
|
||||||
|
_field_type = "oneOf"
|
||||||
|
case {"type": _}:
|
||||||
|
_field_type = properties["type"]
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Invalid JSON Schema: {properties}")
|
||||||
|
|
||||||
_field_type, _field_args = GenericTypeParser.get_impl(
|
_field_type, _field_args = GenericTypeParser.get_impl(
|
||||||
properties["type"]
|
_field_type
|
||||||
).from_properties(name, properties)
|
).from_properties(name, properties)
|
||||||
|
|
||||||
_field_args = _field_args or {}
|
_field_args = _field_args or {}
|
||||||
|
|||||||
@@ -281,3 +281,33 @@ class TestSchemaConverter(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(obj.address.street, "123 Main St")
|
self.assertEqual(obj.address.street, "123 Main St")
|
||||||
self.assertEqual(obj.address.city, "Springfield")
|
self.assertEqual(obj.address.city, "Springfield")
|
||||||
|
|
||||||
|
def test_all_of(self):
|
||||||
|
schema = {
|
||||||
|
"title": "Person",
|
||||||
|
"description": "A person",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"allOf": [
|
||||||
|
{"type": "string", "maxLength": 4},
|
||||||
|
{"type": "string", "minLength": 1},
|
||||||
|
{"type": "string", "minLength": 2},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Model = SchemaConverter.build(schema)
|
||||||
|
|
||||||
|
obj = Model(
|
||||||
|
name="J",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(obj.name, "J")
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Model(name="John Invalid")
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Model(name="")
|
||||||
|
|||||||
Reference in New Issue
Block a user