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
|
||||
################
|
||||
* `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>`__
|
||||
|
|
|
@ -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
|
||||
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.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.exceptions import (
|
||||
FormatterNotFoundError,
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
from functools import partial
|
||||
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 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.models import Schema
|
||||
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.exceptions import (
|
||||
UnmarshalError, ValidateError, InvalidSchemaValue,
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
openapi-spec-validator
|
||||
openapi-schema-validator
|
||||
six
|
||||
lazy-object-proxy
|
||||
strict_rfc3339
|
||||
isodate
|
||||
attrs
|
||||
parse==1.14.0
|
||||
more-itertools>=5.0.0
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
openapi-spec-validator
|
||||
openapi-schema-validator
|
||||
six
|
||||
lazy-object-proxy
|
||||
backports.functools-lru-cache
|
||||
backports.functools-partialmethod
|
||||
enum34
|
||||
strict_rfc3339
|
||||
attrs
|
||||
more-itertools==5.0.0
|
||||
|
|
|
@ -24,10 +24,9 @@ setup_requires =
|
|||
setuptools
|
||||
install_requires =
|
||||
openapi-spec-validator
|
||||
openapi-schema-validator
|
||||
six
|
||||
lazy-object-proxy
|
||||
strict_rfc3339
|
||||
isodate
|
||||
attrs
|
||||
werkzeug
|
||||
parse
|
||||
|
|
|
@ -312,11 +312,11 @@ class TestSchemaValidate(object):
|
|||
u('2018-01-02T23:59:59Z'),
|
||||
])
|
||||
@mock.patch(
|
||||
'openapi_core.schema_validator._format.'
|
||||
'openapi_schema_validator._format.'
|
||||
'DATETIME_HAS_STRICT_RFC3339', True
|
||||
)
|
||||
@mock.patch(
|
||||
'openapi_core.schema_validator._format.'
|
||||
'openapi_schema_validator._format.'
|
||||
'DATETIME_HAS_ISODATE', False
|
||||
)
|
||||
def test_string_format_datetime_strict_rfc3339(
|
||||
|
@ -332,11 +332,11 @@ class TestSchemaValidate(object):
|
|||
u('2018-01-02T23:59:59Z'),
|
||||
])
|
||||
@mock.patch(
|
||||
'openapi_core.schema_validator._format.'
|
||||
'openapi_schema_validator._format.'
|
||||
'DATETIME_HAS_STRICT_RFC3339', False
|
||||
)
|
||||
@mock.patch(
|
||||
'openapi_core.schema_validator._format.'
|
||||
'openapi_schema_validator._format.'
|
||||
'DATETIME_HAS_ISODATE', True
|
||||
)
|
||||
def test_string_format_datetime_isodate(self, value, validator_factory):
|
||||
|
|
Loading…
Reference in a new issue