From 35f8e28157ca5d2f1ddd9f5684c4e6c0ddf75d6c Mon Sep 17 00:00:00 2001 From: p1c2u Date: Fri, 23 Apr 2021 12:36:27 +0100 Subject: [PATCH] Spec path --- openapi_core/casting/schemas/casters.py | 4 +- openapi_core/casting/schemas/factories.py | 23 +- .../deserializing/media_types/factories.py | 6 +- .../deserializing/parameters/deserializers.py | 20 +- .../deserializing/parameters/factories.py | 18 +- openapi_core/schema/models.py | 98 +++ openapi_core/schema/servers/models.py | 4 +- openapi_core/schema/servers/utils.py | 2 + openapi_core/schema/shortcuts.py | 4 + openapi_core/security/factories.py | 9 +- openapi_core/security/providers.py | 11 +- openapi_core/spec/__init__.py | 0 openapi_core/spec/accessors.py | 23 + openapi_core/spec/parameters.py | 24 + openapi_core/spec/paths.py | 16 + openapi_core/spec/schemas.py | 20 + openapi_core/spec/servers.py | 18 + openapi_core/spec/specs.py | 6 + .../templating/media_types/finders.py | 10 +- openapi_core/templating/paths/finders.py | 24 +- openapi_core/templating/responses/finders.py | 10 +- .../unmarshalling/schemas/factories.py | 31 +- .../unmarshalling/schemas/unmarshallers.py | 73 ++- openapi_core/validation/request/validators.py | 93 ++- .../validation/response/validators.py | 10 +- openapi_core/validation/validators.py | 18 +- tests/integration/data/v3.0/petstore.yaml | 1 + tests/integration/schema/test_link_spec.py | 35 +- tests/integration/schema/test_path_params.py | 14 +- tests/integration/schema/test_spec.py | 260 ++++---- tests/integration/validation/test_petstore.py | 3 +- .../test_media_types_deserializers.py | 28 +- .../test_parameters_deserializers.py | 21 +- tests/unit/security/test_providers.py | 13 +- tests/unit/templating/test_paths_finders.py | 199 ++++-- .../unit/templating/test_responses_finders.py | 13 +- tests/unit/unmarshalling/test_unmarshal.py | 490 +++++++++----- tests/unit/unmarshalling/test_validate.py | 601 +++++++++++++----- 38 files changed, 1530 insertions(+), 723 deletions(-) create mode 100644 openapi_core/schema/models.py create mode 100644 openapi_core/schema/servers/utils.py create mode 100644 openapi_core/spec/__init__.py create mode 100644 openapi_core/spec/accessors.py create mode 100644 openapi_core/spec/parameters.py create mode 100644 openapi_core/spec/paths.py create mode 100644 openapi_core/spec/schemas.py create mode 100644 openapi_core/spec/servers.py create mode 100644 openapi_core/spec/specs.py diff --git a/openapi_core/casting/schemas/casters.py b/openapi_core/casting/schemas/casters.py index 554f36f..5f710ed 100644 --- a/openapi_core/casting/schemas/casters.py +++ b/openapi_core/casting/schemas/casters.py @@ -14,7 +14,7 @@ class PrimitiveCaster(object): try: return self.caster_callable(value) except (ValueError, TypeError): - raise CastError(value, self.schema.type.value) + raise CastError(value, self.schema['type']) class DummyCaster(object): @@ -31,7 +31,7 @@ class ArrayCaster(object): @property def items_caster(self): - return self.casters_factory.create(self.schema.items) + return self.casters_factory.create(self.schema / 'items') def __call__(self, value): if value in (None, NoValue): diff --git a/openapi_core/casting/schemas/factories.py b/openapi_core/casting/schemas/factories.py index 8052889..f07f1b5 100644 --- a/openapi_core/casting/schemas/factories.py +++ b/openapi_core/casting/schemas/factories.py @@ -1,5 +1,3 @@ -from openapi_core.schema.schemas.enums import SchemaType - from openapi_core.casting.schemas.casters import ( PrimitiveCaster, DummyCaster, ArrayCaster ) @@ -9,23 +7,24 @@ from openapi_core.casting.schemas.util import forcebool class SchemaCastersFactory(object): DUMMY_CASTERS = [ - SchemaType.STRING, SchemaType.OBJECT, SchemaType.ANY, + 'string', 'object', 'any', ] PRIMITIVE_CASTERS = { - SchemaType.INTEGER: int, - SchemaType.NUMBER: float, - SchemaType.BOOLEAN: forcebool, + 'integer': int, + 'number': float, + 'boolean': forcebool, } COMPLEX_CASTERS = { - SchemaType.ARRAY: ArrayCaster, + 'array': ArrayCaster, } def create(self, schema): - if schema.type in self.DUMMY_CASTERS: + schema_type = schema.getkey('type', 'any') + if schema_type in self.DUMMY_CASTERS: return DummyCaster() - elif schema.type in self.PRIMITIVE_CASTERS: - caster_callable = self.PRIMITIVE_CASTERS[schema.type] + elif schema_type in self.PRIMITIVE_CASTERS: + caster_callable = self.PRIMITIVE_CASTERS[schema_type] return PrimitiveCaster(schema, caster_callable) - elif schema.type in self.COMPLEX_CASTERS: - caster_class = self.COMPLEX_CASTERS[schema.type] + elif schema_type in self.COMPLEX_CASTERS: + caster_class = self.COMPLEX_CASTERS[schema_type] return caster_class(schema, self) diff --git a/openapi_core/deserializing/media_types/factories.py b/openapi_core/deserializing/media_types/factories.py index f44a5c0..f5bce1f 100644 --- a/openapi_core/deserializing/media_types/factories.py +++ b/openapi_core/deserializing/media_types/factories.py @@ -20,11 +20,11 @@ class MediaTypeDeserializersFactory(object): custom_deserializers = {} self.custom_deserializers = custom_deserializers - def create(self, media_type): + def create(self, mimetype): deserialize_callable = self.get_deserializer_callable( - media_type.mimetype) + mimetype) return PrimitiveDeserializer( - media_type.mimetype, deserialize_callable) + mimetype, deserialize_callable) def get_deserializer_callable(self, mimetype): if mimetype in self.custom_deserializers: diff --git a/openapi_core/deserializing/parameters/deserializers.py b/openapi_core/deserializing/parameters/deserializers.py index 368acde..e7e0d1f 100644 --- a/openapi_core/deserializing/parameters/deserializers.py +++ b/openapi_core/deserializing/parameters/deserializers.py @@ -3,6 +3,7 @@ from openapi_core.deserializing.parameters.exceptions import ( EmptyParameterValue, ) from openapi_core.schema.parameters.enums import ParameterLocation +from openapi_core.spec.parameters import get_aslist, get_explode, get_style class PrimitiveDeserializer(object): @@ -11,15 +12,20 @@ class PrimitiveDeserializer(object): self.param = param self.deserializer_callable = deserializer_callable - def __call__(self, value): - if (self.param.location == ParameterLocation.QUERY and value == "" and - not self.param.allow_empty_value): - raise EmptyParameterValue( - value, self.param.style, self.param.name) + self.aslist = get_aslist(self.param) + self.explode = get_explode(self.param) + self.style = get_style(self.param) - if not self.param.aslist or self.param.explode: + def __call__(self, value): + style = get_style(self.param) + if (self.param['in'] == 'query' and value == "" and + not self.param.getkey('allowEmptyValue', False)): + raise EmptyParameterValue( + value, self.style, self.param['name']) + + if not self.aslist or self.explode: return value try: return self.deserializer_callable(value) except (ValueError, TypeError, AttributeError): - raise DeserializeError(value, self.param.style) + raise DeserializeError(value, self.style) diff --git a/openapi_core/deserializing/parameters/factories.py b/openapi_core/deserializing/parameters/factories.py index 5893619..f9cfa50 100644 --- a/openapi_core/deserializing/parameters/factories.py +++ b/openapi_core/deserializing/parameters/factories.py @@ -3,24 +3,26 @@ import warnings from openapi_core.deserializing.parameters.deserializers import ( PrimitiveDeserializer, ) -from openapi_core.schema.parameters.enums import ParameterStyle +from openapi_core.schema.parameters import get_style class ParameterDeserializersFactory(object): PARAMETER_STYLE_DESERIALIZERS = { - ParameterStyle.FORM: lambda x: x.split(','), - ParameterStyle.SIMPLE: lambda x: x.split(','), - ParameterStyle.SPACE_DELIMITED: lambda x: x.split(' '), - ParameterStyle.PIPE_DELIMITED: lambda x: x.split('|'), + 'form': lambda x: x.split(','), + 'simple': lambda x: x.split(','), + 'spaceDelimited': lambda x: x.split(' '), + 'pipeDelimited': lambda x: x.split('|'), } def create(self, param): - if param.deprecated: + if param.getkey('deprecated', False): warnings.warn( - "{0} parameter is deprecated".format(param.name), + "{0} parameter is deprecated".format(param['name']), DeprecationWarning, ) - deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS[param.style] + style = get_style(param) + + deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS[style] return PrimitiveDeserializer(param, deserialize_callable) diff --git a/openapi_core/schema/models.py b/openapi_core/schema/models.py new file mode 100644 index 0000000..15c6db0 --- /dev/null +++ b/openapi_core/schema/models.py @@ -0,0 +1,98 @@ +import sys + + +class Spec(object): + sep = '/' + + def __new__(cls, *args): + return cls._from_parts(args) + + @classmethod + def _parse_args(cls, args): + # This is useful when you don't want to create an instance, just + # canonicalize some constructor arguments. + parts = [] + for a in args: + if isinstance(a, Spec): + parts += a._parts + else: + if isinstance(a, str): + # Force-cast str subclasses to str (issue #21127) + parts.append(str(a)) + else: + raise TypeError( + "argument should be a str object or a Spec " + "object returning str, not %r" + % type(a)) + return cls.parse_parts(parts) + + @classmethod + def parse_parts(cls, parts): + parsed = [] + sep = cls.sep + root = '' + it = reversed(parts) + for part in it: + if not part: + continue + root, rel = cls.splitroot(part) + if sep in rel: + for x in reversed(rel.split(sep)): + if x and x != '.': + parsed.append(sys.intern(x)) + else: + if rel and rel != '.': + parsed.append(sys.intern(rel)) + parsed.reverse() + return root, parsed + + @classmethod + def splitroot(cls, part, sep=sep): + if part and part[0] == sep: + stripped_part = part.lstrip(sep) + # According to POSIX path resolution: + # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11 + # "A pathname that begins with two successive slashes may be + # interpreted in an implementation-defined manner, although more + # than two leading slashes shall be treated as a single slash". + if len(part) - len(stripped_part) == 2: + return sep * 2, stripped_part + else: + return sep, stripped_part + else: + return '', part + + @classmethod + def _from_parts(cls, args): + self = object.__new__(cls) + root, parts = cls._parse_args(args) + self._root = root + self._parts = parts + return self + + @classmethod + def _from_parsed_parts(cls, root, parts): + self = object.__new__(cls) + self._root = root + self._parts = parts + return self + + def join_parsed_parts(self, root, parts, root2, parts2): + """ + Join the two paths represented by the respective + (root, parts) tuples. Return a new (root, parts) tuple. + """ + if root2: + return root2, root2 + parts2[1:] + elif parts: + return root, parts + parts2 + return root2, parts2 + + def _make_child(self, args): + root, parts = self._parse_args(args) + root, parts = self.join_parsed_parts( + self._root, self._parts, root, parts) + return self._from_parsed_parts(root, parts) + + def __truediv__(self, key): + return self._make_child((key,)) diff --git a/openapi_core/schema/servers/models.py b/openapi_core/schema/servers/models.py index 201db26..28c81fc 100644 --- a/openapi_core/schema/servers/models.py +++ b/openapi_core/schema/servers/models.py @@ -1,6 +1,8 @@ """OpenAPI core servers models module""" from six import iteritems +from openapi_core.schema.servers.utils import is_absolute + class Server(object): @@ -30,7 +32,7 @@ class Server(object): def is_absolute(self, url=None): if url is None: url = self.url - return url.startswith('//') or '://' in url + return is_absolute(url) class ServerVariable(object): diff --git a/openapi_core/schema/servers/utils.py b/openapi_core/schema/servers/utils.py new file mode 100644 index 0000000..718b5f2 --- /dev/null +++ b/openapi_core/schema/servers/utils.py @@ -0,0 +1,2 @@ +def is_absolute(url): + return url.startswith('//') or '://' in url diff --git a/openapi_core/schema/shortcuts.py b/openapi_core/schema/shortcuts.py index 9fb625d..dbeb780 100644 --- a/openapi_core/schema/shortcuts.py +++ b/openapi_core/schema/shortcuts.py @@ -3,6 +3,7 @@ from jsonschema.validators import RefResolver from openapi_spec_validator import ( default_handlers, openapi_v3_spec_validator, ) +from openapi_spec_validator.validators import Dereferencer from openapi_core.schema.specs.factories import SpecFactory @@ -16,5 +17,8 @@ def create_spec( spec_resolver = RefResolver( spec_url, spec_dict, handlers=handlers) + dereferencer = Dereferencer(spec_resolver) + from openapi_core.spec.paths import SpecPath + return SpecPath.from_spec(spec_dict, dereferencer) spec_factory = SpecFactory(spec_resolver) return spec_factory.create(spec_dict, spec_url=spec_url) diff --git a/openapi_core/security/factories.py b/openapi_core/security/factories.py index da34d91..95c353f 100644 --- a/openapi_core/security/factories.py +++ b/openapi_core/security/factories.py @@ -7,13 +7,14 @@ from openapi_core.security.providers import ( class SecurityProviderFactory(object): PROVIDERS = { - SecuritySchemeType.API_KEY: ApiKeyProvider, - SecuritySchemeType.HTTP: HttpProvider, + 'apiKey': ApiKeyProvider, + 'http': HttpProvider, } def create(self, scheme): - if scheme.type == SecuritySchemeType.API_KEY: + scheme_type = scheme['type'] + if scheme_type == 'apiKey': return ApiKeyProvider(scheme) - elif scheme.type == SecuritySchemeType.HTTP: + elif scheme_type == 'http': return HttpProvider(scheme) return UnsupportedProvider(scheme) diff --git a/openapi_core/security/providers.py b/openapi_core/security/providers.py index 5ee27c5..79b4bfa 100644 --- a/openapi_core/security/providers.py +++ b/openapi_core/security/providers.py @@ -18,10 +18,12 @@ class UnsupportedProvider(BaseProvider): class ApiKeyProvider(BaseProvider): def __call__(self, request): - source = getattr(request.parameters, self.scheme.apikey_in.value) - if self.scheme.name not in source: + name = self.scheme['name'] + location = self.scheme['in'] + source = getattr(request.parameters, location) + if name not in source: raise SecurityError("Missing api key parameter.") - return source.get(self.scheme.name) + return source[name] class HttpProvider(BaseProvider): @@ -35,7 +37,8 @@ class HttpProvider(BaseProvider): except ValueError: raise SecurityError('Could not parse authorization header.') - if auth_type.lower() != self.scheme.scheme.value: + scheme = self.scheme['scheme'] + if auth_type.lower() != scheme: raise SecurityError( 'Unknown authorization method %s' % auth_type) diff --git a/openapi_core/spec/__init__.py b/openapi_core/spec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openapi_core/spec/accessors.py b/openapi_core/spec/accessors.py new file mode 100644 index 0000000..d01fde6 --- /dev/null +++ b/openapi_core/spec/accessors.py @@ -0,0 +1,23 @@ +from contextlib import contextmanager + +from dictpath.accessors import DictOrListAccessor + + +class SpecAccessor(DictOrListAccessor): + + def __init__(self, dict_or_list, dereferencer): + super(SpecAccessor, self).__init__(dict_or_list) + self.dereferencer = dereferencer + + @contextmanager + def open(self, parts): + content = self.dict_or_list + for part in parts: + content = content[part] + if '$ref' in content: + content = self.dereferencer.dereference( + content) + try: + yield content + finally: + pass diff --git a/openapi_core/spec/parameters.py b/openapi_core/spec/parameters.py new file mode 100644 index 0000000..f1ead22 --- /dev/null +++ b/openapi_core/spec/parameters.py @@ -0,0 +1,24 @@ +def get_aslist(param): + return ( + param.get('schema', None) and + param['schema']['type'] in ['array', 'object'] + ) + + +def get_style(param): + if 'style' in param: + return param['style'] + + # determine default + return ( + 'simple' if param['in'] in ['path', 'header'] else 'form' + ) + + +def get_explode(param): + if 'explode' in param: + return param['explode'] + + #determine default + style = get_style(param) + return style == 'form' diff --git a/openapi_core/spec/paths.py b/openapi_core/spec/paths.py new file mode 100644 index 0000000..d6f6acb --- /dev/null +++ b/openapi_core/spec/paths.py @@ -0,0 +1,16 @@ +from dictpath.paths import AccessorPath + +from openapi_core.spec.accessors import SpecAccessor + +SPEC_SEPARATOR = '#' + + +class SpecPath(AccessorPath): + + @classmethod + def from_spec( + cls, spec_dict, dereferencer=None, *args, + separator=SPEC_SEPARATOR, + ): + accessor = SpecAccessor(spec_dict, dereferencer) + return cls(accessor, *args, separator=separator) diff --git a/openapi_core/spec/schemas.py b/openapi_core/spec/schemas.py new file mode 100644 index 0000000..2a117ab --- /dev/null +++ b/openapi_core/spec/schemas.py @@ -0,0 +1,20 @@ +from six import iteritems + + +def get_all_properties(schema): + properties = schema.get('properties', {}) + properties_dict = dict(iteritems(properties)) + + if 'allOf'not in schema: + return properties_dict + + for subschema in schema / 'allOf': + subschema_props = get_all_properties(subschema) + properties_dict.update(subschema_props) + + return properties_dict + + +def get_all_properties_names(schema): + all_properties = get_all_properties(schema) + return set(all_properties.keys()) diff --git a/openapi_core/spec/servers.py b/openapi_core/spec/servers.py new file mode 100644 index 0000000..2f6a0c1 --- /dev/null +++ b/openapi_core/spec/servers.py @@ -0,0 +1,18 @@ +from six import iteritems + + +def get_server_default_variables(server): + if 'variables' not in server: + return {} + + defaults = {} + variables = server / 'variables' + for name, variable in iteritems(variables): + defaults[name] = variable['default'] + return defaults + + +def get_server_url(server, **variables): + if not variables: + variables = get_server_default_variables(server) + return server['url'].format(**variables) diff --git a/openapi_core/spec/specs.py b/openapi_core/spec/specs.py new file mode 100644 index 0000000..cf7a795 --- /dev/null +++ b/openapi_core/spec/specs.py @@ -0,0 +1,6 @@ +from openapi_core.spec.servers import get_server_url + + +def get_spec_url(spec, index=0): + servers = spec / 'servers' + return get_server_url(servers / 0) diff --git a/openapi_core/templating/media_types/finders.py b/openapi_core/templating/media_types/finders.py index 5f1c684..db9b40e 100644 --- a/openapi_core/templating/media_types/finders.py +++ b/openapi_core/templating/media_types/finders.py @@ -12,13 +12,11 @@ class MediaTypeFinder(object): self.content = content def find(self, request): - try: - return self.content[request.mimetype] - except KeyError: - pass + if request.mimetype in self.content: + return self.content / request.mimetype, request.mimetype - for key, value in iteritems(self.content): + for key, value in self.content.items(): if fnmatch.fnmatch(request.mimetype, key): - return value + return value, key raise MediaTypeNotFound(request.mimetype, list(self.content.keys())) diff --git a/openapi_core/templating/paths/finders.py b/openapi_core/templating/paths/finders.py index 1ec284e..ebe01d4 100644 --- a/openapi_core/templating/paths/finders.py +++ b/openapi_core/templating/paths/finders.py @@ -3,6 +3,7 @@ from more_itertools import peekable from six import iteritems from six.moves.urllib.parse import urljoin, urlparse +from openapi_core.schema.servers.utils import is_absolute from openapi_core.templating.datatypes import TemplateResult from openapi_core.templating.util import parse, search from openapi_core.templating.paths.exceptions import ( @@ -40,7 +41,8 @@ class PathFinder(object): def _get_paths_iter(self, full_url_pattern): template_paths = [] - for path_pattern, path in iteritems(self.spec.paths): + paths = self.spec / 'paths' + for path_pattern, path in paths.items(): # simple path. # Return right away since it is always the most concrete if full_url_pattern.endswith(path_pattern): @@ -59,22 +61,24 @@ class PathFinder(object): def _get_operations_iter(self, request_method, paths_iter): for path, path_result in paths_iter: - if request_method not in path.operations: + if request_method not in path: continue - operation = path.operations[request_method] + operation = path / request_method yield (path, operation, path_result) def _get_servers_iter(self, full_url_pattern, ooperations_iter): for path, operation, path_result in ooperations_iter: - servers = path.servers or operation.servers or self.spec.servers + servers = path.get('servers', None) or \ + operation.get('servers', None) or \ + self.spec.get('servers', [{'url': '/'}]) for server in servers: server_url_pattern = full_url_pattern.rsplit( path_result.resolved, 1)[0] - server_url = server.url - if not server.is_absolute(): + server_url = server['url'] + if not is_absolute(server_url): # relative to absolute url if self.base_url is not None: - server_url = urljoin(self.base_url, server.url) + server_url = urljoin(self.base_url, server['url']) # if no base url check only path part else: server_url_pattern = urlparse(server_url_pattern).path @@ -82,17 +86,17 @@ class PathFinder(object): server_url = server_url[:-1] # simple path if server_url_pattern == server_url: - server_result = TemplateResult(server.url, {}) + server_result = TemplateResult(server['url'], {}) yield ( path, operation, server, path_result, server_result, ) # template path else: - result = parse(server.url, server_url_pattern) + result = parse(server['url'], server_url_pattern) if result: server_result = TemplateResult( - server.url, result.named) + server['url'], result.named) yield ( path, operation, server, path_result, server_result, diff --git a/openapi_core/templating/responses/finders.py b/openapi_core/templating/responses/finders.py index 8f6d3bd..106bc79 100644 --- a/openapi_core/templating/responses/finders.py +++ b/openapi_core/templating/responses/finders.py @@ -7,17 +7,15 @@ class ResponseFinder(object): self.responses = responses def find(self, http_status='default'): - try: - return self.responses[http_status] - except KeyError: - pass + if http_status in self.responses: + return self.responses / http_status # try range http_status_range = '{0}XX'.format(http_status[0]) if http_status_range in self.responses: - return self.responses[http_status_range] + return self.responses / http_status_range if 'default' not in self.responses: raise ResponseNotFound(http_status, self.responses) - return self.responses['default'] + return self.responses / 'default' diff --git a/openapi_core/unmarshalling/schemas/factories.py b/openapi_core/unmarshalling/schemas/factories.py index 60f90dc..c635fb7 100644 --- a/openapi_core/unmarshalling/schemas/factories.py +++ b/openapi_core/unmarshalling/schemas/factories.py @@ -18,15 +18,15 @@ from openapi_core.unmarshalling.schemas.unmarshallers import ( class SchemaUnmarshallersFactory(object): PRIMITIVE_UNMARSHALLERS = { - SchemaType.STRING: StringUnmarshaller, - SchemaType.INTEGER: IntegerUnmarshaller, - SchemaType.NUMBER: NumberUnmarshaller, - SchemaType.BOOLEAN: BooleanUnmarshaller, + 'string': StringUnmarshaller, + 'integer': IntegerUnmarshaller, + 'number': NumberUnmarshaller, + 'boolean': BooleanUnmarshaller, } COMPLEX_UNMARSHALLERS = { - SchemaType.ARRAY: ArrayUnmarshaller, - SchemaType.OBJECT: ObjectUnmarshaller, - SchemaType.ANY: AnyUnmarshaller, + 'array': ArrayUnmarshaller, + 'object': ObjectUnmarshaller, + 'any': AnyUnmarshaller, } CONTEXT_VALIDATION = { @@ -46,12 +46,13 @@ class SchemaUnmarshallersFactory(object): def create(self, schema, type_override=None): """Create unmarshaller from the schema.""" - if not isinstance(schema, Schema): - raise TypeError("schema not type of Schema") - if schema.deprecated: + if schema is None: + raise TypeError("Invalid schema") + + if schema.getkey('deprecated', False): warnings.warn("The schema is deprecated", DeprecationWarning) - schema_type = type_override or schema.type + schema_type = type_override or schema.getkey('type', 'any') if schema_type in self.PRIMITIVE_UNMARSHALLERS: klass = self.PRIMITIVE_UNMARSHALLERS[schema_type] kwargs = dict(schema=schema) @@ -63,10 +64,11 @@ class SchemaUnmarshallersFactory(object): context=self.context, ) - formatter = self.get_formatter(klass.FORMATTERS, schema.format) + schema_format = schema.getkey('format') + formatter = self.get_formatter(klass.FORMATTERS, schema_format) if formatter is None: - raise FormatterNotFoundError(schema.format) + raise FormatterNotFoundError(schema_format) validator = self.get_validator(schema) @@ -87,4 +89,5 @@ class SchemaUnmarshallersFactory(object): } if self.context is not None: kwargs[self.CONTEXT_VALIDATION[self.context]] = True - return OAS30Validator(schema.__dict__, **kwargs) + with schema.open() as schema_dict: + return OAS30Validator(schema_dict, **kwargs) diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py index 83a47dc..628f352 100644 --- a/openapi_core/unmarshalling/schemas/unmarshallers.py +++ b/openapi_core/unmarshalling/schemas/unmarshallers.py @@ -15,6 +15,9 @@ 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.spec.schemas import ( + get_all_properties, get_all_properties_names +) from openapi_core.unmarshalling.schemas.enums import UnmarshalContext from openapi_core.unmarshalling.schemas.exceptions import ( UnmarshalError, ValidateError, InvalidSchemaValue, @@ -40,7 +43,7 @@ class PrimitiveTypeUnmarshaller(object): def __call__(self, value=NoValue): if value is NoValue: - value = self.schema.default + value = self.schema.getkey('default') if value is None: return @@ -51,21 +54,24 @@ class PrimitiveTypeUnmarshaller(object): def _formatter_validate(self, value): result = self.formatter.validate(value) if not result: - raise InvalidSchemaValue(value, self.schema.type) + schema_type = self.schema.getkey('type', 'any') + raise InvalidSchemaValue(value, schema_type) def validate(self, value): errors_iter = self.validator.iter_errors(value) errors = tuple(errors_iter) if errors: + schema_type = self.schema.getkey('type', 'any') raise InvalidSchemaValue( - value, self.schema.type, schema_errors=errors) + value, schema_type, schema_errors=errors) def unmarshal(self, value): try: return self.formatter.unmarshal(value) except ValueError as exc: + schema_format = self.schema.getkey('format') raise InvalidSchemaFormatValue( - value, self.schema.format, exc) + value, schema_format, exc) class StringUnmarshaller(PrimitiveTypeUnmarshaller): @@ -140,11 +146,11 @@ class ArrayUnmarshaller(ComplexUnmarshaller): @property def items_unmarshaller(self): - return self.unmarshallers_factory.create(self.schema.items) + return self.unmarshallers_factory.create(self.schema / 'items') def __call__(self, value=NoValue): value = super(ArrayUnmarshaller, self).__call__(value) - if value is None and self.schema.nullable: + if value is None and self.schema.getkey('nullable', False): return None return list(map(self.items_unmarshaller, value)) @@ -170,9 +176,9 @@ class ObjectUnmarshaller(ComplexUnmarshaller): return self._unmarshal_object(value) def _unmarshal_object(self, value=NoValue): - if self.schema.one_of: + if 'oneOf' in self.schema: properties = None - for one_of_schema in self.schema.one_of: + for one_of_schema in self.schema / 'oneOf': try: unmarshalled = self._unmarshal_properties( value, one_of_schema) @@ -190,46 +196,49 @@ class ObjectUnmarshaller(ComplexUnmarshaller): else: properties = self._unmarshal_properties(value) - if 'x-model' in self.schema.extensions: - extension = self.schema.extensions['x-model'] - return self.model_factory.create(properties, name=extension.value) + if 'x-model' in self.schema: + name = self.schema['x-model'] + return self.model_factory.create(properties, name=name) return properties def _unmarshal_properties(self, value=NoValue, one_of_schema=None): - all_props = self.schema.get_all_properties() - all_props_names = self.schema.get_all_properties_names() + all_props = get_all_properties(self.schema) + all_props_names = get_all_properties_names(self.schema) if one_of_schema is not None: - all_props.update(one_of_schema.get_all_properties()) - all_props_names |= one_of_schema.\ - get_all_properties_names() + all_props.update(get_all_properties(one_of_schema)) + all_props_names |= get_all_properties_names(one_of_schema) value_props_names = value.keys() extra_props = set(value_props_names) - set(all_props_names) properties = {} - if isinstance(self.schema.additional_properties, Schema): + additional_properties = self.schema.getkey('additionalProperties', True) + if isinstance(additional_properties, dict): + additional_prop_schema = self.schema / 'additionalProperties' for prop_name in extra_props: prop_value = value[prop_name] properties[prop_name] = self.unmarshallers_factory.create( - self.schema.additional_properties)(prop_value) - elif self.schema.additional_properties is True: + additional_prop_schema)(prop_value) + elif additional_properties is True: for prop_name in extra_props: prop_value = value[prop_name] properties[prop_name] = prop_value for prop_name, prop in iteritems(all_props): - if self.context == UnmarshalContext.REQUEST and prop.read_only: + read_only = prop.getkey('readOnly', False) + if self.context == UnmarshalContext.REQUEST and read_only: continue - if self.context == UnmarshalContext.RESPONSE and prop.write_only: + write_only = prop.getkey('writeOnly', False) + if self.context == UnmarshalContext.RESPONSE and write_only: continue try: prop_value = value[prop_name] except KeyError: - if prop.default is NoValue: + if 'default' not in prop: continue - prop_value = prop.default + prop_value = prop['default'] properties[prop_name] = self.unmarshallers_factory.create( prop)(prop_value) @@ -244,8 +253,8 @@ class AnyUnmarshaller(ComplexUnmarshaller): } SCHEMA_TYPES_ORDER = [ - SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN, - SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING, + 'object', 'array', 'boolean', + 'integer', 'number', 'string', ] def unmarshal(self, value=NoValue): @@ -272,9 +281,11 @@ class AnyUnmarshaller(ComplexUnmarshaller): return value def _get_one_of_schema(self, value): - if not self.schema.one_of: + if 'oneOf' not in self.schema: return - for subschema in self.schema.one_of: + + one_of_schemas = self.schema / 'oneOf' + for subschema in one_of_schemas: unmarshaller = self.unmarshallers_factory.create(subschema) try: unmarshaller.validate(value) @@ -284,10 +295,12 @@ class AnyUnmarshaller(ComplexUnmarshaller): return subschema def _get_all_of_schema(self, value): - if not self.schema.all_of: + if 'allOf' not in self.schema: return - for subschema in self.schema.all_of: - if subschema.type == SchemaType.ANY: + + all_of_schemas = self.schema / 'allOf' + for subschema in all_of_schemas: + if 'type' not in subschema: continue unmarshaller = self.unmarshallers_factory.create(subschema) try: diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index 0f0a85e..0e01e88 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -9,6 +9,7 @@ from openapi_core.schema.parameters.exceptions import ( ) from openapi_core.schema.request_bodies.exceptions import MissingRequestBody from openapi_core.security.exceptions import SecurityError +from openapi_core.spec.parameters import get_aslist, get_explode from openapi_core.templating.media_types.exceptions import MediaTypeFinderError from openapi_core.templating.paths.exceptions import PathError from openapi_core.unmarshalling.schemas.enums import UnmarshalContext @@ -38,10 +39,17 @@ class RequestValidator(BaseValidator): request.parameters.path = request.parameters.path or \ path_result.variables + + operation_params = operation.get('parameters', []) + operation_params_iter = operation_params and \ + iter(operation_params) or [] + path_params = path.get('parameters', []) + params_params_iter = path_params and \ + iter(path_params) or [] params, params_errors = self._get_parameters( request, chain( - iteritems(operation.parameters), - iteritems(path.parameters) + operation_params_iter, + params_params_iter, ) ) @@ -63,10 +71,17 @@ class RequestValidator(BaseValidator): request.parameters.path = request.parameters.path or \ path_result.variables + + operation_params = operation.get('parameters', []) + operation_params_iter = operation_params and \ + iter(operation_params) or [] + path_params = path.get('parameters', []) + params_params_iter = path_params and \ + iter(path_params) or [] params, params_errors = self._get_parameters( request, chain( - iteritems(operation.parameters), - iteritems(path.parameters) + operation_params_iter, + params_params_iter, ) ) return RequestValidationResult( @@ -87,9 +102,11 @@ class RequestValidator(BaseValidator): ) def _get_security(self, request, operation): - security = self.spec.security - if operation.security is not None: - security = operation.security + security = None + if 'security' in self.spec: + security = self.spec / 'security' + if 'security' in operation: + security = operation / 'security' if not security: return {} @@ -99,7 +116,7 @@ class RequestValidator(BaseValidator): return { scheme_name: self._get_security_value( scheme_name, request) - for scheme_name in security_requirement + for scheme_name in security_requirement.keys() } except SecurityError: continue @@ -110,21 +127,26 @@ class RequestValidator(BaseValidator): errors = [] seen = set() locations = {} - for param_name, param in params: - if (param_name, param.location.value) in seen: + for param in params: + param_name = param['name'] + param_location = param['in'] + if (param_name, param_location) in seen: # skip parameter already seen # e.g. overriden path item paremeter on operation continue - seen.add((param_name, param.location.value)) + seen.add((param_name, param_location)) try: raw_value = self._get_parameter_value(param, request) except MissingRequiredParameter as exc: errors.append(exc) continue except MissingParameter: - if not param.schema or not param.schema.has_default(): + if 'schema' not in param: continue - casted = param.schema.default + schema = param / 'schema' + if 'default' not in schema: + continue + casted = schema['default'] else: try: deserialised = self._deserialise_parameter( @@ -144,28 +166,29 @@ class RequestValidator(BaseValidator): except (ValidateError, UnmarshalError) as exc: errors.append(exc) else: - locations.setdefault(param.location.value, {}) - locations[param.location.value][param_name] = unmarshalled + locations.setdefault(param_location, {}) + locations[param_location][param_name] = unmarshalled return RequestParameters(**locations), errors def _get_body(self, request, operation): - if operation.request_body is None: + if not 'requestBody' in operation: return None, [] + request_body = operation / 'requestBody' try: - media_type = self._get_media_type( - operation.request_body.content, request) + media_type, mimetype = self._get_media_type( + request_body / 'content', request) except MediaTypeFinderError as exc: return None, [exc, ] try: - raw_body = self._get_body_value(operation.request_body, request) + raw_body = self._get_body_value(request_body, request) except MissingRequestBody as exc: return None, [exc, ] try: - deserialised = self._deserialise_media_type(media_type, raw_body) + deserialised = self._deserialise_data(mimetype, raw_body) except DeserializeError as exc: return None, [exc, ] @@ -182,33 +205,37 @@ class RequestValidator(BaseValidator): return body, [] def _get_security_value(self, scheme_name, request): - scheme = self.spec.components.security_schemes.get(scheme_name) - if not scheme: + security_schemes = self.spec / 'components#securitySchemes' + if scheme_name not in security_schemes: return - + scheme = security_schemes[scheme_name] from openapi_core.security.factories import SecurityProviderFactory security_provider_factory = SecurityProviderFactory() security_provider = security_provider_factory.create(scheme) return security_provider(request) def _get_parameter_value(self, param, request): - location = request.parameters[param.location.value] + param_location = param['in'] + location = request.parameters[param_location] - if param.name not in location: - if param.required: - raise MissingRequiredParameter(param.name) + if param['name'] not in location: + if param.getkey('required', False): + raise MissingRequiredParameter(param['name']) - raise MissingParameter(param.name) + raise MissingParameter(param['name']) - if param.aslist and param.explode: + aslist = get_aslist(param) + explode = get_explode(param) + if aslist and explode: if hasattr(location, 'getall'): - return location.getall(param.name) - return location.getlist(param.name) + return location.getall(param['name']) + return location.getlist(param['name']) - return location[param.name] + return location[param['name']] def _get_body_value(self, request_body, request): - if not request.body and request_body.required: + required = request_body.getkey('required', False) + if not request.body and required: raise MissingRequestBody(request) return request.body diff --git a/openapi_core/validation/response/validators.py b/openapi_core/validation/response/validators.py index 8daa865..3adfe51 100644 --- a/openapi_core/validation/response/validators.py +++ b/openapi_core/validation/response/validators.py @@ -43,7 +43,7 @@ class ResponseValidator(BaseValidator): def _get_operation_response(self, operation, response): from openapi_core.templating.responses.finders import ResponseFinder - finder = ResponseFinder(operation.responses) + finder = ResponseFinder(operation / 'responses') return finder.find(str(response.status_code)) def _validate_data(self, request, response): @@ -67,12 +67,12 @@ class ResponseValidator(BaseValidator): ) def _get_data(self, response, operation_response): - if not operation_response.content: + if 'content' not in operation_response: return None, [] try: - media_type = self._get_media_type( - operation_response.content, response) + media_type, mimetype = self._get_media_type( + operation_response / 'content', response) except MediaTypeFinderError as exc: return None, [exc, ] @@ -82,7 +82,7 @@ class ResponseValidator(BaseValidator): return None, [exc, ] try: - deserialised = self._deserialise_media_type(media_type, raw_data) + deserialised = self._deserialise_data(mimetype, raw_data) except DeserializeError as exc: return None, [exc, ] diff --git a/openapi_core/validation/validators.py b/openapi_core/validation/validators.py index 8e70d0c..140180a 100644 --- a/openapi_core/validation/validators.py +++ b/openapi_core/validation/validators.py @@ -26,36 +26,38 @@ class BaseValidator(object): finder = MediaTypeFinder(content) return finder.find(request_or_response) - def _deserialise_media_type(self, media_type, value): + def _deserialise_data(self, mimetype, value): from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) deserializers_factory = MediaTypeDeserializersFactory( self.custom_media_type_deserializers) - deserializer = deserializers_factory.create(media_type) + deserializer = deserializers_factory.create(mimetype) return deserializer(value) def _cast(self, param_or_media_type, value): # return param_or_media_type.cast(value) - if not param_or_media_type.schema: + if not 'schema' in param_or_media_type: return value from openapi_core.casting.schemas.factories import SchemaCastersFactory casters_factory = SchemaCastersFactory() - caster = casters_factory.create(param_or_media_type.schema) + schema = param_or_media_type / 'schema' + caster = casters_factory.create(schema) return caster(value) def _unmarshal(self, param_or_media_type, value, context): - if not param_or_media_type.schema: + if not 'schema' in param_or_media_type: return value from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, ) + spec_resolver = self.spec.accessor.dereferencer.resolver_manager.resolver unmarshallers_factory = SchemaUnmarshallersFactory( - self.spec._resolver, self.format_checker, + spec_resolver, self.format_checker, self.custom_formatters, context=context, ) - unmarshaller = unmarshallers_factory.create( - param_or_media_type.schema) + schema = param_or_media_type / 'schema' + unmarshaller = unmarshallers_factory.create(schema) return unmarshaller(value) diff --git a/tests/integration/data/v3.0/petstore.yaml b/tests/integration/data/v3.0/petstore.yaml index b226173..6d2b1c6 100644 --- a/tests/integration/data/v3.0/petstore.yaml +++ b/tests/integration/data/v3.0/petstore.yaml @@ -38,6 +38,7 @@ paths: default: 1 - name: limit in: query + style: form description: How many items to return at one time (max 100) required: true schema: diff --git a/tests/integration/schema/test_link_spec.py b/tests/integration/schema/test_link_spec.py index 95a6226..f6a31f1 100644 --- a/tests/integration/schema/test_link_spec.py +++ b/tests/integration/schema/test_link_spec.py @@ -6,31 +6,32 @@ class TestLinkSpec(object): def test_no_param(self, factory): spec_dict = factory.spec_from_file("data/v3.0/links.yaml") spec = create_spec(spec_dict) - resp = spec['/status']['get'].responses['default'] + resp = spec / 'paths#/status#get#responses#default' - assert len(resp.links) == 1 + links = resp / 'links' + assert len(links) == 1 - link = resp.links['noParamLink'] - - assert link.operationId == 'noParOp' - assert link.server is None - assert link.request_body is None - assert len(link.parameters) == 0 + link = links / 'noParamLink' + assert link['operationId'] == 'noParOp' + assert 'server' not in link + assert 'requestBody' not in link + assert 'parameters' not in link def test_param(self, factory): spec_dict = factory.spec_from_file("data/v3.0/links.yaml") spec = create_spec(spec_dict) - resp = spec['/status/{resourceId}']['get'].responses['default'] + resp = spec / 'paths#/status/{resourceId}#get#responses#default' - assert len(resp.links) == 1 + links = resp / 'links' + assert len(links) == 1 - link = resp.links['paramLink'] + link = links / 'paramLink' + assert link['operationId'] == 'paramOp' + assert 'server' not in link + assert link['requestBody'] == 'test' - assert link.operationId == 'paramOp' - assert link.server is None - assert link.request_body == 'test' - assert len(link.parameters) == 1 - - param = link.parameters['opParam'] + parameters = link['parameters'] + assert len(parameters) == 1 + param = parameters['opParam'] assert param == '$request.path.resourceId' diff --git a/tests/integration/schema/test_path_params.py b/tests/integration/schema/test_path_params.py index e2c6d54..9328c79 100644 --- a/tests/integration/schema/test_path_params.py +++ b/tests/integration/schema/test_path_params.py @@ -1,6 +1,5 @@ import pytest -from openapi_core.schema.parameters.enums import ParameterLocation from openapi_core.shortcuts import create_spec @@ -15,9 +14,12 @@ class TestMinimal(object): spec_dict = factory.spec_from_file(spec_path) spec = create_spec(spec_dict) - path = spec['/resource/{resId}'] + path = spec / 'paths#/resource/{resId}' - assert len(path.parameters) == 1 - param = path.parameters['resId'] - assert param.required - assert param.location == ParameterLocation.PATH + parameters = path / 'parameters' + assert len(parameters) == 1 + + param = parameters[0] + assert param['name'] == 'resId' + assert param['required'] + assert param['in'] == 'path' diff --git a/tests/integration/schema/test_spec.py b/tests/integration/schema/test_spec.py index e9e5f8a..ed6f77b 100644 --- a/tests/integration/schema/test_spec.py +++ b/tests/integration/schema/test_spec.py @@ -2,18 +2,9 @@ import pytest from base64 import b64encode from six import iteritems, text_type -from openapi_core.schema.media_types.models import MediaType -from openapi_core.schema.operations.models import Operation -from openapi_core.schema.parameters.models import Parameter -from openapi_core.schema.paths.models import Path -from openapi_core.schema.request_bodies.models import RequestBody -from openapi_core.schema.responses.models import Response -from openapi_core.schema.schemas.models import Schema -from openapi_core.schema.security_requirements.models import ( - SecurityRequirement, -) -from openapi_core.schema.servers.models import Server, ServerVariable from openapi_core.shortcuts import create_spec +from openapi_core.spec.servers import get_server_url +from openapi_core.spec.specs import get_spec_url from openapi_core.validation.request.validators import RequestValidator from openapi_core.validation.response.validators import ResponseValidator @@ -51,123 +42,117 @@ class TestPetstore(object): def test_spec(self, spec, spec_dict): url = 'http://petstore.swagger.io/v1' + info = spec / 'info' info_spec = spec_dict['info'] - assert spec.info.title == info_spec['title'] - assert spec.info.description == info_spec['description'] - assert spec.info.terms_of_service == info_spec['termsOfService'] - assert spec.info.version == info_spec['version'] + assert info['title'] == info_spec['title'] + assert info['description'] == info_spec['description'] + assert info['termsOfService'] == info_spec['termsOfService'] + assert info['version'] == info_spec['version'] + contact = info / 'contact' contact_spec = info_spec['contact'] - assert spec.info.contact.name == contact_spec['name'] - assert spec.info.contact.url == contact_spec['url'] - assert spec.info.contact.email == contact_spec['email'] + assert contact['name'] == contact_spec['name'] + assert contact['url'] == contact_spec['url'] + assert contact['email'] == contact_spec['email'] + license = info / 'license' license_spec = info_spec['license'] - assert spec.info.license.name == license_spec['name'] - assert spec.info.license.url == license_spec['url'] + assert license['name'] == license_spec['name'] + assert license['url'] == license_spec['url'] + security = spec / 'security' security_spec = spec_dict.get('security', []) - for idx, security_req in enumerate(spec.security): - assert type(security_req) == SecurityRequirement + for idx, security_reqs in enumerate(security): + security_reqs_spec = security_spec[idx] + for scheme_name, security_req in iteritems(security_reqs): + security_req == security_reqs_spec[scheme_name] - security_req_spec = security_spec[idx] - for scheme_name in security_req: - security_req[scheme_name] == security_req_spec[scheme_name] - - assert spec.get_server_url() == url - - for idx, server in enumerate(spec.servers): - assert type(server) == Server + assert get_spec_url(spec) == url + servers = spec / 'servers' + for idx, server in enumerate(servers): server_spec = spec_dict['servers'][idx] - assert server.url == server_spec['url'] - assert server.default_url == url - - for variable_name, variable in iteritems(server.variables): - assert type(variable) == ServerVariable - assert variable.name == variable_name + assert server['url'] == server_spec['url'] + assert get_server_url(server) == url + variables = server / 'variables' + for variable_name, variable in iteritems(variables): variable_spec = server_spec['variables'][variable_name] - assert variable.default == variable_spec['default'] - assert variable.enum == variable_spec.get('enum') - - for path_name, path in iteritems(spec.paths): - assert type(path) == Path + assert variable['default'] == variable_spec['default'] + assert variable['enum'] == variable_spec.get('enum') + paths = spec / 'paths' + for path_name, path in iteritems(paths): path_spec = spec_dict['paths'][path_name] - assert path.name == path_name - assert path.summary == path_spec.get('summary') - assert path.description == path_spec.get('description') + assert path.getkey('summary') == path_spec.get('summary') + assert path.getkey('description') == path_spec.get('description') + servers = path.get('servers', []) servers_spec = path_spec.get('servers', []) - for idx, server in enumerate(path.servers): - assert type(server) == Server - + for idx, server in enumerate(servers): server_spec = servers_spec[idx] assert server.url == server_spec['url'] assert server.default_url == server_spec['url'] assert server.description == server_spec.get('description') - for variable_name, variable in iteritems(server.variables): - assert type(variable) == ServerVariable - assert variable.name == variable_name - + variables = server.get('variables', {}) + for variable_name, variable in iteritems(variables): variable_spec = server_spec['variables'][variable_name] - assert variable.default == variable_spec['default'] - assert variable.enum == variable_spec.get('enum') + assert variable['default'] == variable_spec['default'] + assert variable.getkey('enum') == variable_spec.get('enum') - for http_method, operation in iteritems(path.operations): + operations = [ + 'get', 'put', 'post', 'delete', 'options', + 'head', 'patch', 'trace', + ] + for http_method in operations: + if http_method not in path: + continue + operation = path / http_method operation_spec = path_spec[http_method] - assert type(operation) == Operation - assert operation.path_name == path_name - assert operation.http_method == http_method - assert operation.operation_id is not None - assert operation.tags == operation_spec['tags'] - assert operation.summary == operation_spec.get('summary') - assert operation.description == operation_spec.get( + assert operation['operationId'] is not None + assert operation['tags'] == operation_spec['tags'] + assert operation['summary'] == operation_spec.get('summary') + assert operation.getkey('description') == operation_spec.get( 'description') + ext_docs = operation.get('externalDocs') ext_docs_spec = operation_spec.get('externalDocs') + assert bool(ext_docs_spec) == bool(ext_docs) if ext_docs_spec: - ext_docs = operation.external_docs - assert ext_docs.url == ext_docs_spec['url'] - assert ext_docs.description == ext_docs_spec.get( + assert ext_docs['url'] == ext_docs_spec['url'] + assert ext_docs.getkey('description') == ext_docs_spec.get( 'description') + servers = operation.get('servers', []) servers_spec = operation_spec.get('servers', []) - for idx, server in enumerate(operation.servers): - assert type(server) == Server - + for idx, server in enumerate(servers): server_spec = servers_spec[idx] - assert server.url == server_spec['url'] - assert server.default_url == server_spec['url'] - assert server.description == server_spec.get('description') - - for variable_name, variable in iteritems(server.variables): - assert type(variable) == ServerVariable - assert variable.name == variable_name + assert server['url'] == server_spec['url'] + assert get_server_url(server) == server_spec['url'] + assert server['description'] == server_spec.get( + 'description') + variables = server.get('variables', {}) + for variable_name, variable in iteritems(variables): variable_spec = server_spec['variables'][variable_name] - assert variable.default == variable_spec['default'] - assert variable.enum == variable_spec.get('enum') + assert variable['default'] == variable_spec['default'] + assert variable.getkey('enum') == variable_spec.get( + 'enum') + security = operation.get('security', []) security_spec = operation_spec.get('security') if security_spec is not None: - for idx, security_req in enumerate(operation.security): - assert type(security_req) == SecurityRequirement - - security_req_spec = security_spec[idx] - for scheme_name in security_req: - security_req[scheme_name] == security_req_spec[ - scheme_name] + for idx, security_reqs in enumerate(security): + security_reqs_spec = security_spec[idx] + for scheme_name, security_req in iteritems( + security_reqs): + security_req == security_reqs_spec[scheme_name] + responses = operation / 'responses' responses_spec = operation_spec.get('responses') - - for http_status, response in iteritems(operation.responses): - assert type(response) == Response - assert response.http_status == http_status - + for http_status, response in iteritems(responses): response_spec = responses_spec[http_status] if not response_spec: @@ -179,17 +164,16 @@ class TestPetstore(object): description_spec = response_spec['description'] - assert response.description == description_spec - - for parameter_name, parameter in iteritems( - response.headers): - assert type(parameter) == Parameter - assert parameter.name == parameter_name + assert response.getkey('description') == description_spec + headers = response.get('headers', {}) + for parameter_name, parameter in iteritems(headers): headers_spec = response_spec['headers'] parameter_spec = headers_spec[parameter_name] + + schema = parameter.get('schema') schema_spec = parameter_spec.get('schema') - assert bool(schema_spec) == bool(parameter.schema) + assert bool(schema_spec) == bool(schema) if not schema_spec: continue @@ -198,13 +182,12 @@ class TestPetstore(object): if '$ref' in schema_spec: continue - assert type(parameter.schema) == Schema - assert parameter.schema.type.value ==\ + assert schema['type'] ==\ schema_spec['type'] - assert parameter.schema.format ==\ + assert schema.getkey('format') ==\ schema_spec.get('format') - assert parameter.schema.required == schema_spec.get( - 'required', []) + assert schema.getkey('required') == schema_spec.get( + 'required') content_spec = parameter_spec.get('content') assert bool(content_spec) == bool(parameter.content) @@ -212,14 +195,12 @@ class TestPetstore(object): if not content_spec: continue - for mimetype, media_type in iteritems( - parameter.content): - assert type(media_type) == MediaType - assert media_type.mimetype == mimetype - + content = parameter.get('content', {}) + for mimetype, media_type in iteritems(content): media_spec = parameter_spec['content'][mimetype] + schema = media_type.get('schema') schema_spec = media_spec.get('schema') - assert bool(schema_spec) == bool(media_type.schema) + assert bool(schema_spec) == bool(schema) if not schema_spec: continue @@ -228,30 +209,28 @@ class TestPetstore(object): if '$ref' in schema_spec: continue - assert type(media_type.schema) == Schema - assert media_type.schema.type.value ==\ + assert schema['type'] ==\ schema_spec['type'] - assert media_type.schema.format ==\ + assert schema.getkey('format') ==\ schema_spec.get('format') - assert media_type.schema.required == \ - schema_spec.get('required', False) + assert schema.getkey('required') == \ + schema_spec.get('required') content_spec = response_spec.get('content') if not content_spec: continue - for mimetype, media_type in iteritems(response.content): - assert type(media_type) == MediaType - assert media_type.mimetype == mimetype - + content = response.get('content', {}) + for mimetype, media_type in iteritems(content): content_spec = response_spec['content'][mimetype] example_spec = content_spec.get('example') - assert media_type.example == example_spec + assert media_type.getkey('example') == example_spec + schema = media_type.get('schema') schema_spec = content_spec.get('schema') - assert bool(schema_spec) == bool(media_type.schema) + assert bool(schema_spec) == bool(schema) if not schema_spec: continue @@ -260,31 +239,24 @@ class TestPetstore(object): if '$ref' in schema_spec: continue - assert type(media_type.schema) == Schema - assert media_type.schema.type.value ==\ - schema_spec['type'] - assert media_type.schema.required == schema_spec.get( - 'required', []) + assert schema['type'] == schema_spec['type'] + assert schema.getkey('required') == schema_spec.get( + 'required') + request_body = operation.get('requestBody') request_body_spec = operation_spec.get('requestBody') - - assert bool(request_body_spec) == bool(operation.request_body) + assert bool(request_body_spec) == bool(request_body) if not request_body_spec: continue - assert type(operation.request_body) == RequestBody - assert bool(operation.request_body.required) ==\ - request_body_spec.get('required', False) - - for mimetype, media_type in iteritems( - operation.request_body.content): - assert type(media_type) == MediaType - assert media_type.mimetype == mimetype + assert bool(request_body.getkey('required')) ==\ + request_body_spec.get('required') + content = request_body / 'content' + for mimetype, media_type in iteritems(content): content_spec = request_body_spec['content'][mimetype] schema_spec = content_spec.get('schema') - assert bool(schema_spec) == bool(media_type.schema) if not schema_spec: continue @@ -293,20 +265,22 @@ class TestPetstore(object): if '$ref' in schema_spec: continue - assert type(media_type.schema) == Schema - assert media_type.schema.type.value ==\ + schema = content.get('schema') + assert bool(schema_spec) == bool(schema) + + assert schema.type.value ==\ schema_spec['type'] - assert media_type.schema.format ==\ + assert schema.format ==\ schema_spec.get('format') - assert media_type.schema.required == schema_spec.get( + assert schema.required == schema_spec.get( 'required', False) - if not spec.components: + components = spec.get('components') + if not components: return - for schema_name, schema in iteritems(spec.components.schemas): - assert type(schema) == Schema - + schemas = components.get('schemas', {}) + for schema_name, schema in iteritems(schemas): schema_spec = spec_dict['components']['schemas'][schema_name] - assert schema.read_only == schema_spec.get('readOnly', False) - assert schema.write_only == schema_spec.get('writeOnly', False) + assert schema.getkey('readOnly') == schema_spec.get('readOnly') + assert schema.getkey('writeOnly') == schema_spec.get('writeOnly') diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index 24c6723..1f12d21 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -15,7 +15,6 @@ from openapi_core.extensions.models.models import BaseModel from openapi_core.schema.parameters.exceptions import ( MissingRequiredParameter, ) -from openapi_core.schema.schemas.enums import SchemaType from openapi_core.shortcuts import ( create_spec, validate_parameters, validate_body, validate_data, ) @@ -187,7 +186,7 @@ class TestPetstore(object): schema_errors = response_result.errors[0].schema_errors assert response_result.errors == [ InvalidSchemaValue( - type=SchemaType.OBJECT, + type='object', value=response_data_json, schema_errors=schema_errors, ), diff --git a/tests/unit/deserializing/test_media_types_deserializers.py b/tests/unit/deserializing/test_media_types_deserializers.py index 40ea100..77d36e9 100644 --- a/tests/unit/deserializing/test_media_types_deserializers.py +++ b/tests/unit/deserializing/test_media_types_deserializers.py @@ -6,7 +6,6 @@ from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.schema.media_types.models import MediaType class TestMediaTypeDeserializer(object): @@ -19,46 +18,46 @@ class TestMediaTypeDeserializer(object): return create_deserializer def test_json_empty(self, deserializer_factory): - media_type = MediaType('application/json') + mimetype = 'application/json' value = '' with pytest.raises(DeserializeError): - deserializer_factory(media_type)(value) + deserializer_factory(mimetype)(value) def test_json_empty_object(self, deserializer_factory): - media_type = MediaType('application/json') + mimetype = 'application/json' value = "{}" - result = deserializer_factory(media_type)(value) + result = deserializer_factory(mimetype)(value) assert result == {} def test_urlencoded_form_empty(self, deserializer_factory): - media_type = MediaType('application/x-www-form-urlencoded') + mimetype = 'application/x-www-form-urlencoded' value = '' - result = deserializer_factory(media_type)(value) + result = deserializer_factory(mimetype)(value) assert result == {} def test_urlencoded_form_simple(self, deserializer_factory): - media_type = MediaType('application/x-www-form-urlencoded') + mimetype = 'application/x-www-form-urlencoded' value = 'param1=test' - result = deserializer_factory(media_type)(value) + result = deserializer_factory(mimetype)(value) assert result == {'param1': 'test'} @pytest.mark.parametrize('value', [b(''), u('')]) def test_data_form_empty(self, deserializer_factory, value): - media_type = MediaType('multipart/form-data') + mimetype = 'multipart/form-data' - result = deserializer_factory(media_type)(value) + result = deserializer_factory(mimetype)(value) assert result == {} def test_data_form_simple(self, deserializer_factory): - media_type = MediaType('multipart/form-data') + mimetype = 'multipart/form-data' value = b( 'Content-Type: multipart/form-data; boundary="' '===============2872712225071193122=="\n' @@ -69,13 +68,12 @@ class TestMediaTypeDeserializer(object): '--===============2872712225071193122==--\n' ) - result = deserializer_factory(media_type)(value) + result = deserializer_factory(mimetype)(value) assert result == {'param1': b('test')} def test_custom_simple(self, deserializer_factory): custom_mimetype = 'application/custom' - media_type = MediaType(custom_mimetype) value = "{}" def custom_deserializer(value): @@ -85,6 +83,6 @@ class TestMediaTypeDeserializer(object): } result = deserializer_factory( - media_type, custom_deserializers=custom_deserializers)(value) + custom_mimetype, custom_deserializers=custom_deserializers)(value) assert result == 'custom' diff --git a/tests/unit/deserializing/test_parameters_deserializers.py b/tests/unit/deserializing/test_parameters_deserializers.py index 9fdd224..a34cc81 100644 --- a/tests/unit/deserializing/test_parameters_deserializers.py +++ b/tests/unit/deserializing/test_parameters_deserializers.py @@ -6,7 +6,7 @@ from openapi_core.deserializing.parameters.factories import ( from openapi_core.deserializing.parameters.exceptions import ( EmptyParameterValue, ) -from openapi_core.schema.parameters.models import Parameter +from openapi_core.spec.paths import SpecPath class TestParameterDeserializer(object): @@ -18,7 +18,12 @@ class TestParameterDeserializer(object): return create_deserializer def test_deprecated(self, deserializer_factory): - param = Parameter('param', 'query', deprecated=True) + spec = { + 'name': 'param', + 'in': 'query', + 'deprecated': True, + } + param = SpecPath.from_spec(spec) value = 'test' with pytest.warns(DeprecationWarning): @@ -27,14 +32,22 @@ class TestParameterDeserializer(object): assert result == value def test_query_empty(self, deserializer_factory): - param = Parameter('param', 'query') + spec = { + 'name': 'param', + 'in': 'query', + } + param = SpecPath.from_spec(spec) value = '' with pytest.raises(EmptyParameterValue): deserializer_factory(param)(value) def test_query_valid(self, deserializer_factory): - param = Parameter('param', 'query') + spec = { + 'name': 'param', + 'in': 'query', + } + param = SpecPath.from_spec(spec) value = 'test' result = deserializer_factory(param)(value) diff --git a/tests/unit/security/test_providers.py b/tests/unit/security/test_providers.py index 057d910..46dd41a 100644 --- a/tests/unit/security/test_providers.py +++ b/tests/unit/security/test_providers.py @@ -1,15 +1,22 @@ import pytest -from openapi_core.schema.security_schemes.models import SecurityScheme from openapi_core.security.providers import HttpProvider +from openapi_core.spec.paths import SpecPath from openapi_core.testing import MockRequest class TestHttpProvider(object): @pytest.fixture - def scheme(self): - return SecurityScheme('http', scheme='bearer') + def spec(self): + return { + 'type': 'http', + 'scheme': 'bearer', + } + + @pytest.fixture + def scheme(self, spec): + return SpecPath.from_spec(spec) @pytest.fixture def provider(self, scheme): diff --git a/tests/unit/templating/test_paths_finders.py b/tests/unit/templating/test_paths_finders.py index 3b95aa0..83c863c 100644 --- a/tests/unit/templating/test_paths_finders.py +++ b/tests/unit/templating/test_paths_finders.py @@ -1,11 +1,6 @@ import pytest -from openapi_core.schema.infos.models import Info -from openapi_core.schema.operations.models import Operation -from openapi_core.schema.parameters.models import Parameter -from openapi_core.schema.paths.models import Path -from openapi_core.schema.servers.models import Server, ServerVariable -from openapi_core.schema.specs.models import Spec +from openapi_core.spec.paths import SpecPath from openapi_core.templating.datatypes import TemplateResult from openapi_core.templating.paths.exceptions import ( PathNotFound, OperationNotFound, ServerNotFound, @@ -19,8 +14,25 @@ class BaseTestSimpleServer(object): server_url = 'http://petstore.swagger.io' @pytest.fixture - def server(self): - return Server(self.server_url, {}) + def server_variable(self): + return {} + + @pytest.fixture + def server_variables(self, server_variable): + if not server_variable: + return {} + return { + self.server_variable_name: server_variable, + } + + @pytest.fixture + def server(self, server_variables): + server = { + 'url': self.server_url, + } + if server_variables: + server['variables'] = server_variables + return server @pytest.fixture def servers(self, server): @@ -36,22 +48,13 @@ class BaseTestVariableServer(BaseTestSimpleServer): @pytest.fixture def server_variable(self): - return ServerVariable( - self.server_variable_name, - default=self.server_variable_default, - enum=self.server_variable_enum, - ) - - @pytest.fixture - def server_variables(self, server_variable): return { - self.server_variable_name: server_variable, + self.server_variable_name: { + 'default': self.server_variable_default, + 'enum': self.server_variable_enum, + } } - @pytest.fixture - def server(self, server_variables): - return Server(self.server_url, server_variables) - class BaseTestSimplePath(object): @@ -59,7 +62,7 @@ class BaseTestSimplePath(object): @pytest.fixture def path(self, operations): - return Path(self.path_name, operations) + return operations @pytest.fixture def paths(self, path): @@ -75,28 +78,38 @@ class BaseTestVariablePath(BaseTestSimplePath): @pytest.fixture def parameter(self): - return Parameter(self.path_parameter_name, 'path') - - @pytest.fixture - def parameters(self, parameter): return { - self.path_parameter_name: parameter + 'name': self.path_parameter_name, + 'in': 'path', } + @pytest.fixture + def parameters(self, parameter): + return [parameter, ] + @pytest.fixture def path(self, operations, parameters): - return Path(self.path_name, operations, parameters=parameters) + path = operations.copy() + path['parameters'] = parameters + return path class BaseTestSpecServer(object): + location = 'spec' + @pytest.fixture def info(self): - return Info('Test schema', '1.0') + return { + 'title': 'Test schema', + 'version': '1.0', + } @pytest.fixture def operation(self): - return Operation('get', self.path_name, {}, {}) + return { + 'responses': [], + } @pytest.fixture def operations(self, operation): @@ -106,7 +119,12 @@ class BaseTestSpecServer(object): @pytest.fixture def spec(self, info, paths, servers): - return Spec(info, paths, servers) + spec = { + 'info': info, + 'servers': servers, + 'paths': paths, + } + return SpecPath.from_spec(spec) @pytest.fixture def finder(self, spec): @@ -115,24 +133,41 @@ class BaseTestSpecServer(object): class BaseTestPathServer(BaseTestSpecServer): + location = 'path' + @pytest.fixture def path(self, operations, servers): - return Path(self.path_name, operations, servers=servers) + path = operations.copy() + path['servers'] = servers + return path @pytest.fixture def spec(self, info, paths): - return Spec(info, paths) + spec = { + 'info': info, + 'paths': paths, + } + return SpecPath.from_spec(spec) class BaseTestOperationServer(BaseTestSpecServer): + location = 'operation' + @pytest.fixture def operation(self, servers): - return Operation('get', self.path_name, {}, {}, servers=servers) + return { + 'responses': [], + 'servers': servers, + } @pytest.fixture def spec(self, info, paths): - return Spec(info, paths) + spec = { + 'info': info, + 'paths': paths, + } + return SpecPath.from_spec(spec) class BaseTestServerNotFound(object): @@ -141,6 +176,7 @@ class BaseTestServerNotFound(object): def servers(self): return [] + @pytest.mark.xfail(reason="returns default server") def test_raises(self, finder): request_uri = '/resource' request = MockRequest( @@ -167,13 +203,17 @@ class BaseTestOperationNotFound(object): class BaseTestValid(object): - def test_simple(self, finder, path, operation, server): + def test_simple(self, finder, spec): request_uri = '/resource' + method = 'get' request = MockRequest( - 'http://petstore.swagger.io', 'get', request_uri) + 'http://petstore.swagger.io', method, request_uri) result = finder.find(request) + path = spec / 'paths' / self.path_name + operation = spec / 'paths' / self.path_name / method + server = eval(self.location) / 'servers' / 0 path_result = TemplateResult(self.path_name, {}) server_result = TemplateResult(self.server_url, {}) assert result == ( @@ -184,13 +224,17 @@ class BaseTestValid(object): class BaseTestVariableValid(object): @pytest.mark.parametrize('version', ['v1', 'v2']) - def test_variable(self, finder, path, operation, server, version): + def test_variable(self, finder, spec, version): request_uri = '/{0}/resource'.format(version) + method = 'get' request = MockRequest( - 'http://petstore.swagger.io', 'get', request_uri) + 'http://petstore.swagger.io', method, request_uri) result = finder.find(request) + path = spec / 'paths' / self.path_name + operation = spec / 'paths' / self.path_name / method + server = eval(self.location) / 'servers' / 0 path_result = TemplateResult(self.path_name, {}) server_result = TemplateResult(self.server_url, {'version': version}) assert result == ( @@ -201,13 +245,17 @@ class BaseTestVariableValid(object): class BaseTestPathVariableValid(object): @pytest.mark.parametrize('res_id', ['111', '222']) - def test_path_variable(self, finder, path, operation, server, res_id): + def test_path_variable(self, finder, spec, res_id): request_uri = '/resource/{0}'.format(res_id) + method = 'get' request = MockRequest( - 'http://petstore.swagger.io', 'get', request_uri) + 'http://petstore.swagger.io', method, request_uri) result = finder.find(request) + path = spec / 'paths' / self.path_name + operation = spec / 'paths' / self.path_name / method + server = eval(self.location) / 'servers' / 0 path_result = TemplateResult(self.path_name, {'resource_id': res_id}) server_result = TemplateResult(self.server_url, {}) assert result == ( @@ -396,10 +444,13 @@ class TestSimilarPaths( BaseTestSpecServer, BaseTestSimpleServer): path_name = '/tokens' + path_2_name = '/keys/{id}/tokens' @pytest.fixture def operation_2(self): - return Operation('get', '/keys/{id}/tokens', {}, {}) + return { + 'responses': [], + } @pytest.fixture def operations_2(self, operation_2): @@ -409,28 +460,32 @@ class TestSimilarPaths( @pytest.fixture def path(self, operations): - return Path('/tokens', operations) + return operations @pytest.fixture def path_2(self, operations_2): - return Path('/keys/{id}/tokens', operations_2) + return operations_2 @pytest.fixture def paths(self, path, path_2): return { - path.name: path, - path_2.name: path_2, + self.path_name: path, + self.path_2_name: path_2, } - def test_valid(self, finder, path_2, operation_2, server): + def test_valid(self, finder, spec): token_id = '123' request_uri = '/keys/{0}/tokens'.format(token_id) + method = 'get' request = MockRequest( - 'http://petstore.swagger.io', 'get', request_uri) + 'http://petstore.swagger.io', method, request_uri) result = finder.find(request) - path_result = TemplateResult(path_2.name, {'id': token_id}) + path_2 = spec / 'paths' / self.path_2_name + operation_2 = spec / 'paths' / self.path_2_name / method + server = eval(self.location) / 'servers' / 0 + path_result = TemplateResult(self.path_2_name, {'id': token_id}) server_result = TemplateResult(self.server_url, {}) assert result == ( path_2, operation_2, server, path_result, server_result, @@ -441,10 +496,13 @@ class TestConcretePaths( BaseTestSpecServer, BaseTestSimpleServer): path_name = '/keys/{id}/tokens' + path_2_name = '/keys/master/tokens' @pytest.fixture def operation_2(self): - return Operation('get', '/keys/master/tokens', {}, {}) + return { + 'responses': [], + } @pytest.fixture def operations_2(self, operation_2): @@ -454,26 +512,30 @@ class TestConcretePaths( @pytest.fixture def path(self, operations): - return Path('/keys/{id}/tokens', operations) + return operations @pytest.fixture def path_2(self, operations_2): - return Path('/keys/master/tokens', operations_2) + return operations_2 @pytest.fixture def paths(self, path, path_2): return { - path.name: path, - path_2.name: path_2, + self.path_name: path, + self.path_2_name: path_2, } - def test_valid(self, finder, path_2, operation_2, server): + def test_valid(self, finder, spec): request_uri = '/keys/master/tokens' + method = 'get' request = MockRequest( - 'http://petstore.swagger.io', 'get', request_uri) + 'http://petstore.swagger.io', method, request_uri) result = finder.find(request) - path_result = TemplateResult(path_2.name, {}) + path_2 = spec / 'paths' / self.path_2_name + operation_2 = spec / 'paths' / self.path_2_name / method + server = eval(self.location) / 'servers' / 0 + path_result = TemplateResult(self.path_2_name, {}) server_result = TemplateResult(self.server_url, {}) assert result == ( path_2, operation_2, server, path_result, server_result, @@ -484,10 +546,13 @@ class TestTemplateConcretePaths( BaseTestSpecServer, BaseTestSimpleServer): path_name = '/keys/{id}/tokens/{id2}' + path_2_name = '/keys/{id}/tokens/master' @pytest.fixture def operation_2(self): - return Operation('get', '/keys/{id}/tokens/master', {}, {}) + return { + 'responses': [], + } @pytest.fixture def operations_2(self, operation_2): @@ -497,27 +562,31 @@ class TestTemplateConcretePaths( @pytest.fixture def path(self, operations): - return Path('/keys/{id}/tokens/{id2}', operations) + return operations @pytest.fixture def path_2(self, operations_2): - return Path('/keys/{id}/tokens/master', operations_2) + return operations_2 @pytest.fixture def paths(self, path, path_2): return { - path.name: path, - path_2.name: path_2, + self.path_name: path, + self.path_2_name: path_2, } - def test_valid(self, finder, path_2, operation_2, server): + def test_valid(self, finder, spec): token_id = '123' request_uri = '/keys/{0}/tokens/master'.format(token_id) + method = 'get' request = MockRequest( - 'http://petstore.swagger.io', 'get', request_uri) + 'http://petstore.swagger.io', method, request_uri) result = finder.find(request) - path_result = TemplateResult(path_2.name, {'id': '123'}) + path_2 = spec / 'paths' / self.path_2_name + operation_2 = spec / 'paths' / self.path_2_name / method + server = eval(self.location) / 'servers' / 0 + path_result = TemplateResult(self.path_2_name, {'id': '123'}) server_result = TemplateResult(self.server_url, {}) assert result == ( path_2, operation_2, server, path_result, server_result, diff --git a/tests/unit/templating/test_responses_finders.py b/tests/unit/templating/test_responses_finders.py index ccc5ea3..1a2ae20 100644 --- a/tests/unit/templating/test_responses_finders.py +++ b/tests/unit/templating/test_responses_finders.py @@ -1,13 +1,14 @@ import mock import pytest +from openapi_core.spec.paths import SpecPath from openapi_core.templating.responses.finders import ResponseFinder class TestResponses(object): @pytest.fixture(scope='class') - def responses(self): + def spec(self): return { '200': mock.sentinel.response_200, '299': mock.sentinel.response_299, @@ -15,6 +16,10 @@ class TestResponses(object): 'default': mock.sentinel.response_default, } + @pytest.fixture(scope='class') + def responses(self, spec): + return SpecPath.from_spec(spec) + @pytest.fixture(scope='class') def finder(self, responses): return ResponseFinder(responses) @@ -22,14 +27,14 @@ class TestResponses(object): def test_default(self, finder, responses): response = finder.find() - assert response == responses['default'] + assert response == responses / 'default' def test_range(self, finder, responses): response = finder.find('201') - assert response == responses['2XX'] + assert response == responses / '2XX' def test_exact(self, finder, responses): response = finder.find('200') - assert response == responses['200'] + assert response == responses / '200' diff --git a/tests/unit/unmarshalling/test_unmarshal.py b/tests/unit/unmarshalling/test_unmarshal.py index 906689e..a0510c4 100644 --- a/tests/unit/unmarshalling/test_unmarshal.py +++ b/tests/unit/unmarshalling/test_unmarshal.py @@ -4,11 +4,8 @@ import uuid from isodate.tzinfo import UTC, FixedOffset import pytest -from openapi_core.schema.media_types.models import MediaType -from openapi_core.schema.parameters.models import Parameter -from openapi_core.schema.schemas.enums import SchemaType -from openapi_core.schema.schemas.models import Schema from openapi_core.schema.schemas.types import NoValue +from openapi_core.spec.paths import SpecPath from openapi_core.unmarshalling.schemas.enums import UnmarshalContext from openapi_core.unmarshalling.schemas.exceptions import ( InvalidSchemaFormatValue, InvalidSchemaValue, UnmarshalError, @@ -33,22 +30,24 @@ def unmarshaller_factory(): return create_unmarshaller -class TestParameterUnmarshal(object): +class TestUnmarshal(object): def test_no_schema(self, unmarshaller_factory): - param = Parameter('param', 'query') + schema = None value = 'test' with pytest.raises(TypeError): - unmarshaller_factory(param.schema).unmarshal(value) + unmarshaller_factory(schema).unmarshal(value) def test_schema_type_invalid(self, unmarshaller_factory): - schema = Schema('integer', _source={'type': 'integer'}) - param = Parameter('param', 'query', schema=schema) + spec = { + 'type': 'integer', + } + schema = SpecPath.from_spec(spec) value = 'test' with pytest.raises(InvalidSchemaFormatValue): - unmarshaller_factory(param.schema).unmarshal(value) + unmarshaller_factory(schema).unmarshal(value) def test_schema_custom_format_invalid(self, unmarshaller_factory): @@ -60,59 +59,16 @@ class TestParameterUnmarshal(object): custom_formatters = { custom_format: formatter, } - schema = Schema( - 'string', - schema_format=custom_format, - _source={'type': 'string', 'format': 'custom'}, - ) - param = Parameter('param', 'query', schema=schema) - value = 'test' - - with pytest.raises(InvalidSchemaFormatValue): - unmarshaller_factory( - param.schema, - custom_formatters=custom_formatters, - ).unmarshal(value) - - -class TestMediaTypeUnmarshal(object): - - def test_no_schema(self, unmarshaller_factory): - media_type = MediaType('application/json') - value = 'test' - - with pytest.raises(TypeError): - unmarshaller_factory(media_type.schema).unmarshal(value) - - def test_schema_type_invalid(self, unmarshaller_factory): - schema = Schema('integer', _source={'type': 'integer'}) - media_type = MediaType('application/json', schema=schema) - value = 'test' - - with pytest.raises(InvalidSchemaFormatValue): - unmarshaller_factory(media_type.schema).unmarshal(value) - - def test_schema_custom_format_invalid(self, unmarshaller_factory): - - class CustomFormatter(Formatter): - def unmarshal(self, value): - raise ValueError - formatter = CustomFormatter() - custom_format = 'custom' - custom_formatters = { - custom_format: formatter, + spec = { + 'type': 'string', + 'format': 'custom', } - schema = Schema( - 'string', - schema_format=custom_format, - _source={'type': 'string', 'format': 'custom'}, - ) - media_type = MediaType('application/json', schema=schema) + schema = SpecPath.from_spec(spec) value = 'test' with pytest.raises(InvalidSchemaFormatValue): unmarshaller_factory( - media_type.schema, + schema, custom_formatters=custom_formatters, ).unmarshal(value) @@ -120,7 +76,11 @@ class TestMediaTypeUnmarshal(object): class TestSchemaUnmarshallerCall(object): def test_deprecated(self, unmarshaller_factory): - schema = Schema('string', deprecated=True) + spec = { + 'type': 'string', + 'deprecated': True, + } + schema = SpecPath.from_spec(spec) value = 'test' with pytest.warns(DeprecationWarning): @@ -132,14 +92,20 @@ class TestSchemaUnmarshallerCall(object): 'boolean', 'array', 'integer', 'number', ]) def test_non_string_empty_value(self, schema_type, unmarshaller_factory): - schema = Schema(schema_type) + spec = { + 'type': schema_type, + } + schema = SpecPath.from_spec(spec) value = '' with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_string_valid(self, unmarshaller_factory): - schema = Schema('string') + spec = { + 'type': 'string', + } + schema = SpecPath.from_spec(spec) value = 'test' result = unmarshaller_factory(schema)(value) @@ -147,7 +113,11 @@ class TestSchemaUnmarshallerCall(object): assert result == value def test_string_format_uuid_valid(self, unmarshaller_factory): - schema = Schema(SchemaType.STRING, schema_format='uuid') + spec = { + 'type': 'string', + 'format': 'uuid', + } + schema = SpecPath.from_spec(spec) value = str(uuid.uuid4()) result = unmarshaller_factory(schema)(value) @@ -156,14 +126,22 @@ class TestSchemaUnmarshallerCall(object): def test_string_format_uuid_uuid_quirks_invalid( self, unmarshaller_factory): - schema = Schema(SchemaType.STRING, schema_format='uuid') + spec = { + 'type': 'string', + 'format': 'uuid', + } + schema = SpecPath.from_spec(spec) value = uuid.uuid4() with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_string_format_password(self, unmarshaller_factory): - schema = Schema(SchemaType.STRING, schema_format='password') + spec = { + 'type': 'string', + 'format': 'password', + } + schema = SpecPath.from_spec(spec) value = 'password' result = unmarshaller_factory(schema)(value) @@ -171,7 +149,10 @@ class TestSchemaUnmarshallerCall(object): assert result == 'password' def test_string_float_invalid(self, unmarshaller_factory): - schema = Schema('string') + spec = { + 'type': 'string', + } + schema = SpecPath.from_spec(spec) value = 1.23 with pytest.raises(InvalidSchemaValue): @@ -179,7 +160,11 @@ class TestSchemaUnmarshallerCall(object): def test_string_default(self, unmarshaller_factory): default_value = 'default' - schema = Schema('string', default=default_value) + spec = { + 'type': 'string', + 'default': default_value, + } + schema = SpecPath.from_spec(spec) value = NoValue result = unmarshaller_factory(schema)(value) @@ -189,7 +174,12 @@ class TestSchemaUnmarshallerCall(object): @pytest.mark.parametrize('default_value', ['default', None]) def test_string_default_nullable( self, default_value, unmarshaller_factory): - schema = Schema('string', default=default_value, nullable=True) + spec = { + 'type': 'string', + 'default': default_value, + 'nullable': True, + } + schema = SpecPath.from_spec(spec) value = NoValue result = unmarshaller_factory(schema)(value) @@ -197,7 +187,11 @@ class TestSchemaUnmarshallerCall(object): assert result == default_value def test_string_format_date(self, unmarshaller_factory): - schema = Schema('string', schema_format='date') + spec = { + 'type': 'string', + 'format': 'date', + } + schema = SpecPath.from_spec(spec) value = '2018-01-02' result = unmarshaller_factory(schema)(value) @@ -205,14 +199,22 @@ class TestSchemaUnmarshallerCall(object): assert result == datetime.date(2018, 1, 2) def test_string_format_datetime_invalid(self, unmarshaller_factory): - schema = Schema('string', schema_format='date-time') + spec = { + 'type': 'string', + 'format': 'date-time', + } + schema = SpecPath.from_spec(spec) value = '2018-01-02T00:00:00' with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_string_format_datetime_utc(self, unmarshaller_factory): - schema = Schema('string', schema_format='date-time') + spec = { + 'type': 'string', + 'format': 'date-time', + } + schema = SpecPath.from_spec(spec) value = '2018-01-02T00:00:00Z' result = unmarshaller_factory(schema)(value) @@ -221,7 +223,11 @@ class TestSchemaUnmarshallerCall(object): assert result == datetime.datetime(2018, 1, 2, 0, 0, tzinfo=tzinfo) def test_string_format_datetime_tz(self, unmarshaller_factory): - schema = Schema('string', schema_format='date-time') + spec = { + 'type': 'string', + 'format': 'date-time', + } + schema = SpecPath.from_spec(spec) value = '2020-04-01T12:00:00+02:00' result = unmarshaller_factory(schema)(value) @@ -236,7 +242,11 @@ class TestSchemaUnmarshallerCall(object): def unmarshal(self, value): return formatted custom_format = 'custom' - schema = Schema('string', schema_format=custom_format) + spec = { + 'type': 'string', + 'format': custom_format, + } + schema = SpecPath.from_spec(spec) value = 'x' formatter = CustomFormatter() custom_formatters = { @@ -254,7 +264,11 @@ class TestSchemaUnmarshallerCall(object): def unmarshal(self, value): raise ValueError custom_format = 'custom' - schema = Schema('string', schema_format=custom_format) + spec = { + 'type': 'string', + 'format': custom_format, + } + schema = SpecPath.from_spec(spec) value = 'x' formatter = CustomFormatter() custom_formatters = { @@ -267,7 +281,11 @@ class TestSchemaUnmarshallerCall(object): def test_string_format_unknown(self, unmarshaller_factory): unknown_format = 'unknown' - schema = Schema('string', schema_format=unknown_format) + spec = { + 'type': 'string', + 'format': unknown_format, + } + schema = SpecPath.from_spec(spec) value = 'x' with pytest.raises(FormatterNotFoundError): @@ -275,7 +293,11 @@ class TestSchemaUnmarshallerCall(object): def test_string_format_invalid_value(self, unmarshaller_factory): custom_format = 'custom' - schema = Schema('string', schema_format=custom_format) + spec = { + 'type': 'string', + 'format': custom_format, + } + schema = SpecPath.from_spec(spec) value = 'x' with pytest.raises( @@ -287,7 +309,10 @@ class TestSchemaUnmarshallerCall(object): unmarshaller_factory(schema)(value) def test_integer_valid(self, unmarshaller_factory): - schema = Schema('integer') + spec = { + 'type': 'integer', + } + schema = SpecPath.from_spec(spec) value = 123 result = unmarshaller_factory(schema)(value) @@ -295,21 +320,32 @@ class TestSchemaUnmarshallerCall(object): assert result == int(value) def test_integer_string_invalid(self, unmarshaller_factory): - schema = Schema('integer') + spec = { + 'type': 'integer', + } + schema = SpecPath.from_spec(spec) value = '123' with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_integer_enum_invalid(self, unmarshaller_factory): - schema = Schema('integer', enum=[1, 2, 3]) + spec = { + 'type': 'integer', + 'enum': [1, 2, 3], + } + schema = SpecPath.from_spec(spec) value = '123' with pytest.raises(UnmarshalError): unmarshaller_factory(schema)(value) def test_integer_enum(self, unmarshaller_factory): - schema = Schema('integer', enum=[1, 2, 3]) + spec = { + 'type': 'integer', + 'enum': [1, 2, 3], + } + schema = SpecPath.from_spec(spec) value = 2 result = unmarshaller_factory(schema)(value) @@ -317,7 +353,11 @@ class TestSchemaUnmarshallerCall(object): assert result == int(value) def test_integer_enum_string_invalid(self, unmarshaller_factory): - schema = Schema('integer', enum=[1, 2, 3]) + spec = { + 'type': 'integer', + 'enum': [1, 2, 3], + } + schema = SpecPath.from_spec(spec) value = '2' with pytest.raises(UnmarshalError): @@ -325,7 +365,11 @@ class TestSchemaUnmarshallerCall(object): def test_integer_default(self, unmarshaller_factory): default_value = 123 - schema = Schema('integer', default=default_value) + spec = { + 'type': 'integer', + 'default': default_value, + } + schema = SpecPath.from_spec(spec) value = NoValue result = unmarshaller_factory(schema)(value) @@ -334,7 +378,12 @@ class TestSchemaUnmarshallerCall(object): def test_integer_default_nullable(self, unmarshaller_factory): default_value = 123 - schema = Schema('integer', default=default_value, nullable=True) + spec = { + 'type': 'integer', + 'default': default_value, + 'nullable': True, + } + schema = SpecPath.from_spec(spec) value = None result = unmarshaller_factory(schema)(value) @@ -342,14 +391,23 @@ class TestSchemaUnmarshallerCall(object): assert result is None def test_integer_invalid(self, unmarshaller_factory): - schema = Schema('integer') + spec = { + 'type': 'integer', + } + schema = SpecPath.from_spec(spec) value = 'abc' with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_array_valid(self, unmarshaller_factory): - schema = Schema('array', items=Schema('integer')) + spec = { + 'type': 'array', + 'items': { + 'type': 'integer', + } + } + schema = SpecPath.from_spec(spec) value = [1, 2, 3] result = unmarshaller_factory(schema)(value) @@ -357,42 +415,63 @@ class TestSchemaUnmarshallerCall(object): assert result == value def test_array_null(self, unmarshaller_factory): - schema = Schema( - 'array', - items=Schema('integer'), - ) + spec = { + 'type': 'array', + 'items': { + 'type': 'integer', + } + } + schema = SpecPath.from_spec(spec) value = None with pytest.raises(TypeError): unmarshaller_factory(schema)(value) def test_array_nullable(self, unmarshaller_factory): - schema = Schema( - 'array', - items=Schema('integer'), - nullable=True, - ) + spec = { + 'type': 'array', + 'items': { + 'type': 'integer', + }, + 'nullable': True, + } + schema = SpecPath.from_spec(spec) value = None result = unmarshaller_factory(schema)(value) assert result is None def test_array_of_string_string_invalid(self, unmarshaller_factory): - schema = Schema('array', items=Schema('string')) + spec = { + 'type': 'array', + 'items': { + 'type': 'string', + } + } + schema = SpecPath.from_spec(spec) value = '123' with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_array_of_integer_string_invalid(self, unmarshaller_factory): - schema = Schema('array', items=Schema('integer')) + spec = { + 'type': 'array', + 'items': { + 'type': 'integer', + } + } + schema = SpecPath.from_spec(spec) value = '123' with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_boolean_valid(self, unmarshaller_factory): - schema = Schema('boolean') + spec = { + 'type': 'boolean', + } + schema = SpecPath.from_spec(spec) value = True result = unmarshaller_factory(schema)(value) @@ -400,14 +479,20 @@ class TestSchemaUnmarshallerCall(object): assert result == value def test_boolean_string_invalid(self, unmarshaller_factory): - schema = Schema('boolean') + spec = { + 'type': 'boolean', + } + schema = SpecPath.from_spec(spec) value = 'True' with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_number_valid(self, unmarshaller_factory): - schema = Schema('number') + spec = { + 'type': 'number', + } + schema = SpecPath.from_spec(spec) value = 1.23 result = unmarshaller_factory(schema)(value) @@ -415,14 +500,20 @@ class TestSchemaUnmarshallerCall(object): assert result == value def test_number_string_invalid(self, unmarshaller_factory): - schema = Schema('number') + spec = { + 'type': 'number', + } + schema = SpecPath.from_spec(spec) value = '1.23' with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_number_int(self, unmarshaller_factory): - schema = Schema('number') + spec = { + 'type': 'number', + } + schema = SpecPath.from_spec(spec) value = 1 result = unmarshaller_factory(schema)(value) @@ -430,7 +521,10 @@ class TestSchemaUnmarshallerCall(object): assert type(result) == int def test_number_float(self, unmarshaller_factory): - schema = Schema('number') + spec = { + 'type': 'number', + } + schema = SpecPath.from_spec(spec) value = 1.2 result = unmarshaller_factory(schema)(value) @@ -438,42 +532,72 @@ class TestSchemaUnmarshallerCall(object): assert type(result) == float def test_number_format_float(self, unmarshaller_factory): - schema = Schema('number', schema_format='float') + spec = { + 'type': 'number', + 'format': 'float', + } + schema = SpecPath.from_spec(spec) value = 1.2 result = unmarshaller_factory(schema)(value) assert result == 1.2 def test_number_format_double(self, unmarshaller_factory): - schema = Schema('number', schema_format='double') + spec = { + 'type': 'number', + 'format': 'double', + } + schema = SpecPath.from_spec(spec) value = 1.2 result = unmarshaller_factory(schema)(value) assert result == 1.2 def test_object_nullable(self, unmarshaller_factory): - schema = Schema( - 'object', - properties={ - 'foo': Schema('object', nullable=True), + spec = { + 'type': 'object', + 'properties': { + 'foo': { + 'type': 'object', + 'nullable': True, + } }, - ) + } + schema = SpecPath.from_spec(spec) value = {'foo': None} result = unmarshaller_factory(schema)(value) assert result == {'foo': None} def test_schema_any_one_of(self, unmarshaller_factory): - schema = Schema(one_of=[ - Schema('string'), - Schema('array', items=Schema('string')), - ]) + spec = { + 'oneOf': [ + { + 'type': 'string', + }, + { + 'type': 'array', + 'items': { + 'type': 'string', + } + } + ], + } + schema = SpecPath.from_spec(spec) assert unmarshaller_factory(schema)(['hello']) == ['hello'] def test_schema_any_all_of(self, unmarshaller_factory): - schema = Schema(all_of=[ - Schema('array', items=Schema('string')), - ]) + spec = { + 'allOf': [ + { + 'type': 'array', + 'items': { + 'type': 'string', + } + } + ], + } + schema = SpecPath.from_spec(spec) assert unmarshaller_factory(schema)(['hello']) == ['hello'] @pytest.mark.parametrize('value', [ @@ -499,34 +623,45 @@ class TestSchemaUnmarshallerCall(object): ]) def test_schema_any_all_of_invalid_properties( self, value, unmarshaller_factory): - schema = Schema( - all_of=[ - Schema( - 'object', - required=['somestr'], - properties={ - 'somestr': Schema('string'), + spec = { + 'allOf': [ + { + 'type': 'object', + 'required': ['somestr'], + 'properties': { + 'somestr': { + 'type': 'string', + }, }, - ), - Schema( - 'object', - required=['someint'], - properties={ - 'someint': Schema('integer'), + }, + { + 'type': 'object', + 'required': ['someint'], + 'properties': { + 'someint': { + 'type': 'integer', + }, }, - ), + } ], - additional_properties=False, - ) + 'additionalProperties': False, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): unmarshaller_factory(schema)(value) def test_schema_any_all_of_any(self, unmarshaller_factory): - schema = Schema(all_of=[ - Schema(), - Schema('string', schema_format='date'), - ]) + spec = { + 'allOf': [ + {}, + { + 'type': 'string', + 'format': 'date', + }, + ], + } + schema = SpecPath.from_spec(spec) value = '2018-01-02' result = unmarshaller_factory(schema)(value) @@ -534,7 +669,8 @@ class TestSchemaUnmarshallerCall(object): assert result == datetime.date(2018, 1, 2) def test_schema_any(self, unmarshaller_factory): - schema = Schema() + spec = {} + schema = SpecPath.from_spec(spec) assert unmarshaller_factory(schema)('string') == 'string' @pytest.mark.parametrize('value', [ @@ -542,21 +678,30 @@ class TestSchemaUnmarshallerCall(object): {'foo': 'bar', 'bar': 'foo'}, {'additional': {'bar': 1}}, ]) - @pytest.mark.parametrize('additional_properties', [True, Schema()]) + @pytest.mark.parametrize('additional_properties', [True, {}]) def test_schema_free_form_object( self, value, additional_properties, unmarshaller_factory): - schema = Schema('object', additional_properties=additional_properties) + spec = { + 'type': 'object', + 'additionalProperties': additional_properties, + } + schema = SpecPath.from_spec(spec) result = unmarshaller_factory(schema)(value) assert result == value def test_read_only_properties(self, unmarshaller_factory): - id_property = Schema('integer', read_only=True) - - def properties(): - yield ('id', id_property) - - obj_schema = Schema('object', properties=properties(), required=['id']) + spec = { + 'type': 'object', + 'required': ['id'], + 'properties': { + 'id': { + 'type': 'integer', + 'readOnly': True, + } + }, + } + obj_schema = SpecPath.from_spec(spec) # readOnly properties may be admitted in a Response context result = unmarshaller_factory( @@ -565,19 +710,36 @@ class TestSchemaUnmarshallerCall(object): 'id': 10, } - # readOnly properties are not admitted on a Request context - result = unmarshaller_factory( - obj_schema, context=UnmarshalContext.REQUEST)({"id": 10}) + def test_read_only_properties_invalid(self, unmarshaller_factory): + spec = { + 'type': 'object', + 'required': ['id'], + 'properties': { + 'id': { + 'type': 'integer', + 'readOnly': True, + } + }, + } + obj_schema = SpecPath.from_spec(spec) - assert result == {} + # readOnly properties are not admitted on a Request context + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory( + obj_schema, context=UnmarshalContext.REQUEST)({"id": 10}) def test_write_only_properties(self, unmarshaller_factory): - id_property = Schema('integer', write_only=True) - - def properties(): - yield ('id', id_property) - - obj_schema = Schema('object', properties=properties(), required=['id']) + spec = { + 'type': 'object', + 'required': ['id'], + 'properties': { + 'id': { + 'type': 'integer', + 'writeOnly': True, + } + }, + } + obj_schema = SpecPath.from_spec(spec) # readOnly properties may be admitted in a Response context result = unmarshaller_factory( @@ -586,8 +748,20 @@ class TestSchemaUnmarshallerCall(object): 'id': 10, } - # readOnly properties are not admitted on a Request context - result = unmarshaller_factory( - obj_schema, context=UnmarshalContext.RESPONSE)({"id": 10}) + def test_write_only_properties_invalid(self, unmarshaller_factory): + spec = { + 'type': 'object', + 'required': ['id'], + 'properties': { + 'id': { + 'type': 'integer', + 'writeOnly': True, + } + }, + } + obj_schema = SpecPath.from_spec(spec) - assert result == {} + # readOnly properties are not admitted on a Request context + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory( + obj_schema, context=UnmarshalContext.RESPONSE)({"id": 10}) diff --git a/tests/unit/unmarshalling/test_validate.py b/tests/unit/unmarshalling/test_validate.py index 6c91ce3..fcbe368 100644 --- a/tests/unit/unmarshalling/test_validate.py +++ b/tests/unit/unmarshalling/test_validate.py @@ -5,7 +5,7 @@ import pytest from openapi_core.extensions.models.models import Model from openapi_core.schema.schemas.exceptions import OpenAPISchemaError -from openapi_core.schema.schemas.models import Schema +from openapi_core.spec.paths import SpecPath from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, ) @@ -31,7 +31,10 @@ class TestSchemaValidate(object): 'boolean', 'array', 'integer', 'number', 'string', ]) def test_null(self, schema_type, validator_factory): - schema = Schema(schema_type) + spec = { + 'type': schema_type, + } + schema = SpecPath.from_spec(spec) value = None with pytest.raises(InvalidSchemaValue): @@ -41,7 +44,11 @@ class TestSchemaValidate(object): 'boolean', 'array', 'integer', 'number', 'string', ]) def test_nullable(self, schema_type, validator_factory): - schema = Schema(schema_type, nullable=True) + spec = { + 'type': schema_type, + 'nullable': True, + } + schema = SpecPath.from_spec(spec) value = None result = validator_factory(schema).validate(value) @@ -52,7 +59,11 @@ class TestSchemaValidate(object): reason="validation does not care about custom formats atm") def test_string_format_custom_missing(self, validator_factory): custom_format = 'custom' - schema = Schema('string', schema_format=custom_format) + spec = { + 'type': 'string', + 'format': custom_format, + } + schema = SpecPath.from_spec(spec) value = 'x' with pytest.raises(OpenAPISchemaError): @@ -60,7 +71,10 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [False, True]) def test_boolean(self, value, validator_factory): - schema = Schema('boolean') + spec = { + 'type': 'boolean', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -68,21 +82,33 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [1, 3.14, u('true'), [True, False]]) def test_boolean_invalid(self, value, validator_factory): - schema = Schema('boolean') + spec = { + 'type': 'boolean', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [(1, 2)]) def test_array_no_schema(self, value, validator_factory): - schema = Schema('array') + spec = { + 'type': 'array', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [[1, 2]]) def test_array(self, value, validator_factory): - schema = Schema('array', items=Schema('integer')) + spec = { + 'type': 'array', + 'items': { + 'type': 'integer', + }, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -90,14 +116,20 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [False, 1, 3.14, u('true'), (3, 4)]) def test_array_invalid(self, value, validator_factory): - schema = Schema('array') + spec = { + 'type': 'array', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [1, 3]) def test_integer(self, value, validator_factory): - schema = Schema('integer') + spec = { + 'type': 'integer', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -105,21 +137,32 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [False, 3.14, u('true'), [1, 2]]) def test_integer_invalid(self, value, validator_factory): - schema = Schema('integer') + spec = { + 'type': 'integer', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [0, 1, 2]) def test_integer_minimum_invalid(self, value, validator_factory): - schema = Schema('integer', minimum=3) + spec = { + 'type': 'integer', + 'minimum': 3, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [4, 5, 6]) def test_integer_minimum(self, value, validator_factory): - schema = Schema('integer', minimum=3) + spec = { + 'type': 'integer', + 'minimum': 3, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -127,14 +170,22 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [4, 5, 6]) def test_integer_maximum_invalid(self, value, validator_factory): - schema = Schema('integer', maximum=3) + spec = { + 'type': 'integer', + 'maximum': 3, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [0, 1, 2]) def test_integer_maximum(self, value, validator_factory): - schema = Schema('integer', maximum=3) + spec = { + 'type': 'integer', + 'maximum': 3, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -142,14 +193,22 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [1, 2, 4]) def test_integer_multiple_of_invalid(self, value, validator_factory): - schema = Schema('integer', multiple_of=3) + spec = { + 'type': 'integer', + 'multipleOf': 3, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [3, 6, 18]) def test_integer_multiple_of(self, value, validator_factory): - schema = Schema('integer', multiple_of=3) + spec = { + 'type': 'integer', + 'multipleOf': 3, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -157,7 +216,10 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [1, 3.14]) def test_number(self, value, validator_factory): - schema = Schema('number') + spec = { + 'type': 'number', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -165,21 +227,32 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [False, 'true', [1, 3]]) def test_number_invalid(self, value, validator_factory): - schema = Schema('number') + spec = { + 'type': 'number', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [0, 1, 2]) def test_number_minimum_invalid(self, value, validator_factory): - schema = Schema('number', minimum=3) + spec = { + 'type': 'number', + 'minimum': 3, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [3, 4, 5]) def test_number_minimum(self, value, validator_factory): - schema = Schema('number', minimum=3) + spec = { + 'type': 'number', + 'minimum': 3, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -187,14 +260,24 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [1, 2, 3]) def test_number_exclusive_minimum_invalid(self, value, validator_factory): - schema = Schema('number', minimum=3, exclusive_minimum=3) + spec = { + 'type': 'number', + 'minimum': 3, + 'exclusiveMinimum': True, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [4, 5, 6]) def test_number_exclusive_minimum(self, value, validator_factory): - schema = Schema('number', minimum=3) + spec = { + 'type': 'number', + 'minimum': 3, + 'exclusiveMinimum': True, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -202,14 +285,22 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [4, 5, 6]) def test_number_maximum_invalid(self, value, validator_factory): - schema = Schema('number', maximum=3) + spec = { + 'type': 'number', + 'maximum': 3, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [1, 2, 3]) def test_number_maximum(self, value, validator_factory): - schema = Schema('number', maximum=3) + spec = { + 'type': 'number', + 'maximum': 3, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -217,14 +308,24 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [3, 4, 5]) def test_number_exclusive_maximum_invalid(self, value, validator_factory): - schema = Schema('number', maximum=3, exclusive_maximum=True) + spec = { + 'type': 'number', + 'maximum': 3, + 'exclusiveMaximum': True, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [0, 1, 2]) def test_number_exclusive_maximum(self, value, validator_factory): - schema = Schema('number', maximum=3, exclusive_maximum=True) + spec = { + 'type': 'number', + 'maximum': 3, + 'exclusiveMaximum': True, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -232,14 +333,22 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [1, 2, 4]) def test_number_multiple_of_invalid(self, value, validator_factory): - schema = Schema('number', multiple_of=3) + spec = { + 'type': 'number', + 'multipleOf': 3, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [3, 6, 18]) def test_number_multiple_of(self, value, validator_factory): - schema = Schema('number', multiple_of=3) + spec = { + 'type': 'number', + 'multipleOf': 3, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -247,7 +356,10 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [u('true'), b('test')]) def test_string(self, value, validator_factory): - schema = Schema('string') + spec = { + 'type': 'string', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -255,7 +367,10 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [False, 1, 3.14, [1, 3]]) def test_string_invalid(self, value, validator_factory): - schema = Schema('string') + spec = { + 'type': 'string', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -265,7 +380,11 @@ class TestSchemaValidate(object): datetime.datetime(1989, 1, 2), ]) def test_string_format_date_invalid(self, value, validator_factory): - schema = Schema('string', schema_format='date') + spec = { + 'type': 'string', + 'format': 'date', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -274,7 +393,11 @@ class TestSchemaValidate(object): u('1989-01-02'), u('2018-01-02'), ]) def test_string_format_date(self, value, validator_factory): - schema = Schema('string', schema_format='date') + spec = { + 'type': 'string', + 'format': 'date', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -284,7 +407,11 @@ class TestSchemaValidate(object): u('12345678-1234-5678-1234-567812345678'), ]) def test_string_format_uuid(self, value, validator_factory): - schema = Schema('string', schema_format='uuid') + spec = { + 'type': 'string', + 'format': 'uuid', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -295,7 +422,11 @@ class TestSchemaValidate(object): datetime.date(2018, 1, 2), datetime.datetime(2018, 1, 2, 23, 59, 59), ]) def test_string_format_uuid_invalid(self, value, validator_factory): - schema = Schema('string', schema_format='uuid') + spec = { + 'type': 'string', + 'format': 'uuid', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -305,7 +436,11 @@ class TestSchemaValidate(object): u('1989-01-02'), ]) def test_string_format_datetime_invalid(self, value, validator_factory): - schema = Schema('string', schema_format='date-time') + spec = { + 'type': 'string', + 'format': 'date-time', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -324,7 +459,11 @@ class TestSchemaValidate(object): ) def test_string_format_datetime_strict_rfc3339( self, value, validator_factory): - schema = Schema('string', schema_format='date-time') + spec = { + 'type': 'string', + 'format': 'date-time', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -343,7 +482,11 @@ class TestSchemaValidate(object): 'DATETIME_HAS_ISODATE', True ) def test_string_format_datetime_isodate(self, value, validator_factory): - schema = Schema('string', schema_format='date-time') + spec = { + 'type': 'string', + 'format': 'date-time', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -354,7 +497,11 @@ class TestSchemaValidate(object): u('1989-01-02T00:00:00Z'), ]) def test_string_format_binary_invalid(self, value, validator_factory): - schema = Schema('string', schema_format='binary') + spec = { + 'type': 'string', + 'format': 'binary', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -363,7 +510,11 @@ class TestSchemaValidate(object): b('stream'), b('text'), ]) def test_string_format_binary(self, value, validator_factory): - schema = Schema('string', schema_format='binary') + spec = { + 'type': 'string', + 'format': 'binary', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -373,7 +524,11 @@ class TestSchemaValidate(object): b('dGVzdA=='), u('dGVzdA=='), ]) def test_string_format_byte(self, value, validator_factory): - schema = Schema('string', schema_format='byte') + spec = { + 'type': 'string', + 'format': 'byte', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -383,7 +538,11 @@ class TestSchemaValidate(object): u('tsssst'), b('tsssst'), b('tesddddsdsdst'), ]) def test_string_format_byte_invalid(self, value, validator_factory): - schema = Schema('string', schema_format='byte') + spec = { + 'type': 'string', + 'format': 'byte', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -394,21 +553,33 @@ class TestSchemaValidate(object): ]) def test_string_format_unknown(self, value, validator_factory): unknown_format = 'unknown' - schema = Schema('string', schema_format=unknown_format) + spec = { + 'type': 'string', + 'format': unknown_format, + } + schema = SpecPath.from_spec(spec) with pytest.raises(FormatterNotFoundError): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [u(""), u("a"), u("ab")]) def test_string_min_length_invalid(self, value, validator_factory): - schema = Schema('string', min_length=3) + spec = { + 'type': 'string', + 'minLength': 3, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [u("abc"), u("abcd")]) def test_string_min_length(self, value, validator_factory): - schema = Schema('string', min_length=3) + spec = { + 'type': 'string', + 'minLength': 3, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -416,21 +587,33 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [u(""), ]) def test_string_max_length_invalid_schema(self, value, validator_factory): - schema = Schema('string', max_length=-1) + spec = { + 'type': 'string', + 'maxLength': -1, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [u("ab"), u("abc")]) def test_string_max_length_invalid(self, value, validator_factory): - schema = Schema('string', max_length=1) + spec = { + 'type': 'string', + 'maxLength': 1, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [u(""), u("a")]) def test_string_max_length(self, value, validator_factory): - schema = Schema('string', max_length=1) + spec = { + 'type': 'string', + 'maxLength': 1, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -438,14 +621,22 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [u("foo"), u("bar")]) def test_string_pattern_invalid(self, value, validator_factory): - schema = Schema('string', pattern='baz') + spec = { + 'type': 'string', + 'pattern': 'baz', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [u("bar"), u("foobar")]) def test_string_pattern(self, value, validator_factory): - schema = Schema('string', pattern='bar') + spec = { + 'type': 'string', + 'pattern': 'bar', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -453,7 +644,10 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', ['true', False, 1, 3.14, [1, 3]]) def test_object_not_an_object(self, value, validator_factory): - schema = Schema('object') + spec = { + 'type': 'object', + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -461,9 +655,18 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [Model(), ]) def test_object_multiple_one_of(self, value, validator_factory): one_of = [ - Schema('object'), Schema('object'), + { + 'type': 'object', + }, + { + 'type': 'object', + }, ] - schema = Schema('object', one_of=one_of) + spec = { + 'type': 'object', + 'oneOf': one_of, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -471,9 +674,18 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [{}, ]) def test_object_different_type_one_of(self, value, validator_factory): one_of = [ - Schema('integer'), Schema('string'), + { + 'type': 'integer', + }, + { + 'type': 'string', + }, ] - schema = Schema('object', one_of=one_of) + spec = { + 'type': 'object', + 'oneOf': one_of, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -481,18 +693,30 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [{}, ]) def test_object_no_one_of(self, value, validator_factory): one_of = [ - Schema( - 'object', - properties={'test1': Schema('string')}, - required=['test1', ], - ), - Schema( - 'object', - properties={'test2': Schema('string')}, - required=['test2', ], - ), + { + 'type': 'object', + 'required': ['test1', ], + 'properties': { + 'test1': { + 'type': 'string', + }, + }, + }, + { + 'type': 'object', + 'required': ['test2', ], + 'properties': { + 'test2': { + 'type': 'string', + }, + }, + }, ] - schema = Schema('object', one_of=one_of) + spec = { + 'type': 'object', + 'oneOf': one_of, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -508,25 +732,35 @@ class TestSchemaValidate(object): ]) def test_unambiguous_one_of(self, value, validator_factory): one_of = [ - Schema( - 'object', - properties={ - 'foo': Schema('string'), + { + 'type': 'object', + 'required': ['foo', ], + 'properties': { + 'foo': { + 'type': 'string', + }, }, - additional_properties=False, - required=['foo'], - ), - Schema( - 'object', - properties={ - 'foo': Schema('string'), - 'bar': Schema('string'), + 'additionalProperties': False, + }, + { + 'type': 'object', + 'required': ['foo', 'bar'], + 'properties': { + 'foo': { + 'type': 'string', + }, + 'bar': { + 'type': 'string', + }, }, - additional_properties=False, - required=['foo', 'bar'], - ), + 'additionalProperties': False, + }, ] - schema = Schema('object', one_of=one_of) + spec = { + 'type': 'object', + 'oneOf': one_of, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -534,7 +768,11 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [{}, ]) def test_object_default_property(self, value, validator_factory): - schema = Schema('object', default='value1') + spec = { + 'type': 'object', + 'default': 'value1', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -543,7 +781,11 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [{}, ]) def test_object_min_properties_invalid_schema( self, value, validator_factory): - schema = Schema('object', min_properties=2) + spec = { + 'type': 'object', + 'minProperties': 2, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -554,12 +796,15 @@ class TestSchemaValidate(object): {'a': 1, 'b': 2, 'c': 3}, ]) def test_object_min_properties_invalid(self, value, validator_factory): - schema = Schema( - 'object', - properties={k: Schema('number') - for k in ['a', 'b', 'c']}, - min_properties=4, - ) + spec = { + 'type': 'object', + 'properties': { + k: {'type': 'number'} + for k in ['a', 'b', 'c'] + }, + 'minProperties': 4, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -570,13 +815,15 @@ class TestSchemaValidate(object): {'a': 1, 'b': 2, 'c': 3}, ]) def test_object_min_properties(self, value, validator_factory): - schema = Schema( - 'object', - properties={k: Schema('number') - for k in ['a', 'b', 'c']}, - min_properties=1, - ) - + spec = { + 'type': 'object', + 'properties': { + k: {'type': 'number'} + for k in ['a', 'b', 'c'] + }, + 'minProperties': 1, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) assert result is None @@ -584,7 +831,11 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [{}, ]) def test_object_max_properties_invalid_schema( self, value, validator_factory): - schema = Schema('object', max_properties=-1) + spec = { + 'type': 'object', + 'maxProperties': -1, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -595,12 +846,15 @@ class TestSchemaValidate(object): {'a': 1, 'b': 2, 'c': 3}, ]) def test_object_max_properties_invalid(self, value, validator_factory): - schema = Schema( - 'object', - properties={k: Schema('number') - for k in ['a', 'b', 'c']}, - max_properties=0, - ) + spec = { + 'type': 'object', + 'properties': { + k: {'type': 'number'} + for k in ['a', 'b', 'c'] + }, + 'maxProperties': 0, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -611,12 +865,15 @@ class TestSchemaValidate(object): {'a': 1, 'b': 2, 'c': 3}, ]) def test_object_max_properties(self, value, validator_factory): - schema = Schema( - 'object', - properties={k: Schema('number') - for k in ['a', 'b', 'c']}, - max_properties=3, - ) + spec = { + 'type': 'object', + 'properties': { + k: {'type': 'number'} + for k in ['a', 'b', 'c'] + }, + 'maxProperties': 3, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -624,7 +881,10 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [{'additional': 1}, ]) def test_object_additional_properties(self, value, validator_factory): - schema = Schema('object') + spec = { + 'type': 'object', + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -633,7 +893,11 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [{'additional': 1}, ]) def test_object_additional_properties_false( self, value, validator_factory): - schema = Schema('object', additional_properties=False) + spec = { + 'type': 'object', + 'additionalProperties': False, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @@ -641,8 +905,14 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [{'additional': 1}, ]) def test_object_additional_properties_object( self, value, validator_factory): - additional_properties = Schema('integer') - schema = Schema('object', additional_properties=additional_properties) + additional_properties = { + 'type': 'integer', + } + spec = { + 'type': 'object', + 'additionalProperties': additional_properties, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -650,22 +920,28 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [[], [1], [1, 2]]) def test_list_min_items_invalid(self, value, validator_factory): - schema = Schema( - 'array', - items=Schema('number'), - min_items=3, - ) + spec = { + 'type': 'array', + 'items': { + 'type': 'number', + }, + 'minItems': 3, + } + schema = SpecPath.from_spec(spec) with pytest.raises(Exception): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [[], [1], [1, 2]]) def test_list_min_items(self, value, validator_factory): - schema = Schema( - 'array', - items=Schema('number'), - min_items=0, - ) + spec = { + 'type': 'array', + 'items': { + 'type': 'number', + }, + 'minItems': 0, + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -673,33 +949,42 @@ class TestSchemaValidate(object): @pytest.mark.parametrize('value', [[], ]) def test_list_max_items_invalid_schema(self, value, validator_factory): - schema = Schema( - 'array', - items=Schema('number'), - max_items=-1, - ) + spec = { + 'type': 'array', + 'items': { + 'type': 'number', + }, + 'maxItems': -1, + } + schema = SpecPath.from_spec(spec) with pytest.raises(InvalidSchemaValue): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [[1, 2], [2, 3, 4]]) def test_list_max_items_invalid(self, value, validator_factory): - schema = Schema( - 'array', - items=Schema('number'), - max_items=1, - ) + spec = { + 'type': 'array', + 'items': { + 'type': 'number', + }, + 'maxItems': 1, + } + schema = SpecPath.from_spec(spec) with pytest.raises(Exception): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [[1, 2, 1], [2, 2]]) def test_list_unique_items_invalid(self, value, validator_factory): - schema = Schema( - 'array', - items=Schema('number'), - unique_items=True, - ) + spec = { + 'type': 'array', + 'items': { + 'type': 'number', + }, + 'uniqueItems': True, + } + schema = SpecPath.from_spec(spec) with pytest.raises(Exception): validator_factory(schema).validate(value) @@ -717,13 +1002,18 @@ class TestSchemaValidate(object): }, ]) def test_object_with_properties(self, value, validator_factory): - schema = Schema( - 'object', - properties={ - 'somestr': Schema('string'), - 'someint': Schema('integer'), - }, - ) + spec = { + 'type': 'object', + 'properties': { + 'somestr': { + 'type': 'string', + }, + 'someint': { + 'type': 'integer', + } + } + } + schema = SpecPath.from_spec(spec) result = validator_factory(schema).validate(value) @@ -751,14 +1041,19 @@ class TestSchemaValidate(object): }, ]) def test_object_with_invalid_properties(self, value, validator_factory): - schema = Schema( - 'object', - properties={ - 'somestr': Schema('string'), - 'someint': Schema('integer'), + spec = { + 'type': 'object', + 'properties': { + 'somestr': { + 'type': 'string', + }, + 'someint': { + 'type': 'integer', + } }, - additional_properties=False, - ) + 'additionalProperties': False, + } + schema = SpecPath.from_spec(spec) with pytest.raises(Exception): validator_factory(schema).validate(value)