Separates PR for Better Testing and Readability
This commit is contained in:
@@ -124,26 +124,3 @@ class GenericTypeParser(ABC, Generic[T]):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _has_meaningful_constraints(field_props):
|
|
||||||
"""
|
|
||||||
Check if field properties contain meaningful constraints that require Field wrapping.
|
|
||||||
|
|
||||||
Returns False if:
|
|
||||||
- field_props is None or empty
|
|
||||||
- field_props only contains {'default': None}
|
|
||||||
|
|
||||||
Returns True if:
|
|
||||||
- field_props contains a non-None default value
|
|
||||||
- field_props contains other constraint properties (min_length, max_length, pattern, etc.)
|
|
||||||
"""
|
|
||||||
if not field_props:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# If only default is set and it's None, no meaningful constraints
|
|
||||||
if field_props == {"default": None}:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# If there are multiple properties or non-None default, that's meaningful
|
|
||||||
return True
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from jambo.types.json_schema_type import JSONSchemaNativeTypes
|
|||||||
from jambo.types.type_parser_options import TypeParserOptions
|
from jambo.types.type_parser_options import TypeParserOptions
|
||||||
|
|
||||||
from pydantic import AfterValidator
|
from pydantic import AfterValidator
|
||||||
from typing_extensions import Annotated, Any, Literal, Unpack
|
from typing_extensions import Annotated, Any, Unpack
|
||||||
|
|
||||||
|
|
||||||
class ConstTypeParser(GenericTypeParser):
|
class ConstTypeParser(GenericTypeParser):
|
||||||
@@ -33,19 +33,11 @@ class ConstTypeParser(GenericTypeParser):
|
|||||||
return const_type, parsed_properties
|
return const_type, parsed_properties
|
||||||
|
|
||||||
def _build_const_type(self, const_value):
|
def _build_const_type(self, const_value):
|
||||||
# Try to use Literal for hashable types (required for discriminated unions)
|
def _validate_const_value(value: Any) -> Any:
|
||||||
# Fall back to validator approach for non-hashable types
|
if value != const_value:
|
||||||
try:
|
raise ValueError(
|
||||||
# Test if the value is hashable (can be used in Literal)
|
f"Value must be equal to the constant value: {const_value}"
|
||||||
hash(const_value)
|
)
|
||||||
return Literal[const_value]
|
return value
|
||||||
except TypeError:
|
|
||||||
# Non-hashable type (like list, dict), use validator approach
|
|
||||||
def _validate_const_value(value: Any) -> Any:
|
|
||||||
if value != const_value:
|
|
||||||
raise ValueError(
|
|
||||||
f"Value must be equal to the constant value: {const_value}"
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
|
|
||||||
return Annotated[type(const_value), AfterValidator(_validate_const_value)]
|
return Annotated[type(const_value), AfterValidator(_validate_const_value)]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from jambo.parser._type_parser import GenericTypeParser
|
from jambo.parser._type_parser import GenericTypeParser
|
||||||
from jambo.types.type_parser_options import TypeParserOptions
|
from jambo.types.type_parser_options import TypeParserOptions
|
||||||
|
|
||||||
from pydantic import Field, BeforeValidator, TypeAdapter, ValidationError
|
from pydantic import BeforeValidator, Field, TypeAdapter, ValidationError
|
||||||
from typing_extensions import Annotated, Union, Unpack, Any
|
from typing_extensions import Annotated, Any, Union, Unpack
|
||||||
|
|
||||||
|
|
||||||
class OneOfTypeParser(GenericTypeParser):
|
class OneOfTypeParser(GenericTypeParser):
|
||||||
@@ -11,7 +11,7 @@ class OneOfTypeParser(GenericTypeParser):
|
|||||||
json_schema_type = "oneOf"
|
json_schema_type = "oneOf"
|
||||||
|
|
||||||
def from_properties_impl(
|
def from_properties_impl(
|
||||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||||
):
|
):
|
||||||
if "oneOf" not in properties:
|
if "oneOf" not in properties:
|
||||||
raise ValueError(f"Invalid JSON Schema: {properties}")
|
raise ValueError(f"Invalid JSON Schema: {properties}")
|
||||||
@@ -42,7 +42,9 @@ class OneOfTypeParser(GenericTypeParser):
|
|||||||
if discriminator and isinstance(discriminator, dict):
|
if discriminator and isinstance(discriminator, dict):
|
||||||
property_name = discriminator.get("propertyName")
|
property_name = discriminator.get("propertyName")
|
||||||
if property_name:
|
if property_name:
|
||||||
validated_type = Annotated[union_type, Field(discriminator=property_name)]
|
validated_type = Annotated[
|
||||||
|
union_type, Field(discriminator=property_name)
|
||||||
|
]
|
||||||
return validated_type, mapped_properties
|
return validated_type, mapped_properties
|
||||||
|
|
||||||
def validate_one_of(value: Any) -> Any:
|
def validate_one_of(value: Any) -> Any:
|
||||||
@@ -59,11 +61,34 @@ class OneOfTypeParser(GenericTypeParser):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if matched_count == 0:
|
if matched_count == 0:
|
||||||
raise ValueError(f"Value does not match any of the oneOf schemas")
|
raise ValueError("Value does not match any of the oneOf schemas")
|
||||||
elif matched_count > 1:
|
elif matched_count > 1:
|
||||||
raise ValueError(f"Value matches multiple oneOf schemas, exactly one expected")
|
raise ValueError(
|
||||||
|
"Value matches multiple oneOf schemas, exactly one expected"
|
||||||
|
)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
validated_type = Annotated[union_type, BeforeValidator(validate_one_of)]
|
validated_type = Annotated[union_type, BeforeValidator(validate_one_of)]
|
||||||
return validated_type, mapped_properties
|
return validated_type, mapped_properties
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _has_meaningful_constraints(field_props):
|
||||||
|
"""
|
||||||
|
Check if field properties contain meaningful constraints that require Field wrapping.
|
||||||
|
Returns False if:
|
||||||
|
- field_props is None or empty
|
||||||
|
- field_props only contains {'default': None}
|
||||||
|
Returns True if:
|
||||||
|
- field_props contains a non-None default value
|
||||||
|
- field_props contains other constraint properties (min_length, max_length, pattern, etc.)
|
||||||
|
"""
|
||||||
|
if not field_props:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# If only default is set and it's None, no meaningful constraints
|
||||||
|
if field_props == {"default": None}:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# If there are multiple properties or non-None default, that's meaningful
|
||||||
|
return True
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class StringTypeParser(GenericTypeParser):
|
|||||||
"maxLength": "max_length",
|
"maxLength": "max_length",
|
||||||
"minLength": "min_length",
|
"minLength": "min_length",
|
||||||
"pattern": "pattern",
|
"pattern": "pattern",
|
||||||
|
"format": "format",
|
||||||
}
|
}
|
||||||
|
|
||||||
format_type_mapping = {
|
format_type_mapping = {
|
||||||
@@ -37,9 +38,7 @@ class StringTypeParser(GenericTypeParser):
|
|||||||
def from_properties_impl(
|
def from_properties_impl(
|
||||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||||
):
|
):
|
||||||
mapped_properties = self.mappings_properties_builder(
|
mapped_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||||
properties, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
format_type = properties.get("format")
|
format_type = properties.get("format")
|
||||||
if not format_type:
|
if not format_type:
|
||||||
@@ -52,8 +51,4 @@ class StringTypeParser(GenericTypeParser):
|
|||||||
if format_type in self.format_pattern_mapping:
|
if format_type in self.format_pattern_mapping:
|
||||||
mapped_properties["pattern"] = self.format_pattern_mapping[format_type]
|
mapped_properties["pattern"] = self.format_pattern_mapping[format_type]
|
||||||
|
|
||||||
if "json_schema_extra" not in mapped_properties:
|
|
||||||
mapped_properties["json_schema_extra"] = {}
|
|
||||||
mapped_properties["json_schema_extra"]["format"] = format_type
|
|
||||||
|
|
||||||
return mapped_type, mapped_properties
|
return mapped_type, mapped_properties
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
from jambo.parser import ConstTypeParser
|
from jambo.parser import ConstTypeParser
|
||||||
|
|
||||||
from typing_extensions import Annotated, Literal, get_args, get_origin
|
from typing_extensions import Annotated, get_args, get_origin
|
||||||
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
|
|
||||||
class TestConstTypeParser(TestCase):
|
class TestConstTypeParser(TestCase):
|
||||||
def test_const_type_parser_hashable_value(self):
|
def test_const_type_parser(self):
|
||||||
"""Test const parser with hashable values (uses Literal)"""
|
|
||||||
parser = ConstTypeParser()
|
parser = ConstTypeParser()
|
||||||
|
|
||||||
expected_const_value = "United States of America"
|
expected_const_value = "United States of America"
|
||||||
@@ -17,60 +16,8 @@ class TestConstTypeParser(TestCase):
|
|||||||
"country", properties
|
"country", properties
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check that we get a Literal type for hashable values
|
|
||||||
self.assertEqual(get_origin(parsed_type), Literal)
|
|
||||||
self.assertEqual(get_args(parsed_type), (expected_const_value,))
|
|
||||||
|
|
||||||
self.assertEqual(parsed_properties["default"], expected_const_value)
|
|
||||||
|
|
||||||
def test_const_type_parser_non_hashable_value(self):
|
|
||||||
"""Test const parser with non-hashable values (uses Annotated with validator)"""
|
|
||||||
parser = ConstTypeParser()
|
|
||||||
|
|
||||||
expected_const_value = [1, 2, 3] # Lists are not hashable
|
|
||||||
properties = {"const": expected_const_value}
|
|
||||||
|
|
||||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
|
||||||
"list_const", properties
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check that we get an Annotated type for non-hashable values
|
|
||||||
self.assertEqual(get_origin(parsed_type), Annotated)
|
self.assertEqual(get_origin(parsed_type), Annotated)
|
||||||
self.assertIn(list, get_args(parsed_type))
|
self.assertIn(str, get_args(parsed_type))
|
||||||
|
|
||||||
self.assertEqual(parsed_properties["default"], expected_const_value)
|
|
||||||
|
|
||||||
def test_const_type_parser_integer_value(self):
|
|
||||||
"""Test const parser with integer values (uses Literal)"""
|
|
||||||
parser = ConstTypeParser()
|
|
||||||
|
|
||||||
expected_const_value = 42
|
|
||||||
properties = {"const": expected_const_value}
|
|
||||||
|
|
||||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
|
||||||
"int_const", properties
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check that we get a Literal type for hashable values
|
|
||||||
self.assertEqual(get_origin(parsed_type), Literal)
|
|
||||||
self.assertEqual(get_args(parsed_type), (expected_const_value,))
|
|
||||||
|
|
||||||
self.assertEqual(parsed_properties["default"], expected_const_value)
|
|
||||||
|
|
||||||
def test_const_type_parser_boolean_value(self):
|
|
||||||
"""Test const parser with boolean values (uses Literal)"""
|
|
||||||
parser = ConstTypeParser()
|
|
||||||
|
|
||||||
expected_const_value = True
|
|
||||||
properties = {"const": expected_const_value}
|
|
||||||
|
|
||||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
|
||||||
"bool_const", properties
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check that we get a Literal type for hashable values
|
|
||||||
self.assertEqual(get_origin(parsed_type), Literal)
|
|
||||||
self.assertEqual(get_args(parsed_type), (expected_const_value,))
|
|
||||||
|
|
||||||
self.assertEqual(parsed_properties["default"], expected_const_value)
|
self.assertEqual(parsed_properties["default"], expected_const_value)
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"email": {"type": "string", "format": "email"}
|
"email": {"type": "string", "format": "email"}
|
||||||
},
|
},
|
||||||
"required": ["email"],
|
"required": ["email"],
|
||||||
"additionalProperties": False
|
"additionalProperties": False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -105,8 +105,8 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"phone": {"type": "string", "pattern": "^[0-9-]+$"}
|
"phone": {"type": "string", "pattern": "^[0-9-]+$"}
|
||||||
},
|
},
|
||||||
"required": ["phone"],
|
"required": ["phone"],
|
||||||
"additionalProperties": False
|
"additionalProperties": False,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -135,25 +135,23 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"const": "cat"},
|
"type": {"const": "cat"},
|
||||||
"meows": {"type": "boolean"}
|
"meows": {"type": "boolean"},
|
||||||
},
|
},
|
||||||
"required": ["type", "meows"]
|
"required": ["type", "meows"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"const": "dog"},
|
"type": {"const": "dog"},
|
||||||
"barks": {"type": "boolean"}
|
"barks": {"type": "boolean"},
|
||||||
},
|
},
|
||||||
"required": ["type", "barks"]
|
"required": ["type", "barks"],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"discriminator": {
|
"discriminator": {"propertyName": "type"},
|
||||||
"propertyName": "type"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["pet"]
|
"required": ["pet"],
|
||||||
}
|
}
|
||||||
|
|
||||||
Model = SchemaConverter.build(schema)
|
Model = SchemaConverter.build(schema)
|
||||||
@@ -183,29 +181,33 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"vehicle_type": {"const": "car"},
|
"vehicle_type": {"const": "car"},
|
||||||
"doors": {"type": "integer", "minimum": 2, "maximum": 4}
|
"doors": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 2,
|
||||||
|
"maximum": 4,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"required": ["vehicle_type", "doors"]
|
"required": ["vehicle_type", "doors"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"vehicle_type": {"const": "motorcycle"},
|
"vehicle_type": {"const": "motorcycle"},
|
||||||
"engine_size": {"type": "number", "minimum": 125}
|
"engine_size": {"type": "number", "minimum": 125},
|
||||||
},
|
},
|
||||||
"required": ["vehicle_type", "engine_size"]
|
"required": ["vehicle_type", "engine_size"],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"discriminator": {
|
"discriminator": {
|
||||||
"propertyName": "vehicle_type",
|
"propertyName": "vehicle_type",
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"car": "#/properties/vehicle/oneOf/0",
|
"car": "#/properties/vehicle/oneOf/0",
|
||||||
"motorcycle": "#/properties/vehicle/oneOf/1"
|
"motorcycle": "#/properties/vehicle/oneOf/1",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["vehicle"]
|
"required": ["vehicle"],
|
||||||
}
|
}
|
||||||
|
|
||||||
Model = SchemaConverter.build(schema)
|
Model = SchemaConverter.build(schema)
|
||||||
@@ -229,25 +231,23 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"const": "circle"},
|
"type": {"const": "circle"},
|
||||||
"radius": {"type": "number", "minimum": 0}
|
"radius": {"type": "number", "minimum": 0},
|
||||||
},
|
},
|
||||||
"required": ["type", "radius"]
|
"required": ["type", "radius"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"const": "square"},
|
"type": {"const": "square"},
|
||||||
"side": {"type": "number", "minimum": 0}
|
"side": {"type": "number", "minimum": 0},
|
||||||
},
|
},
|
||||||
"required": ["type", "side"]
|
"required": ["type", "side"],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"discriminator": {
|
"discriminator": {"propertyName": "type"},
|
||||||
"propertyName": "type"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["shape"]
|
"required": ["shape"],
|
||||||
}
|
}
|
||||||
|
|
||||||
Model = SchemaConverter.build(schema)
|
Model = SchemaConverter.build(schema)
|
||||||
@@ -283,9 +283,7 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"title": "Test",
|
"title": "Test",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"value": {
|
"value": {"oneOf": None},
|
||||||
"oneOf": None
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +300,7 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
{"type": "string"},
|
{"type": "string"},
|
||||||
{"type": "integer"},
|
{"type": "integer"},
|
||||||
],
|
],
|
||||||
"default": "test"
|
"default": "test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -321,7 +319,7 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
{"type": "string", "minLength": 5},
|
{"type": "string", "minLength": 5},
|
||||||
{"type": "integer", "minimum": 10},
|
{"type": "integer", "minimum": 10},
|
||||||
],
|
],
|
||||||
"default": "hi"
|
"default": "hi",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -340,20 +338,20 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"const": "a"},
|
"type": {"const": "a"},
|
||||||
"value": {"type": "string"}
|
"value": {"type": "string"},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"const": "b"},
|
"type": {"const": "b"},
|
||||||
"value": {"type": "integer"}
|
"value": {"type": "integer"},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"discriminator": {} # discriminator without propertyName
|
"discriminator": {}, # discriminator without propertyName
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Model = SchemaConverter.build(schema)
|
Model = SchemaConverter.build(schema)
|
||||||
@@ -383,23 +381,18 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"properties": {
|
"properties": {
|
||||||
"value": {
|
"value": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{"type": "object", "properties": {"data": {"type": "string"}}},
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {"type": "string"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {"type": "string"},
|
"data": {"type": "string"},
|
||||||
"optional": {"type": "string"}
|
"optional": {"type": "string"},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"discriminator": {} # discriminator without propertyName
|
"discriminator": {}, # discriminator without propertyName
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Model = SchemaConverter.build(schema)
|
Model = SchemaConverter.build(schema)
|
||||||
@@ -419,11 +412,11 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"value": {
|
"value": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{"type": "string", "maxLength": 6},
|
{"type": "string", "maxLength": 6},
|
||||||
{"type": "string", "minLength": 4}
|
{"type": "string", "minLength": 4},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["value"]
|
"required": ["value"],
|
||||||
}
|
}
|
||||||
|
|
||||||
Model = SchemaConverter.build(schema)
|
Model = SchemaConverter.build(schema)
|
||||||
@@ -453,26 +446,24 @@ class TestOneOfTypeParser(TestCase):
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"const": "circle"},
|
"type": {"const": "circle"},
|
||||||
"radius": {"type": "number", "minimum": 0}
|
"radius": {"type": "number", "minimum": 0},
|
||||||
},
|
},
|
||||||
"required": ["type", "radius"]
|
"required": ["type", "radius"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"const": "rectangle"},
|
"type": {"const": "rectangle"},
|
||||||
"width": {"type": "number", "minimum": 0},
|
"width": {"type": "number", "minimum": 0},
|
||||||
"height": {"type": "number", "minimum": 0}
|
"height": {"type": "number", "minimum": 0},
|
||||||
},
|
},
|
||||||
"required": ["type", "width", "height"]
|
"required": ["type", "width", "height"],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"discriminator": {
|
"discriminator": {"propertyName": "type"},
|
||||||
"propertyName": "type"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["shape"]
|
"required": ["shape"],
|
||||||
}
|
}
|
||||||
|
|
||||||
Model = SchemaConverter.build(schema)
|
Model = SchemaConverter.build(schema)
|
||||||
|
|||||||
Reference in New Issue
Block a user