Skip to content

Commit f16a375

Browse files
committed
Added support for Array types.
1 parent baa97e0 commit f16a375

File tree

4 files changed

+254
-14
lines changed

4 files changed

+254
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ site/
88
__pycache__
99
.cache
1010
.coverage
11+
.idea/

openapi_codec/encode.py

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ def _get_field_description(field):
110110
return field.schema.description
111111

112112

113+
def _get_schema_type(schema):
114+
return {
115+
coreschema.String: 'string',
116+
coreschema.Integer: 'integer',
117+
coreschema.Number: 'number',
118+
coreschema.Boolean: 'boolean',
119+
coreschema.Array: 'array',
120+
coreschema.Object: 'object',
121+
}.get(schema.__class__, 'string')
122+
123+
113124
def _get_field_type(field):
114125
if getattr(field, 'type', None) is not None:
115126
# Deprecated
@@ -118,14 +129,27 @@ def _get_field_type(field):
118129
if field.schema is None:
119130
return 'string'
120131

121-
return {
122-
coreschema.String: 'string',
123-
coreschema.Integer: 'integer',
124-
coreschema.Number: 'number',
125-
coreschema.Boolean: 'boolean',
126-
coreschema.Array: 'array',
127-
coreschema.Object: 'object',
128-
}.get(field.schema.__class__, 'string')
132+
return _get_schema_type(field.schema)
133+
134+
135+
def _get_array_schema_items(field):
136+
array_items = {}
137+
138+
item_type = field.schema.items
139+
if not isinstance(item_type, coreschema.Anything):
140+
array_items['type'] = _get_schema_type(item_type) or 'string'
141+
if isinstance(item_type, coreschema.Object) and field.schema.items.properties is not None:
142+
# This is an Array of Objects
143+
array_props = {}
144+
for prop_key, prop_value in field.schema.items.properties.items():
145+
array_props[prop_key] = {
146+
'description': getattr(prop_value, 'description', ''),
147+
'type': _get_schema_type(prop_value) or 'string',
148+
}
149+
150+
array_items['properties'] = array_props
151+
152+
return array_items
129153

130154

131155
def _get_parameters(link, encoding):
@@ -151,7 +175,7 @@ def _get_parameters(link, encoding):
151175
'type': field_type,
152176
}
153177
if field_type == 'array':
154-
parameter['items'] = {'type': 'string'}
178+
parameter['items'] = _get_array_schema_items(field)
155179
parameters.append(parameter)
156180
else:
157181
# Expand coreapi fields with location='form' into a single swagger
@@ -162,22 +186,27 @@ def _get_parameters(link, encoding):
162186
'type': field_type,
163187
}
164188
if field_type == 'array':
165-
schema_property['items'] = {'type': 'string'}
189+
schema_property['items'] = _get_array_schema_items(field)
166190
properties[field.name] = schema_property
167191
if field.required:
168192
required.append(field.name)
169193
elif location == 'body':
194+
schema = {
195+
'type': field_type or 'string',
196+
}
197+
170198
if encoding == 'application/octet-stream':
171199
# https://github.com/OAI/OpenAPI-Specification/issues/50#issuecomment-112063782
172200
schema = {'type': 'string', 'format': 'binary'}
173-
else:
174-
schema = {}
201+
elif field_type == 'array':
202+
schema['items'] = _get_array_schema_items(field)
203+
175204
parameter = {
176205
'name': field.name,
177206
'required': field.required,
178207
'in': location,
179208
'description': field_description,
180-
'schema': schema
209+
'schema': schema,
181210
}
182211
parameters.append(parameter)
183212
else:
@@ -189,7 +218,7 @@ def _get_parameters(link, encoding):
189218
'type': field_type or 'string',
190219
}
191220
if field_type == 'array':
192-
parameter['items'] = {'type': 'string'}
221+
parameter['items'] = _get_array_schema_items(field)
193222
parameters.append(parameter)
194223

195224
if properties:

tests/test_encode.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,26 @@ def test_expected_fields(self):
106106
'type': 'string' # Everything is a string for now.
107107
}
108108
self.assertEquals(self.swagger[0], expected)
109+
110+
111+
class TestDeprecatedFieldTypeParameters(TestCase):
112+
def setUp(self):
113+
self.field = coreapi.Field(
114+
name='email',
115+
required=True,
116+
location='query',
117+
description='A valid email address.',
118+
type='string',
119+
)
120+
self.swagger = _get_parameters(coreapi.Link(fields=[self.field]), encoding='')
121+
122+
def test_expected_fields(self):
123+
self.assertEquals(len(self.swagger), 1)
124+
expected = {
125+
'name': self.field.name,
126+
'required': self.field.required,
127+
'in': 'query',
128+
'description': self.field.description,
129+
'type': 'string' # Everything is a string for now.
130+
}
131+
assert self.swagger[0] == expected

tests/test_encode_arrays.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import coreapi
2+
import coreschema
3+
from openapi_codec.encode import _get_parameters, _get_schema_type
4+
from unittest import TestCase
5+
6+
7+
def make_array_json(json_template, array_schema, location, field=None):
8+
if location == 'body':
9+
# In Swagger 2.0, arrays in the body are defined in the schema attribute
10+
schema = {
11+
'schema': array_schema
12+
}
13+
for key in schema:
14+
json_template[key] = schema[key]
15+
elif location == 'form':
16+
json_template['schema']['properties'][field.name] = array_schema
17+
else:
18+
# In Swagger 2.0, arrays not in the body are defined right in the field properties
19+
schema = array_schema
20+
for key in schema:
21+
json_template[key] = schema[key]
22+
23+
return json_template
24+
25+
26+
class TestArrayParameters(TestCase):
27+
def setUp(self):
28+
self.maxDiff = None
29+
self.encoding = ''
30+
31+
self.definitions = []
32+
for location in ['body', 'query']:
33+
field = coreapi.Field(
34+
name='data',
35+
required=True,
36+
location=location,
37+
description='Array of Anything',
38+
schema=coreschema.Array()
39+
)
40+
self.definitions.append(dict(
41+
python=field,
42+
json=make_array_json({
43+
'name': field.name,
44+
'required': field.required,
45+
'in': location,
46+
'description': field.description,
47+
}, {
48+
'type': 'array',
49+
'items': {},
50+
}, location)
51+
))
52+
53+
for schema_type in coreschema.__all__:
54+
schema = None
55+
native_type = None
56+
57+
try:
58+
schema = schema_type()
59+
native_type = _get_schema_type(schema)
60+
except Exception:
61+
pass
62+
63+
if native_type is not None and (isinstance(schema_type, coreschema.String) or native_type != 'string'):
64+
field = coreapi.Field(
65+
name='data',
66+
required=True,
67+
location=location,
68+
description='Array of %s' % native_type.capitalize() + 's',
69+
schema=coreschema.Array(items=schema)
70+
)
71+
self.definitions.append(dict(
72+
python=field,
73+
json=make_array_json({
74+
'name': field.name,
75+
'required': field.required,
76+
'in': location,
77+
'description': field.description,
78+
}, {
79+
'type': 'array',
80+
'items': {
81+
'type': native_type,
82+
}
83+
}, location)
84+
))
85+
86+
field = coreapi.Field(
87+
name='data',
88+
required=True,
89+
location=location,
90+
description='Array of Objects with Properties',
91+
schema=coreschema.Array(
92+
items=coreschema.Object(
93+
properties={
94+
'id': coreschema.Integer(),
95+
}
96+
)
97+
)
98+
)
99+
100+
self.definitions.append(dict(
101+
python=field,
102+
json=make_array_json({
103+
'name': field.name,
104+
'required': field.required,
105+
'in': location,
106+
'description': field.description,
107+
}, {
108+
'type': 'array',
109+
'items': {
110+
'type': 'object',
111+
'properties': {
112+
'id': {
113+
'description': '',
114+
'type': 'integer'
115+
},
116+
},
117+
}
118+
}, location)
119+
))
120+
121+
def test_expected_path_fields(self):
122+
for d in self.definitions:
123+
swagger = _get_parameters(coreapi.Link(fields=[d['python']]), encoding=self.encoding)
124+
self.assertEquals(swagger[0], d['json'], msg='Encoded JSON value didn\'t match for %s' % d['python'].description)
125+
126+
127+
class TestArrayFormParameters(TestArrayParameters):
128+
def setUp(self):
129+
self.maxDiff = None
130+
self.encoding = ''
131+
self.definitions = []
132+
133+
location = 'form'
134+
135+
field = coreapi.Field(
136+
name='data',
137+
required=True,
138+
location=location,
139+
description='Array of Anything',
140+
schema=coreschema.Array()
141+
)
142+
self.definitions.append(dict(
143+
python=field,
144+
json=make_array_json({
145+
'name': field.name,
146+
'in': 'body',
147+
'schema': {
148+
'required': [field.name],
149+
'type': 'object',
150+
'properties': {},
151+
},
152+
}, {
153+
'type': 'array',
154+
'description': field.description,
155+
'items': {},
156+
}, location, field)
157+
))
158+
159+
160+
class TestArrayEncodedFormParameters(TestArrayParameters):
161+
def setUp(self):
162+
self.maxDiff = None
163+
self.encoding = 'multipart/form-data'
164+
self.definitions = []
165+
166+
location = 'form'
167+
swagger_location = 'formData'
168+
169+
field = coreapi.Field(
170+
name='data',
171+
required=True,
172+
location=location,
173+
description='Array of Anything',
174+
schema=coreschema.Array()
175+
)
176+
self.definitions.append(dict(
177+
python=field,
178+
json=make_array_json({
179+
'name': field.name,
180+
'required': field.required,
181+
'in': swagger_location,
182+
'description': field.description,
183+
}, {
184+
'type': 'array',
185+
'items': {},
186+
}, swagger_location)
187+
))

0 commit comments

Comments
 (0)