From 3ab55e44bd15744e48930acde9d6db1fe64b787b Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 28 Jul 2018 21:57:49 +0100 Subject: [PATCH] Lazy schema references --- openapi_core/schema/properties/generators.py | 7 +-- openapi_core/schema/schemas/factories.py | 2 +- openapi_core/schema/schemas/registries.py | 18 +++++-- openapi_core/schema/schemas/util.py | 5 ++ requirements.txt | 1 + tests/integration/data/v3.0/petstore.yaml | 2 + tests/unit/schema/test_schemas_registry.py | 49 ++++++++++++++++++++ 7 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 tests/unit/schema/test_schemas_registry.py diff --git a/openapi_core/schema/properties/generators.py b/openapi_core/schema/properties/generators.py index 4e3d702..b0485b0 100644 --- a/openapi_core/schema/properties/generators.py +++ b/openapi_core/schema/properties/generators.py @@ -4,8 +4,9 @@ from six import iteritems class PropertiesGenerator(object): - def __init__(self, dereferencer): + def __init__(self, dereferencer, schemas_registry): self.dereferencer = dereferencer + self.schemas_registry = schemas_registry def generate(self, properties): for property_name, schema_spec in iteritems(properties): @@ -13,5 +14,5 @@ class PropertiesGenerator(object): yield property_name, schema def _create_schema(self, schema_spec): - from openapi_core.schema.schemas.factories import SchemaFactory - return SchemaFactory(self.dereferencer).create(schema_spec) + schema, _ = self.schemas_registry.get_or_create(schema_spec) + return schema diff --git a/openapi_core/schema/schemas/factories.py b/openapi_core/schema/schemas/factories.py index b054898..adad164 100644 --- a/openapi_core/schema/schemas/factories.py +++ b/openapi_core/schema/schemas/factories.py @@ -61,7 +61,7 @@ class SchemaFactory(object): @property @lru_cache() def properties_generator(self): - return PropertiesGenerator(self.dereferencer) + return PropertiesGenerator(self.dereferencer, self) def _create_items(self, items_spec): return self.create(items_spec) diff --git a/openapi_core/schema/schemas/registries.py b/openapi_core/schema/schemas/registries.py index ad62cdb..3a6d963 100644 --- a/openapi_core/schema/schemas/registries.py +++ b/openapi_core/schema/schemas/registries.py @@ -1,7 +1,10 @@ """OpenAPI core schemas registries module""" import logging +from lazy_object_proxy import Proxy + from openapi_core.schema.schemas.factories import SchemaFactory +from openapi_core.schema.schemas.util import dicthash log = logging.getLogger(__name__) @@ -13,10 +16,17 @@ class SchemaRegistry(SchemaFactory): self._schemas = {} def get_or_create(self, schema_spec): + schema_hash = dicthash(schema_spec) schema_deref = self.dereferencer.dereference(schema_spec) - model = schema_deref.get('x-model', None) - if model and model in self._schemas: - return self._schemas[model], False + if schema_hash in self._schemas: + return self._schemas[schema_hash], False - return self.create(schema_deref), True + if '$ref' in schema_spec: + schema = Proxy(lambda: self.create(schema_deref)) + else: + schema = self.create(schema_deref) + + self._schemas[schema_hash] = schema + + return schema, True diff --git a/openapi_core/schema/schemas/util.py b/openapi_core/schema/schemas/util.py index cf2c917..18c0585 100644 --- a/openapi_core/schema/schemas/util.py +++ b/openapi_core/schema/schemas/util.py @@ -1,5 +1,6 @@ """OpenAPI core schemas util module""" from distutils.util import strtobool +from json import dumps def forcebool(val): @@ -7,3 +8,7 @@ def forcebool(val): val = strtobool(val) return bool(val) + + +def dicthash(d): + return hash(dumps(d, sort_keys=True)) diff --git a/requirements.txt b/requirements.txt index 7b2dc82..6792e19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ openapi-spec-validator six yarl<1.2.0 +lazy-object-proxy diff --git a/tests/integration/data/v3.0/petstore.yaml b/tests/integration/data/v3.0/petstore.yaml index d4b5907..e573a4e 100644 --- a/tests/integration/data/v3.0/petstore.yaml +++ b/tests/integration/data/v3.0/petstore.yaml @@ -298,6 +298,8 @@ components: properties: rootCause: type: string + suberror: + $ref: "#/components/schemas/ExtendedError" additionalProperties: type: string responses: diff --git a/tests/unit/schema/test_schemas_registry.py b/tests/unit/schema/test_schemas_registry.py new file mode 100644 index 0000000..712032a --- /dev/null +++ b/tests/unit/schema/test_schemas_registry.py @@ -0,0 +1,49 @@ +import pytest + +from jsonschema.validators import RefResolver +from openapi_spec_validator.validators import Dereferencer +from openapi_spec_validator import default_handlers + +from openapi_core.schema.schemas.registries import SchemaRegistry + + +class TestSchemaRegistryGetOrCreate(object): + + @pytest.fixture + def schema_dict(self): + return { + 'type': 'object', + 'properties': { + 'message': { + 'type': 'string', + }, + 'suberror': { + '$ref': '#/components/schemas/Error', + }, + }, + } + + @pytest.fixture + def spec_dict(self, schema_dict): + return { + 'components': { + 'schemas': { + 'Error': schema_dict, + }, + }, + } + + @pytest.fixture + def dereferencer(self, spec_dict): + spec_resolver = RefResolver('', spec_dict, handlers=default_handlers) + return Dereferencer(spec_resolver) + + @pytest.fixture + def schemas_registry(self, dereferencer): + return SchemaRegistry(dereferencer) + + def test_recursion(self, schemas_registry, schema_dict): + schema, _ = schemas_registry.get_or_create(schema_dict) + + assert schema.properties['suberror'] ==\ + schema.properties['suberror'].properties['suberror']