From 35f8e28157ca5d2f1ddd9f5684c4e6c0ddf75d6c Mon Sep 17 00:00:00 2001 From: p1c2u Date: Fri, 23 Apr 2021 12:36:27 +0100 Subject: [PATCH 1/8] 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) From 74e33bac23ebc07964f3460f423927827b699a91 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Tue, 27 Apr 2021 22:16:30 +0100 Subject: [PATCH 2/8] Get rid of schema package --- openapi_core/casting/schemas/casters.py | 2 +- .../deserializing/parameters/deserializers.py | 1 - openapi_core/exceptions.py | 68 +++++++ openapi_core/schema/__init__.py | 0 openapi_core/schema/components/__init__.py | 0 openapi_core/schema/components/factories.py | 60 ------ openapi_core/schema/components/models.py | 15 -- openapi_core/schema/contacts/__init__.py | 0 openapi_core/schema/contacts/factories.py | 25 --- openapi_core/schema/contacts/models.py | 11 -- openapi_core/schema/content/__init__.py | 0 openapi_core/schema/content/factories.py | 21 --- openapi_core/schema/content/models.py | 5 - openapi_core/schema/exceptions.py | 6 - openapi_core/schema/extensions/__init__.py | 0 openapi_core/schema/extensions/generators.py | 16 -- openapi_core/schema/extensions/models.py | 9 - openapi_core/schema/external_docs/__init__.py | 0 .../schema/external_docs/factories.py | 26 --- openapi_core/schema/external_docs/models.py | 11 -- openapi_core/schema/infos/__init__.py | 0 openapi_core/schema/infos/factories.py | 52 ------ openapi_core/schema/infos/models.py | 17 -- openapi_core/schema/licenses/__init__.py | 0 openapi_core/schema/licenses/factories.py | 24 --- openapi_core/schema/licenses/models.py | 10 - openapi_core/schema/links/__init__.py | 0 openapi_core/schema/links/generators.py | 44 ----- openapi_core/schema/links/models.py | 26 --- openapi_core/schema/media_types/__init__.py | 0 openapi_core/schema/media_types/exceptions.py | 16 -- openapi_core/schema/media_types/generators.py | 40 ---- openapi_core/schema/media_types/models.py | 12 -- openapi_core/schema/models.py | 98 ---------- openapi_core/schema/operations/__init__.py | 0 openapi_core/schema/operations/exceptions.py | 17 -- openapi_core/schema/operations/generators.py | 114 ------------ openapi_core/schema/operations/models.py | 30 --- openapi_core/schema/parameters/__init__.py | 0 openapi_core/schema/parameters/enums.py | 25 --- openapi_core/schema/parameters/exceptions.py | 29 --- openapi_core/schema/parameters/factories.py | 54 ------ openapi_core/schema/parameters/generators.py | 30 --- openapi_core/schema/parameters/models.py | 53 ------ openapi_core/schema/paths/__init__.py | 0 openapi_core/schema/paths/exceptions.py | 15 -- openapi_core/schema/paths/generators.py | 62 ------- openapi_core/schema/paths/models.py | 22 --- openapi_core/schema/properties/__init__.py | 0 openapi_core/schema/properties/generators.py | 18 -- .../schema/request_bodies/__init__.py | 0 .../schema/request_bodies/exceptions.py | 15 -- .../schema/request_bodies/factories.py | 36 ---- openapi_core/schema/request_bodies/models.py | 11 -- openapi_core/schema/responses/__init__.py | 0 openapi_core/schema/responses/exceptions.py | 15 -- openapi_core/schema/responses/generators.py | 61 ------ openapi_core/schema/responses/models.py | 15 -- openapi_core/schema/schemas/__init__.py | 0 openapi_core/schema/schemas/enums.py | 28 --- openapi_core/schema/schemas/exceptions.py | 5 - openapi_core/schema/schemas/factories.py | 175 ------------------ openapi_core/schema/schemas/generators.py | 20 -- openapi_core/schema/schemas/models.py | 95 ---------- openapi_core/schema/schemas/registries.py | 32 ---- openapi_core/schema/schemas/types.py | 14 -- openapi_core/schema/schemas/util.py | 15 -- .../schema/security_requirements/__init__.py | 0 .../security_requirements/generators.py | 15 -- .../schema/security_requirements/models.py | 6 - .../schema/security_schemes/__init__.py | 0 openapi_core/schema/security_schemes/enums.py | 27 --- .../schema/security_schemes/generators.py | 37 ---- .../schema/security_schemes/models.py | 22 --- openapi_core/schema/servers/__init__.py | 0 openapi_core/schema/servers/exceptions.py | 16 -- openapi_core/schema/servers/generators.py | 67 ------- openapi_core/schema/servers/models.py | 45 ----- openapi_core/schema/servers/utils.py | 2 - openapi_core/schema/shortcuts.py | 24 --- openapi_core/schema/specs/__init__.py | 0 openapi_core/schema/specs/factories.py | 93 ---------- openapi_core/schema/specs/models.py | 70 ------- openapi_core/security/factories.py | 1 - openapi_core/shortcuts.py | 21 ++- openapi_core/spec/servers.py | 4 + openapi_core/templating/paths/finders.py | 2 +- openapi_core/types.py | 1 + .../unmarshalling/schemas/factories.py | 12 +- .../unmarshalling/schemas/unmarshallers.py | 38 ++-- openapi_core/validation/request/shortcuts.py | 8 +- openapi_core/validation/request/validators.py | 5 +- .../validation/response/validators.py | 2 +- tests/integration/validation/test_petstore.py | 4 +- .../integration/validation/test_validators.py | 6 +- tests/unit/schema/test_links.py | 44 ----- tests/unit/schema/test_operations.py | 19 -- tests/unit/schema/test_parameters.py | 33 ---- tests/unit/schema/test_paths.py | 21 --- tests/unit/schema/test_request_bodies.py | 21 --- tests/unit/schema/test_schemas.py | 20 -- tests/unit/schema/test_schemas_registry.py | 49 ----- tests/unit/schema/test_specs.py | 50 ----- tests/unit/unmarshalling/test_unmarshal.py | 2 +- tests/unit/unmarshalling/test_validate.py | 2 +- 105 files changed, 129 insertions(+), 2281 deletions(-) delete mode 100644 openapi_core/schema/__init__.py delete mode 100644 openapi_core/schema/components/__init__.py delete mode 100644 openapi_core/schema/components/factories.py delete mode 100644 openapi_core/schema/components/models.py delete mode 100644 openapi_core/schema/contacts/__init__.py delete mode 100644 openapi_core/schema/contacts/factories.py delete mode 100644 openapi_core/schema/contacts/models.py delete mode 100644 openapi_core/schema/content/__init__.py delete mode 100644 openapi_core/schema/content/factories.py delete mode 100644 openapi_core/schema/content/models.py delete mode 100644 openapi_core/schema/exceptions.py delete mode 100644 openapi_core/schema/extensions/__init__.py delete mode 100644 openapi_core/schema/extensions/generators.py delete mode 100644 openapi_core/schema/extensions/models.py delete mode 100644 openapi_core/schema/external_docs/__init__.py delete mode 100644 openapi_core/schema/external_docs/factories.py delete mode 100644 openapi_core/schema/external_docs/models.py delete mode 100644 openapi_core/schema/infos/__init__.py delete mode 100644 openapi_core/schema/infos/factories.py delete mode 100644 openapi_core/schema/infos/models.py delete mode 100644 openapi_core/schema/licenses/__init__.py delete mode 100644 openapi_core/schema/licenses/factories.py delete mode 100644 openapi_core/schema/licenses/models.py delete mode 100644 openapi_core/schema/links/__init__.py delete mode 100644 openapi_core/schema/links/generators.py delete mode 100644 openapi_core/schema/links/models.py delete mode 100644 openapi_core/schema/media_types/__init__.py delete mode 100644 openapi_core/schema/media_types/exceptions.py delete mode 100644 openapi_core/schema/media_types/generators.py delete mode 100644 openapi_core/schema/media_types/models.py delete mode 100644 openapi_core/schema/models.py delete mode 100644 openapi_core/schema/operations/__init__.py delete mode 100644 openapi_core/schema/operations/exceptions.py delete mode 100644 openapi_core/schema/operations/generators.py delete mode 100644 openapi_core/schema/operations/models.py delete mode 100644 openapi_core/schema/parameters/__init__.py delete mode 100644 openapi_core/schema/parameters/enums.py delete mode 100644 openapi_core/schema/parameters/exceptions.py delete mode 100644 openapi_core/schema/parameters/factories.py delete mode 100644 openapi_core/schema/parameters/generators.py delete mode 100644 openapi_core/schema/parameters/models.py delete mode 100644 openapi_core/schema/paths/__init__.py delete mode 100644 openapi_core/schema/paths/exceptions.py delete mode 100644 openapi_core/schema/paths/generators.py delete mode 100644 openapi_core/schema/paths/models.py delete mode 100644 openapi_core/schema/properties/__init__.py delete mode 100644 openapi_core/schema/properties/generators.py delete mode 100644 openapi_core/schema/request_bodies/__init__.py delete mode 100644 openapi_core/schema/request_bodies/exceptions.py delete mode 100644 openapi_core/schema/request_bodies/factories.py delete mode 100644 openapi_core/schema/request_bodies/models.py delete mode 100644 openapi_core/schema/responses/__init__.py delete mode 100644 openapi_core/schema/responses/exceptions.py delete mode 100644 openapi_core/schema/responses/generators.py delete mode 100644 openapi_core/schema/responses/models.py delete mode 100644 openapi_core/schema/schemas/__init__.py delete mode 100644 openapi_core/schema/schemas/enums.py delete mode 100644 openapi_core/schema/schemas/exceptions.py delete mode 100644 openapi_core/schema/schemas/factories.py delete mode 100644 openapi_core/schema/schemas/generators.py delete mode 100644 openapi_core/schema/schemas/models.py delete mode 100644 openapi_core/schema/schemas/registries.py delete mode 100644 openapi_core/schema/schemas/types.py delete mode 100644 openapi_core/schema/schemas/util.py delete mode 100644 openapi_core/schema/security_requirements/__init__.py delete mode 100644 openapi_core/schema/security_requirements/generators.py delete mode 100644 openapi_core/schema/security_requirements/models.py delete mode 100644 openapi_core/schema/security_schemes/__init__.py delete mode 100644 openapi_core/schema/security_schemes/enums.py delete mode 100644 openapi_core/schema/security_schemes/generators.py delete mode 100644 openapi_core/schema/security_schemes/models.py delete mode 100644 openapi_core/schema/servers/__init__.py delete mode 100644 openapi_core/schema/servers/exceptions.py delete mode 100644 openapi_core/schema/servers/generators.py delete mode 100644 openapi_core/schema/servers/models.py delete mode 100644 openapi_core/schema/servers/utils.py delete mode 100644 openapi_core/schema/shortcuts.py delete mode 100644 openapi_core/schema/specs/__init__.py delete mode 100644 openapi_core/schema/specs/factories.py delete mode 100644 openapi_core/schema/specs/models.py create mode 100644 openapi_core/types.py delete mode 100644 tests/unit/schema/test_links.py delete mode 100644 tests/unit/schema/test_operations.py delete mode 100644 tests/unit/schema/test_parameters.py delete mode 100644 tests/unit/schema/test_paths.py delete mode 100644 tests/unit/schema/test_request_bodies.py delete mode 100644 tests/unit/schema/test_schemas.py delete mode 100644 tests/unit/schema/test_schemas_registry.py delete mode 100644 tests/unit/schema/test_specs.py diff --git a/openapi_core/casting/schemas/casters.py b/openapi_core/casting/schemas/casters.py index 5f710ed..25e35ea 100644 --- a/openapi_core/casting/schemas/casters.py +++ b/openapi_core/casting/schemas/casters.py @@ -1,5 +1,5 @@ from openapi_core.casting.schemas.exceptions import CastError -from openapi_core.schema.schemas.types import NoValue +from openapi_core.types import NoValue class PrimitiveCaster(object): diff --git a/openapi_core/deserializing/parameters/deserializers.py b/openapi_core/deserializing/parameters/deserializers.py index e7e0d1f..b422467 100644 --- a/openapi_core/deserializing/parameters/deserializers.py +++ b/openapi_core/deserializing/parameters/deserializers.py @@ -2,7 +2,6 @@ from openapi_core.deserializing.exceptions import DeserializeError 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 diff --git a/openapi_core/exceptions.py b/openapi_core/exceptions.py index 504173c..6ce0572 100644 --- a/openapi_core/exceptions.py +++ b/openapi_core/exceptions.py @@ -1,5 +1,73 @@ """OpenAPI core exceptions module""" +import attr class OpenAPIError(Exception): pass + + +class OpenAPIMediaTypeError(OpenAPIError): + pass + + +@attr.s(hash=True) +class InvalidContentType(OpenAPIMediaTypeError): + mimetype = attr.ib() + + def __str__(self): + return "Content for following mimetype not found: {0}".format( + self.mimetype) + + +class OpenAPIParameterError(OpenAPIError): + pass + + +class MissingParameterError(OpenAPIParameterError): + """Missing parameter error""" + pass + + +@attr.s(hash=True) +class MissingParameter(MissingParameterError): + name = attr.ib() + + def __str__(self): + return "Missing parameter (without default value): {0}".format( + self.name) + + +@attr.s(hash=True) +class MissingRequiredParameter(MissingParameterError): + name = attr.ib() + + def __str__(self): + return "Missing required parameter: {0}".format(self.name) + + +class OpenAPIRequestBodyError(OpenAPIError): + pass + + +@attr.s(hash=True) +class MissingRequestBody(OpenAPIRequestBodyError): + request = attr.ib() + + def __str__(self): + return "Missing required request body" + + +class OpenAPIResponseError(OpenAPIError): + pass + + +@attr.s(hash=True) +class MissingResponseContent(OpenAPIResponseError): + response = attr.ib() + + def __str__(self): + return "Missing response content" + + +class OpenAPISchemaError(OpenAPIError): + pass diff --git a/openapi_core/schema/__init__.py b/openapi_core/schema/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/components/__init__.py b/openapi_core/schema/components/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/components/factories.py b/openapi_core/schema/components/factories.py deleted file mode 100644 index 6afab0a..0000000 --- a/openapi_core/schema/components/factories.py +++ /dev/null @@ -1,60 +0,0 @@ -from openapi_core.compat import lru_cache -from openapi_core.schema.components.models import Components -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.schemas.generators import SchemasGenerator -from openapi_core.schema.security_schemes.generators import ( - SecuritySchemesGenerator, -) - - -class ComponentsFactory(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def create(self, components_spec): - components_deref = self.dereferencer.dereference(components_spec) - - schemas_spec = components_deref.get('schemas', {}) - responses_spec = components_deref.get('responses', {}) - parameters_spec = components_deref.get('parameters', {}) - request_bodies_spec = components_deref.get('requestBodies', {}) - security_schemes_spec = components_deref.get('securitySchemes', {}) - - extensions = self.extensions_generator.generate(components_deref) - - schemas = self.schemas_generator.generate(schemas_spec) - responses = self._generate_response(responses_spec) - parameters = self._generate_parameters(parameters_spec) - request_bodies = self._generate_request_bodies(request_bodies_spec) - security_schemes = self._generate_security_schemes( - security_schemes_spec) - return Components( - schemas=list(schemas), responses=responses, parameters=parameters, - request_bodies=request_bodies, security_schemes=security_schemes, - extensions=extensions, - ) - - @property - @lru_cache() - def schemas_generator(self): - return SchemasGenerator(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) - - def _generate_response(self, responses_spec): - return responses_spec - - def _generate_parameters(self, parameters_spec): - return parameters_spec - - def _generate_request_bodies(self, request_bodies_spec): - return request_bodies_spec - - def _generate_security_schemes(self, security_schemes_spec): - return SecuritySchemesGenerator(self.dereferencer).generate( - security_schemes_spec) diff --git a/openapi_core/schema/components/models.py b/openapi_core/schema/components/models.py deleted file mode 100644 index 11a605b..0000000 --- a/openapi_core/schema/components/models.py +++ /dev/null @@ -1,15 +0,0 @@ -class Components(object): - """Represents an OpenAPI Components in a service.""" - - def __init__( - self, schemas=None, responses=None, parameters=None, - request_bodies=None, security_schemes=None, extensions=None): - self.schemas = schemas and dict(schemas) or {} - self.responses = responses and dict(responses) or {} - self.parameters = parameters and dict(parameters) or {} - self.request_bodies = request_bodies and dict(request_bodies) or {} - self.security_schemes = ( - security_schemes and dict(security_schemes) or {} - ) - - self.extensions = extensions and dict(extensions) or {} diff --git a/openapi_core/schema/contacts/__init__.py b/openapi_core/schema/contacts/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/contacts/factories.py b/openapi_core/schema/contacts/factories.py deleted file mode 100644 index d1724d9..0000000 --- a/openapi_core/schema/contacts/factories.py +++ /dev/null @@ -1,25 +0,0 @@ -"""OpenAPI core contacts factories module""" -from openapi_core.compat import lru_cache -from openapi_core.schema.contacts.models import Contact -from openapi_core.schema.extensions.generators import ExtensionsGenerator - - -class ContactFactory(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def create(self, contact_spec): - contact_deref = self.dereferencer.dereference(contact_spec) - name = contact_deref.get('name') - url = contact_deref.get('url') - email = contact_deref.get('email') - - extensions = self.extensions_generator.generate(contact_deref) - - return Contact(name=name, url=url, email=email, extensions=extensions) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/contacts/models.py b/openapi_core/schema/contacts/models.py deleted file mode 100644 index 9e5bb2a..0000000 --- a/openapi_core/schema/contacts/models.py +++ /dev/null @@ -1,11 +0,0 @@ -"""OpenAPI core contacts models module""" - - -class Contact(object): - - def __init__(self, name=None, url=None, email=None, extensions=None): - self.name = name - self.url = url - self.email = email - - self.extensions = extensions and dict(extensions) or {} diff --git a/openapi_core/schema/content/__init__.py b/openapi_core/schema/content/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/content/factories.py b/openapi_core/schema/content/factories.py deleted file mode 100644 index 8c7b4f0..0000000 --- a/openapi_core/schema/content/factories.py +++ /dev/null @@ -1,21 +0,0 @@ -"""OpenAPI core content factories module""" -from openapi_core.compat import lru_cache -from openapi_core.schema.content.models import Content -from openapi_core.schema.media_types.generators import MediaTypeGenerator - - -class ContentFactory(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def create(self, content_spec): - media_types = self.media_types_generator.generate(content_spec) - - return Content(media_types) - - @property - @lru_cache() - def media_types_generator(self): - return MediaTypeGenerator(self.dereferencer, self.schemas_registry) diff --git a/openapi_core/schema/content/models.py b/openapi_core/schema/content/models.py deleted file mode 100644 index 14c066f..0000000 --- a/openapi_core/schema/content/models.py +++ /dev/null @@ -1,5 +0,0 @@ -"""OpenAPI core content models module""" - - -class Content(dict): - pass diff --git a/openapi_core/schema/exceptions.py b/openapi_core/schema/exceptions.py deleted file mode 100644 index 3c1e93d..0000000 --- a/openapi_core/schema/exceptions.py +++ /dev/null @@ -1,6 +0,0 @@ -"""OpenAPI core schema exceptions module""" -from openapi_core.exceptions import OpenAPIError - - -class OpenAPIMappingError(OpenAPIError): - pass diff --git a/openapi_core/schema/extensions/__init__.py b/openapi_core/schema/extensions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/extensions/generators.py b/openapi_core/schema/extensions/generators.py deleted file mode 100644 index 1cfc459..0000000 --- a/openapi_core/schema/extensions/generators.py +++ /dev/null @@ -1,16 +0,0 @@ -"""OpenAPI core extensions generators module""" -from six import iteritems - -from openapi_core.schema.extensions.models import Extension - - -class ExtensionsGenerator(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def generate(self, item_spec): - for field_name, value in iteritems(item_spec): - if not field_name.startswith('x-'): - continue - yield field_name, Extension(field_name, value) diff --git a/openapi_core/schema/extensions/models.py b/openapi_core/schema/extensions/models.py deleted file mode 100644 index 5575cfc..0000000 --- a/openapi_core/schema/extensions/models.py +++ /dev/null @@ -1,9 +0,0 @@ -"""OpenAPI core extensions models module""" - - -class Extension(object): - """Represents an OpenAPI Extension.""" - - def __init__(self, field_name, value=None): - self.field_name = field_name - self.value = value diff --git a/openapi_core/schema/external_docs/__init__.py b/openapi_core/schema/external_docs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/external_docs/factories.py b/openapi_core/schema/external_docs/factories.py deleted file mode 100644 index a1ef10b..0000000 --- a/openapi_core/schema/external_docs/factories.py +++ /dev/null @@ -1,26 +0,0 @@ -"""OpenAPI core external docs factories module""" -from openapi_core.compat import lru_cache -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.external_docs.models import ExternalDocumentation - - -class ExternalDocumentationFactory(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def create(self, external_doc_spec): - url = external_doc_spec['url'] - description = external_doc_spec.get('description') - - extensions = self.extensions_generator.generate(external_doc_spec) - - return ExternalDocumentation( - url, - description=description, extensions=extensions, - ) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/external_docs/models.py b/openapi_core/schema/external_docs/models.py deleted file mode 100644 index b0d0a0e..0000000 --- a/openapi_core/schema/external_docs/models.py +++ /dev/null @@ -1,11 +0,0 @@ -"""OpenAPI core external docs models module""" - - -class ExternalDocumentation(object): - """Represents an OpenAPI External Documentation.""" - - def __init__(self, url, description=None, extensions=None): - self.url = url - self.description = description - - self.extensions = extensions and dict(extensions) or {} diff --git a/openapi_core/schema/infos/__init__.py b/openapi_core/schema/infos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/infos/factories.py b/openapi_core/schema/infos/factories.py deleted file mode 100644 index 20b8b60..0000000 --- a/openapi_core/schema/infos/factories.py +++ /dev/null @@ -1,52 +0,0 @@ -"""OpenAPI core infos factories module""" -from openapi_core.compat import lru_cache -from openapi_core.schema.contacts.factories import ContactFactory -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.infos.models import Info -from openapi_core.schema.licenses.factories import LicenseFactory - - -class InfoFactory(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def create(self, info_spec): - info_deref = self.dereferencer.dereference(info_spec) - title = info_deref['title'] - version = info_deref['version'] - description = info_deref.get('description') - terms_of_service = info_deref.get('termsOfService') - - extensions = self.extensions_generator.generate(info_deref) - - contact = None - if 'contact' in info_deref: - contact_spec = info_deref.get('contact') - contact = self.contact_factory.create(contact_spec) - - license = None - if 'license' in info_deref: - license_spec = info_deref.get('license') - license = self.license_factory.create(license_spec) - - return Info( - title, version, - description=description, terms_of_service=terms_of_service, - contact=contact, license=license, extensions=extensions, - ) - - @property - @lru_cache() - def contact_factory(self): - return ContactFactory(self.dereferencer) - - @property - @lru_cache() - def license_factory(self): - return LicenseFactory(self.dereferencer) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/infos/models.py b/openapi_core/schema/infos/models.py deleted file mode 100644 index 9ccd66a..0000000 --- a/openapi_core/schema/infos/models.py +++ /dev/null @@ -1,17 +0,0 @@ -"""OpenAPI core infos models module""" - - -class Info(object): - - def __init__( - self, title, version, description=None, terms_of_service=None, - contact=None, license=None, extensions=None, - ): - self.title = title - self.version = version - self.description = description - self.terms_of_service = terms_of_service - self.contact = contact - self.license = license - - self.extensions = extensions and dict(extensions) or {} diff --git a/openapi_core/schema/licenses/__init__.py b/openapi_core/schema/licenses/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/licenses/factories.py b/openapi_core/schema/licenses/factories.py deleted file mode 100644 index 294babe..0000000 --- a/openapi_core/schema/licenses/factories.py +++ /dev/null @@ -1,24 +0,0 @@ -"""OpenAPI core licenses factories module""" -from openapi_core.compat import lru_cache -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.licenses.models import License - - -class LicenseFactory(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def create(self, license_spec): - license_deref = self.dereferencer.dereference(license_spec) - name = license_deref['name'] - url = license_deref.get('url') - - extensions = self.extensions_generator.generate(license_deref) - - return License(name, url=url, extensions=extensions) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/licenses/models.py b/openapi_core/schema/licenses/models.py deleted file mode 100644 index c6dd506..0000000 --- a/openapi_core/schema/licenses/models.py +++ /dev/null @@ -1,10 +0,0 @@ -"""OpenAPI core licenses models module""" - - -class License(object): - - def __init__(self, name, url=None, extensions=None): - self.name = name - self.url = url - - self.extensions = extensions and dict(extensions) or {} diff --git a/openapi_core/schema/links/__init__.py b/openapi_core/schema/links/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/links/generators.py b/openapi_core/schema/links/generators.py deleted file mode 100644 index 34add75..0000000 --- a/openapi_core/schema/links/generators.py +++ /dev/null @@ -1,44 +0,0 @@ -"""OpenAPI core links generators module""" -from six import iteritems - -from openapi_core.compat import lru_cache -from openapi_core.schema.links.models import Link -from openapi_core.schema.parameters.generators import ParametersGenerator -from openapi_core.schema.servers.generators import ServersGenerator - - -class LinksGenerator(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def generate(self, links): - for link_name, link in iteritems(links): - link_deref = self.dereferencer.dereference(link) - operation_id = link_deref.get('operationId') - parameters = link_deref.get('parameters', {}) - request_body = link_deref.get('requestBody') # string or dict - description = link_deref.get('description') - server_spec = link_deref.get('server') - server = self.servers_generator.generate(server_spec) \ - if server_spec is not None \ - else None - - yield link_name, Link( - operation_id, - parameters, - request_body, - description, - server - ) - - @property - @lru_cache() - def parameters_generator(self): - return ParametersGenerator(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def servers_generator(self): - return ServersGenerator(self.dereferencer) diff --git a/openapi_core/schema/links/models.py b/openapi_core/schema/links/models.py deleted file mode 100644 index f62dbac..0000000 --- a/openapi_core/schema/links/models.py +++ /dev/null @@ -1,26 +0,0 @@ -"""OpenAPI core links models module""" - - -class Link(object): - """Represents an OpenAPI Link.""" - - def __init__( - self, - operation_id, - parameters, - request_body, - description, - server - ): - """ - request_body is assumed to be either a string (JSON, YAML or - runtime expression) or an object (deserialized JSON or YAML) - """ - self.operationId = operation_id - self.description = description - self.server = server - self.parameters = dict(parameters) if parameters else {} - self.request_body = request_body - - def __getitem__(self, item): - return self.parameters[item] diff --git a/openapi_core/schema/media_types/__init__.py b/openapi_core/schema/media_types/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/media_types/exceptions.py b/openapi_core/schema/media_types/exceptions.py deleted file mode 100644 index a69c383..0000000 --- a/openapi_core/schema/media_types/exceptions.py +++ /dev/null @@ -1,16 +0,0 @@ -import attr - -from openapi_core.schema.exceptions import OpenAPIMappingError - - -class OpenAPIMediaTypeError(OpenAPIMappingError): - pass - - -@attr.s(hash=True) -class InvalidContentType(OpenAPIMediaTypeError): - mimetype = attr.ib() - - def __str__(self): - return "Content for following mimetype not found: {0}".format( - self.mimetype) diff --git a/openapi_core/schema/media_types/generators.py b/openapi_core/schema/media_types/generators.py deleted file mode 100644 index dcaff53..0000000 --- a/openapi_core/schema/media_types/generators.py +++ /dev/null @@ -1,40 +0,0 @@ -"""OpenAPI core media types generators module""" -from six import iteritems - -from openapi_core.compat import lru_cache -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.media_types.models import MediaType - - -class MediaTypeGenerator(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def generate(self, content): - for mimetype, media_type in iteritems(content): - schema_spec = media_type.get('schema') - - example_spec = media_type.get('example') - example_type = type(example_spec) - if example_type is dict: - example = self.dereferencer.dereference(example_spec) - else: - example = example_spec - - extensions = self.extensions_generator.generate(media_type) - - schema = None - if schema_spec: - schema, _ = self.schemas_registry.get_or_create(schema_spec) - - yield mimetype, MediaType( - mimetype, - schema=schema, example=example, extensions=extensions, - ) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/media_types/models.py b/openapi_core/schema/media_types/models.py deleted file mode 100644 index 76079f7..0000000 --- a/openapi_core/schema/media_types/models.py +++ /dev/null @@ -1,12 +0,0 @@ -"""OpenAPI core media types models module""" - - -class MediaType(object): - """Represents an OpenAPI MediaType.""" - - def __init__(self, mimetype, schema=None, example=None, extensions=None): - self.mimetype = mimetype - self.schema = schema - self.example = example - - self.extensions = extensions and dict(extensions) or {} diff --git a/openapi_core/schema/models.py b/openapi_core/schema/models.py deleted file mode 100644 index 15c6db0..0000000 --- a/openapi_core/schema/models.py +++ /dev/null @@ -1,98 +0,0 @@ -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/operations/__init__.py b/openapi_core/schema/operations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/operations/exceptions.py b/openapi_core/schema/operations/exceptions.py deleted file mode 100644 index e78708a..0000000 --- a/openapi_core/schema/operations/exceptions.py +++ /dev/null @@ -1,17 +0,0 @@ -import attr - -from openapi_core.schema.exceptions import OpenAPIMappingError - - -class OpenAPIOperationError(OpenAPIMappingError): - pass - - -@attr.s(hash=True) -class InvalidOperation(OpenAPIOperationError): - path_pattern = attr.ib() - http_method = attr.ib() - - def __str__(self): - return "Unknown operation path {0} with method {1}".format( - self.path_pattern, self.http_method) diff --git a/openapi_core/schema/operations/generators.py b/openapi_core/schema/operations/generators.py deleted file mode 100644 index d30166c..0000000 --- a/openapi_core/schema/operations/generators.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -"""OpenAPI core operations models module""" -from six import iteritems -from openapi_spec_validator.validators import PathItemValidator - -from openapi_core.compat import lru_cache -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.external_docs.factories import ( - ExternalDocumentationFactory, -) -from openapi_core.schema.operations.models import Operation -from openapi_core.schema.parameters.generators import ParametersGenerator -from openapi_core.schema.request_bodies.factories import RequestBodyFactory -from openapi_core.schema.responses.generators import ResponsesGenerator -from openapi_core.schema.security_requirements.generators import ( - SecurityRequirementsGenerator, -) -from openapi_core.schema.servers.generators import ServersGenerator - - -class OperationsGenerator(object): - """Represents an OpenAPI Operation in a service.""" - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def generate(self, path_name, path): - path_deref = self.dereferencer.dereference(path) - for http_method, operation in iteritems(path_deref): - if http_method not in PathItemValidator.OPERATIONS: - continue - - operation_deref = self.dereferencer.dereference(operation) - responses_spec = operation_deref['responses'] - responses = self.responses_generator.generate(responses_spec) - deprecated = operation_deref.get('deprecated', False) - parameters_list = operation_deref.get('parameters', []) - parameters = self.parameters_generator.generate_from_list( - parameters_list) - operation_id = operation_deref.get('operationId') - tags_list = operation_deref.get('tags', []) - summary = operation_deref.get('summary') - description = operation_deref.get('description') - servers_spec = operation_deref.get('servers', []) - - servers = self.servers_generator.generate(servers_spec) - - security = None - if 'security' in operation_deref: - security_spec = operation_deref.get('security') - security = self.security_requirements_generator.generate( - security_spec) - - extensions = self.extensions_generator.generate(operation_deref) - - external_docs = None - if 'externalDocs' in operation_deref: - external_docs_spec = operation_deref.get('externalDocs') - external_docs = self.external_docs_factory.create( - external_docs_spec) - - request_body = None - if 'requestBody' in operation_deref: - request_body_spec = operation_deref.get('requestBody') - request_body = self.request_body_factory.create( - request_body_spec) - - yield ( - http_method, - Operation( - http_method, path_name, responses, list(parameters), - summary=summary, description=description, - external_docs=external_docs, security=security, - request_body=request_body, deprecated=deprecated, - operation_id=operation_id, tags=list(tags_list), - servers=list(servers), extensions=extensions, - ), - ) - - @property - @lru_cache() - def responses_generator(self): - return ResponsesGenerator(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def parameters_generator(self): - return ParametersGenerator(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def external_docs_factory(self): - return ExternalDocumentationFactory(self.dereferencer) - - @property - @lru_cache() - def request_body_factory(self): - return RequestBodyFactory(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def security_requirements_generator(self): - return SecurityRequirementsGenerator(self.dereferencer) - - @property - @lru_cache() - def servers_generator(self): - return ServersGenerator(self.dereferencer) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/operations/models.py b/openapi_core/schema/operations/models.py deleted file mode 100644 index cb6c2fc..0000000 --- a/openapi_core/schema/operations/models.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -"""OpenAPI core operations models module""" - - -class Operation(object): - """Represents an OpenAPI Operation.""" - - def __init__( - self, http_method, path_name, responses, parameters, - summary=None, description=None, external_docs=None, security=None, - request_body=None, deprecated=False, operation_id=None, tags=None, - servers=None, extensions=None): - self.http_method = http_method - self.path_name = path_name - self.responses = dict(responses) - self.parameters = dict(parameters) - self.summary = summary - self.description = description - self.external_docs = external_docs - self.security = security and list(security) - self.request_body = request_body - self.deprecated = deprecated - self.operation_id = operation_id - self.tags = tags - self.servers = servers - - self.extensions = extensions and dict(extensions) or {} - - def __getitem__(self, name): - return self.parameters[name] diff --git a/openapi_core/schema/parameters/__init__.py b/openapi_core/schema/parameters/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/parameters/enums.py b/openapi_core/schema/parameters/enums.py deleted file mode 100644 index 51fa238..0000000 --- a/openapi_core/schema/parameters/enums.py +++ /dev/null @@ -1,25 +0,0 @@ -"""OpenAPI core parameters enums module""" -from enum import Enum - - -class ParameterLocation(Enum): - - PATH = 'path' - QUERY = 'query' - HEADER = 'header' - COOKIE = 'cookie' - - @classmethod - def has_value(cls, value): - return (any(value == item.value for item in cls)) - - -class ParameterStyle(Enum): - - MATRIX = 'matrix' - LABEL = 'label' - FORM = 'form' - SIMPLE = 'simple' - SPACE_DELIMITED = 'spaceDelimited' - PIPE_DELIMITED = 'pipeDelimited' - DEEP_OBJECT = 'deepObject' diff --git a/openapi_core/schema/parameters/exceptions.py b/openapi_core/schema/parameters/exceptions.py deleted file mode 100644 index e9d7c26..0000000 --- a/openapi_core/schema/parameters/exceptions.py +++ /dev/null @@ -1,29 +0,0 @@ -import attr - -from openapi_core.schema.exceptions import OpenAPIMappingError - - -class OpenAPIParameterError(OpenAPIMappingError): - pass - - -class MissingParameterError(OpenAPIParameterError): - """Missing parameter error""" - pass - - -@attr.s(hash=True) -class MissingParameter(MissingParameterError): - name = attr.ib() - - def __str__(self): - return "Missing parameter (without default value): {0}".format( - self.name) - - -@attr.s(hash=True) -class MissingRequiredParameter(MissingParameterError): - name = attr.ib() - - def __str__(self): - return "Missing required parameter: {0}".format(self.name) diff --git a/openapi_core/schema/parameters/factories.py b/openapi_core/schema/parameters/factories.py deleted file mode 100644 index 22a8d19..0000000 --- a/openapi_core/schema/parameters/factories.py +++ /dev/null @@ -1,54 +0,0 @@ -"""OpenAPI core parameters factories module""" -from openapi_core.compat import lru_cache -from openapi_core.schema.content.factories import ContentFactory -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.parameters.models import Parameter - - -class ParameterFactory(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def create(self, parameter_spec, parameter_name=None): - parameter_deref = self.dereferencer.dereference(parameter_spec) - - parameter_name = parameter_name or parameter_deref['name'] - parameter_in = parameter_deref.get('in', 'header') - - allow_empty_value = parameter_deref.get('allowEmptyValue') - required = parameter_deref.get('required', False) - - style = parameter_deref.get('style') - explode = parameter_deref.get('explode') - - schema_spec = parameter_deref.get('schema', None) - schema = None - if schema_spec: - schema, _ = self.schemas_registry.get_or_create(schema_spec) - - content_spec = parameter_deref.get('content', None) - content = None - if content_spec: - content = self.content_factory.create(content_spec) - - extensions = self.extensions_generator.generate(parameter_deref) - - return Parameter( - parameter_name, parameter_in, - schema=schema, required=required, - allow_empty_value=allow_empty_value, - style=style, explode=explode, content=content, - extensions=extensions, - ) - - @property - @lru_cache() - def content_factory(self): - return ContentFactory(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/parameters/generators.py b/openapi_core/schema/parameters/generators.py deleted file mode 100644 index 0f5d78e..0000000 --- a/openapi_core/schema/parameters/generators.py +++ /dev/null @@ -1,30 +0,0 @@ -"""OpenAPI core parameters generators module""" -from six import iteritems - -from openapi_core.compat import lru_cache -from openapi_core.schema.parameters.factories import ParameterFactory - - -class ParametersGenerator(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def generate(self, parameters): - for parameter_name, parameter_spec in iteritems(parameters): - parameter = self.parameter_factory.create( - parameter_spec, parameter_name=parameter_name) - - yield (parameter_name, parameter) - - def generate_from_list(self, parameters_list): - for parameter_spec in parameters_list: - parameter = self.parameter_factory.create(parameter_spec) - - yield (parameter.name, parameter) - - @property - @lru_cache() - def parameter_factory(self): - return ParameterFactory(self.dereferencer, self.schemas_registry) diff --git a/openapi_core/schema/parameters/models.py b/openapi_core/schema/parameters/models.py deleted file mode 100644 index 87102b7..0000000 --- a/openapi_core/schema/parameters/models.py +++ /dev/null @@ -1,53 +0,0 @@ -"""OpenAPI core parameters models module""" -import logging - -from openapi_core.schema.parameters.enums import ( - ParameterLocation, ParameterStyle, -) -from openapi_core.schema.schemas.enums import SchemaType - -log = logging.getLogger(__name__) - - -class Parameter(object): - """Represents an OpenAPI operation Parameter.""" - - def __init__( - self, name, location, schema=None, required=False, - deprecated=False, allow_empty_value=False, - items=None, style=None, explode=None, content=None, - extensions=None): - self.name = name - self.location = ParameterLocation(location) - self.schema = schema - self.required = ( - True if self.location == ParameterLocation.PATH else required - ) - self.deprecated = deprecated - self.allow_empty_value = ( - allow_empty_value if self.location == ParameterLocation.QUERY - else False - ) - self.items = items - self.style = ParameterStyle(style or self.default_style) - self.explode = self.default_explode if explode is None else explode - self.content = content - self.extensions = extensions and dict(extensions) or {} - - @property - def aslist(self): - return ( - self.schema and - self.schema.type in [SchemaType.ARRAY, SchemaType.OBJECT] - ) - - @property - def default_style(self): - simple_locations = [ParameterLocation.PATH, ParameterLocation.HEADER] - return ( - 'simple' if self.location in simple_locations else "form" - ) - - @property - def default_explode(self): - return self.style == ParameterStyle.FORM diff --git a/openapi_core/schema/paths/__init__.py b/openapi_core/schema/paths/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/paths/exceptions.py b/openapi_core/schema/paths/exceptions.py deleted file mode 100644 index 6a28773..0000000 --- a/openapi_core/schema/paths/exceptions.py +++ /dev/null @@ -1,15 +0,0 @@ -import attr - -from openapi_core.schema.exceptions import OpenAPIMappingError - - -class OpenAPIPathError(OpenAPIMappingError): - pass - - -@attr.s(hash=True) -class InvalidPath(OpenAPIPathError): - path_pattern = attr.ib() - - def __str__(self): - return "Unknown path {0}".format(self.path_pattern) diff --git a/openapi_core/schema/paths/generators.py b/openapi_core/schema/paths/generators.py deleted file mode 100644 index 82809f2..0000000 --- a/openapi_core/schema/paths/generators.py +++ /dev/null @@ -1,62 +0,0 @@ -"""OpenAPI core paths generators module""" -from six import iteritems - -from openapi_core.compat import lru_cache -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.operations.generators import OperationsGenerator -from openapi_core.schema.parameters.generators import ParametersGenerator -from openapi_core.schema.paths.models import Path -from openapi_core.schema.servers.generators import ServersGenerator - - -class PathsGenerator(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def generate(self, paths): - paths_deref = self.dereferencer.dereference(paths) - for path_name, path_spec in iteritems(paths_deref): - path_deref = self.dereferencer.dereference(path_spec) - - parameters_list = path_deref.get('parameters', []) - summary = path_deref.get('summary') - description = path_deref.get('description') - servers_spec = path_deref.get('servers', []) - - operations = self.operations_generator.generate( - path_name, path_deref) - servers = self.servers_generator.generate(servers_spec) - parameters = self.parameters_generator.generate_from_list( - parameters_list) - extensions = self.extensions_generator.generate(path_deref) - - yield ( - path_name, - Path( - path_name, list(operations), parameters=list(parameters), - summary=summary, description=description, - servers=list(servers), extensions=extensions, - ), - ) - - @property - @lru_cache() - def operations_generator(self): - return OperationsGenerator(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def servers_generator(self): - return ServersGenerator(self.dereferencer) - - @property - @lru_cache() - def parameters_generator(self): - return ParametersGenerator(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/paths/models.py b/openapi_core/schema/paths/models.py deleted file mode 100644 index 74a57a9..0000000 --- a/openapi_core/schema/paths/models.py +++ /dev/null @@ -1,22 +0,0 @@ -"""OpenAPI core paths models module""" - - -class Path(object): - """Represents an OpenAPI Path.""" - - def __init__( - self, name, operations, - summary=None, description=None, parameters=None, servers=None, - extensions=None, - ): - self.name = name - self.operations = dict(operations) - self.summary = summary - self.description = description - self.servers = servers - self.parameters = dict(parameters) if parameters else {} - - self.extensions = extensions and dict(extensions) or {} - - def __getitem__(self, http_method): - return self.operations[http_method] diff --git a/openapi_core/schema/properties/__init__.py b/openapi_core/schema/properties/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/properties/generators.py b/openapi_core/schema/properties/generators.py deleted file mode 100644 index b0485b0..0000000 --- a/openapi_core/schema/properties/generators.py +++ /dev/null @@ -1,18 +0,0 @@ -"""OpenAPI core properties generators module""" -from six import iteritems - - -class PropertiesGenerator(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def generate(self, properties): - for property_name, schema_spec in iteritems(properties): - schema = self._create_schema(schema_spec) - yield property_name, schema - - def _create_schema(self, schema_spec): - schema, _ = self.schemas_registry.get_or_create(schema_spec) - return schema diff --git a/openapi_core/schema/request_bodies/__init__.py b/openapi_core/schema/request_bodies/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/request_bodies/exceptions.py b/openapi_core/schema/request_bodies/exceptions.py deleted file mode 100644 index a84937b..0000000 --- a/openapi_core/schema/request_bodies/exceptions.py +++ /dev/null @@ -1,15 +0,0 @@ -import attr - -from openapi_core.schema.exceptions import OpenAPIMappingError - - -class OpenAPIRequestBodyError(OpenAPIMappingError): - pass - - -@attr.s(hash=True) -class MissingRequestBody(OpenAPIRequestBodyError): - request = attr.ib() - - def __str__(self): - return "Missing required request body" diff --git a/openapi_core/schema/request_bodies/factories.py b/openapi_core/schema/request_bodies/factories.py deleted file mode 100644 index 620b92e..0000000 --- a/openapi_core/schema/request_bodies/factories.py +++ /dev/null @@ -1,36 +0,0 @@ -"""OpenAPI core request bodies factories module""" -from openapi_core.compat import lru_cache -from openapi_core.schema.content.factories import ContentFactory -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.request_bodies.models import RequestBody - - -class RequestBodyFactory(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def create(self, request_body_spec): - request_body_deref = self.dereferencer.dereference( - request_body_spec) - content_spec = request_body_deref['content'] - content = self.content_factory.create(content_spec) - required = request_body_deref.get('required', False) - - extensions = self.extensions_generator.generate(request_body_deref) - - return RequestBody( - content, - required=required, extensions=extensions, - ) - - @property - @lru_cache() - def content_factory(self): - return ContentFactory(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/request_bodies/models.py b/openapi_core/schema/request_bodies/models.py deleted file mode 100644 index cb7b23d..0000000 --- a/openapi_core/schema/request_bodies/models.py +++ /dev/null @@ -1,11 +0,0 @@ -"""OpenAPI core request bodies models module""" - - -class RequestBody(object): - """Represents an OpenAPI RequestBody.""" - - def __init__(self, content, required=False, extensions=None): - self.content = content - self.required = required - - self.extensions = extensions and dict(extensions) or {} diff --git a/openapi_core/schema/responses/__init__.py b/openapi_core/schema/responses/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/responses/exceptions.py b/openapi_core/schema/responses/exceptions.py deleted file mode 100644 index d7eb76b..0000000 --- a/openapi_core/schema/responses/exceptions.py +++ /dev/null @@ -1,15 +0,0 @@ -import attr - -from openapi_core.schema.exceptions import OpenAPIMappingError - - -class OpenAPIResponseError(OpenAPIMappingError): - pass - - -@attr.s(hash=True) -class MissingResponseContent(OpenAPIResponseError): - response = attr.ib() - - def __str__(self): - return "Missing response content" diff --git a/openapi_core/schema/responses/generators.py b/openapi_core/schema/responses/generators.py deleted file mode 100644 index 019235f..0000000 --- a/openapi_core/schema/responses/generators.py +++ /dev/null @@ -1,61 +0,0 @@ -"""OpenAPI core responses generators module""" -from six import iteritems - -from openapi_core.compat import lru_cache -from openapi_core.schema.content.factories import ContentFactory -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.links.generators import LinksGenerator -from openapi_core.schema.parameters.generators import ParametersGenerator -from openapi_core.schema.responses.models import Response - - -class ResponsesGenerator(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def generate(self, responses): - for http_status, response in iteritems(responses): - response_deref = self.dereferencer.dereference(response) - description = response_deref['description'] - headers = response_deref.get('headers') - content_spec = response_deref.get('content') - links_dict = response_deref.get('links', {}) - links = self.links_generator.generate(links_dict) - - extensions = self.extensions_generator.generate(response_deref) - - content = None - if content_spec: - content = self.content_factory.create(content_spec) - - parameters = None - if headers: - parameters = self.parameters_generator.generate(headers) - - yield http_status, Response( - http_status, description, - content=content, headers=parameters, links=links, - extensions=extensions, - ) - - @property - @lru_cache() - def content_factory(self): - return ContentFactory(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def parameters_generator(self): - return ParametersGenerator(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def links_generator(self): - return LinksGenerator(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/responses/models.py b/openapi_core/schema/responses/models.py deleted file mode 100644 index e2e237d..0000000 --- a/openapi_core/schema/responses/models.py +++ /dev/null @@ -1,15 +0,0 @@ -"""OpenAPI core responses models module""" - - -class Response(object): - - def __init__( - self, http_status, description, headers=None, content=None, - links=None, extensions=None): - self.http_status = http_status - self.description = description - self.headers = headers and dict(headers) or {} - self.content = content - self.links = links and dict(links) or {} - - self.extensions = extensions and dict(extensions) or {} diff --git a/openapi_core/schema/schemas/__init__.py b/openapi_core/schema/schemas/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/schemas/enums.py b/openapi_core/schema/schemas/enums.py deleted file mode 100644 index 8b77e7c..0000000 --- a/openapi_core/schema/schemas/enums.py +++ /dev/null @@ -1,28 +0,0 @@ -"""OpenAPI core schemas enums module""" -from enum import Enum - - -class SchemaType(Enum): - - ANY = None - INTEGER = 'integer' - NUMBER = 'number' - STRING = 'string' - BOOLEAN = 'boolean' - ARRAY = 'array' - OBJECT = 'object' - - -class SchemaFormat(Enum): - - NONE = None - INT32 = 'int32' - INT64 = 'int64' - FLOAT = 'float' - DOUBLE = 'double' - BYTE = 'byte' - BINARY = 'binary' - DATE = 'date' - DATETIME = 'date-time' - PASSWORD = 'password' - UUID = 'uuid' diff --git a/openapi_core/schema/schemas/exceptions.py b/openapi_core/schema/schemas/exceptions.py deleted file mode 100644 index 1487bf7..0000000 --- a/openapi_core/schema/schemas/exceptions.py +++ /dev/null @@ -1,5 +0,0 @@ -from openapi_core.schema.exceptions import OpenAPIMappingError - - -class OpenAPISchemaError(OpenAPIMappingError): - pass diff --git a/openapi_core/schema/schemas/factories.py b/openapi_core/schema/schemas/factories.py deleted file mode 100644 index 55b48fe..0000000 --- a/openapi_core/schema/schemas/factories.py +++ /dev/null @@ -1,175 +0,0 @@ -"""OpenAPI core schemas factories module""" -import logging - -from six import iteritems - -from openapi_core.compat import lru_cache -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.properties.generators import PropertiesGenerator -from openapi_core.schema.schemas.models import Schema -from openapi_core.schema.schemas.types import Contribution, NoValue - -log = logging.getLogger(__name__) - - -class SchemaFactory(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def create(self, schema_spec): - schema_deref = self.dereferencer.dereference(schema_spec) - - schema_type = schema_deref.get('type', None) - schema_format = schema_deref.get('format') - required = schema_deref.get('required', False) - default = schema_deref.get('default', NoValue) - properties_spec = schema_deref.get('properties', None) - items_spec = schema_deref.get('items', None) - nullable = schema_deref.get('nullable', False) - enum = schema_deref.get('enum', None) - deprecated = schema_deref.get('deprecated', False) - all_of_spec = schema_deref.get('allOf', None) - one_of_spec = schema_deref.get('oneOf', None) - additional_properties_spec = schema_deref.get('additionalProperties', - True) - min_items = schema_deref.get('minItems', None) - max_items = schema_deref.get('maxItems', None) - min_length = schema_deref.get('minLength', None) - max_length = schema_deref.get('maxLength', None) - pattern = schema_deref.get('pattern', None) - unique_items = schema_deref.get('uniqueItems', None) - minimum = schema_deref.get('minimum', None) - maximum = schema_deref.get('maximum', None) - multiple_of = schema_deref.get('multipleOf', None) - exclusive_minimum = schema_deref.get('exclusiveMinimum', False) - exclusive_maximum = schema_deref.get('exclusiveMaximum', False) - min_properties = schema_deref.get('minProperties', None) - max_properties = schema_deref.get('maxProperties', None) - read_only = schema_deref.get('readOnly', False) - write_only = schema_deref.get('writeOnly', False) - - extensions = self.extensions_generator.generate(schema_deref) - - properties = None - if properties_spec: - properties = self.properties_generator.generate(properties_spec) - - all_of = [] - if all_of_spec: - all_of = list(map(self.create, all_of_spec)) - - one_of = [] - if one_of_spec: - one_of = list(map(self.create, one_of_spec)) - - items = None - if items_spec: - items = self._create_items(items_spec) - - additional_properties = additional_properties_spec - if isinstance(additional_properties_spec, dict): - additional_properties = self.create(additional_properties_spec) - - return Schema( - schema_type=schema_type, properties=properties, - items=items, schema_format=schema_format, required=required, - default=default, nullable=nullable, enum=enum, - deprecated=deprecated, all_of=all_of, one_of=one_of, - additional_properties=additional_properties, - min_items=min_items, max_items=max_items, min_length=min_length, - max_length=max_length, pattern=pattern, unique_items=unique_items, - minimum=minimum, maximum=maximum, multiple_of=multiple_of, - exclusive_maximum=exclusive_maximum, - exclusive_minimum=exclusive_minimum, - min_properties=min_properties, max_properties=max_properties, - read_only=read_only, write_only=write_only, extensions=extensions, - _source=schema_deref, - ) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) - - @property - @lru_cache() - def properties_generator(self): - return PropertiesGenerator(self.dereferencer, self) - - def _create_items(self, items_spec): - return self.create(items_spec) - - -class SchemaDictFactory(object): - - contributions = ( - Contribution('type', src_prop_attr='value'), - Contribution('format'), - Contribution('properties', is_dict=True, dest_default={}), - Contribution('required', dest_default=[]), - Contribution('default'), - Contribution('nullable', dest_default=False), - Contribution( - 'all_of', - dest_prop_name='allOf', is_list=True, dest_default=[], - ), - Contribution( - 'one_of', - dest_prop_name='oneOf', is_list=True, dest_default=[], - ), - Contribution( - 'additional_properties', - dest_prop_name='additionalProperties', dest_default=True, - ), - Contribution('min_items', dest_prop_name='minItems'), - Contribution('max_items', dest_prop_name='maxItems'), - Contribution('min_length', dest_prop_name='minLength'), - Contribution('max_length', dest_prop_name='maxLength'), - Contribution('pattern', src_prop_attr='pattern'), - Contribution( - 'unique_items', - dest_prop_name='uniqueItems', dest_default=False, - ), - Contribution('minimum'), - Contribution('maximum'), - Contribution('multiple_of', dest_prop_name='multipleOf'), - Contribution( - 'exclusive_minimum', - dest_prop_name='exclusiveMinimum', dest_default=False, - ), - Contribution( - 'exclusive_maximum', - dest_prop_name='exclusiveMaximum', dest_default=False, - ), - Contribution('min_properties', dest_prop_name='minProperties'), - Contribution('max_properties', dest_prop_name='maxProperties'), - ) - - def create(self, schema): - schema_dict = {} - for contrib in self.contributions: - self._contribute(schema, schema_dict, contrib) - return schema_dict - - def _contribute(self, schema, schema_dict, contrib): - def src_map(x): - return getattr(x, '__dict__') - src_val = getattr(schema, contrib.src_prop_name) - - if src_val and contrib.src_prop_attr: - src_val = getattr(src_val, contrib.src_prop_attr) - - if contrib.is_list: - src_val = list(map(src_map, src_val)) - if contrib.is_dict: - src_val = dict( - (k, src_map(v)) - for k, v in iteritems(src_val) - ) - - if src_val == contrib.dest_default: - return - - dest_prop_name = contrib.dest_prop_name or contrib.src_prop_name - schema_dict[dest_prop_name] = src_val diff --git a/openapi_core/schema/schemas/generators.py b/openapi_core/schema/schemas/generators.py deleted file mode 100644 index 59fd548..0000000 --- a/openapi_core/schema/schemas/generators.py +++ /dev/null @@ -1,20 +0,0 @@ -"""OpenAPI core schemas generators module""" -import logging - -from six import iteritems - -log = logging.getLogger(__name__) - - -class SchemasGenerator(object): - - def __init__(self, dereferencer, schemas_registry): - self.dereferencer = dereferencer - self.schemas_registry = schemas_registry - - def generate(self, schemas_spec): - schemas_deref = self.dereferencer.dereference(schemas_spec) - - for schema_name, schema_spec in iteritems(schemas_deref): - schema, _ = self.schemas_registry.get_or_create(schema_spec) - yield schema_name, schema diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py deleted file mode 100644 index a4109c4..0000000 --- a/openapi_core/schema/schemas/models.py +++ /dev/null @@ -1,95 +0,0 @@ -"""OpenAPI core schemas models module""" -import attr -import logging -import re - -from openapi_core.schema.schemas.enums import SchemaType -from openapi_core.schema.schemas.types import NoValue - -log = logging.getLogger(__name__) - - -@attr.s -class Format(object): - unmarshal = attr.ib() - validate = attr.ib() - - -class Schema(object): - """Represents an OpenAPI Schema.""" - - def __init__( - self, schema_type=None, properties=None, items=None, - schema_format=None, required=None, default=NoValue, nullable=False, - enum=None, deprecated=False, all_of=None, one_of=None, - additional_properties=True, min_items=None, max_items=None, - min_length=None, max_length=None, pattern=None, unique_items=False, - minimum=None, maximum=None, multiple_of=None, - exclusive_minimum=False, exclusive_maximum=False, - min_properties=None, max_properties=None, - read_only=False, write_only=False, extensions=None, - _source=None): - self.type = SchemaType(schema_type) - self.properties = properties and dict(properties) or {} - self.items = items - self.format = schema_format - self.required = required or [] - self.default = default - self.nullable = nullable - self.enum = enum - self.deprecated = deprecated - self.all_of = all_of and list(all_of) or [] - self.one_of = one_of and list(one_of) or [] - self.additional_properties = additional_properties - self.min_items = int(min_items) if min_items is not None else None - self.max_items = int(max_items) if max_items is not None else None - self.min_length = int(min_length) if min_length is not None else None - self.max_length = int(max_length) if max_length is not None else None - self.pattern = pattern and re.compile(pattern) or None - self.unique_items = unique_items - self.minimum = int(minimum) if minimum is not None else None - self.maximum = int(maximum) if maximum is not None else None - self.multiple_of = int(multiple_of)\ - if multiple_of is not None else None - self.exclusive_minimum = exclusive_minimum - self.exclusive_maximum = exclusive_maximum - self.min_properties = int(min_properties)\ - if min_properties is not None else None - self.max_properties = int(max_properties)\ - if max_properties is not None else None - self.read_only = read_only - self.write_only = write_only - - self.extensions = extensions and dict(extensions) or {} - - self._all_required_properties_cache = None - self._all_optional_properties_cache = None - - self._source = _source - - @property - def __dict__(self): - return self._source or self.to_dict() - - def to_dict(self): - from openapi_core.schema.schemas.factories import SchemaDictFactory - return SchemaDictFactory().create(self) - - def __getitem__(self, name): - return self.properties[name] - - def has_default(self): - return self.default is not NoValue - - def get_all_properties(self): - properties = self.properties.copy() - - for subschema in self.all_of: - subschema_props = subschema.get_all_properties() - properties.update(subschema_props) - - return properties - - def get_all_properties_names(self): - all_properties = self.get_all_properties() - return set(all_properties.keys()) diff --git a/openapi_core/schema/schemas/registries.py b/openapi_core/schema/schemas/registries.py deleted file mode 100644 index 3a6d963..0000000 --- a/openapi_core/schema/schemas/registries.py +++ /dev/null @@ -1,32 +0,0 @@ -"""OpenAPI core schemas registries module""" -import logging - -from lazy_object_proxy import Proxy - -from openapi_core.schema.schemas.factories import SchemaFactory -from openapi_core.schema.schemas.util import dicthash - -log = logging.getLogger(__name__) - - -class SchemaRegistry(SchemaFactory): - - def __init__(self, dereferencer): - super(SchemaRegistry, self).__init__(dereferencer) - self._schemas = {} - - def get_or_create(self, schema_spec): - schema_hash = dicthash(schema_spec) - schema_deref = self.dereferencer.dereference(schema_spec) - - if schema_hash in self._schemas: - return self._schemas[schema_hash], False - - if '$ref' in schema_spec: - schema = Proxy(lambda: self.create(schema_deref)) - else: - schema = self.create(schema_deref) - - self._schemas[schema_hash] = schema - - return schema, True diff --git a/openapi_core/schema/schemas/types.py b/openapi_core/schema/schemas/types.py deleted file mode 100644 index 56f7445..0000000 --- a/openapi_core/schema/schemas/types.py +++ /dev/null @@ -1,14 +0,0 @@ -import attr - - -NoValue = object() - - -@attr.s(hash=True) -class Contribution(object): - src_prop_name = attr.ib() - src_prop_attr = attr.ib(default=None) - dest_prop_name = attr.ib(default=None) - is_list = attr.ib(default=False) - is_dict = attr.ib(default=False) - dest_default = attr.ib(default=None) diff --git a/openapi_core/schema/schemas/util.py b/openapi_core/schema/schemas/util.py deleted file mode 100644 index dcbf2fd..0000000 --- a/openapi_core/schema/schemas/util.py +++ /dev/null @@ -1,15 +0,0 @@ -"""OpenAPI core schemas util module""" -from distutils.util import strtobool -from six import string_types -from json import dumps - - -def forcebool(val): - if isinstance(val, string_types): - val = strtobool(val) - - return bool(val) - - -def dicthash(d): - return hash(dumps(d, sort_keys=True)) diff --git a/openapi_core/schema/security_requirements/__init__.py b/openapi_core/schema/security_requirements/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/security_requirements/generators.py b/openapi_core/schema/security_requirements/generators.py deleted file mode 100644 index 7ccb8ea..0000000 --- a/openapi_core/schema/security_requirements/generators.py +++ /dev/null @@ -1,15 +0,0 @@ -"""OpenAPI core security requirements generators module""" -from openapi_core.schema.security_requirements.models import ( - SecurityRequirement, -) - - -class SecurityRequirementsGenerator(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def generate(self, security_spec): - security_deref = self.dereferencer.dereference(security_spec) - for security_requirement_spec in security_deref: - yield SecurityRequirement(security_requirement_spec) diff --git a/openapi_core/schema/security_requirements/models.py b/openapi_core/schema/security_requirements/models.py deleted file mode 100644 index b99f070..0000000 --- a/openapi_core/schema/security_requirements/models.py +++ /dev/null @@ -1,6 +0,0 @@ -"""OpenAPI core security requirements models module""" - - -class SecurityRequirement(dict): - """Represents an OpenAPI Security Requirement.""" - pass diff --git a/openapi_core/schema/security_schemes/__init__.py b/openapi_core/schema/security_schemes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/security_schemes/enums.py b/openapi_core/schema/security_schemes/enums.py deleted file mode 100644 index b849c3c..0000000 --- a/openapi_core/schema/security_schemes/enums.py +++ /dev/null @@ -1,27 +0,0 @@ -"""OpenAPI core security schemes enums module""" -from enum import Enum - - -class SecuritySchemeType(Enum): - - API_KEY = 'apiKey' - HTTP = 'http' - OAUTH2 = 'oauth2' - OPEN_ID_CONNECT = 'openIdConnect' - - -class ApiKeyLocation(Enum): - - QUERY = 'query' - HEADER = 'header' - COOKIE = 'cookie' - - @classmethod - def has_value(cls, value): - return (any(value == item.value for item in cls)) - - -class HttpAuthScheme(Enum): - - BASIC = 'basic' - BEARER = 'bearer' diff --git a/openapi_core/schema/security_schemes/generators.py b/openapi_core/schema/security_schemes/generators.py deleted file mode 100644 index 62dc489..0000000 --- a/openapi_core/schema/security_schemes/generators.py +++ /dev/null @@ -1,37 +0,0 @@ -"""OpenAPI core security schemes generators module""" -import logging - -from six import iteritems - -from openapi_core.schema.security_schemes.models import SecurityScheme - -log = logging.getLogger(__name__) - - -class SecuritySchemesGenerator(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def generate(self, security_schemes_spec): - security_schemes_deref = self.dereferencer.dereference( - security_schemes_spec) - - for scheme_name, scheme_spec in iteritems(security_schemes_deref): - scheme_deref = self.dereferencer.dereference(scheme_spec) - scheme_type = scheme_deref['type'] - description = scheme_deref.get('description') - name = scheme_deref.get('name') - apikey_in = scheme_deref.get('in') - scheme = scheme_deref.get('scheme') - bearer_format = scheme_deref.get('bearerFormat') - flows = scheme_deref.get('flows') - open_id_connect_url = scheme_deref.get('openIdConnectUrl') - - scheme = SecurityScheme( - scheme_type, description=description, name=name, - apikey_in=apikey_in, scheme=scheme, - bearer_format=bearer_format, flows=flows, - open_id_connect_url=open_id_connect_url, - ) - yield scheme_name, scheme diff --git a/openapi_core/schema/security_schemes/models.py b/openapi_core/schema/security_schemes/models.py deleted file mode 100644 index d50f0b1..0000000 --- a/openapi_core/schema/security_schemes/models.py +++ /dev/null @@ -1,22 +0,0 @@ -"""OpenAPI core security schemes models module""" -from openapi_core.schema.security_schemes.enums import ( - SecuritySchemeType, ApiKeyLocation, HttpAuthScheme, -) - - -class SecurityScheme(object): - """Represents an OpenAPI Security Scheme.""" - - def __init__( - self, scheme_type, description=None, name=None, apikey_in=None, - scheme=None, bearer_format=None, flows=None, - open_id_connect_url=None, - ): - self.type = SecuritySchemeType(scheme_type) - self.description = description - self.name = name - self.apikey_in = apikey_in and ApiKeyLocation(apikey_in) - self.scheme = scheme and HttpAuthScheme(scheme) - self.bearer_format = bearer_format - self.flows = flows - self.open_id_connect_url = open_id_connect_url diff --git a/openapi_core/schema/servers/__init__.py b/openapi_core/schema/servers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/servers/exceptions.py b/openapi_core/schema/servers/exceptions.py deleted file mode 100644 index b8076e9..0000000 --- a/openapi_core/schema/servers/exceptions.py +++ /dev/null @@ -1,16 +0,0 @@ -import attr - -from openapi_core.schema.exceptions import OpenAPIMappingError - - -class OpenAPIServerError(OpenAPIMappingError): - pass - - -@attr.s(hash=True) -class InvalidServer(OpenAPIServerError): - full_url_pattern = attr.ib() - - def __str__(self): - return "Invalid request server {0}".format( - self.full_url_pattern) diff --git a/openapi_core/schema/servers/generators.py b/openapi_core/schema/servers/generators.py deleted file mode 100644 index ca8dbc7..0000000 --- a/openapi_core/schema/servers/generators.py +++ /dev/null @@ -1,67 +0,0 @@ -"""OpenAPI core servers generators module""" -from six import iteritems - -from openapi_core.compat import lru_cache -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.servers.models import Server, ServerVariable - - -class ServersGenerator(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def generate(self, servers_spec): - servers_deref = self.dereferencer.dereference(servers_spec) - for server_spec in servers_deref: - url = server_spec['url'] - variables_spec = server_spec.get('variables', {}) - description = server_spec.get('description') - - extensions = self.extensions_generator.generate(server_spec) - - variables = None - if variables_spec: - variables = self.variables_generator.generate(variables_spec) - - yield Server( - url, - variables=variables, description=description, - extensions=extensions, - ) - - @property - @lru_cache() - def variables_generator(self): - return ServerVariablesGenerator(self.dereferencer) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) - - -class ServerVariablesGenerator(object): - - def __init__(self, dereferencer): - self.dereferencer = dereferencer - - def generate(self, variables_spec): - variables_deref = self.dereferencer.dereference(variables_spec) - - for variable_name, variable_spec in iteritems(variables_deref): - default = variable_spec['default'] - enum = variable_spec.get('enum') - - extensions = self.extensions_generator.generate(variable_spec) - - variable = ServerVariable( - variable_name, default, - enum=enum, extensions=extensions, - ) - yield variable_name, variable - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/servers/models.py b/openapi_core/schema/servers/models.py deleted file mode 100644 index 28c81fc..0000000 --- a/openapi_core/schema/servers/models.py +++ /dev/null @@ -1,45 +0,0 @@ -"""OpenAPI core servers models module""" -from six import iteritems - -from openapi_core.schema.servers.utils import is_absolute - - -class Server(object): - - def __init__(self, url, variables=None, description=None, extensions=None): - self.url = url - self.variables = variables and dict(variables) or {} - self.description = description - - self.extensions = extensions and dict(extensions) or {} - - @property - def default_url(self): - return self.get_url() - - @property - def default_variables(self): - defaults = {} - for name, variable in iteritems(self.variables): - defaults[name] = variable.default - return defaults - - def get_url(self, **variables): - if not variables: - variables = self.default_variables - return self.url.format(**variables) - - def is_absolute(self, url=None): - if url is None: - url = self.url - return is_absolute(url) - - -class ServerVariable(object): - - def __init__(self, name, default, enum=None, extensions=None): - self.name = name - self.default = default - self.enum = enum and list(enum) or [] - - self.extensions = extensions and dict(extensions) or {} diff --git a/openapi_core/schema/servers/utils.py b/openapi_core/schema/servers/utils.py deleted file mode 100644 index 718b5f2..0000000 --- a/openapi_core/schema/servers/utils.py +++ /dev/null @@ -1,2 +0,0 @@ -def is_absolute(url): - return url.startswith('//') or '://' in url diff --git a/openapi_core/schema/shortcuts.py b/openapi_core/schema/shortcuts.py deleted file mode 100644 index dbeb780..0000000 --- a/openapi_core/schema/shortcuts.py +++ /dev/null @@ -1,24 +0,0 @@ -"""OpenAPI core schema shortcuts module""" -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 - - -def create_spec( - spec_dict, spec_url='', handlers=default_handlers, - validate_spec=True, -): - if validate_spec: - openapi_v3_spec_validator.validate(spec_dict, spec_url=spec_url) - - 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/schema/specs/__init__.py b/openapi_core/schema/specs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/schema/specs/factories.py b/openapi_core/schema/specs/factories.py deleted file mode 100644 index 1871c84..0000000 --- a/openapi_core/schema/specs/factories.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -"""OpenAPI core specs factories module""" - -from openapi_spec_validator.validators import Dereferencer - -from openapi_core.compat import lru_cache -from openapi_core.schema.components.factories import ComponentsFactory -from openapi_core.schema.extensions.generators import ExtensionsGenerator -from openapi_core.schema.infos.factories import InfoFactory -from openapi_core.schema.paths.generators import PathsGenerator -from openapi_core.schema.schemas.registries import SchemaRegistry -from openapi_core.schema.security_requirements.generators import ( - SecurityRequirementsGenerator, -) -from openapi_core.schema.servers.generators import ServersGenerator -from openapi_core.schema.specs.models import Spec - - -class SpecFactory(object): - - def __init__(self, spec_resolver): - self.spec_resolver = spec_resolver - - def create(self, spec_dict, spec_url=''): - spec_dict_deref = self.dereferencer.dereference(spec_dict) - - info_spec = spec_dict_deref.get('info', {}) - servers_spec = spec_dict_deref.get('servers', []) - paths = spec_dict_deref.get('paths', {}) - components_spec = spec_dict_deref.get('components', {}) - security_spec = spec_dict_deref.get('security', []) - - if not servers_spec: - servers_spec = [ - {'url': '/'}, - ] - - extensions = self.extensions_generator.generate(spec_dict_deref) - - info = self.info_factory.create(info_spec) - servers = self.servers_generator.generate(servers_spec) - paths = self.paths_generator.generate(paths) - components = self.components_factory.create(components_spec) - - security = self.security_requirements_generator.generate( - security_spec) - - spec = Spec( - info, list(paths), servers=list(servers), components=components, - security=list(security), extensions=extensions, - _resolver=self.spec_resolver, - ) - return spec - - @property - @lru_cache() - def dereferencer(self): - return Dereferencer(self.spec_resolver) - - @property - @lru_cache() - def schemas_registry(self): - return SchemaRegistry(self.dereferencer) - - @property - @lru_cache() - def info_factory(self): - return InfoFactory(self.dereferencer) - - @property - @lru_cache() - def servers_generator(self): - return ServersGenerator(self.dereferencer) - - @property - @lru_cache() - def paths_generator(self): - return PathsGenerator(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def components_factory(self): - return ComponentsFactory(self.dereferencer, self.schemas_registry) - - @property - @lru_cache() - def security_requirements_generator(self): - return SecurityRequirementsGenerator(self.dereferencer) - - @property - @lru_cache() - def extensions_generator(self): - return ExtensionsGenerator(self.dereferencer) diff --git a/openapi_core/schema/specs/models.py b/openapi_core/schema/specs/models.py deleted file mode 100644 index 6c24ab9..0000000 --- a/openapi_core/schema/specs/models.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -"""OpenAPI core specs models module""" -import logging - -from openapi_core.compat import partialmethod -from openapi_core.schema.operations.exceptions import InvalidOperation -from openapi_core.schema.paths.exceptions import InvalidPath -from openapi_core.schema.servers.exceptions import InvalidServer - - -log = logging.getLogger(__name__) - - -class Spec(object): - """Represents an OpenAPI Specification for a service.""" - - def __init__( - self, info, paths, servers=None, components=None, - security=None, extensions=None, _resolver=None): - self.info = info - self.paths = paths and dict(paths) - self.servers = servers or [] - self.components = components - self.security = security - - self.extensions = extensions and dict(extensions) or {} - - self._resolver = _resolver - - def __getitem__(self, path_pattern): - return self.get_path(path_pattern) - - @property - def default_url(self): - return self.servers[0].default_url - - def get_server(self, full_url_pattern): - for spec_server in self.servers: - if spec_server.default_url in full_url_pattern: - return spec_server - - raise InvalidServer(full_url_pattern) - - def get_server_url(self, index=0): - return self.servers[index].default_url - - def get_path(self, path_pattern): - try: - return self.paths[path_pattern] - except KeyError: - raise InvalidPath(path_pattern) - - def get_operation(self, path_pattern, http_method): - try: - return self.paths[path_pattern].operations[http_method] - except KeyError: - raise InvalidOperation(path_pattern, http_method) - - def get_schema(self, name): - return self.components.schemas[name] - - # operations shortcuts - - get = partialmethod(get_operation, http_method='get') - put = partialmethod(get_operation, http_method='put') - post = partialmethod(get_operation, http_method='post') - delete = partialmethod(get_operation, http_method='delete') - options = partialmethod(get_operation, http_method='options') - head = partialmethod(get_operation, http_method='head') - patch = partialmethod(get_operation, http_method='patch') diff --git a/openapi_core/security/factories.py b/openapi_core/security/factories.py index 95c353f..57f4d6e 100644 --- a/openapi_core/security/factories.py +++ b/openapi_core/security/factories.py @@ -1,4 +1,3 @@ -from openapi_core.schema.security_schemes.enums import SecuritySchemeType from openapi_core.security.providers import ( ApiKeyProvider, HttpProvider, UnsupportedProvider, ) diff --git a/openapi_core/shortcuts.py b/openapi_core/shortcuts.py index 5d09a0e..3bd32f0 100644 --- a/openapi_core/shortcuts.py +++ b/openapi_core/shortcuts.py @@ -1,6 +1,12 @@ """OpenAPI core shortcuts module""" # backward compatibility -from openapi_core.schema.shortcuts import create_spec +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.spec.paths import SpecPath from openapi_core.validation.request.shortcuts import ( spec_validate_body as validate_body, spec_validate_parameters as validate_parameters, @@ -15,3 +21,16 @@ __all__ = [ 'create_spec', 'validate_body', 'validate_parameters', 'validate_data', 'RequestValidator', 'ResponseValidator', ] + + +def create_spec( + spec_dict, spec_url='', handlers=default_handlers, + validate_spec=True, +): + if validate_spec: + openapi_v3_spec_validator.validate(spec_dict, spec_url=spec_url) + + spec_resolver = RefResolver( + spec_url, spec_dict, handlers=handlers) + dereferencer = Dereferencer(spec_resolver) + return SpecPath.from_spec(spec_dict, dereferencer) diff --git a/openapi_core/spec/servers.py b/openapi_core/spec/servers.py index 2f6a0c1..80e32cc 100644 --- a/openapi_core/spec/servers.py +++ b/openapi_core/spec/servers.py @@ -1,6 +1,10 @@ from six import iteritems +def is_absolute(url): + return url.startswith('//') or '://' in url + + def get_server_default_variables(server): if 'variables' not in server: return {} diff --git a/openapi_core/templating/paths/finders.py b/openapi_core/templating/paths/finders.py index ebe01d4..42fd741 100644 --- a/openapi_core/templating/paths/finders.py +++ b/openapi_core/templating/paths/finders.py @@ -3,7 +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.spec.servers 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 ( diff --git a/openapi_core/types.py b/openapi_core/types.py new file mode 100644 index 0000000..8a8874d --- /dev/null +++ b/openapi_core/types.py @@ -0,0 +1 @@ +NoValue = object() diff --git a/openapi_core/unmarshalling/schemas/factories.py b/openapi_core/unmarshalling/schemas/factories.py index c635fb7..00717de 100644 --- a/openapi_core/unmarshalling/schemas/factories.py +++ b/openapi_core/unmarshalling/schemas/factories.py @@ -2,8 +2,6 @@ import warnings from openapi_schema_validator import OAS30Validator -from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat -from openapi_core.schema.schemas.models import Schema from openapi_core.unmarshalling.schemas.enums import UnmarshalContext from openapi_core.unmarshalling.schemas.exceptions import ( FormatterNotFoundError, @@ -74,13 +72,11 @@ class SchemaUnmarshallersFactory(object): return klass(formatter, validator, **kwargs) - def get_formatter(self, default_formatters, type_format=SchemaFormat.NONE): + def get_formatter(self, default_formatters, type_format=None): try: - schema_format = SchemaFormat(type_format) - except ValueError: - return self.custom_formatters.get(type_format) - else: - return default_formatters.get(schema_format) + return self.custom_formatters[type_format] + except KeyError: + return default_formatters.get(type_format) def get_validator(self, schema): kwargs = { diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py index 628f352..def8024 100644 --- a/openapi_core/unmarshalling/schemas/unmarshallers.py +++ b/openapi_core/unmarshalling/schemas/unmarshallers.py @@ -12,12 +12,10 @@ from six import text_type, binary_type from six import iteritems 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.types import NoValue from openapi_core.unmarshalling.schemas.enums import UnmarshalContext from openapi_core.unmarshalling.schemas.exceptions import ( UnmarshalError, ValidateError, InvalidSchemaValue, @@ -77,20 +75,20 @@ class PrimitiveTypeUnmarshaller(object): class StringUnmarshaller(PrimitiveTypeUnmarshaller): FORMATTERS = { - SchemaFormat.NONE: Formatter.from_callables( + None: Formatter.from_callables( partial(is_string, None), text_type), - SchemaFormat.PASSWORD: Formatter.from_callables( + 'password': Formatter.from_callables( partial(oas30_format_checker.check, format='password'), text_type), - SchemaFormat.DATE: Formatter.from_callables( + 'date': Formatter.from_callables( partial(oas30_format_checker.check, format='date'), format_date), - SchemaFormat.DATETIME: Formatter.from_callables( + 'date-time': Formatter.from_callables( partial(oas30_format_checker.check, format='date-time'), parse_datetime), - SchemaFormat.BINARY: Formatter.from_callables( + 'binary': Formatter.from_callables( partial(oas30_format_checker.check, format='binary'), binary_type), - SchemaFormat.UUID: Formatter.from_callables( + 'uuid': Formatter.from_callables( partial(oas30_format_checker.check, format='uuid'), format_uuid), - SchemaFormat.BYTE: Formatter.from_callables( + 'byte': Formatter.from_callables( partial(oas30_format_checker.check, format='byte'), format_byte), } @@ -98,11 +96,11 @@ class StringUnmarshaller(PrimitiveTypeUnmarshaller): class IntegerUnmarshaller(PrimitiveTypeUnmarshaller): FORMATTERS = { - SchemaFormat.NONE: Formatter.from_callables( + None: Formatter.from_callables( partial(is_integer, None), int), - SchemaFormat.INT32: Formatter.from_callables( + 'int32': Formatter.from_callables( partial(oas30_format_checker.check, format='int32'), int), - SchemaFormat.INT64: Formatter.from_callables( + 'int64': Formatter.from_callables( partial(oas30_format_checker.check, format='int64'), int), } @@ -110,11 +108,11 @@ class IntegerUnmarshaller(PrimitiveTypeUnmarshaller): class NumberUnmarshaller(PrimitiveTypeUnmarshaller): FORMATTERS = { - SchemaFormat.NONE: Formatter.from_callables( + None: Formatter.from_callables( partial(is_number, None), format_number), - SchemaFormat.FLOAT: Formatter.from_callables( + 'float': Formatter.from_callables( partial(oas30_format_checker.check, format='float'), float), - SchemaFormat.DOUBLE: Formatter.from_callables( + 'double': Formatter.from_callables( partial(oas30_format_checker.check, format='double'), float), } @@ -122,7 +120,7 @@ class NumberUnmarshaller(PrimitiveTypeUnmarshaller): class BooleanUnmarshaller(PrimitiveTypeUnmarshaller): FORMATTERS = { - SchemaFormat.NONE: Formatter.from_callables( + None: Formatter.from_callables( partial(is_bool, None), forcebool), } @@ -140,7 +138,7 @@ class ComplexUnmarshaller(PrimitiveTypeUnmarshaller): class ArrayUnmarshaller(ComplexUnmarshaller): FORMATTERS = { - SchemaFormat.NONE: Formatter.from_callables( + None: Formatter.from_callables( partial(is_array, None), list), } @@ -158,7 +156,7 @@ class ArrayUnmarshaller(ComplexUnmarshaller): class ObjectUnmarshaller(ComplexUnmarshaller): FORMATTERS = { - SchemaFormat.NONE: Formatter.from_callables( + None: Formatter.from_callables( partial(is_object, None), dict), } @@ -249,7 +247,7 @@ class ObjectUnmarshaller(ComplexUnmarshaller): class AnyUnmarshaller(ComplexUnmarshaller): FORMATTERS = { - SchemaFormat.NONE: Formatter(), + None: Formatter(), } SCHEMA_TYPES_ORDER = [ diff --git a/openapi_core/validation/request/shortcuts.py b/openapi_core/validation/request/shortcuts.py index 83ed4ab..55e2c9e 100644 --- a/openapi_core/validation/request/shortcuts.py +++ b/openapi_core/validation/request/shortcuts.py @@ -1,12 +1,10 @@ """OpenAPI core validation request shortcuts module""" import warnings -from openapi_core.schema.media_types.exceptions import OpenAPIMediaTypeError -from openapi_core.schema.parameters.exceptions import OpenAPIParameterError -from openapi_core.schema.request_bodies.exceptions import ( - OpenAPIRequestBodyError, +from openapi_core.exceptions import ( + OpenAPIMediaTypeError, OpenAPIParameterError, OpenAPIRequestBodyError, + OpenAPISchemaError, ) -from openapi_core.schema.schemas.exceptions import OpenAPISchemaError from openapi_core.validation.request.validators import RequestValidator ERRORS_BODY = ( diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index 0e01e88..9a08f0a 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -4,10 +4,9 @@ from six import iteritems from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError -from openapi_core.schema.parameters.exceptions import ( - MissingRequiredParameter, MissingParameter, +from openapi_core.exceptions import ( + MissingRequiredParameter, MissingParameter, MissingRequestBody, ) -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 diff --git a/openapi_core/validation/response/validators.py b/openapi_core/validation/response/validators.py index 3adfe51..4c68540 100644 --- a/openapi_core/validation/response/validators.py +++ b/openapi_core/validation/response/validators.py @@ -1,7 +1,7 @@ """OpenAPI core validation response validators module""" from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError -from openapi_core.schema.responses.exceptions import MissingResponseContent +from openapi_core.exceptions import MissingResponseContent from openapi_core.templating.media_types.exceptions import MediaTypeFinderError from openapi_core.templating.paths.exceptions import PathError from openapi_core.templating.responses.exceptions import ResponseFinderError diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index 1f12d21..7e28649 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -12,9 +12,7 @@ from openapi_core.deserializing.parameters.exceptions import ( EmptyParameterValue, ) from openapi_core.extensions.models.models import BaseModel -from openapi_core.schema.parameters.exceptions import ( - MissingRequiredParameter, -) +from openapi_core.exceptions import MissingRequiredParameter from openapi_core.shortcuts import ( create_spec, validate_parameters, validate_body, validate_data, ) diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py index 3448588..0e4873f 100644 --- a/tests/integration/validation/test_validators.py +++ b/tests/integration/validation/test_validators.py @@ -6,9 +6,9 @@ from six import text_type from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.extensions.models.models import BaseModel -from openapi_core.schema.parameters.exceptions import MissingRequiredParameter -from openapi_core.schema.request_bodies.exceptions import MissingRequestBody -from openapi_core.schema.responses.exceptions import MissingResponseContent +from openapi_core.exceptions import ( + MissingRequiredParameter, MissingRequestBody, MissingResponseContent, +) from openapi_core.shortcuts import create_spec from openapi_core.templating.media_types.exceptions import MediaTypeNotFound from openapi_core.templating.paths.exceptions import ( diff --git a/tests/unit/schema/test_links.py b/tests/unit/schema/test_links.py deleted file mode 100644 index 346900f..0000000 --- a/tests/unit/schema/test_links.py +++ /dev/null @@ -1,44 +0,0 @@ -import mock -import pytest - -from openapi_core.schema.links.models import Link -from openapi_core.schema.servers.models import Server - - -class TestLinks(object): - - @pytest.fixture - def link_factory(self): - def link_factory(request_body, server): - parameters = { - 'par1': mock.sentinel.par1, - 'par2': mock.sentinel.par2, - } - return Link( - 'op_id', - parameters, - request_body, - 'Test link', - server - ) - return link_factory - - servers = [ - None, - Server("https://bad.remote.domain.net/"), - Server("http://localhost") - ] - - request_body_list = [ - None, - "request", - '{"request": "value", "opt": 2}', - {"request": "value", "opt": 2} - ] - - @pytest.mark.parametrize("server", servers) - @pytest.mark.parametrize("request_body", request_body_list) - def test_iteritems(self, link_factory, request_body, server): - link = link_factory(request_body, server) - for par_name in link.parameters: - assert link[par_name] == link.parameters[par_name] diff --git a/tests/unit/schema/test_operations.py b/tests/unit/schema/test_operations.py deleted file mode 100644 index b1518ac..0000000 --- a/tests/unit/schema/test_operations.py +++ /dev/null @@ -1,19 +0,0 @@ -import mock -import pytest - -from openapi_core.schema.operations.models import Operation - - -class TestSchemas(object): - - @pytest.fixture - def operation(self): - parameters = { - 'parameter_1': mock.sentinel.parameter_1, - 'parameter_2': mock.sentinel.parameter_2, - } - return Operation('get', '/path', {}, parameters=parameters) - - def test_iteritems(self, operation): - for name in operation.parameters: - assert operation[name] == operation.parameters[name] diff --git a/tests/unit/schema/test_parameters.py b/tests/unit/schema/test_parameters.py deleted file mode 100644 index 379bd44..0000000 --- a/tests/unit/schema/test_parameters.py +++ /dev/null @@ -1,33 +0,0 @@ -from openapi_core.schema.parameters.enums import ParameterStyle -from openapi_core.schema.parameters.models import Parameter - - -class TestParameterInit(object): - - def test_path(self): - param = Parameter('param', 'path') - - assert param.allow_empty_value is False - assert param.style == ParameterStyle.SIMPLE - assert param.explode is False - - def test_query(self): - param = Parameter('param', 'query') - - assert param.allow_empty_value is False - assert param.style == ParameterStyle.FORM - assert param.explode is True - - def test_header(self): - param = Parameter('param', 'header') - - assert param.allow_empty_value is False - assert param.style == ParameterStyle.SIMPLE - assert param.explode is False - - def test_cookie(self): - param = Parameter('param', 'cookie') - - assert param.allow_empty_value is False - assert param.style == ParameterStyle.FORM - assert param.explode is True diff --git a/tests/unit/schema/test_paths.py b/tests/unit/schema/test_paths.py deleted file mode 100644 index bd6c83d..0000000 --- a/tests/unit/schema/test_paths.py +++ /dev/null @@ -1,21 +0,0 @@ -import mock -import pytest - -from openapi_core.schema.paths.models import Path - - -class TestPaths(object): - - @pytest.fixture - def path(self): - operations = { - 'get': mock.sentinel.get, - 'post': mock.sentinel.post, - } - return Path('/path', operations) - - @property - def test_iteritems(self, path): - for http_method in path.operations: - assert path[http_method] ==\ - path.operations[http_method] diff --git a/tests/unit/schema/test_request_bodies.py b/tests/unit/schema/test_request_bodies.py deleted file mode 100644 index cf82eda..0000000 --- a/tests/unit/schema/test_request_bodies.py +++ /dev/null @@ -1,21 +0,0 @@ -import mock -import pytest - -from openapi_core.schema.request_bodies.models import RequestBody - - -class TestRequestBodies(object): - - @pytest.fixture - def request_body(self): - content = { - 'application/json': mock.sentinel.application_json, - 'text/csv': mock.sentinel.text_csv, - } - return RequestBody(content) - - @property - def test_iteritems(self, request_body): - for mimetype in request_body.content: - assert request_body[mimetype] ==\ - request_body.content[mimetype] diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py deleted file mode 100644 index e9f8b06..0000000 --- a/tests/unit/schema/test_schemas.py +++ /dev/null @@ -1,20 +0,0 @@ -import mock -import pytest - -from openapi_core.schema.schemas.models import Schema - - -class TestSchemaIteritems(object): - - @pytest.fixture - def schema(self): - properties = { - 'application/json': mock.sentinel.application_json, - 'text/csv': mock.sentinel.text_csv, - } - return Schema('object', properties=properties) - - @property - def test_valid(self, schema): - for name in schema.properties: - assert schema[name] == schema.properties[name] diff --git a/tests/unit/schema/test_schemas_registry.py b/tests/unit/schema/test_schemas_registry.py deleted file mode 100644 index 712032a..0000000 --- a/tests/unit/schema/test_schemas_registry.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest - -from jsonschema.validators import RefResolver -from openapi_spec_validator.validators import Dereferencer -from openapi_spec_validator import default_handlers - -from openapi_core.schema.schemas.registries import SchemaRegistry - - -class TestSchemaRegistryGetOrCreate(object): - - @pytest.fixture - def schema_dict(self): - return { - 'type': 'object', - 'properties': { - 'message': { - 'type': 'string', - }, - 'suberror': { - '$ref': '#/components/schemas/Error', - }, - }, - } - - @pytest.fixture - def spec_dict(self, schema_dict): - return { - 'components': { - 'schemas': { - 'Error': schema_dict, - }, - }, - } - - @pytest.fixture - def dereferencer(self, spec_dict): - spec_resolver = RefResolver('', spec_dict, handlers=default_handlers) - return Dereferencer(spec_resolver) - - @pytest.fixture - def schemas_registry(self, dereferencer): - return SchemaRegistry(dereferencer) - - def test_recursion(self, schemas_registry, schema_dict): - schema, _ = schemas_registry.get_or_create(schema_dict) - - assert schema.properties['suberror'] ==\ - schema.properties['suberror'].properties['suberror'] diff --git a/tests/unit/schema/test_specs.py b/tests/unit/schema/test_specs.py deleted file mode 100644 index c8a0f0d..0000000 --- a/tests/unit/schema/test_specs.py +++ /dev/null @@ -1,50 +0,0 @@ -import mock -import pytest - -from openapi_core.schema.operations.exceptions import InvalidOperation -from openapi_core.schema.paths.models import Path -from openapi_core.schema.specs.models import Spec - - -class TestSpecs(object): - - @pytest.fixture - def path1(self): - operations = { - 'get': mock.sentinel.path1_get, - } - return Path('path1', operations) - - @pytest.fixture - def path2(self): - operations = { - 'post': mock.sentinel.path2_psot, - } - return Path('path2', operations) - - @pytest.fixture - def spec(self, path1, path2): - servers = [] - paths = { - '/path1': path1, - '/path2': path2, - } - return Spec(servers, paths) - - def test_iteritems(self, spec): - for path_name in spec.paths: - assert spec[path_name] ==\ - spec.paths[path_name] - - def test_valid(self, spec): - operation = spec.get_operation('/path1', 'get') - - assert operation == mock.sentinel.path1_get - - def test_invalid_path(self, spec): - with pytest.raises(InvalidOperation): - spec.get_operation('/path3', 'get') - - def test_invalid_method(self, spec): - with pytest.raises(InvalidOperation): - spec.get_operation('/path1', 'post') diff --git a/tests/unit/unmarshalling/test_unmarshal.py b/tests/unit/unmarshalling/test_unmarshal.py index a0510c4..ed98789 100644 --- a/tests/unit/unmarshalling/test_unmarshal.py +++ b/tests/unit/unmarshalling/test_unmarshal.py @@ -4,8 +4,8 @@ import uuid from isodate.tzinfo import UTC, FixedOffset import pytest -from openapi_core.schema.schemas.types import NoValue from openapi_core.spec.paths import SpecPath +from openapi_core.types import NoValue from openapi_core.unmarshalling.schemas.enums import UnmarshalContext from openapi_core.unmarshalling.schemas.exceptions import ( InvalidSchemaFormatValue, InvalidSchemaValue, UnmarshalError, diff --git a/tests/unit/unmarshalling/test_validate.py b/tests/unit/unmarshalling/test_validate.py index fcbe368..8dad170 100644 --- a/tests/unit/unmarshalling/test_validate.py +++ b/tests/unit/unmarshalling/test_validate.py @@ -4,7 +4,7 @@ import mock import pytest from openapi_core.extensions.models.models import Model -from openapi_core.schema.schemas.exceptions import OpenAPISchemaError +from openapi_core.exceptions import OpenAPISchemaError from openapi_core.spec.paths import SpecPath from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, From d8a36cc998a857cd2cb3a2f615e0281be8a22002 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Tue, 27 Apr 2021 22:39:28 +0100 Subject: [PATCH 3/8] New schema package --- openapi_core/casting/schemas/casters.py | 2 ++ openapi_core/deserializing/parameters/deserializers.py | 2 +- openapi_core/schema/__init__.py | 0 openapi_core/{spec => schema}/parameters.py | 0 openapi_core/{spec => schema}/schemas.py | 2 ++ openapi_core/{spec => schema}/servers.py | 2 ++ openapi_core/{spec => schema}/specs.py | 4 +++- openapi_core/spec/paths.py | 6 ++---- openapi_core/templating/media_types/finders.py | 1 + openapi_core/templating/paths/finders.py | 4 +++- openapi_core/templating/responses/finders.py | 2 ++ openapi_core/unmarshalling/schemas/unmarshallers.py | 3 ++- openapi_core/validation/request/validators.py | 3 ++- openapi_core/validation/response/validators.py | 2 ++ openapi_core/validation/validators.py | 2 ++ tests/integration/schema/test_link_spec.py | 2 ++ tests/integration/schema/test_path_params.py | 2 ++ tests/integration/schema/test_spec.py | 5 +++-- tests/unit/templating/test_paths_finders.py | 1 + tests/unit/templating/test_responses_finders.py | 1 + 20 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 openapi_core/schema/__init__.py rename openapi_core/{spec => schema}/parameters.py (100%) rename openapi_core/{spec => schema}/schemas.py (93%) rename openapi_core/{spec => schema}/servers.py (94%) rename openapi_core/{spec => schema}/specs.py (54%) diff --git a/openapi_core/casting/schemas/casters.py b/openapi_core/casting/schemas/casters.py index 25e35ea..048de19 100644 --- a/openapi_core/casting/schemas/casters.py +++ b/openapi_core/casting/schemas/casters.py @@ -1,3 +1,5 @@ +from __future__ import division + from openapi_core.casting.schemas.exceptions import CastError from openapi_core.types import NoValue diff --git a/openapi_core/deserializing/parameters/deserializers.py b/openapi_core/deserializing/parameters/deserializers.py index b422467..7eaf6c5 100644 --- a/openapi_core/deserializing/parameters/deserializers.py +++ b/openapi_core/deserializing/parameters/deserializers.py @@ -2,7 +2,7 @@ from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.deserializing.parameters.exceptions import ( EmptyParameterValue, ) -from openapi_core.spec.parameters import get_aslist, get_explode, get_style +from openapi_core.schema.parameters import get_aslist, get_explode, get_style class PrimitiveDeserializer(object): diff --git a/openapi_core/schema/__init__.py b/openapi_core/schema/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openapi_core/spec/parameters.py b/openapi_core/schema/parameters.py similarity index 100% rename from openapi_core/spec/parameters.py rename to openapi_core/schema/parameters.py diff --git a/openapi_core/spec/schemas.py b/openapi_core/schema/schemas.py similarity index 93% rename from openapi_core/spec/schemas.py rename to openapi_core/schema/schemas.py index 2a117ab..79a5d16 100644 --- a/openapi_core/spec/schemas.py +++ b/openapi_core/schema/schemas.py @@ -1,3 +1,5 @@ +from __future__ import division + from six import iteritems diff --git a/openapi_core/spec/servers.py b/openapi_core/schema/servers.py similarity index 94% rename from openapi_core/spec/servers.py rename to openapi_core/schema/servers.py index 80e32cc..6685451 100644 --- a/openapi_core/spec/servers.py +++ b/openapi_core/schema/servers.py @@ -1,3 +1,5 @@ +from __future__ import division + from six import iteritems diff --git a/openapi_core/spec/specs.py b/openapi_core/schema/specs.py similarity index 54% rename from openapi_core/spec/specs.py rename to openapi_core/schema/specs.py index cf7a795..15f461d 100644 --- a/openapi_core/spec/specs.py +++ b/openapi_core/schema/specs.py @@ -1,4 +1,6 @@ -from openapi_core.spec.servers import get_server_url +from __future__ import division + +from openapi_core.schema.servers import get_server_url def get_spec_url(spec, index=0): diff --git a/openapi_core/spec/paths.py b/openapi_core/spec/paths.py index d6f6acb..8377aa0 100644 --- a/openapi_core/spec/paths.py +++ b/openapi_core/spec/paths.py @@ -8,9 +8,7 @@ SPEC_SEPARATOR = '#' class SpecPath(AccessorPath): @classmethod - def from_spec( - cls, spec_dict, dereferencer=None, *args, - separator=SPEC_SEPARATOR, - ): + def from_spec(cls, spec_dict, dereferencer=None, *args, **kwargs): + separator = kwargs.pop('separator', SPEC_SEPARATOR) accessor = SpecAccessor(spec_dict, dereferencer) return cls(accessor, *args, separator=separator) diff --git a/openapi_core/templating/media_types/finders.py b/openapi_core/templating/media_types/finders.py index db9b40e..0bd3006 100644 --- a/openapi_core/templating/media_types/finders.py +++ b/openapi_core/templating/media_types/finders.py @@ -1,4 +1,5 @@ """OpenAPI core templating media types finders module""" +from __future__ import division import fnmatch from six import iteritems diff --git a/openapi_core/templating/paths/finders.py b/openapi_core/templating/paths/finders.py index 42fd741..cd7eb64 100644 --- a/openapi_core/templating/paths/finders.py +++ b/openapi_core/templating/paths/finders.py @@ -1,9 +1,11 @@ """OpenAPI core templating paths finders module""" +from __future__ import division + from more_itertools import peekable from six import iteritems from six.moves.urllib.parse import urljoin, urlparse -from openapi_core.spec.servers import is_absolute +from openapi_core.schema.servers 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 ( diff --git a/openapi_core/templating/responses/finders.py b/openapi_core/templating/responses/finders.py index 106bc79..d50876b 100644 --- a/openapi_core/templating/responses/finders.py +++ b/openapi_core/templating/responses/finders.py @@ -1,3 +1,5 @@ +from __future__ import division + from openapi_core.templating.responses.exceptions import ResponseNotFound diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py index def8024..e9bd6ec 100644 --- a/openapi_core/unmarshalling/schemas/unmarshallers.py +++ b/openapi_core/unmarshalling/schemas/unmarshallers.py @@ -1,3 +1,4 @@ +from __future__ import division from functools import partial import logging @@ -12,7 +13,7 @@ from six import text_type, binary_type from six import iteritems from openapi_core.extensions.models.factories import ModelFactory -from openapi_core.spec.schemas import ( +from openapi_core.schema.schemas import ( get_all_properties, get_all_properties_names ) from openapi_core.types import NoValue diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index 9a08f0a..1414b91 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -1,4 +1,5 @@ """OpenAPI core validation request validators module""" +from __future__ import division from itertools import chain from six import iteritems @@ -8,7 +9,7 @@ from openapi_core.exceptions import ( MissingRequiredParameter, MissingParameter, MissingRequestBody, ) from openapi_core.security.exceptions import SecurityError -from openapi_core.spec.parameters import get_aslist, get_explode +from openapi_core.schema.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 diff --git a/openapi_core/validation/response/validators.py b/openapi_core/validation/response/validators.py index 4c68540..4bf59fb 100644 --- a/openapi_core/validation/response/validators.py +++ b/openapi_core/validation/response/validators.py @@ -1,4 +1,6 @@ """OpenAPI core validation response validators module""" +from __future__ import division + from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.exceptions import MissingResponseContent diff --git a/openapi_core/validation/validators.py b/openapi_core/validation/validators.py index 140180a..51bf56c 100644 --- a/openapi_core/validation/validators.py +++ b/openapi_core/validation/validators.py @@ -1,4 +1,6 @@ """OpenAPI core validation validators module""" +from __future__ import division + from openapi_core.unmarshalling.schemas.util import build_format_checker diff --git a/tests/integration/schema/test_link_spec.py b/tests/integration/schema/test_link_spec.py index f6a31f1..80bc1d8 100644 --- a/tests/integration/schema/test_link_spec.py +++ b/tests/integration/schema/test_link_spec.py @@ -1,3 +1,5 @@ +from __future__ import division + from openapi_core.shortcuts import create_spec diff --git a/tests/integration/schema/test_path_params.py b/tests/integration/schema/test_path_params.py index 9328c79..50570c0 100644 --- a/tests/integration/schema/test_path_params.py +++ b/tests/integration/schema/test_path_params.py @@ -1,3 +1,5 @@ +from __future__ import division + import pytest from openapi_core.shortcuts import create_spec diff --git a/tests/integration/schema/test_spec.py b/tests/integration/schema/test_spec.py index ed6f77b..8773eb0 100644 --- a/tests/integration/schema/test_spec.py +++ b/tests/integration/schema/test_spec.py @@ -1,10 +1,11 @@ +from __future__ import division import pytest from base64 import b64encode from six import iteritems, text_type 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.schema.servers import get_server_url +from openapi_core.schema.specs import get_spec_url from openapi_core.validation.request.validators import RequestValidator from openapi_core.validation.response.validators import ResponseValidator diff --git a/tests/unit/templating/test_paths_finders.py b/tests/unit/templating/test_paths_finders.py index 83c863c..fe023a3 100644 --- a/tests/unit/templating/test_paths_finders.py +++ b/tests/unit/templating/test_paths_finders.py @@ -1,3 +1,4 @@ +from __future__ import division import pytest from openapi_core.spec.paths import SpecPath diff --git a/tests/unit/templating/test_responses_finders.py b/tests/unit/templating/test_responses_finders.py index 1a2ae20..11b4fac 100644 --- a/tests/unit/templating/test_responses_finders.py +++ b/tests/unit/templating/test_responses_finders.py @@ -1,3 +1,4 @@ +from __future__ import division import mock import pytest From bc4f0c7e404561bef269d633c3d6c27567382678 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Wed, 28 Apr 2021 23:08:52 +0100 Subject: [PATCH 4/8] Dictpath dependency --- requirements.txt | 1 + setup.cfg | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 08cebe5..b2d723d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ isodate==0.6.0 +dictpath==0.1.3 openapi-spec-validator openapi-schema-validator six diff --git a/setup.cfg b/setup.cfg index 0a2f5ce..4936a19 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ setup_requires = setuptools install_requires = isodate + dictpath openapi-spec-validator openapi-schema-validator six From c407e960ca85e0a69154f537a4a0f3ef350a536e Mon Sep 17 00:00:00 2001 From: p1c2u Date: Wed, 28 Apr 2021 23:17:06 +0100 Subject: [PATCH 5/8] Linting fixes --- openapi_core/deserializing/parameters/deserializers.py | 1 - openapi_core/schema/parameters.py | 2 +- openapi_core/templating/media_types/finders.py | 2 -- openapi_core/templating/paths/finders.py | 1 - openapi_core/unmarshalling/schemas/unmarshallers.py | 3 ++- openapi_core/validation/request/validators.py | 3 +-- openapi_core/validation/validators.py | 7 ++++--- 7 files changed, 8 insertions(+), 11 deletions(-) diff --git a/openapi_core/deserializing/parameters/deserializers.py b/openapi_core/deserializing/parameters/deserializers.py index 7eaf6c5..5dd9135 100644 --- a/openapi_core/deserializing/parameters/deserializers.py +++ b/openapi_core/deserializing/parameters/deserializers.py @@ -16,7 +16,6 @@ class PrimitiveDeserializer(object): self.style = get_style(self.param) 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( diff --git a/openapi_core/schema/parameters.py b/openapi_core/schema/parameters.py index f1ead22..78d39db 100644 --- a/openapi_core/schema/parameters.py +++ b/openapi_core/schema/parameters.py @@ -19,6 +19,6 @@ def get_explode(param): if 'explode' in param: return param['explode'] - #determine default + # determine default style = get_style(param) return style == 'form' diff --git a/openapi_core/templating/media_types/finders.py b/openapi_core/templating/media_types/finders.py index 0bd3006..a677781 100644 --- a/openapi_core/templating/media_types/finders.py +++ b/openapi_core/templating/media_types/finders.py @@ -2,8 +2,6 @@ from __future__ import division import fnmatch -from six import iteritems - from openapi_core.templating.media_types.exceptions import MediaTypeNotFound diff --git a/openapi_core/templating/paths/finders.py b/openapi_core/templating/paths/finders.py index cd7eb64..4daf6a3 100644 --- a/openapi_core/templating/paths/finders.py +++ b/openapi_core/templating/paths/finders.py @@ -2,7 +2,6 @@ from __future__ import division from more_itertools import peekable -from six import iteritems from six.moves.urllib.parse import urljoin, urlparse from openapi_core.schema.servers import is_absolute diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py index e9bd6ec..9fd0789 100644 --- a/openapi_core/unmarshalling/schemas/unmarshallers.py +++ b/openapi_core/unmarshalling/schemas/unmarshallers.py @@ -213,7 +213,8 @@ class ObjectUnmarshaller(ComplexUnmarshaller): extra_props = set(value_props_names) - set(all_props_names) properties = {} - additional_properties = self.schema.getkey('additionalProperties', True) + additional_properties = self.schema.getkey( + 'additionalProperties', True) if isinstance(additional_properties, dict): additional_prop_schema = self.schema / 'additionalProperties' for prop_name in extra_props: diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index 1414b91..fb01adf 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -1,7 +1,6 @@ """OpenAPI core validation request validators module""" from __future__ import division from itertools import chain -from six import iteritems from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError @@ -172,7 +171,7 @@ class RequestValidator(BaseValidator): return RequestParameters(**locations), errors def _get_body(self, request, operation): - if not 'requestBody' in operation: + if 'requestBody' not in operation: return None, [] request_body = operation / 'requestBody' diff --git a/openapi_core/validation/validators.py b/openapi_core/validation/validators.py index 51bf56c..62a70a7 100644 --- a/openapi_core/validation/validators.py +++ b/openapi_core/validation/validators.py @@ -39,7 +39,7 @@ class BaseValidator(object): def _cast(self, param_or_media_type, value): # return param_or_media_type.cast(value) - if not 'schema' in param_or_media_type: + if 'schema' not in param_or_media_type: return value from openapi_core.casting.schemas.factories import SchemaCastersFactory @@ -49,13 +49,14 @@ class BaseValidator(object): return caster(value) def _unmarshal(self, param_or_media_type, value, context): - if not 'schema' in param_or_media_type: + if 'schema' not in param_or_media_type: return value from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, ) - spec_resolver = self.spec.accessor.dereferencer.resolver_manager.resolver + spec_resolver = self.spec.accessor.dereferencer.resolver_manager.\ + resolver unmarshallers_factory = SchemaUnmarshallersFactory( spec_resolver, self.format_checker, self.custom_formatters, context=context, From 90060b9c8a372691ff0045d600d8bc57abba8411 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Fri, 30 Apr 2021 08:50:20 +0100 Subject: [PATCH 6/8] Move create_spec shortcut --- openapi_core/shortcuts.py | 21 +-------------------- openapi_core/spec/shortcuts.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 openapi_core/spec/shortcuts.py diff --git a/openapi_core/shortcuts.py b/openapi_core/shortcuts.py index 3bd32f0..504ae10 100644 --- a/openapi_core/shortcuts.py +++ b/openapi_core/shortcuts.py @@ -1,12 +1,6 @@ """OpenAPI core shortcuts module""" # backward compatibility -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.spec.paths import SpecPath +from openapi_core.spec.shortcuts import create_spec from openapi_core.validation.request.shortcuts import ( spec_validate_body as validate_body, spec_validate_parameters as validate_parameters, @@ -21,16 +15,3 @@ __all__ = [ 'create_spec', 'validate_body', 'validate_parameters', 'validate_data', 'RequestValidator', 'ResponseValidator', ] - - -def create_spec( - spec_dict, spec_url='', handlers=default_handlers, - validate_spec=True, -): - if validate_spec: - openapi_v3_spec_validator.validate(spec_dict, spec_url=spec_url) - - spec_resolver = RefResolver( - spec_url, spec_dict, handlers=handlers) - dereferencer = Dereferencer(spec_resolver) - return SpecPath.from_spec(spec_dict, dereferencer) diff --git a/openapi_core/spec/shortcuts.py b/openapi_core/spec/shortcuts.py new file mode 100644 index 0000000..2df933d --- /dev/null +++ b/openapi_core/spec/shortcuts.py @@ -0,0 +1,21 @@ +"""OpenAPI core spec shortcuts module""" +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.spec.paths import SpecPath + + +def create_spec( + spec_dict, spec_url='', handlers=default_handlers, + validate_spec=True, +): + if validate_spec: + openapi_v3_spec_validator.validate(spec_dict, spec_url=spec_url) + + spec_resolver = RefResolver( + spec_url, spec_dict, handlers=handlers) + dereferencer = Dereferencer(spec_resolver) + return SpecPath.from_spec(spec_dict, dereferencer) From 9b2a4d81478c4785b2eed3a7fae895882a01bf45 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 1 May 2021 12:04:47 +0100 Subject: [PATCH 7/8] Schema default value test --- tests/integration/data/v3.0/petstore.yaml | 2 +- tests/integration/validation/test_petstore.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/data/v3.0/petstore.yaml b/tests/integration/data/v3.0/petstore.yaml index 6d2b1c6..9db095b 100644 --- a/tests/integration/data/v3.0/petstore.yaml +++ b/tests/integration/data/v3.0/petstore.yaml @@ -335,12 +335,12 @@ components: Error: type: object required: - - code - message properties: code: type: integer format: int32 + default: 400 message: type: string ExtendedError: diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index 7e28649..4992e33 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -1150,7 +1150,6 @@ class TestPetstore(object): rootCause = 'Tag already exist' additionalinfo = 'Tag Dog already exist' data_json = { - 'code': code, 'message': message, 'correlationId': str(correlationId), 'rootCause': rootCause, From bbeb43c300228f51f73392dab33c210fa56f1532 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sun, 2 May 2021 20:24:07 +0100 Subject: [PATCH 8/8] Get rid of unused exceptions --- openapi_core/exceptions.py | 17 ----------------- openapi_core/validation/request/shortcuts.py | 11 ----------- tests/unit/unmarshalling/test_validate.py | 5 +---- 3 files changed, 1 insertion(+), 32 deletions(-) diff --git a/openapi_core/exceptions.py b/openapi_core/exceptions.py index 6ce0572..0d33884 100644 --- a/openapi_core/exceptions.py +++ b/openapi_core/exceptions.py @@ -6,19 +6,6 @@ class OpenAPIError(Exception): pass -class OpenAPIMediaTypeError(OpenAPIError): - pass - - -@attr.s(hash=True) -class InvalidContentType(OpenAPIMediaTypeError): - mimetype = attr.ib() - - def __str__(self): - return "Content for following mimetype not found: {0}".format( - self.mimetype) - - class OpenAPIParameterError(OpenAPIError): pass @@ -67,7 +54,3 @@ class MissingResponseContent(OpenAPIResponseError): def __str__(self): return "Missing response content" - - -class OpenAPISchemaError(OpenAPIError): - pass diff --git a/openapi_core/validation/request/shortcuts.py b/openapi_core/validation/request/shortcuts.py index 55e2c9e..8539f9d 100644 --- a/openapi_core/validation/request/shortcuts.py +++ b/openapi_core/validation/request/shortcuts.py @@ -1,19 +1,8 @@ """OpenAPI core validation request shortcuts module""" import warnings -from openapi_core.exceptions import ( - OpenAPIMediaTypeError, OpenAPIParameterError, OpenAPIRequestBodyError, - OpenAPISchemaError, -) from openapi_core.validation.request.validators import RequestValidator -ERRORS_BODY = ( - OpenAPIRequestBodyError, OpenAPIMediaTypeError, OpenAPISchemaError, -) -ERRORS_PARAMETERS = ( - OpenAPIParameterError, -) - def validate_request(validator, request, failsafe=None): if failsafe is None: diff --git a/tests/unit/unmarshalling/test_validate.py b/tests/unit/unmarshalling/test_validate.py index 8dad170..b73c341 100644 --- a/tests/unit/unmarshalling/test_validate.py +++ b/tests/unit/unmarshalling/test_validate.py @@ -4,7 +4,6 @@ import mock import pytest from openapi_core.extensions.models.models import Model -from openapi_core.exceptions import OpenAPISchemaError from openapi_core.spec.paths import SpecPath from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, @@ -55,8 +54,6 @@ class TestSchemaValidate(object): assert result is None - @pytest.mark.xfail( - reason="validation does not care about custom formats atm") def test_string_format_custom_missing(self, validator_factory): custom_format = 'custom' spec = { @@ -66,7 +63,7 @@ class TestSchemaValidate(object): schema = SpecPath.from_spec(spec) value = 'x' - with pytest.raises(OpenAPISchemaError): + with pytest.raises(FormatterNotFoundError): validator_factory(schema).validate(value) @pytest.mark.parametrize('value', [False, True])