Bump upperbound version for jsonschema to 5.0.0 (#1447)

* Drop usage of private jsonschema modules

* Bump upper boundry for jsonschema version to 5.0.0

* Create validate_defaults function by passing in the instance_validator

The instance_validator was previously set as an attribute on the
OpenApiValidator. However with the jsonschema upgrade to 4.0.0, this
attribute is no longer maintained while descending into the spec.
Presumably because jsonschema now relies on attrs.resolve.
This commit is contained in:
Robbe Sneyders
2022-01-29 20:30:56 +01:00
committed by GitHub
parent 425d5d551b
commit 86af42d6b9
3 changed files with 43 additions and 41 deletions

View File

@@ -2,10 +2,11 @@
Module containing all code related to json schema validation. Module containing all code related to json schema validation.
""" """
import typing as t
from collections.abc import Mapping from collections.abc import Mapping
from copy import deepcopy from copy import deepcopy
from jsonschema import Draft4Validator, RefResolver, _utils from jsonschema import Draft4Validator, RefResolver
from jsonschema.exceptions import RefResolutionError, ValidationError # noqa from jsonschema.exceptions import RefResolutionError, ValidationError # noqa
from jsonschema.validators import extend from jsonschema.validators import extend
from openapi_spec_validator.handlers import UrlHandler from openapi_spec_validator.handlers import UrlHandler
@@ -54,22 +55,16 @@ def resolve_refs(spec, store=None, handlers=None):
return res return res
def validate_type(validator, types, instance, schema): def allow_nullable(validation_fn: t.Callable) -> t.Callable:
"""Extend an existing validation function, so it allows nullable values to be null."""
def nullable_validation_fn(validator, to_validate, instance, schema):
if instance is None and (schema.get('x-nullable') is True or schema.get('nullable')): if instance is None and (schema.get('x-nullable') is True or schema.get('nullable')):
return return
types = _utils.ensure_list(types) yield from validation_fn(validator, to_validate, instance, schema)
if not any(validator.is_type(instance, type) for type in types): return nullable_validation_fn
yield ValidationError(_utils.types_msg(instance, types))
def validate_enum(validator, enums, instance, schema):
if instance is None and (schema.get('x-nullable') is True or schema.get('nullable')):
return
if instance not in enums:
yield ValidationError(f"{instance!r} is not one of {enums!r}")
def validate_required(validator, required, instance, schema): def validate_required(validator, required, instance, schema):
@@ -100,14 +95,14 @@ def validate_writeOnly(validator, wo, instance, schema):
Draft4RequestValidator = extend(Draft4Validator, { Draft4RequestValidator = extend(Draft4Validator, {
'type': validate_type, 'type': allow_nullable(Draft4Validator.VALIDATORS['type']),
'enum': validate_enum, 'enum': allow_nullable(Draft4Validator.VALIDATORS['enum']),
'required': validate_required, 'required': validate_required,
'readOnly': validate_readOnly}) 'readOnly': validate_readOnly})
Draft4ResponseValidator = extend(Draft4Validator, { Draft4ResponseValidator = extend(Draft4Validator, {
'type': validate_type, 'type': allow_nullable(Draft4Validator.VALIDATORS['type']),
'enum': validate_enum, 'enum': allow_nullable(Draft4Validator.VALIDATORS['enum']),
'required': validate_required, 'required': validate_required,
'writeOnly': validate_writeOnly, 'writeOnly': validate_writeOnly,
'x-writeOnly': validate_writeOnly}) 'x-writeOnly': validate_writeOnly})

View File

@@ -6,6 +6,7 @@ import abc
import copy import copy
import json import json
import pathlib import pathlib
import typing as t
from collections.abc import Mapping from collections.abc import Mapping
from urllib.parse import urlsplit from urllib.parse import urlsplit
@@ -24,33 +25,37 @@ from .utils import deep_get
validate_properties = Draft4Validator.VALIDATORS["properties"] validate_properties = Draft4Validator.VALIDATORS["properties"]
def validate_defaults(validator, properties, instance, schema): def create_validate_default_fn(instance_validator: Draft4Validator) -> t.Callable:
"""Creates a validation function for property defaults. This validation function will be used
by a validator that validates an openapi spec against the openapi schema. The default value
however needs to be validated against the openapi spec itself.
:param instance_validator: A validator to validate defaults against the openapi spec itself
instead of against the openapi schema.
:return: A validation function for property defaults using the passed in instance_validator
"""
def validate_defaults(validator, properties, instance, schema):
"""Validate `properties` subschema. """Validate `properties` subschema.
Enforcing each default value validates against the schema in which it resides. Enforcing each default value validates against the schema in which it resides.
""" """
valid = True valid = True
for error in validate_properties( for error in validate_properties(validator, properties, instance, schema):
validator, properties, instance, schema,
):
valid = False valid = False
yield error yield error
# Validate default only when the subschema has validated successfully # Validate default only when the subschema has validated successfully
# and only when an instance validator is available. if not valid:
if not valid or not hasattr(validator, 'instance_validator'):
return return
if isinstance(instance, dict) and 'default' in instance: if isinstance(instance, dict) and 'default' in instance:
for error in validator.instance_validator.iter_errors( for error in instance_validator.iter_errors(instance['default'], instance):
instance['default'],
instance
):
yield error yield error
return validate_defaults
OpenApiValidator = extend_validator(
Draft4Validator, {"properties": validate_defaults},
)
NO_SPEC_VERSION_ERR_MSG = """Unable to get the spec version. NO_SPEC_VERSION_ERR_MSG = """Unable to get the spec version.
You are missing either '"swagger": "2.0"' or '"openapi": "3.0.0"' You are missing either '"swagger": "2.0"' or '"openapi": "3.0.0"'
@@ -83,8 +88,10 @@ class Specification(Mapping):
""" validate spec against schema """ validate spec against schema
""" """
try: try:
instance_validator = Draft4Validator(spec)
validate_defaults = create_validate_default_fn(instance_validator)
OpenApiValidator = extend_validator(Draft4Validator, {"properties": validate_defaults})
validator = OpenApiValidator(cls.openapi_schema) validator = OpenApiValidator(cls.openapi_schema)
validator.instance_validator = Draft4Validator(spec)
validator.validate(spec) validator.validate(spec)
except jsonschema.exceptions.ValidationError as e: except jsonschema.exceptions.ValidationError as e:
raise InvalidSpecification.create_from(e) raise InvalidSpecification.create_from(e)

View File

@@ -21,7 +21,7 @@ version = read_version('connexion')
install_requires = [ install_requires = [
'clickclick>=1.2,<21', 'clickclick>=1.2,<21',
'jsonschema>=2.5.1,<4', 'jsonschema>=2.5.1,<5',
'PyYAML>=5.1,<6', 'PyYAML>=5.1,<6',
'requests>=2.9.1,<3', 'requests>=2.9.1,<3',
'inflection>=0.3.1,<0.6', 'inflection>=0.3.1,<0.6',