Skip to content

Commit 60779ef

Browse files
committed
Fixing response documentation for OpenAPI 3.x
1 parent b51962a commit 60779ef

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

flask_apispec/annotations.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ def wrapper(func):
3737
return activate(func)
3838
return wrapper
3939

40-
41-
def marshal_with(schema, code='default', description='', inherit=None, apply=None):
40+
def marshal_with(schema, code='default', description='', content_type=None,
41+
inherit=None, apply=None):
4242
"""Marshal the return value of the decorated view function using the
4343
specified schema.
4444
@@ -57,6 +57,7 @@ def get_pet(pet_id):
5757
:param schema: :class:`Schema <marshmallow.Schema>` class or instance, or `None`
5858
:param code: Optional HTTP response code
5959
:param description: Optional response description
60+
:param content_type: Optional response content type header (only used in OpenAPI 3.x)
6061
:param inherit: Inherit schemas from parent classes
6162
:param apply: Marshal response with specified schema
6263
"""
@@ -65,6 +66,7 @@ def wrapper(func):
6566
code: {
6667
'schema': schema or {},
6768
'description': description,
69+
'content_type': content_type,
6870
},
6971
}
7072
annotate(func, 'schemas', [options], inherit=inherit, apply=apply)

flask_apispec/apidoc.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,26 @@ def get_parameters(self, rule, view, docs, parent=None):
9393

9494
def get_responses(self, view, parent=None):
9595
annotation = resolve_annotations(view, 'schemas', parent)
96-
return merge_recursive(annotation.options)
96+
options = []
97+
for option in annotation.options:
98+
exploded = {}
99+
for status_code, meta in option.items():
100+
if self.spec.openapi_version.major < 3:
101+
meta.pop('content_type', None)
102+
exploded[status_code] = meta
103+
else:
104+
content_type = meta['content_type'] or 'application/json'
105+
exploded[status_code] = {
106+
'content': {
107+
content_type: {
108+
'schema': meta['schema']
109+
}
110+
}
111+
}
112+
if meta['description']:
113+
exploded[status_code]['description'] = meta['description']
114+
options.append(exploded)
115+
return merge_recursive(options)
97116

98117
class ViewConverter(Converter):
99118

tests/test_openapi.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ def spec(marshmallow_plugin):
2424
plugins=[marshmallow_plugin],
2525
)
2626

27+
@pytest.fixture
28+
def spec_oapi3(marshmallow_plugin):
29+
return APISpec(
30+
title='title',
31+
version='v1',
32+
openapi_version='3.0',
33+
plugins=[marshmallow_plugin],
34+
)
35+
2736
@pytest.fixture()
2837
def openapi(marshmallow_plugin):
2938
return marshmallow_plugin.openapi
@@ -88,6 +97,51 @@ def test_responses(self, schemas, path, openapi):
8897
def test_tags(self, path):
8998
assert path['get']['tags'] == ['band']
9099

100+
class TestFunctionView_OpenAPI3:
101+
102+
@pytest.fixture
103+
def function_view(self, app, models, schemas):
104+
@app.route('/bands/<int:band_id>/')
105+
@doc(tags=['band'])
106+
@use_kwargs({'name': fields.Str(missing='queen')}, locations=('query',))
107+
@marshal_with(schemas.BandSchema, description='a band', content_type='text/json')
108+
def get_band(band_id):
109+
return models.Band(name='slowdive', genre='spacerock')
110+
111+
return get_band
112+
113+
@pytest.fixture
114+
def path(self, app, spec_oapi3, function_view):
115+
converter = ViewConverter(app=app, spec=spec_oapi3)
116+
paths = converter.convert(function_view)
117+
for path in paths:
118+
spec_oapi3.path(**path)
119+
return spec_oapi3._paths['/bands/{band_id}/']
120+
121+
def test_params(self, app, path):
122+
params = path['get']['parameters']
123+
rule = app.url_map._rules_by_endpoint['get_band'][0]
124+
expected = (
125+
[{
126+
'in': 'query',
127+
'name': 'name',
128+
'required': False,
129+
'schema': {
130+
'type': 'string',
131+
'default': 'queen',
132+
}
133+
}] + rule_to_params(rule)
134+
)
135+
assert params == expected
136+
137+
def test_responses(self, schemas, path, openapi):
138+
response = path['get']['responses']['default']
139+
assert response['description'] == 'a band'
140+
assert response['content'] == {'text/json': {'schema': {'$ref': ref_path(openapi.spec) + 'Band'}}}
141+
142+
def test_tags(self, path):
143+
assert path['get']['tags'] == ['band']
144+
91145
class TestArgSchema:
92146

93147
@pytest.fixture

0 commit comments

Comments
 (0)