Use openapi-schema-validator library

This commit is contained in:
p1c2u 2020-03-05 11:28:21 +00:00
parent 8c1e331377
commit e817973435
12 changed files with 15 additions and 335 deletions

View file

@ -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>`__

View file

@ -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']

View file

@ -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()

View file

@ -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,
},
)

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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):