Hello community, here is the log from the commit of package python-openapi-core for openSUSE:Factory checked in at 2019-07-23 22:40:55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-openapi-core (Old) and /work/SRC/openSUSE:Factory/.python-openapi-core.new.4126 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-openapi-core" Tue Jul 23 22:40:55 2019 rev:3 rq:717909 version:0.11.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-openapi-core/python-openapi-core.changes 2019-06-06 18:18:13.196670257 +0200 +++ /work/SRC/openSUSE:Factory/.python-openapi-core.new.4126/python-openapi-core.changes 2019-07-23 22:41:01.306899418 +0200 @@ -1,0 +2,11 @@ +Tue Jul 23 13:45:01 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 0.11.0: + * End of Python 3.4 support (#136) + * Add support for one-of with any type (#133) + * Modify FlaskOpenAPIRequest to accommodate path variables (#141) + * Primitive types unmarshallers (#138) + * attr errors hashable fix (#143) + * Parameters on path item object support (#144) + +------------------------------------------------------------------- Old: ---- 0.10.0.tar.gz New: ---- 0.11.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-openapi-core.spec ++++++ --- /var/tmp/diff_new_pack.GC3GWq/_old 2019-07-23 22:41:01.814898774 +0200 +++ /var/tmp/diff_new_pack.GC3GWq/_new 2019-07-23 22:41:01.814898774 +0200 @@ -17,9 +17,8 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} -# remove with update to > 0.5.0; atm it is not working on py2 at all Name: python-openapi-core -Version: 0.10.0 +Version: 0.11.0 Release: 0 Summary: Adds client-side and server-side support for the oas3 License: BSD-3-Clause ++++++ 0.10.0.tar.gz -> 0.11.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/.travis.yml new/openapi-core-0.11.0/.travis.yml --- old/openapi-core-0.10.0/.travis.yml 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/.travis.yml 2019-06-18 23:42:44.000000000 +0200 @@ -3,7 +3,6 @@ matrix: include: - python: 2.7 - - python: 3.4 - python: 3.5 - python: 3.6 - python: 3.7 @@ -14,12 +13,12 @@ allow_failures: - python: nightly before_install: -- if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then pip install 'coverage<4.0.0'; fi - pip install codecov - pip install 'py>=1.5.0' install: -- pip install -e . +- pip install -r requirements.txt - pip install -r requirements_dev.txt +- pip install -e . script: - python setup.py test after_success: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/__init__.py new/openapi-core-0.11.0/openapi_core/__init__.py --- old/openapi-core-0.10.0/openapi_core/__init__.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/__init__.py 2019-06-18 23:42:44.000000000 +0200 @@ -6,7 +6,7 @@ __author__ = 'Artur Maciag' __email__ = 'maciag.ar...@gmail.com' -__version__ = '0.10.0' +__version__ = '0.11.0' __url__ = 'https://github.com/p1c2u/openapi-core' __license__ = 'BSD 3-Clause License' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/content/exceptions.py new/openapi-core-0.11.0/openapi_core/schema/content/exceptions.py --- old/openapi-core-0.10.0/openapi_core/schema/content/exceptions.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/content/exceptions.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,13 +1,13 @@ -from openapi_core.schema.exceptions import OpenAPIMappingError - import attr +from openapi_core.schema.exceptions import OpenAPIMappingError + class OpenAPIContentError(OpenAPIMappingError): pass -@attr.s +@attr.s(hash=True) class MimeTypeNotFound(OpenAPIContentError): mimetype = attr.ib() availableMimetypes = attr.ib() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/content/models.py new/openapi-core-0.11.0/openapi_core/schema/content/models.py --- old/openapi-core-0.10.0/openapi_core/schema/content/models.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/content/models.py 2019-06-18 23:42:44.000000000 +0200 @@ -18,4 +18,4 @@ if fnmatch.fnmatch(mimetype, key): return value - raise MimeTypeNotFound(mimetype, self.keys()) + raise MimeTypeNotFound(mimetype, list(self.keys())) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/media_types/exceptions.py new/openapi-core-0.11.0/openapi_core/schema/media_types/exceptions.py --- old/openapi-core-0.10.0/openapi_core/schema/media_types/exceptions.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/media_types/exceptions.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,19 +1,21 @@ -from openapi_core.schema.exceptions import OpenAPIMappingError - import attr +from openapi_core.schema.exceptions import OpenAPIMappingError + class OpenAPIMediaTypeError(OpenAPIMappingError): pass -@attr.s + +@attr.s(hash=True) class InvalidMediaTypeValue(OpenAPIMediaTypeError): original_exception = attr.ib() def __str__(self): return "Mimetype invalid: {0}".format(self.original_exception) -@attr.s + +@attr.s(hash=True) class InvalidContentType(OpenAPIMediaTypeError): mimetype = attr.ib() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/media_types/models.py new/openapi-core-0.11.0/openapi_core/schema/media_types/models.py --- old/openapi-core-0.10.0/openapi_core/schema/media_types/models.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/media_types/models.py 2019-06-18 23:42:44.000000000 +0200 @@ -47,6 +47,7 @@ raise InvalidMediaTypeValue(exc) try: - return self.schema.validate(unmarshalled, custom_formatters=custom_formatters) + return self.schema.validate( + unmarshalled, custom_formatters=custom_formatters) except OpenAPISchemaError as exc: raise InvalidMediaTypeValue(exc) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/operations/exceptions.py new/openapi-core-0.11.0/openapi_core/schema/operations/exceptions.py --- old/openapi-core-0.10.0/openapi_core/schema/operations/exceptions.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/operations/exceptions.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,13 +1,13 @@ -from openapi_core.schema.exceptions import OpenAPIMappingError - import attr +from openapi_core.schema.exceptions import OpenAPIMappingError + class OpenAPIOperationError(OpenAPIMappingError): pass -@attr.s +@attr.s(hash=True) class InvalidOperation(OpenAPIOperationError): path_pattern = attr.ib() http_method = attr.ib() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/parameters/exceptions.py new/openapi-core-0.11.0/openapi_core/schema/parameters/exceptions.py --- old/openapi-core-0.10.0/openapi_core/schema/parameters/exceptions.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/parameters/exceptions.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,13 +1,13 @@ -from openapi_core.schema.exceptions import OpenAPIMappingError - import attr +from openapi_core.schema.exceptions import OpenAPIMappingError + class OpenAPIParameterError(OpenAPIMappingError): pass -@attr.s +@attr.s(hash=True) class MissingParameter(OpenAPIParameterError): name = attr.ib() @@ -15,7 +15,7 @@ return "Missing parameter (without default value): {0}".format(self.name) -@attr.s +@attr.s(hash=True) class MissingRequiredParameter(OpenAPIParameterError): name = attr.ib() @@ -23,7 +23,7 @@ return "Missing required parameter: {0}".format(self.name) -@attr.s +@attr.s(hash=True) class EmptyParameterValue(OpenAPIParameterError): name = attr.ib() @@ -31,7 +31,7 @@ return "Value of parameter cannot be empty: {0}".format(self.name) -@attr.s +@attr.s(hash=True) class InvalidParameterValue(OpenAPIParameterError): name = attr.ib() original_exception = attr.ib() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/parameters/models.py new/openapi-core-0.11.0/openapi_core/schema/parameters/models.py --- old/openapi-core-0.10.0/openapi_core/schema/parameters/models.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/parameters/models.py 2019-06-18 23:42:44.000000000 +0200 @@ -118,6 +118,7 @@ raise InvalidParameterValue(self.name, exc) try: - return self.schema.validate(unmarshalled, custom_formatters=custom_formatters) + return self.schema.validate( + unmarshalled, custom_formatters=custom_formatters) except OpenAPISchemaError as exc: raise InvalidParameterValue(self.name, exc) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/paths/exceptions.py new/openapi-core-0.11.0/openapi_core/schema/paths/exceptions.py --- old/openapi-core-0.10.0/openapi_core/schema/paths/exceptions.py 1970-01-01 01:00:00.000000000 +0100 +++ new/openapi-core-0.11.0/openapi_core/schema/paths/exceptions.py 2019-06-18 23:42:44.000000000 +0200 @@ -0,0 +1,15 @@ +import attr + +from openapi_core.schema.exceptions import OpenAPIMappingError + + +class OpenAPIPathError(OpenAPIMappingError): + pass + + +@attr.s(hash=True) +class InvalidPath(OpenAPIPathError): + path_pattern = attr.ib() + + def __str__(self): + return "Unknown path {0}".format(self.path_pattern) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/request_bodies/exceptions.py new/openapi-core-0.11.0/openapi_core/schema/request_bodies/exceptions.py --- old/openapi-core-0.10.0/openapi_core/schema/request_bodies/exceptions.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/request_bodies/exceptions.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,13 +1,13 @@ -from openapi_core.schema.exceptions import OpenAPIMappingError - import attr +from openapi_core.schema.exceptions import OpenAPIMappingError + class OpenAPIRequestBodyError(OpenAPIMappingError): pass -@attr.s +@attr.s(hash=True) class MissingRequestBody(OpenAPIRequestBodyError): request = attr.ib() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/responses/exceptions.py new/openapi-core-0.11.0/openapi_core/schema/responses/exceptions.py --- old/openapi-core-0.10.0/openapi_core/schema/responses/exceptions.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/responses/exceptions.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,13 +1,13 @@ -from openapi_core.schema.exceptions import OpenAPIMappingError - import attr +from openapi_core.schema.exceptions import OpenAPIMappingError + class OpenAPIResponseError(OpenAPIMappingError): pass -@attr.s +@attr.s(hash=True) class InvalidResponse(OpenAPIResponseError): http_status = attr.ib() responses = attr.ib() @@ -16,7 +16,7 @@ return "Unknown response http status: {0}".format(str(self.http_status)) -@attr.s +@attr.s(hash=True) class MissingResponseContent(OpenAPIResponseError): response = attr.ib() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/schemas/exceptions.py new/openapi-core-0.11.0/openapi_core/schema/schemas/exceptions.py --- old/openapi-core-0.10.0/openapi_core/schema/schemas/exceptions.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/schemas/exceptions.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,13 +1,13 @@ -from openapi_core.schema.exceptions import OpenAPIMappingError - import attr +from openapi_core.schema.exceptions import OpenAPIMappingError + class OpenAPISchemaError(OpenAPIMappingError): pass -@attr.s +@attr.s(hash=True) class NoValidSchema(OpenAPISchemaError): value = attr.ib() @@ -15,7 +15,7 @@ return "No valid schema found for value: {0}".format(self.value) -@attr.s +@attr.s(hash=True) class UndefinedItemsSchema(OpenAPISchemaError): type = attr.ib() @@ -23,7 +23,7 @@ return "Null value for schema type {0}".format(self.type) -@attr.s +@attr.s(hash=True) class InvalidSchemaValue(OpenAPISchemaError): msg = attr.ib() value = attr.ib() @@ -32,7 +32,8 @@ def __str__(self): return self.msg.format(value=self.value, type=self.type) -@attr.s + +@attr.s(hash=True) class InvalidCustomFormatSchemaValue(InvalidSchemaValue): original_exception = attr.ib() @@ -40,14 +41,15 @@ return self.msg.format(value=self.value, type=self.type, exception=self.original_exception) -@attr.s +@attr.s(hash=True) class UndefinedSchemaProperty(OpenAPISchemaError): extra_props = attr.ib() def __str__(self): return "Extra unexpected properties found in schema: {0}".format(self.extra_props) -@attr.s + +@attr.s(hash=True) class InvalidSchemaProperty(OpenAPISchemaError): property_name = attr.ib() original_exception = attr.ib() @@ -55,7 +57,8 @@ def __str__(self): return "Invalid schema property {0}: {1}".format(self.property_name, self.original_exception) -@attr.s + +@attr.s(hash=True) class MissingSchemaProperty(OpenAPISchemaError): property_name = attr.ib() @@ -63,7 +66,7 @@ return "Missing schema property: {0}".format(self.property_name) -@attr.s +@attr.s(hash=True) class NoOneOfSchema(OpenAPISchemaError): type = attr.ib() @@ -71,9 +74,22 @@ return "Exactly one valid schema type {0} should be valid, None found.".format(self.type) -@attr.s +@attr.s(hash=True) class MultipleOneOfSchema(OpenAPISchemaError): type = attr.ib() def __str__(self): return "Exactly one schema type {0} should be valid, more than one found".format(self.type) + + +class UnmarshallerError(OpenAPIMappingError): + pass + + +class UnmarshallerStrictTypeError(UnmarshallerError): + value = attr.ib() + types = attr.ib() + + def __str__(self): + return "Value {value} is not one of types {types}".format( + self.value, self.types) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/schemas/models.py new/openapi-core-0.11.0/openapi_core/schema/schemas/models.py --- old/openapi-core-0.10.0/openapi_core/schema/schemas/models.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/schemas/models.py 2019-06-18 23:42:44.000000000 +0200 @@ -2,7 +2,6 @@ import attr import functools import logging -from base64 import b64decode, b64encode from collections import defaultdict from datetime import date, datetime from uuid import UUID @@ -17,6 +16,7 @@ InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty, OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema, UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty, + UnmarshallerStrictTypeError, ) from openapi_core.schema.schemas.util import ( forcebool, format_date, format_datetime, format_byte, format_uuid, @@ -156,14 +156,19 @@ return set(required) def get_cast_mapping(self, custom_formatters=None, strict=True): + primitive_unmarshallers = self.get_primitive_unmarshallers( + custom_formatters=custom_formatters) + + primitive_unmarshallers_partial = dict( + (t, functools.partial(u, type_format=self.format, strict=strict)) + for t, u in primitive_unmarshallers.items() + ) + pass_defaults = lambda f: functools.partial( f, custom_formatters=custom_formatters, strict=strict) mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy() + mapping.update(primitive_unmarshallers_partial) mapping.update({ - SchemaType.STRING: pass_defaults(self._unmarshal_string), - SchemaType.BOOLEAN: pass_defaults(self._unmarshal_boolean), - SchemaType.INTEGER: pass_defaults(self._unmarshal_integer), - SchemaType.NUMBER: pass_defaults(self._unmarshal_number), SchemaType.ANY: pass_defaults(self._unmarshal_any), SchemaType.ARRAY: pass_defaults(self._unmarshal_collection), SchemaType.OBJECT: pass_defaults(self._unmarshal_object), @@ -185,6 +190,10 @@ raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type) return self.default + if self.enum and value not in self.enum: + raise InvalidSchemaValue( + "Value {value} not in enum choices: {type}", value, self.enum) + cast_mapping = self.get_cast_mapping( custom_formatters=custom_formatters, strict=strict) @@ -194,6 +203,9 @@ cast_callable = cast_mapping[self.type] try: return cast_callable(value) + except UnmarshallerStrictTypeError: + raise InvalidSchemaValue( + "Value {value} is not of type {type}", value, self.type) except ValueError: raise InvalidSchemaValue( "Failed to cast value {value} to type {type}", value, self.type) @@ -208,69 +220,27 @@ if casted is None and not self.required: return None - if self.enum and casted not in self.enum: - raise InvalidSchemaValue( - "Value {value} not in enum choices: {type}", value, self.enum) - return casted - def _unmarshal_string(self, value, custom_formatters=None, strict=True): - if strict and not isinstance(value, (text_type, binary_type)): - raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) - - try: - schema_format = SchemaFormat(self.format) - except ValueError: - msg = "Unsupported format {type} unmarshalling for value {value}" - if custom_formatters is not None: - formatstring = custom_formatters.get(self.format) - if formatstring is None: - raise InvalidSchemaValue(msg, value, self.format) - else: - raise InvalidSchemaValue(msg, value, self.format) - else: - formatstring = self.STRING_FORMAT_CALLABLE_GETTER[schema_format] - - try: - return formatstring.unmarshal(value) - except ValueError as exc: - raise InvalidCustomFormatSchemaValue( - "Failed to format value {value} to format {type}: {exception}", value, self.format, exc) - - def _unmarshal_integer(self, value, custom_formatters=None, strict=True): - if strict and not isinstance(value, integer_types): - raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) - - return int(value) - - def _unmarshal_number(self, value, custom_formatters=None, strict=True): - if strict and not isinstance(value, (float, ) + integer_types): - raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) - - try: - schema_format = SchemaFormat(self.format) - except ValueError: - msg = "Unsupported format {type} unmarshalling for value {value}" - if custom_formatters is not None: - formatnumber = custom_formatters.get(self.format) - if formatnumber is None: - raise InvalidSchemaValue(msg, value, self.format) - else: - raise InvalidSchemaValue(msg, value, self.format) - else: - formatnumber = self.NUMBER_FORMAT_CALLABLE_GETTER[schema_format] + def get_primitive_unmarshallers(self, **options): + from openapi_core.schema.schemas.unmarshallers import ( + StringUnmarshaller, BooleanUnmarshaller, IntegerUnmarshaller, + NumberUnmarshaller, + ) - try: - return formatnumber.unmarshal(value) - except ValueError as exc: - raise InvalidCustomFormatSchemaValue( - "Failed to format value {value} to format {type}: {exception}", value, self.format, exc) - - def _unmarshal_boolean(self, value, custom_formatters=None, strict=True): - if strict and not isinstance(value, (bool, )): - raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) + unmarshallers_classes = { + SchemaType.STRING: StringUnmarshaller, + SchemaType.BOOLEAN: BooleanUnmarshaller, + SchemaType.INTEGER: IntegerUnmarshaller, + SchemaType.NUMBER: NumberUnmarshaller, + } + + unmarshallers = dict( + (t, klass(**options)) + for t, klass in unmarshallers_classes.items() + ) - return forcebool(value) + return unmarshallers def _unmarshal_any(self, value, custom_formatters=None, strict=True): types_resolve_order = [ @@ -278,13 +248,32 @@ SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING, ] cast_mapping = self.get_cast_mapping() - for schema_type in types_resolve_order: - cast_callable = cast_mapping[schema_type] - try: - return cast_callable(value) - # @todo: remove ValueError when validation separated - except (OpenAPISchemaError, TypeError, ValueError): - continue + if self.one_of: + result = None + for subschema in self.one_of: + try: + casted = subschema.cast(value, custom_formatters) + except (OpenAPISchemaError, TypeError, ValueError): + continue + else: + if result is not None: + raise MultipleOneOfSchema(self.type) + result = casted + + if result is None: + raise NoOneOfSchema(self.type) + + return result + else: + for schema_type in types_resolve_order: + cast_callable = cast_mapping[schema_type] + try: + return cast_callable(value) + except UnmarshallerStrictTypeError: + continue + # @todo: remove ValueError when validation separated + except (OpenAPISchemaError, TypeError, ValueError): + continue raise NoValidSchema(value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/schemas/unmarshallers.py new/openapi-core-0.11.0/openapi_core/schema/schemas/unmarshallers.py --- old/openapi-core-0.10.0/openapi_core/schema/schemas/unmarshallers.py 1970-01-01 01:00:00.000000000 +0100 +++ new/openapi-core-0.11.0/openapi_core/schema/schemas/unmarshallers.py 2019-06-18 23:42:44.000000000 +0200 @@ -0,0 +1,107 @@ +from six import text_type, binary_type, integer_types + +from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType +from openapi_core.schema.schemas.exceptions import ( + InvalidSchemaValue, InvalidCustomFormatSchemaValue, + OpenAPISchemaError, MultipleOneOfSchema, NoOneOfSchema, + InvalidSchemaProperty, + UnmarshallerStrictTypeError, +) +from openapi_core.schema.schemas.util import ( + forcebool, format_date, format_datetime, format_byte, format_uuid, + format_number, +) + + +class StrictUnmarshaller(object): + + STRICT_TYPES = () + + def __call__(self, value, type_format=SchemaFormat.NONE, strict=True): + if self.STRICT_TYPES and strict and not isinstance( + value, self.STRICT_TYPES): + raise UnmarshallerStrictTypeError(value, self.STRICT_TYPES) + + return value + + +class PrimitiveTypeUnmarshaller(StrictUnmarshaller): + + FORMATTERS = { + SchemaFormat.NONE: lambda x: x, + } + + def __init__(self, custom_formatters=None): + if custom_formatters is None: + custom_formatters = {} + self.custom_formatters = custom_formatters + + def __call__(self, value, type_format=SchemaFormat.NONE, strict=True): + value = super(PrimitiveTypeUnmarshaller, self).__call__( + value, type_format=type_format, strict=strict) + + try: + schema_format = SchemaFormat(type_format) + except ValueError: + formatter = self.custom_formatters.get(type_format) + else: + formatters = self.get_formatters() + formatter = formatters.get(schema_format) + + if formatter is None: + raise InvalidSchemaValue( + "Unsupported format {type} unmarshalling " + "for value {value}", + value, type_format) + + try: + return formatter(value) + except ValueError as exc: + raise InvalidCustomFormatSchemaValue( + "Failed to format value {value} to format {type}: {exception}", + value, type_format, exc) + + def get_formatters(self): + return self.FORMATTERS + + +class StringUnmarshaller(PrimitiveTypeUnmarshaller): + + STRICT_TYPES = (text_type, binary_type) + FORMATTERS = { + SchemaFormat.NONE: text_type, + SchemaFormat.PASSWORD: text_type, + SchemaFormat.DATE: format_date, + SchemaFormat.DATETIME: format_datetime, + SchemaFormat.BINARY: binary_type, + SchemaFormat.UUID: format_uuid, + SchemaFormat.BYTE: format_byte, + } + + +class IntegerUnmarshaller(PrimitiveTypeUnmarshaller): + + STRICT_TYPES = integer_types + FORMATTERS = { + SchemaFormat.NONE: int, + SchemaFormat.INT32: int, + SchemaFormat.INT64: int, + } + + +class NumberUnmarshaller(PrimitiveTypeUnmarshaller): + + STRICT_TYPES = (float, ) + integer_types + FORMATTERS = { + SchemaFormat.NONE: format_number, + SchemaFormat.FLOAT: float, + SchemaFormat.DOUBLE: float, + } + + +class BooleanUnmarshaller(PrimitiveTypeUnmarshaller): + + STRICT_TYPES = (bool, ) + FORMATTERS = { + SchemaFormat.NONE: forcebool, + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/servers/exceptions.py new/openapi-core-0.11.0/openapi_core/schema/servers/exceptions.py --- old/openapi-core-0.10.0/openapi_core/schema/servers/exceptions.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/servers/exceptions.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,13 +1,13 @@ -from openapi_core.schema.exceptions import OpenAPIMappingError - import attr +from openapi_core.schema.exceptions import OpenAPIMappingError + class OpenAPIServerError(OpenAPIMappingError): pass -@attr.s +@attr.s(hash=True) class InvalidServer(OpenAPIServerError): full_url_pattern = attr.ib() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/schema/specs/models.py new/openapi-core-0.11.0/openapi_core/schema/specs/models.py --- old/openapi-core-0.10.0/openapi_core/schema/specs/models.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/schema/specs/models.py 2019-06-18 23:42:44.000000000 +0200 @@ -4,6 +4,7 @@ from openapi_core.compat import partialmethod from openapi_core.schema.operations.exceptions import InvalidOperation +from openapi_core.schema.paths.exceptions import InvalidPath from openapi_core.schema.servers.exceptions import InvalidServer @@ -19,8 +20,8 @@ self.servers = servers or [] self.components = components - def __getitem__(self, path_name): - return self.paths[path_name] + def __getitem__(self, path_pattern): + return self.get_path(path_pattern) @property def default_url(self): @@ -36,6 +37,12 @@ def get_server_url(self, index=0): return self.servers[index].default_url + def get_path(self, path_pattern): + try: + return self.paths[path_pattern] + except KeyError: + raise InvalidPath(path_pattern) + def get_operation(self, path_pattern, http_method): try: return self.paths[path_pattern].operations[http_method] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/validation/request/models.py new/openapi-core-0.11.0/openapi_core/validation/request/models.py --- old/openapi-core-0.10.0/openapi_core/validation/request/models.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/validation/request/models.py 2019-06-18 23:42:44.000000000 +0200 @@ -16,6 +16,16 @@ def __setitem__(self, location, value): raise NotImplementedError + def __add__(self, other): + if not isinstance(other, self.__class__): + raise ValueError("Invalid type") + + for location in self.valid_locations: + if location in other: + self[location].update(other[location]) + + return self + @classmethod def validate_location(cls, location): if location not in cls.valid_locations: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/validation/request/validators.py new/openapi-core-0.11.0/openapi_core/validation/request/validators.py --- old/openapi-core-0.10.0/openapi_core/validation/request/validators.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/validation/request/validators.py 2019-06-18 23:42:44.000000000 +0200 @@ -27,17 +27,25 @@ ) try: + path = self.spec[operation_pattern] + # don't process if operation errors + except OpenAPIMappingError as exc: + return RequestValidationResult([exc, ], None, None) + + path_params, path_params_errors = self._get_parameters(request, path) + + try: operation = self.spec.get_operation( operation_pattern, request.method) # don't process if operation errors except OpenAPIMappingError as exc: return RequestValidationResult([exc, ], None, None) - params, params_errors = self._get_parameters(request, operation) + op_params, op_params_errors = self._get_parameters(request, operation) body, body_errors = self._get_body(request, operation) - errors = params_errors + body_errors - return RequestValidationResult(errors, body, params) + errors = path_params_errors + op_params_errors + body_errors + return RequestValidationResult(errors, body, path_params + op_params) def _get_parameters(self, request, operation): errors = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/openapi_core/wrappers/flask.py new/openapi-core-0.11.0/openapi_core/wrappers/flask.py --- old/openapi-core-0.10.0/openapi_core/wrappers/flask.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/openapi_core/wrappers/flask.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,9 +1,16 @@ """OpenAPI core wrappers module""" +import re + from openapi_core.wrappers.base import BaseOpenAPIRequest, BaseOpenAPIResponse +# http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules +PATH_PARAMETER_PATTERN = r'<(?:(?:string|int|float|path|uuid):)?(\w+)>' + class FlaskOpenAPIRequest(BaseOpenAPIRequest): + path_regex = re.compile(PATH_PARAMETER_PATTERN) + def __init__(self, request): self.request = request @@ -24,7 +31,7 @@ if self.request.url_rule is None: return self.path - return self.request.url_rule.rule + return self.path_regex.sub(r'{\1}', self.request.url_rule.rule) @property def parameters(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/setup.py new/openapi-core-0.11.0/setup.py --- old/openapi-core-0.10.0/setup.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/setup.py 2019-06-18 23:42:44.000000000 +0200 @@ -74,7 +74,6 @@ "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: OS Independent", 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/integration/data/v3.0/flask_wrapper.yaml new/openapi-core-0.11.0/tests/integration/data/v3.0/flask_wrapper.yaml --- old/openapi-core-0.10.0/tests/integration/data/v3.0/flask_wrapper.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/openapi-core-0.11.0/tests/integration/data/v3.0/flask_wrapper.yaml 2019-06-18 23:42:44.000000000 +0200 @@ -0,0 +1,19 @@ +openapi: "3.0.0" +info: + title: Basic OpenAPI specification used with test_wrappers.TestFlaskOpenAPIIValidation + version: "0.1" +servers: + - url: 'http://localhost' +paths: + '/browse/{id}/': + parameters: + - name: id + in: path + required: true + description: the ID of the resource to retrieve + schema: + type: integer + get: + responses: + default: + description: Return the resource. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/integration/test_minimal.py new/openapi-core-0.11.0/tests/integration/test_minimal.py --- old/openapi-core-0.10.0/tests/integration/test_minimal.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/integration/test_minimal.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,6 +1,7 @@ import pytest from openapi_core.schema.operations.exceptions import InvalidOperation +from openapi_core.schema.paths.exceptions import InvalidPath from openapi_core.shortcuts import create_spec from openapi_core.validation.request.validators import RequestValidator from openapi_core.wrappers.mock import MockRequest @@ -39,7 +40,7 @@ spec_dict = factory.spec_from_file(spec_path) spec = create_spec(spec_dict) validator = RequestValidator(spec) - request = MockRequest(server, "get", "/nonexistent") + request = MockRequest(server, "post", "/status") result = validator.validate(request) @@ -47,3 +48,18 @@ assert isinstance(result.errors[0], InvalidOperation) assert result.body is None assert result.parameters == {} + + @pytest.mark.parametrize("server", servers) + @pytest.mark.parametrize("spec_path", spec_paths) + def test_invalid_path(self, factory, server, spec_path): + spec_dict = factory.spec_from_file(spec_path) + spec = create_spec(spec_dict) + validator = RequestValidator(spec) + request = MockRequest(server, "get", "/nonexistent") + + result = validator.validate(request) + + assert len(result.errors) == 1 + assert isinstance(result.errors[0], InvalidPath) + assert result.body is None + assert result.parameters == {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/integration/test_petstore.py new/openapi-core-0.11.0/tests/integration/test_petstore.py --- old/openapi-core-0.10.0/tests/integration/test_petstore.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/integration/test_petstore.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,5 +1,6 @@ import json import pytest +from datetime import datetime from base64 import b64encode from uuid import UUID from six import iteritems, text_type @@ -19,7 +20,7 @@ from openapi_core.schema.responses.models import Response from openapi_core.schema.schemas.enums import SchemaType from openapi_core.schema.schemas.exceptions import ( - NoValidSchema, InvalidSchemaProperty, InvalidSchemaValue, + InvalidSchemaProperty, InvalidSchemaValue, ) from openapi_core.schema.schemas.models import Schema from openapi_core.schema.servers.exceptions import InvalidServer @@ -1213,7 +1214,7 @@ assert parameters == {} assert isinstance(body, BaseModel) - assert body.created == created + assert body.created == datetime(2016, 4, 16, 16, 6, 5) assert body.name == pet_name code = 400 @@ -1257,7 +1258,7 @@ ) parameters = request.get_parameters(spec) - with pytest.raises(NoValidSchema): + with pytest.raises(InvalidMediaTypeValue): request.get_body(spec) assert parameters == {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/integration/test_validators.py new/openapi-core-0.11.0/tests/integration/test_validators.py --- old/openapi-core-0.10.0/tests/integration/test_validators.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/integration/test_validators.py 2019-06-18 23:42:44.000000000 +0200 @@ -9,6 +9,8 @@ from openapi_core.extensions.models.models import BaseModel from openapi_core.schema.operations.exceptions import InvalidOperation from openapi_core.schema.parameters.exceptions import MissingRequiredParameter +from openapi_core.schema.parameters.exceptions import InvalidParameterValue +from openapi_core.schema.paths.exceptions import InvalidPath from openapi_core.schema.request_bodies.exceptions import MissingRequestBody from openapi_core.schema.responses.exceptions import ( MissingResponseContent, InvalidResponse, @@ -54,12 +56,22 @@ assert result.body is None assert result.parameters == {} - def test_invalid_operation(self, validator): + def test_invalid_path(self, validator): request = MockRequest(self.host_url, 'get', '/v1') result = validator.validate(request) assert len(result.errors) == 1 + assert type(result.errors[0]) == InvalidPath + assert result.body is None + assert result.parameters == {} + + def test_invalid_operation(self, validator): + request = MockRequest(self.host_url, 'patch', '/v1/pets') + + result = validator.validate(request) + + assert len(result.errors) == 1 assert type(result.errors[0]) == InvalidOperation assert result.body is None assert result.parameters == {} @@ -220,6 +232,80 @@ } +class TestPathItemParamsValidator(object): + + @pytest.fixture + def spec_dict(self, factory): + return { + "openapi": "3.0.0", + "info": { + "title": "Test path item parameter validation", + "version": "0.1", + }, + "paths": { + "/resource": { + "parameters": [ + { + "name": "resId", + "in": "query", + "required": True, + "schema": { + "type": "integer", + }, + }, + ], + "get": { + "responses": { + "default": { + "description": "Return the resource." + } + } + } + } + } + } + + @pytest.fixture + def spec(self, spec_dict): + return create_spec(spec_dict) + + @pytest.fixture + def validator(self, spec): + return RequestValidator(spec) + + def test_request_missing_param(self, validator): + request = MockRequest('http://example.com', 'get', '/resource') + result = validator.validate(request) + + assert len(result.errors) == 1 + assert type(result.errors[0]) == MissingRequiredParameter + assert result.body is None + assert result.parameters == {} + + def test_request_invalid_param(self, validator): + request = MockRequest( + 'http://example.com', 'get', '/resource', + args={'resId': 'invalid'}, + ) + result = validator.validate(request) + + assert len(result.errors) == 1 + assert type(result.errors[0]) == InvalidParameterValue + assert result.body is None + assert result.parameters == {} + + def test_request_valid_param(self, validator): + request = MockRequest( + 'http://example.com', 'get', '/resource', + args={'resId': '10'}, + ) + result = validator.validate(request) + + assert len(result.errors) == 0 + assert result.body is None + assert result.parameters == {'query': {'resId': 10}} + + class TestResponseValidator(object): host_url = 'http://petstore.swagger.io' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/integration/test_wrappers.py new/openapi-core-0.11.0/tests/integration/test_wrappers.py --- old/openapi-core-0.10.0/tests/integration/test_wrappers.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/integration/test_wrappers.py 2019-06-18 23:42:44.000000000 +0200 @@ -1,54 +1,61 @@ -import pytest - from flask.wrappers import Request, Response from werkzeug.datastructures import EnvironHeaders, ImmutableMultiDict from werkzeug.routing import Map, Rule, Subdomain from werkzeug.test import create_environ -from openapi_core.wrappers.flask import ( - FlaskOpenAPIRequest, FlaskOpenAPIResponse, -) - +import pytest +from openapi_core.shortcuts import create_spec +from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.request.validators import RequestValidator +from openapi_core.wrappers.flask import (FlaskOpenAPIRequest, + FlaskOpenAPIResponse) + + +@pytest.fixture +def environ_factory(): + return create_environ + + +@pytest.fixture +def map(): + return Map([ + # Static URLs + Rule('/', endpoint='static/index'), + Rule('/about', endpoint='static/about'), + Rule('/help', endpoint='static/help'), + # Knowledge Base + Subdomain('kb', [ + Rule('/', endpoint='kb/index'), + Rule('/browse/', endpoint='kb/browse'), + Rule('/browse/<int:id>/', endpoint='kb/browse'), + Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse') + ]) + ], default_subdomain='www') -class TestFlaskOpenAPIRequest(object): +@pytest.fixture +def request_factory(map, environ_factory): server_name = 'localhost' - @pytest.fixture - def environ_factory(self): - return create_environ - - @pytest.fixture - def map(self): - return Map([ - # Static URLs - Rule('/', endpoint='static/index'), - Rule('/about', endpoint='static/about'), - Rule('/help', endpoint='static/help'), - # Knowledge Base - Subdomain('kb', [ - Rule('/', endpoint='kb/index'), - Rule('/browse/', endpoint='kb/browse'), - Rule('/browse/<int:id>/', endpoint='kb/browse'), - Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse') - ]) - ], default_subdomain='www') + def create_request(method, path, subdomain=None, query_string=None): + environ = environ_factory(query_string=query_string) + req = Request(environ) + urls = map.bind_to_environ( + environ, server_name=server_name, subdomain=subdomain) + req.url_rule, req.view_args = urls.match( + path, method, return_rule=True) + return req + return create_request + + +@pytest.fixture +def response_factory(): + def create_response(data, status_code=200): + return Response(data, status=status_code) + return create_response - @pytest.fixture - def request_factory(self, map, environ_factory): - def create_request(method, path, subdomain=None, query_string=None): - environ = environ_factory(query_string=query_string) - req = Request(environ) - urls = map.bind_to_environ( - environ, server_name=self.server_name, subdomain=subdomain) - req.url_rule, req.view_args = urls.match( - path, method, return_rule=True) - return req - return create_request - @pytest.fixture - def openapi_request(self, request): - return FlaskOpenAPIRequest(request) +class TestFlaskOpenAPIRequest(object): def test_simple(self, request_factory, request): request = request_factory('GET', '/', subdomain='www') @@ -115,19 +122,13 @@ assert openapi_request.host_url == request.host_url assert openapi_request.path == request.path assert openapi_request.method == request.method.lower() - assert openapi_request.path_pattern == request.url_rule.rule + assert openapi_request.path_pattern == '/browse/{id}/' assert openapi_request.body == request.data assert openapi_request.mimetype == request.mimetype class TestFlaskOpenAPIResponse(object): - @pytest.fixture - def response_factory(self): - def create_response(data, status_code=200): - return Response(data, status=status_code) - return create_response - def test_invalid_server(self, response_factory): response = response_factory('Not Found', status_code=404) @@ -137,3 +138,30 @@ assert openapi_response.data == response.data assert openapi_response.status_code == response._status_code assert openapi_response.mimetype == response.mimetype + + +class TestFlaskOpenAPIValidation(object): + + @pytest.fixture + def flask_spec(self, factory): + specfile = 'data/v3.0/flask_wrapper.yaml' + return create_spec(factory.spec_from_file(specfile)) + + def test_response_validator_path_pattern(self, + flask_spec, + request_factory, + response_factory): + validator = ResponseValidator(flask_spec) + request = request_factory('GET', '/browse/12/', subdomain='kb') + openapi_request = FlaskOpenAPIRequest(request) + response = response_factory('Some item', status_code=200) + openapi_response = FlaskOpenAPIResponse(response) + result = validator.validate(openapi_request, openapi_response) + assert not result.errors + + def test_request_validator_path_pattern(self, flask_spec, request_factory): + validator = RequestValidator(flask_spec) + request = request_factory('GET', '/browse/12/', subdomain='kb') + openapi_request = FlaskOpenAPIRequest(request) + result = validator.validate(openapi_request) + assert not result.errors diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/unit/schema/test_links.py new/openapi-core-0.11.0/tests/unit/schema/test_links.py --- old/openapi-core-0.10.0/tests/unit/schema/test_links.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/unit/schema/test_links.py 2019-06-18 23:42:44.000000000 +0200 @@ -40,5 +40,5 @@ @pytest.mark.parametrize("request_body", request_body_list) def test_iteritems(self, link_factory, request_body, server): link = link_factory(request_body, server) - for par_name in link.parameters.keys(): + for par_name in link.parameters: assert link[par_name] == link.parameters[par_name] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/unit/schema/test_operations.py new/openapi-core-0.11.0/tests/unit/schema/test_operations.py --- old/openapi-core-0.10.0/tests/unit/schema/test_operations.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/unit/schema/test_operations.py 2019-06-18 23:42:44.000000000 +0200 @@ -15,7 +15,7 @@ return Operation('get', '/path', {}, parameters=parameters) def test_iteritems(self, operation): - for name in operation.parameters.keys(): + for name in operation.parameters: assert operation[name] == operation.parameters[name] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/unit/schema/test_paths.py new/openapi-core-0.11.0/tests/unit/schema/test_paths.py --- old/openapi-core-0.10.0/tests/unit/schema/test_paths.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/unit/schema/test_paths.py 2019-06-18 23:42:44.000000000 +0200 @@ -16,6 +16,6 @@ @property def test_iteritems(self, path): - for http_method in path.operations.keys(): + for http_method in path.operations: assert path[http_method] ==\ path.operations[http_method] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/unit/schema/test_request_bodies.py new/openapi-core-0.11.0/tests/unit/schema/test_request_bodies.py --- old/openapi-core-0.10.0/tests/unit/schema/test_request_bodies.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/unit/schema/test_request_bodies.py 2019-06-18 23:42:44.000000000 +0200 @@ -16,6 +16,6 @@ @property def test_iteritems(self, request_body): - for mimetype in request_body.content.keys(): + for mimetype in request_body.content: assert request_body[mimetype] ==\ request_body.content[mimetype] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/unit/schema/test_schemas.py new/openapi-core-0.11.0/tests/unit/schema/test_schemas.py --- old/openapi-core-0.10.0/tests/unit/schema/test_schemas.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/unit/schema/test_schemas.py 2019-06-18 23:42:44.000000000 +0200 @@ -27,7 +27,7 @@ @property def test_valid(self, schema): - for name in schema.properties.keys(): + for name in schema.properties: assert schema[name] == schema.properties[name] @@ -300,6 +300,33 @@ assert result == 1.2 + def test_schema_any_one_of(self): + schema = Schema(one_of=[ + Schema('string'), + Schema('array', items=Schema('string')), + ]) + assert schema.unmarshal(['hello']) == ['hello'] + + def test_schema_any_one_of_mutiple(self): + schema = Schema(one_of=[ + Schema('array', items=Schema('string')), + Schema('array', items=Schema('number')), + ]) + with pytest.raises(MultipleOneOfSchema): + schema.unmarshal([]) + + def test_schema_any_one_of_no_valid(self): + schema = Schema(one_of=[ + Schema('array', items=Schema('string')), + Schema('array', items=Schema('number')), + ]) + with pytest.raises(NoOneOfSchema): + schema.unmarshal({}) + + def test_schema_any(self): + schema = Schema() + assert schema.unmarshal('string') == 'string' + class TestSchemaValidate(object): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openapi-core-0.10.0/tests/unit/schema/test_specs.py new/openapi-core-0.11.0/tests/unit/schema/test_specs.py --- old/openapi-core-0.10.0/tests/unit/schema/test_specs.py 2019-05-21 14:21:42.000000000 +0200 +++ new/openapi-core-0.11.0/tests/unit/schema/test_specs.py 2019-06-18 23:42:44.000000000 +0200 @@ -32,7 +32,7 @@ return Spec(servers, paths) def test_iteritems(self, spec): - for path_name in spec.paths.keys(): + for path_name in spec.paths: assert spec[path_name] ==\ spec.paths[path_name]