From 7272b1a74b55537661d37015f428d324e43c1d12 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 13 Apr 2025 02:37:21 -0300 Subject: [PATCH] Implements Object Defaults --- jambo/parser/float_type_parser.py | 4 +++- jambo/parser/int_type_parser.py | 4 +++- jambo/parser/object_type_parser.py | 14 +++++++------ jambo/schema_converter.py | 12 +++++------- pyproject.toml | 1 + tests/parser/test_object_type_parser.py | 26 +++++++++++++++++++++++++ tests/test_schema_converter.py | 25 ++++++++++++++++++++++++ 7 files changed, 71 insertions(+), 15 deletions(-) diff --git a/jambo/parser/float_type_parser.py b/jambo/parser/float_type_parser.py index c303ff7..a6dcdd5 100644 --- a/jambo/parser/float_type_parser.py +++ b/jambo/parser/float_type_parser.py @@ -1,5 +1,7 @@ from jambo.parser._type_parser import GenericTypeParser -from jambo.utils.properties_builder.numeric_properties_builder import numeric_properties_builder +from jambo.utils.properties_builder.numeric_properties_builder import ( + numeric_properties_builder, +) class FloatTypeParser(GenericTypeParser): diff --git a/jambo/parser/int_type_parser.py b/jambo/parser/int_type_parser.py index 365e346..1ef907b 100644 --- a/jambo/parser/int_type_parser.py +++ b/jambo/parser/int_type_parser.py @@ -1,5 +1,7 @@ from jambo.parser._type_parser import GenericTypeParser -from jambo.utils.properties_builder.numeric_properties_builder import numeric_properties_builder +from jambo.utils.properties_builder.numeric_properties_builder import ( + numeric_properties_builder, +) class IntTypeParser(GenericTypeParser): diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 20f775e..7c0c363 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -10,10 +10,12 @@ class ObjectTypeParser(GenericTypeParser): def from_properties(name, properties): from jambo.schema_converter import SchemaConverter - if "default" in properties: - raise RuntimeError("Default values for objects are not supported.") + type_parsing = SchemaConverter.build_object(name, properties) + type_properties = {} - return ( - SchemaConverter.build_object(name, properties), - {}, # The second argument is not used in this case - ) + if "default" in properties: + type_properties["default_factory"] = lambda: type_parsing.model_validate( + properties["default"] + ) + + return type_parsing, type_properties diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index bc0ced9..5ed8afb 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -1,13 +1,11 @@ from jambo.parser import GenericTypeParser +from jambo.types.json_schema_type import JSONSchema from jsonschema.exceptions import SchemaError from jsonschema.protocols import Validator from pydantic import create_model from pydantic.fields import Field - -from typing import Type - -from jambo.types.json_schema_type import JSONSchema +from pydantic.main import ModelT class SchemaConverter: @@ -20,7 +18,7 @@ class SchemaConverter: """ @staticmethod - def build(schema: JSONSchema) -> Type: + def build(schema: JSONSchema) -> ModelT: """ Converts a JSON Schema to a Pydantic model. :param schema: The JSON Schema to convert. @@ -35,7 +33,7 @@ class SchemaConverter: def build_object( name: str, schema: JSONSchema, - ) -> Type: + ) -> ModelT: """ Converts a JSON Schema object to a Pydantic model given a name. :param name: @@ -60,7 +58,7 @@ class SchemaConverter: @staticmethod def _build_model_from_properties( model_name: str, model_properties: dict, required_keys: list[str] - ) -> Type: + ) -> ModelT: properties = SchemaConverter._parse_properties(model_properties, required_keys) return create_model(model_name, **properties) diff --git a/pyproject.toml b/pyproject.toml index 7476ee8..2882920 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,3 +66,4 @@ section-order=[ "third-party", "standard-library", ] +lines-after-imports = 2 diff --git a/tests/parser/test_object_type_parser.py b/tests/parser/test_object_type_parser.py index 8e86d44..1c1fea7 100644 --- a/tests/parser/test_object_type_parser.py +++ b/tests/parser/test_object_type_parser.py @@ -21,3 +21,29 @@ class TestObjectTypeParser(TestCase): self.assertEqual(obj.name, "name") self.assertEqual(obj.age, 10) + + def test_object_type_parser_with_default(self): + parser = ObjectTypeParser() + + properties = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + }, + "default": { + "name": "default_name", + "age": 20, + }, + } + + _, type_validator = parser.from_properties("placeholder", properties) + + # Check default value + default_obj = type_validator["default_factory"]() + self.assertEqual(default_obj.name, "default_name") + self.assertEqual(default_obj.age, 20) + + # Chekc default factory new object id + new_obj = type_validator["default_factory"]() + self.assertNotEqual(id(default_obj), id(new_obj)) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 443e0b2..53a7e52 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -256,3 +256,28 @@ class TestSchemaConverter(TestCase): model_set = SchemaConverter.build(schema_set) self.assertEqual(model_set().friends, {"John", "Jane"}) + + def test_default_for_object(self): + schema = { + "title": "Person", + "description": "A person", + "type": "object", + "properties": { + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "default": {"street": "123 Main St", "city": "Springfield"}, + }, + }, + "required": ["address"], + } + + model = SchemaConverter.build(schema) + + obj = model(address={"street": "123 Main St", "city": "Springfield"}) + + self.assertEqual(obj.address.street, "123 Main St") + self.assertEqual(obj.address.city, "Springfield")