From c2b9e8daf821f3530d89ea24196b1a411abcd1bd Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 18:20:08 -0300 Subject: [PATCH] fix: fixes implementation of save object to cache and adds tests --- jambo/parser/object_type_parser.py | 35 +++++++++++++++++------------- tests/test_schema_converter.py | 25 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 92b6a9f..fc10cb5 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -18,12 +18,6 @@ class ObjectTypeParser(GenericTypeParser): def from_properties_impl( self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ) -> tuple[type[BaseModel], dict]: - ref_cache = kwargs.get("ref_cache") - if ref_cache is None: - raise InternalAssertionException( - "`ref_cache` must be provided in kwargs for RefTypeParser" - ) - type_parsing = self.to_model( name, properties.get("properties", {}), @@ -46,14 +40,6 @@ class ObjectTypeParser(GenericTypeParser): type_parsing.model_validate(example) for example in example_values ] - if name in ref_cache and isinstance(ref_cache[name], type): - warnings.warn( - f"Type '{name}' is already in the ref_cache and will be overwritten." - " This may indicate a circular reference in the schema or a collision in the schema.", - UserWarning, - ) - ref_cache[name] = type_parsing - return type_parsing, type_properties @classmethod @@ -71,10 +57,29 @@ class ObjectTypeParser(GenericTypeParser): :param required_keys: List of required keys in the schema. :return: A Pydantic model class. """ + ref_cache = kwargs.get("ref_cache") + if ref_cache is None: + raise InternalAssertionException( + "`ref_cache` must be provided in kwargs for ObjectTypeParser" + ) + + if (model := ref_cache.get(name)) is not None and isinstance(model, type): + return model + model_config = ConfigDict(validate_assignment=True) fields = cls._parse_properties(properties, required_keys, **kwargs) - return create_model(name, __config__=model_config, **fields) # type: ignore + model = create_model(name, __config__=model_config, **fields) # type: ignore + + if name in ref_cache and isinstance(ref_cache[name], type): + warnings.warn( + f"Type '{name}' is already in the ref_cache and will be overwritten." + " This may indicate a circular reference in the schema or a collision in the schema.", + UserWarning, + ) + ref_cache[name] = model + + return model @classmethod def _parse_properties( diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index cbc6f72..887a548 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -866,3 +866,28 @@ class TestSchemaConverter(TestCase): ) self.assertFalse(object_.model_fields["description"].is_required()) # FAIL + + def test_instance_level_ref_cache(self): + ref_cache = {} + + schema = { + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "emergency_contact": { + "$ref": "#", + }, + }, + "required": ["name", "age"], + } + + converter1 = SchemaConverter(ref_cache) + model1 = converter1.build_with_instance(schema) + + converter2 = SchemaConverter(ref_cache) + model2 = converter2.build_with_instance(schema) + + self.assertIs(converter1._ref_cache, converter2._ref_cache) + self.assertIs(model1, model2)