diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a63e2405..cb9218e8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -35,6 +35,7 @@ Bug Fixes :: * Add python version requirement on setup.py (#586) [jason-the-j] + * Fix reference resolution for definitions in schema. (#553) [peter-doggart] .. _section-1.3.0: diff --git a/flask_restx/api.py b/flask_restx/api.py index a76ae3a2..83ad9ec9 100644 --- a/flask_restx/api.py +++ b/flask_restx/api.py @@ -16,7 +16,7 @@ from flask.signals import got_request_exception -from jsonschema import RefResolver +from referencing import Registry from werkzeug.utils import cached_property from werkzeug.datastructures import Headers @@ -133,7 +133,7 @@ def __init__( format_checker=None, url_scheme=None, default_swagger_filename="swagger.json", - **kwargs + **kwargs, ): self.version = version self.title = title or "API" @@ -825,7 +825,44 @@ def payload(self): @property def refresolver(self): if not self._refresolver: - self._refresolver = RefResolver.from_schema(self.__schema__) + # Create a registry that can resolve references within our schema + registry = Registry() + schema = self.__schema__ + + # If schema has definitions, register it + if "definitions" in schema: + schema_id = schema.get("$id", "http://localhost/schema.json") + registry = registry.with_resource(schema_id, schema) + else: + # If no definitions in schema, register all models individually + for name, model in self.models.items(): + model_schema = model.__schema__ + # Add $id to the model schema so it can be referenced + if "$id" not in model_schema: + model_schema = model_schema.copy() + model_schema["$id"] = ( + f"http://localhost/schema.json#/definitions/{name}" + ) + registry = registry.with_resource( + f"http://localhost/schema.json#/definitions/{name}", + model_schema, + ) + + # Also register the root schema with definitions + if self.models: + definitions = {} + for name, model in self.models.items(): + definitions[name] = model.__schema__ + + schema_with_definitions = { + "$id": "http://localhost/schema.json", + "definitions": definitions, + } + registry = registry.with_resource( + "http://localhost/schema.json", schema_with_definitions + ) + + self._refresolver = registry return self._refresolver @staticmethod @@ -861,7 +898,7 @@ def _blueprint_setup_add_url_rule_patch( "%s.%s" % (blueprint_setup.blueprint.name, endpoint), view_func, defaults=defaults, - **options + **options, ) def _deferred_blueprint_init(self, setup_state): diff --git a/flask_restx/model.py b/flask_restx/model.py index dea43c6a..b72496cf 100644 --- a/flask_restx/model.py +++ b/flask_restx/model.py @@ -11,6 +11,7 @@ from .errors import abort from jsonschema import Draft4Validator +from jsonschema.validators import validator_for from jsonschema.exceptions import ValidationError from .utils import not_none @@ -89,9 +90,48 @@ def inherit(cls, name, *parents): return model def validate(self, data, resolver=None, format_checker=None): - validator = Draft4Validator( - self.__schema__, resolver=resolver, format_checker=format_checker - ) + # For backward compatibility, resolver can be either a RefResolver or a Registry + if resolver is not None and hasattr(resolver, "resolve"): + # Old RefResolver - convert to registry + registry = None + validator = Draft4Validator( + self.__schema__, resolver=resolver, format_checker=format_checker + ) + else: + # New Registry or None + # If we have a registry, we need to create a schema that includes definitions + schema_to_validate = self.__schema__ + if resolver is not None: + # Check if the schema has $ref that need to be resolved + import json + + schema_str = json.dumps(self.__schema__) + if '"$ref"' in schema_str: + # Create a schema with inline definitions from the registry + definitions = {} + for uri in resolver: + resource = resolver[uri] + if isinstance(resource, dict) and "definitions" in resource: + definitions.update(resource["definitions"]) + + if definitions: + # Create a new schema that includes the definitions + schema_to_validate = { + "$id": "http://localhost/schema.json", + "definitions": definitions, + **self.__schema__, + } + + ValidatorClass = validator_for(schema_to_validate) + if resolver is not None: + validator = ValidatorClass( + schema_to_validate, registry=resolver, format_checker=format_checker + ) + else: + validator = ValidatorClass( + schema_to_validate, format_checker=format_checker + ) + try: validator.validate(data) except ValidationError: diff --git a/requirements/install.pip b/requirements/install.pip index d4de41a8..f24652ba 100644 --- a/requirements/install.pip +++ b/requirements/install.pip @@ -1,5 +1,6 @@ aniso8601>=0.82 jsonschema +referencing Flask>=0.8, !=2.0.0 werkzeug!=2.0.0 importlib_resources