Skip to content
This repository was archived by the owner on Mar 18, 2019. It is now read-only.

Encode arrays #52

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ site/
__pycache__
.cache
.coverage
.idea/
60 changes: 46 additions & 14 deletions openapi_codec/encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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:
Expand Down
23 changes: 23 additions & 0 deletions tests/test_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
201 changes: 201 additions & 0 deletions tests/test_encode_arrays.py
Original file line number Diff line number Diff line change
@@ -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)
))
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py35,py34,py27
envlist = py37,py36,py35,py34,py27
[testenv]
deps = -rrequirements.txt
commands = ./runtests
Expand Down