diff --git a/.gitignore b/.gitignore index 2350847..2ef7406 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ site/ __pycache__ .cache .coverage +.idea/ diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index 13b3ef4..e752f40 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -110,6 +110,17 @@ def _get_field_description(field): return field.schema.description +def _get_schema_type(schema): + return { + coreschema.String: 'string', + coreschema.Integer: 'integer', + coreschema.Number: 'number', + coreschema.Boolean: 'boolean', + coreschema.Array: 'array', + coreschema.Object: 'object', + }.get(schema.__class__, 'string') + + def _get_field_type(field): if getattr(field, 'type', None) is not None: # Deprecated @@ -118,14 +129,30 @@ def _get_field_type(field): if field.schema is None: return 'string' - return { - coreschema.String: 'string', - coreschema.Integer: 'integer', - coreschema.Number: 'number', - coreschema.Boolean: 'boolean', - coreschema.Array: 'array', - coreschema.Object: 'object', - }.get(field.schema.__class__, 'string') + return _get_schema_type(field.schema) + + +def _get_array_schema_items(field): + array_items = {} + + if isinstance(field.schema.items, list): + raise TypeError('Swagger 2.0 spec does not allow the items property to be a list of item types.') + + item_type = field.schema.items + if not isinstance(item_type, coreschema.Anything): + array_items['type'] = _get_schema_type(item_type) or 'string' + if isinstance(item_type, coreschema.Object) and field.schema.items.properties is not None: + # This is an Array of Objects + array_props = {} + for prop_key, prop_value in field.schema.items.properties.items(): + array_props[prop_key] = { + 'description': getattr(prop_value, 'description', ''), + 'type': _get_schema_type(prop_value) or 'string', + } + + array_items['properties'] = array_props + + return array_items def _get_parameters(link, encoding): @@ -151,7 +178,7 @@ def _get_parameters(link, encoding): 'type': field_type, } if field_type == 'array': - parameter['items'] = {'type': 'string'} + parameter['items'] = _get_array_schema_items(field) parameters.append(parameter) else: # Expand coreapi fields with location='form' into a single swagger @@ -162,22 +189,27 @@ def _get_parameters(link, encoding): 'type': field_type, } if field_type == 'array': - schema_property['items'] = {'type': 'string'} + schema_property['items'] = _get_array_schema_items(field) properties[field.name] = schema_property if field.required: required.append(field.name) elif location == 'body': + schema = { + 'type': field_type or 'string', + } + if encoding == 'application/octet-stream': # https://github.com/OAI/OpenAPI-Specification/issues/50#issuecomment-112063782 schema = {'type': 'string', 'format': 'binary'} - else: - schema = {} + elif field_type == 'array': + schema['items'] = _get_array_schema_items(field) + parameter = { 'name': field.name, 'required': field.required, 'in': location, 'description': field_description, - 'schema': schema + 'schema': schema, } parameters.append(parameter) else: @@ -189,7 +221,7 @@ def _get_parameters(link, encoding): 'type': field_type or 'string', } if field_type == 'array': - parameter['items'] = {'type': 'string'} + parameter['items'] = _get_array_schema_items(field) parameters.append(parameter) if properties: diff --git a/tests/test_encode.py b/tests/test_encode.py index 429a9f0..9fe221c 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -106,3 +106,26 @@ def test_expected_fields(self): 'type': 'string' # Everything is a string for now. } self.assertEquals(self.swagger[0], expected) + + +class TestDeprecatedFieldTypeParameters(TestCase): + def setUp(self): + self.field = coreapi.Field( + name='email', + required=True, + location='query', + description='A valid email address.', + type='string', + ) + self.swagger = _get_parameters(coreapi.Link(fields=[self.field]), encoding='') + + def test_expected_fields(self): + self.assertEquals(len(self.swagger), 1) + expected = { + 'name': self.field.name, + 'required': self.field.required, + 'in': 'query', + 'description': self.field.description, + 'type': 'string' # Everything is a string for now. + } + assert self.swagger[0] == expected diff --git a/tests/test_encode_arrays.py b/tests/test_encode_arrays.py new file mode 100644 index 0000000..ea0af4d --- /dev/null +++ b/tests/test_encode_arrays.py @@ -0,0 +1,201 @@ +import coreapi +import coreschema +from openapi_codec.encode import _get_parameters, _get_schema_type +from unittest import TestCase + + +def make_array_json(json_template, array_schema, location, field=None): + if location == 'body': + # In Swagger 2.0, arrays in the body are defined in the schema attribute + schema = { + 'schema': array_schema + } + for key in schema: + json_template[key] = schema[key] + elif location == 'form': + json_template['schema']['properties'][field.name] = array_schema + else: + # In Swagger 2.0, arrays not in the body are defined right in the field properties + schema = array_schema + for key in schema: + json_template[key] = schema[key] + + return json_template + + +class TestArrayParameters(TestCase): + def setUp(self): + self.maxDiff = None + self.encoding = '' + + self.definitions = [] + for location in ['body', 'query']: + field = coreapi.Field( + name='data', + required=True, + location=location, + description='Array of Anything', + schema=coreschema.Array() + ) + self.definitions.append(dict( + python=field, + json=make_array_json({ + 'name': field.name, + 'required': field.required, + 'in': location, + 'description': field.description, + }, { + 'type': 'array', + 'items': {}, + }, location) + )) + + for schema_type in coreschema.__all__: + schema = None + native_type = None + + try: + schema = schema_type() + native_type = _get_schema_type(schema) + except Exception: + pass + + if native_type is not None and (isinstance(schema_type, coreschema.String) or native_type != 'string'): + field = coreapi.Field( + name='data', + required=True, + location=location, + description='Array of %s' % native_type.capitalize() + 's', + schema=coreschema.Array(items=schema) + ) + self.definitions.append(dict( + python=field, + json=make_array_json({ + 'name': field.name, + 'required': field.required, + 'in': location, + 'description': field.description, + }, { + 'type': 'array', + 'items': { + 'type': native_type, + } + }, location) + )) + + field = coreapi.Field( + name='data', + required=True, + location=location, + description='Array of Objects with Properties', + schema=coreschema.Array( + items=coreschema.Object( + properties={ + 'id': coreschema.Integer(), + } + ) + ) + ) + + self.definitions.append(dict( + python=field, + json=make_array_json({ + 'name': field.name, + 'required': field.required, + 'in': location, + 'description': field.description, + }, { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'id': { + 'description': '', + 'type': 'integer' + }, + }, + } + }, location) + )) + + def test_expected_path_fields(self): + for d in self.definitions: + swagger = _get_parameters(coreapi.Link(fields=[d['python']]), encoding=self.encoding) + self.assertEquals(swagger[0], d['json'], msg='Encoded JSON value didn\'t match for %s' % d['python'].description) + + def test_unsupported_item_type(self): + field = coreapi.Field( + name='data', + required=True, + location='body', + description='Array of Strings and Integers', + schema=coreschema.Array( + items=[coreschema.String(), coreschema.Integer()], + ) + ) + + with self.assertRaises(TypeError): + _get_parameters(coreapi.Link(fields=[field]), encoding=self.encoding) + + +class TestArrayFormParameters(TestArrayParameters): + def setUp(self): + self.maxDiff = None + self.encoding = '' + self.definitions = [] + + location = 'form' + + field = coreapi.Field( + name='data', + required=True, + location=location, + description='Array of Anything', + schema=coreschema.Array() + ) + self.definitions.append(dict( + python=field, + json=make_array_json({ + 'name': field.name, + 'in': 'body', + 'schema': { + 'required': [field.name], + 'type': 'object', + 'properties': {}, + }, + }, { + 'type': 'array', + 'description': field.description, + 'items': {}, + }, location, field) + )) + + +class TestArrayEncodedFormParameters(TestArrayParameters): + def setUp(self): + self.maxDiff = None + self.encoding = 'multipart/form-data' + self.definitions = [] + + location = 'form' + swagger_location = 'formData' + + field = coreapi.Field( + name='data', + required=True, + location=location, + description='Array of Anything', + schema=coreschema.Array() + ) + self.definitions.append(dict( + python=field, + json=make_array_json({ + 'name': field.name, + 'required': field.required, + 'in': swagger_location, + 'description': field.description, + }, { + 'type': 'array', + 'items': {}, + }, swagger_location) + )) diff --git a/tox.ini b/tox.ini index 3e9707d..e1ba79f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py34,py27 +envlist = py37,py36,py35,py34,py27 [testenv] deps = -rrequirements.txt commands = ./runtests pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy