mirror of
https://github.com/correl/openapi-core.git
synced 2024-11-25 03:00:11 +00:00
Merge pull request #212 from p1c2u/feature/use-openapi-schema-validator-lib
Use openapi-schema-validator library
This commit is contained in:
commit
43bf9a6e8d
12 changed files with 15 additions and 335 deletions
|
@ -324,4 +324,5 @@ You can use RequestsOpenAPIResponse as a Requests response factory:
|
||||||
Related projects
|
Related projects
|
||||||
################
|
################
|
||||||
* `openapi-spec-validator <https://github.com/p1c2u/openapi-spec-validator>`__
|
* `openapi-spec-validator <https://github.com/p1c2u/openapi-spec-validator>`__
|
||||||
|
* `openapi-schema-validator <https://github.com/p1c2u/openapi-schema-validator>`__
|
||||||
* `pyramid_openapi3 <https://github.com/niteoweb/pyramid_openapi3>`__
|
* `pyramid_openapi3 <https://github.com/niteoweb/pyramid_openapi3>`__
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
from openapi_core.schema_validator._format import oas30_format_checker
|
|
||||||
from openapi_core.schema_validator.validators import OAS30Validator
|
|
||||||
|
|
||||||
__all__ = ['OAS30Validator', 'oas30_format_checker']
|
|
|
@ -1,135 +0,0 @@
|
||||||
from base64 import b64encode, b64decode
|
|
||||||
import binascii
|
|
||||||
from datetime import datetime
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from jsonschema._format import FormatChecker
|
|
||||||
from jsonschema.exceptions import FormatError
|
|
||||||
from six import binary_type, text_type, integer_types
|
|
||||||
|
|
||||||
DATETIME_HAS_STRICT_RFC3339 = False
|
|
||||||
DATETIME_HAS_ISODATE = False
|
|
||||||
DATETIME_RAISES = ()
|
|
||||||
|
|
||||||
try:
|
|
||||||
import isodate
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
DATETIME_HAS_ISODATE = True
|
|
||||||
DATETIME_RAISES += (ValueError, isodate.ISO8601Error)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import strict_rfc3339
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
DATETIME_HAS_STRICT_RFC3339 = True
|
|
||||||
DATETIME_RAISES += (ValueError, TypeError)
|
|
||||||
|
|
||||||
|
|
||||||
def is_int32(instance):
|
|
||||||
return isinstance(instance, integer_types)
|
|
||||||
|
|
||||||
|
|
||||||
def is_int64(instance):
|
|
||||||
return isinstance(instance, integer_types)
|
|
||||||
|
|
||||||
|
|
||||||
def is_float(instance):
|
|
||||||
return isinstance(instance, float)
|
|
||||||
|
|
||||||
|
|
||||||
def is_double(instance):
|
|
||||||
# float has double precision in Python
|
|
||||||
# It's double in CPython and Jython
|
|
||||||
return isinstance(instance, float)
|
|
||||||
|
|
||||||
|
|
||||||
def is_binary(instance):
|
|
||||||
return isinstance(instance, binary_type)
|
|
||||||
|
|
||||||
|
|
||||||
def is_byte(instance):
|
|
||||||
if isinstance(instance, text_type):
|
|
||||||
instance = instance.encode()
|
|
||||||
|
|
||||||
try:
|
|
||||||
return b64encode(b64decode(instance)) == instance
|
|
||||||
except TypeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def is_datetime(instance):
|
|
||||||
if not isinstance(instance, (binary_type, text_type)):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if DATETIME_HAS_STRICT_RFC3339:
|
|
||||||
return strict_rfc3339.validate_rfc3339(instance)
|
|
||||||
|
|
||||||
if DATETIME_HAS_ISODATE:
|
|
||||||
return isodate.parse_datetime(instance)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def is_date(instance):
|
|
||||||
if not isinstance(instance, (binary_type, text_type)):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if isinstance(instance, binary_type):
|
|
||||||
instance = instance.decode()
|
|
||||||
|
|
||||||
return datetime.strptime(instance, "%Y-%m-%d")
|
|
||||||
|
|
||||||
|
|
||||||
def is_uuid(instance):
|
|
||||||
if not isinstance(instance, (binary_type, text_type)):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if isinstance(instance, binary_type):
|
|
||||||
instance = instance.decode()
|
|
||||||
|
|
||||||
return text_type(UUID(instance)) == instance
|
|
||||||
|
|
||||||
|
|
||||||
def is_password(instance):
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class OASFormatChecker(FormatChecker):
|
|
||||||
|
|
||||||
checkers = {
|
|
||||||
'int32': (is_int32, ()),
|
|
||||||
'int64': (is_int64, ()),
|
|
||||||
'float': (is_float, ()),
|
|
||||||
'double': (is_double, ()),
|
|
||||||
'byte': (is_byte, (binascii.Error, TypeError)),
|
|
||||||
'binary': (is_binary, ()),
|
|
||||||
'date': (is_date, (ValueError, )),
|
|
||||||
'date-time': (is_datetime, DATETIME_RAISES),
|
|
||||||
'password': (is_password, ()),
|
|
||||||
# non standard
|
|
||||||
'uuid': (is_uuid, (AttributeError, ValueError)),
|
|
||||||
}
|
|
||||||
|
|
||||||
def check(self, instance, format):
|
|
||||||
if format not in self.checkers:
|
|
||||||
raise FormatError(
|
|
||||||
"Format checker for %r format not found" % (format, ))
|
|
||||||
|
|
||||||
func, raises = self.checkers[format]
|
|
||||||
result, cause = None, None
|
|
||||||
try:
|
|
||||||
result = func(instance)
|
|
||||||
except raises as e:
|
|
||||||
cause = e
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
raise FormatError(
|
|
||||||
"%r is not a %r" % (instance, format), cause=cause,
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
oas30_format_checker = OASFormatChecker()
|
|
|
@ -1,21 +0,0 @@
|
||||||
from jsonschema._types import (
|
|
||||||
TypeChecker, is_array, is_bool, is_integer,
|
|
||||||
is_object, is_number,
|
|
||||||
)
|
|
||||||
from six import text_type, binary_type
|
|
||||||
|
|
||||||
|
|
||||||
def is_string(checker, instance):
|
|
||||||
return isinstance(instance, (text_type, binary_type))
|
|
||||||
|
|
||||||
|
|
||||||
oas30_type_checker = TypeChecker(
|
|
||||||
{
|
|
||||||
u"string": is_string,
|
|
||||||
u"number": is_number,
|
|
||||||
u"integer": is_integer,
|
|
||||||
u"boolean": is_bool,
|
|
||||||
u"array": is_array,
|
|
||||||
u"object": is_object,
|
|
||||||
},
|
|
||||||
)
|
|
|
@ -1,87 +0,0 @@
|
||||||
from jsonschema._utils import find_additional_properties, extras_msg
|
|
||||||
from jsonschema.exceptions import ValidationError, FormatError
|
|
||||||
|
|
||||||
|
|
||||||
def type(validator, data_type, instance, schema):
|
|
||||||
if instance is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not validator.is_type(instance, data_type):
|
|
||||||
yield ValidationError("%r is not of type %s" % (instance, data_type))
|
|
||||||
|
|
||||||
|
|
||||||
def format(validator, format, instance, schema):
|
|
||||||
if instance is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if validator.format_checker is not None:
|
|
||||||
try:
|
|
||||||
validator.format_checker.check(instance, format)
|
|
||||||
except FormatError as error:
|
|
||||||
yield ValidationError(error.message, cause=error.cause)
|
|
||||||
|
|
||||||
|
|
||||||
def items(validator, items, instance, schema):
|
|
||||||
if not validator.is_type(instance, "array"):
|
|
||||||
return
|
|
||||||
|
|
||||||
for index, item in enumerate(instance):
|
|
||||||
for error in validator.descend(item, items, path=index):
|
|
||||||
yield error
|
|
||||||
|
|
||||||
|
|
||||||
def nullable(validator, is_nullable, instance, schema):
|
|
||||||
if instance is None and not is_nullable:
|
|
||||||
yield ValidationError("None for not nullable")
|
|
||||||
|
|
||||||
|
|
||||||
def required(validator, required, instance, schema):
|
|
||||||
if not validator.is_type(instance, "object"):
|
|
||||||
return
|
|
||||||
for property in required:
|
|
||||||
if property not in instance:
|
|
||||||
prop_schema = schema['properties'][property]
|
|
||||||
read_only = prop_schema.get('readOnly', False)
|
|
||||||
write_only = prop_schema.get('writeOnly', False)
|
|
||||||
if validator.write and read_only or validator.read and write_only:
|
|
||||||
continue
|
|
||||||
yield ValidationError("%r is a required property" % property)
|
|
||||||
|
|
||||||
|
|
||||||
def additionalProperties(validator, aP, instance, schema):
|
|
||||||
if not validator.is_type(instance, "object"):
|
|
||||||
return
|
|
||||||
|
|
||||||
extras = set(find_additional_properties(instance, schema))
|
|
||||||
|
|
||||||
if not extras:
|
|
||||||
return
|
|
||||||
|
|
||||||
if validator.is_type(aP, "object"):
|
|
||||||
for extra in extras:
|
|
||||||
for error in validator.descend(instance[extra], aP, path=extra):
|
|
||||||
yield error
|
|
||||||
elif validator.is_type(aP, "boolean"):
|
|
||||||
if not aP:
|
|
||||||
error = "Additional properties are not allowed (%s %s unexpected)"
|
|
||||||
yield ValidationError(error % extras_msg(extras))
|
|
||||||
|
|
||||||
|
|
||||||
def readOnly(validator, ro, instance, schema):
|
|
||||||
if not validator.write or not ro:
|
|
||||||
return
|
|
||||||
|
|
||||||
yield ValidationError(
|
|
||||||
"Tried to write read-only proparty with %s" % (instance))
|
|
||||||
|
|
||||||
|
|
||||||
def writeOnly(validator, wo, instance, schema):
|
|
||||||
if not validator.read or not wo:
|
|
||||||
return
|
|
||||||
|
|
||||||
yield ValidationError(
|
|
||||||
"Tried to read write-only proparty with %s" % (instance))
|
|
||||||
|
|
||||||
|
|
||||||
def not_implemented(validator, value, instance, schema):
|
|
||||||
pass
|
|
|
@ -1,72 +0,0 @@
|
||||||
from jsonschema import _legacy_validators, _utils, _validators
|
|
||||||
from jsonschema.validators import create
|
|
||||||
|
|
||||||
from openapi_core.schema_validator import _types as oas_types
|
|
||||||
from openapi_core.schema_validator import _validators as oas_validators
|
|
||||||
|
|
||||||
|
|
||||||
BaseOAS30Validator = create(
|
|
||||||
meta_schema=_utils.load_schema("draft4"),
|
|
||||||
validators={
|
|
||||||
u"multipleOf": _validators.multipleOf,
|
|
||||||
# exclusiveMaximum supported inside maximum_draft3_draft4
|
|
||||||
u"maximum": _legacy_validators.maximum_draft3_draft4,
|
|
||||||
# exclusiveMinimum supported inside minimum_draft3_draft4
|
|
||||||
u"minimum": _legacy_validators.minimum_draft3_draft4,
|
|
||||||
u"maxLength": _validators.maxLength,
|
|
||||||
u"minLength": _validators.minLength,
|
|
||||||
u"pattern": _validators.pattern,
|
|
||||||
u"maxItems": _validators.maxItems,
|
|
||||||
u"minItems": _validators.minItems,
|
|
||||||
u"uniqueItems": _validators.uniqueItems,
|
|
||||||
u"maxProperties": _validators.maxProperties,
|
|
||||||
u"minProperties": _validators.minProperties,
|
|
||||||
u"enum": _validators.enum,
|
|
||||||
# adjusted to OAS
|
|
||||||
u"type": oas_validators.type,
|
|
||||||
u"allOf": _validators.allOf,
|
|
||||||
u"oneOf": _validators.oneOf,
|
|
||||||
u"anyOf": _validators.anyOf,
|
|
||||||
u"not": _validators.not_,
|
|
||||||
u"items": oas_validators.items,
|
|
||||||
u"properties": _validators.properties,
|
|
||||||
u"required": oas_validators.required,
|
|
||||||
u"additionalProperties": oas_validators.additionalProperties,
|
|
||||||
# TODO: adjust description
|
|
||||||
u"format": oas_validators.format,
|
|
||||||
# TODO: adjust default
|
|
||||||
u"$ref": _validators.ref,
|
|
||||||
# fixed OAS fields
|
|
||||||
u"nullable": oas_validators.nullable,
|
|
||||||
u"discriminator": oas_validators.not_implemented,
|
|
||||||
u"readOnly": oas_validators.readOnly,
|
|
||||||
u"writeOnly": oas_validators.writeOnly,
|
|
||||||
u"xml": oas_validators.not_implemented,
|
|
||||||
u"externalDocs": oas_validators.not_implemented,
|
|
||||||
u"example": oas_validators.not_implemented,
|
|
||||||
u"deprecated": oas_validators.not_implemented,
|
|
||||||
},
|
|
||||||
type_checker=oas_types.oas30_type_checker,
|
|
||||||
version="oas30",
|
|
||||||
id_of=lambda schema: schema.get(u"id", ""),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OAS30Validator(BaseOAS30Validator):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.read = kwargs.pop('read', None)
|
|
||||||
self.write = kwargs.pop('write', None)
|
|
||||||
super(OAS30Validator, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def iter_errors(self, instance, _schema=None):
|
|
||||||
if _schema is None:
|
|
||||||
_schema = self.schema
|
|
||||||
|
|
||||||
# append defaults to trigger validator (i.e. nullable)
|
|
||||||
if 'nullable' not in _schema:
|
|
||||||
_schema.update({
|
|
||||||
'nullable': False,
|
|
||||||
})
|
|
||||||
|
|
||||||
return super(OAS30Validator, self).iter_errors(instance, _schema)
|
|
|
@ -1,10 +1,10 @@
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
from openapi_schema_validator import OAS30Validator, oas30_format_checker
|
||||||
|
|
||||||
from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat
|
from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat
|
||||||
from openapi_core.schema.schemas.models import Schema
|
from openapi_core.schema.schemas.models import Schema
|
||||||
from openapi_core.schema_validator import OAS30Validator
|
|
||||||
from openapi_core.schema_validator import oas30_format_checker
|
|
||||||
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
|
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
|
||||||
from openapi_core.unmarshalling.schemas.exceptions import (
|
from openapi_core.unmarshalling.schemas.exceptions import (
|
||||||
FormatterNotFoundError,
|
FormatterNotFoundError,
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from openapi_schema_validator._types import (
|
||||||
|
is_array, is_bool, is_integer,
|
||||||
|
is_object, is_number, is_string,
|
||||||
|
)
|
||||||
|
from openapi_schema_validator._format import oas30_format_checker
|
||||||
from six import text_type, binary_type
|
from six import text_type, binary_type
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
|
@ -8,11 +13,6 @@ from openapi_core.extensions.models.factories import ModelFactory
|
||||||
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
||||||
from openapi_core.schema.schemas.models import Schema
|
from openapi_core.schema.schemas.models import Schema
|
||||||
from openapi_core.schema.schemas.types import NoValue
|
from openapi_core.schema.schemas.types import NoValue
|
||||||
from openapi_core.schema_validator._types import (
|
|
||||||
is_array, is_bool, is_integer,
|
|
||||||
is_object, is_number, is_string,
|
|
||||||
)
|
|
||||||
from openapi_core.schema_validator._format import oas30_format_checker
|
|
||||||
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
|
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
|
||||||
from openapi_core.unmarshalling.schemas.exceptions import (
|
from openapi_core.unmarshalling.schemas.exceptions import (
|
||||||
UnmarshalError, ValidateError, InvalidSchemaValue,
|
UnmarshalError, ValidateError, InvalidSchemaValue,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
openapi-spec-validator
|
openapi-spec-validator
|
||||||
|
openapi-schema-validator
|
||||||
six
|
six
|
||||||
lazy-object-proxy
|
lazy-object-proxy
|
||||||
strict_rfc3339
|
|
||||||
isodate
|
|
||||||
attrs
|
attrs
|
||||||
parse==1.14.0
|
parse==1.14.0
|
||||||
more-itertools>=5.0.0
|
more-itertools>=5.0.0
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
openapi-spec-validator
|
openapi-spec-validator
|
||||||
|
openapi-schema-validator
|
||||||
six
|
six
|
||||||
lazy-object-proxy
|
lazy-object-proxy
|
||||||
backports.functools-lru-cache
|
backports.functools-lru-cache
|
||||||
backports.functools-partialmethod
|
backports.functools-partialmethod
|
||||||
enum34
|
enum34
|
||||||
strict_rfc3339
|
|
||||||
attrs
|
attrs
|
||||||
more-itertools==5.0.0
|
more-itertools==5.0.0
|
||||||
|
|
|
@ -24,10 +24,9 @@ setup_requires =
|
||||||
setuptools
|
setuptools
|
||||||
install_requires =
|
install_requires =
|
||||||
openapi-spec-validator
|
openapi-spec-validator
|
||||||
|
openapi-schema-validator
|
||||||
six
|
six
|
||||||
lazy-object-proxy
|
lazy-object-proxy
|
||||||
strict_rfc3339
|
|
||||||
isodate
|
|
||||||
attrs
|
attrs
|
||||||
werkzeug
|
werkzeug
|
||||||
parse
|
parse
|
||||||
|
|
|
@ -312,11 +312,11 @@ class TestSchemaValidate(object):
|
||||||
u('2018-01-02T23:59:59Z'),
|
u('2018-01-02T23:59:59Z'),
|
||||||
])
|
])
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'openapi_core.schema_validator._format.'
|
'openapi_schema_validator._format.'
|
||||||
'DATETIME_HAS_STRICT_RFC3339', True
|
'DATETIME_HAS_STRICT_RFC3339', True
|
||||||
)
|
)
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'openapi_core.schema_validator._format.'
|
'openapi_schema_validator._format.'
|
||||||
'DATETIME_HAS_ISODATE', False
|
'DATETIME_HAS_ISODATE', False
|
||||||
)
|
)
|
||||||
def test_string_format_datetime_strict_rfc3339(
|
def test_string_format_datetime_strict_rfc3339(
|
||||||
|
@ -332,11 +332,11 @@ class TestSchemaValidate(object):
|
||||||
u('2018-01-02T23:59:59Z'),
|
u('2018-01-02T23:59:59Z'),
|
||||||
])
|
])
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'openapi_core.schema_validator._format.'
|
'openapi_schema_validator._format.'
|
||||||
'DATETIME_HAS_STRICT_RFC3339', False
|
'DATETIME_HAS_STRICT_RFC3339', False
|
||||||
)
|
)
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'openapi_core.schema_validator._format.'
|
'openapi_schema_validator._format.'
|
||||||
'DATETIME_HAS_ISODATE', True
|
'DATETIME_HAS_ISODATE', True
|
||||||
)
|
)
|
||||||
def test_string_format_datetime_isodate(self, value, validator_factory):
|
def test_string_format_datetime_isodate(self, value, validator_factory):
|
||||||
|
|
Loading…
Reference in a new issue