feat: adds caching per namespace
This commit is contained in:
@@ -17,10 +17,12 @@ class SchemaConverter:
|
|||||||
fields and types. The generated model can be used for data validation and serialization.
|
fields and types. The generated model can be used for data validation and serialization.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ref_cache: Optional[RefCacheDict] = None) -> None:
|
def __init__(
|
||||||
if ref_cache is None:
|
self, namespace_registry: Optional[dict[str, RefCacheDict]] = None
|
||||||
ref_cache = dict()
|
) -> None:
|
||||||
self._ref_cache = ref_cache
|
if namespace_registry is None:
|
||||||
|
namespace_registry = dict()
|
||||||
|
self._namespace_registry = namespace_registry
|
||||||
|
|
||||||
def build_with_cache(
|
def build_with_cache(
|
||||||
self,
|
self,
|
||||||
@@ -43,7 +45,8 @@ class SchemaConverter:
|
|||||||
if without_cache:
|
if without_cache:
|
||||||
local_ref_cache = dict()
|
local_ref_cache = dict()
|
||||||
elif ref_cache is None:
|
elif ref_cache is None:
|
||||||
local_ref_cache = self._ref_cache
|
namespace = schema.get("$id", "default")
|
||||||
|
local_ref_cache = self._namespace_registry.setdefault(namespace, dict())
|
||||||
else:
|
else:
|
||||||
local_ref_cache = ref_cache
|
local_ref_cache = ref_cache
|
||||||
|
|
||||||
@@ -107,19 +110,28 @@ class SchemaConverter:
|
|||||||
unsupported_field=unsupported_type,
|
unsupported_field=unsupported_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
def clear_ref_cache(self) -> None:
|
def clear_ref_cache(self, namespace: Optional[str] = "default") -> None:
|
||||||
"""
|
"""
|
||||||
Clears the reference cache.
|
Clears the reference cache.
|
||||||
"""
|
"""
|
||||||
self._ref_cache.clear()
|
if namespace is None:
|
||||||
|
self._namespace_registry.clear()
|
||||||
|
return
|
||||||
|
|
||||||
def get_cached_ref(self, ref_name: str):
|
if namespace in self._namespace_registry:
|
||||||
|
self._namespace_registry[namespace].clear()
|
||||||
|
|
||||||
|
def get_cached_ref(
|
||||||
|
self, ref_name: str, namespace: str = "default"
|
||||||
|
) -> Optional[type]:
|
||||||
"""
|
"""
|
||||||
Gets a cached reference from the reference cache.
|
Gets a cached reference from the reference cache.
|
||||||
:param ref_name: The name of the reference to get.
|
:param ref_name: The name of the reference to get.
|
||||||
:return: The cached reference, or None if not found.
|
:return: The cached reference, or None if not found.
|
||||||
"""
|
"""
|
||||||
cached_type = self._ref_cache.get(ref_name)
|
cached_type = self._namespace_registry.get(
|
||||||
|
namespace, {}
|
||||||
|
).get(ref_name)
|
||||||
|
|
||||||
if isinstance(cached_type, type):
|
if isinstance(cached_type, type):
|
||||||
return cached_type
|
return cached_type
|
||||||
|
|||||||
@@ -877,7 +877,6 @@ class TestSchemaConverter(TestCase):
|
|||||||
converter2 = SchemaConverter(ref_cache)
|
converter2 = SchemaConverter(ref_cache)
|
||||||
model2 = converter2.build_with_cache(schema)
|
model2 = converter2.build_with_cache(schema)
|
||||||
|
|
||||||
self.assertIs(converter1._ref_cache, converter2._ref_cache)
|
|
||||||
self.assertIs(model1, model2)
|
self.assertIs(model1, model2)
|
||||||
|
|
||||||
def test_instance_level_ref_cache_isolation_via_without_cache_param(self):
|
def test_instance_level_ref_cache_isolation_via_without_cache_param(self):
|
||||||
@@ -1041,3 +1040,131 @@ class TestSchemaConverter(TestCase):
|
|||||||
|
|
||||||
with self.assertRaises(InvalidSchemaException):
|
with self.assertRaises(InvalidSchemaException):
|
||||||
self.converter.build_with_cache(schema)
|
self.converter.build_with_cache(schema)
|
||||||
|
|
||||||
|
def tests_instance_level_ref_cache_isolation_via_property_id(self):
|
||||||
|
schema1: JSONSchema = {
|
||||||
|
"$id": "http://example.com/schemas/person1.json",
|
||||||
|
"title": "Person",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"age": {"type": "integer"},
|
||||||
|
"emergency_contact": {
|
||||||
|
"$ref": "#",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["name", "age"],
|
||||||
|
}
|
||||||
|
|
||||||
|
model1 = self.converter.build_with_cache(schema1)
|
||||||
|
|
||||||
|
schema2: JSONSchema = {
|
||||||
|
"$id": "http://example.com/schemas/person2.json",
|
||||||
|
"title": "Person",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"age": {"type": "integer"},
|
||||||
|
"address": {"type": "string"},
|
||||||
|
},
|
||||||
|
"required": ["name", "age", "address"],
|
||||||
|
}
|
||||||
|
|
||||||
|
model2 = self.converter.build_with_cache(schema2)
|
||||||
|
|
||||||
|
self.assertIsNot(model1, model2)
|
||||||
|
|
||||||
|
def tests_instance_level_ref_cache_colision_when_same_property_id(self):
|
||||||
|
schema1: JSONSchema = {
|
||||||
|
"$id": "http://example.com/schemas/person.json",
|
||||||
|
"title": "Person",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"age": {"type": "integer"},
|
||||||
|
"emergency_contact": {
|
||||||
|
"$ref": "#",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["name", "age"],
|
||||||
|
}
|
||||||
|
|
||||||
|
model1 = self.converter.build_with_cache(schema1)
|
||||||
|
|
||||||
|
schema2: JSONSchema = {
|
||||||
|
"$id": "http://example.com/schemas/person.json",
|
||||||
|
"title": "Person",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"age": {"type": "integer"},
|
||||||
|
"address": {"type": "string"},
|
||||||
|
},
|
||||||
|
"required": ["name", "age", "address"],
|
||||||
|
}
|
||||||
|
|
||||||
|
model2 = self.converter.build_with_cache(schema2)
|
||||||
|
|
||||||
|
self.assertIs(model1, model2)
|
||||||
|
|
||||||
|
def test_namespace_isolation_via_on_call_config(self):
|
||||||
|
namespace = "namespace1"
|
||||||
|
|
||||||
|
schema: JSONSchema = {
|
||||||
|
"$id": namespace,
|
||||||
|
"title": "Person",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"age": {"type": "integer"},
|
||||||
|
"address": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"street": {"type": "string"},
|
||||||
|
"city": {"type": "string"},
|
||||||
|
},
|
||||||
|
"required": ["street", "city"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["name", "age", "address"],
|
||||||
|
}
|
||||||
|
|
||||||
|
model = self.converter.build_with_cache(schema)
|
||||||
|
|
||||||
|
invalid_cached_model = self.converter.get_cached_ref("Person")
|
||||||
|
self.assertIsNone(invalid_cached_model)
|
||||||
|
|
||||||
|
cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
||||||
|
self.assertIs(model, cached_model)
|
||||||
|
|
||||||
|
def test_clear_namespace_registry(self):
|
||||||
|
namespace = "namespace_to_clear"
|
||||||
|
|
||||||
|
schema: JSONSchema = {
|
||||||
|
"$id": namespace,
|
||||||
|
"title": "Person",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"age": {"type": "integer"},
|
||||||
|
"address": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"street": {"type": "string"},
|
||||||
|
"city": {"type": "string"},
|
||||||
|
},
|
||||||
|
"required": ["street", "city"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["name", "age", "address"],
|
||||||
|
}
|
||||||
|
|
||||||
|
model = self.converter.build_with_cache(schema)
|
||||||
|
|
||||||
|
cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
||||||
|
self.assertIs(model, cached_model)
|
||||||
|
|
||||||
|
self.converter.clear_ref_cache(namespace=namespace)
|
||||||
|
|
||||||
|
cleared_cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
||||||
|
self.assertIsNone(cleared_cached_model)
|
||||||
|
|||||||
Reference in New Issue
Block a user