Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing response documentation for OpenAPI 3.x #153

Open
wants to merge 1 commit 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
6 changes: 4 additions & 2 deletions flask_apispec/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def wrapper(func):
return activate(func)
return wrapper


def marshal_with(schema, code='default', description='', inherit=None, apply=None):
def marshal_with(schema, code='default', description='', content_type=None,
inherit=None, apply=None):
"""Marshal the return value of the decorated view function using the
specified schema.

Expand All @@ -57,6 +57,7 @@ def get_pet(pet_id):
:param schema: :class:`Schema <marshmallow.Schema>` class or instance, or `None`
:param code: Optional HTTP response code
:param description: Optional response description
:param content_type: Optional response content type header (only used in OpenAPI 3.x)
:param inherit: Inherit schemas from parent classes
:param apply: Marshal response with specified schema
"""
Expand All @@ -65,6 +66,7 @@ def wrapper(func):
code: {
'schema': schema or {},
'description': description,
'content_type': content_type,
},
}
annotate(func, 'schemas', [options], inherit=inherit, apply=apply)
Expand Down
21 changes: 20 additions & 1 deletion flask_apispec/apidoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,26 @@ def get_parameters(self, rule, view, docs, parent=None):

def get_responses(self, view, parent=None):
annotation = resolve_annotations(view, 'schemas', parent)
return merge_recursive(annotation.options)
options = []
for option in annotation.options:
exploded = {}
for status_code, meta in option.items():
if self.spec.openapi_version.major < 3:
meta.pop('content_type', None)
exploded[status_code] = meta
else:
content_type = meta['content_type'] or 'application/json'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rusnyder I think it would be better if you use the get method to get the data so that it won't get errors when content_type does not exist in the meta. Something like this:

content_type = meta.get('content_type') or 'application/json'

exploded[status_code] = {
'content': {
content_type: {
'schema': meta['schema']
}
}
}
if meta['description']:
exploded[status_code]['description'] = meta['description']
options.append(exploded)
return merge_recursive(options)

class ViewConverter(Converter):

Expand Down
54 changes: 54 additions & 0 deletions tests/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ def spec(marshmallow_plugin):
plugins=[marshmallow_plugin],
)

@pytest.fixture
def spec_oapi3(marshmallow_plugin):
return APISpec(
title='title',
version='v1',
openapi_version='3.0',
plugins=[marshmallow_plugin],
)

@pytest.fixture()
def openapi(marshmallow_plugin):
return marshmallow_plugin.openapi
Expand Down Expand Up @@ -88,6 +97,51 @@ def test_responses(self, schemas, path, openapi):
def test_tags(self, path):
assert path['get']['tags'] == ['band']

class TestFunctionView_OpenAPI3:

@pytest.fixture
def function_view(self, app, models, schemas):
@app.route('/bands/<int:band_id>/')
@doc(tags=['band'])
@use_kwargs({'name': fields.Str(missing='queen')}, locations=('query',))
@marshal_with(schemas.BandSchema, description='a band', content_type='text/json')
def get_band(band_id):
return models.Band(name='slowdive', genre='spacerock')

return get_band

@pytest.fixture
def path(self, app, spec_oapi3, function_view):
converter = ViewConverter(app=app, spec=spec_oapi3)
paths = converter.convert(function_view)
for path in paths:
spec_oapi3.path(**path)
return spec_oapi3._paths['/bands/{band_id}/']

def test_params(self, app, path):
params = path['get']['parameters']
rule = app.url_map._rules_by_endpoint['get_band'][0]
expected = (
[{
'in': 'query',
'name': 'name',
'required': False,
'schema': {
'type': 'string',
'default': 'queen',
}
}] + rule_to_params(rule)
)
assert params == expected

def test_responses(self, schemas, path, openapi):
response = path['get']['responses']['default']
assert response['description'] == 'a band'
assert response['content'] == {'text/json': {'schema': {'$ref': ref_path(openapi.spec) + 'Band'}}}

def test_tags(self, path):
assert path['get']['tags'] == ['band']

class TestArgSchema:

@pytest.fixture
Expand Down