diff --git a/flask_rest_api/blueprint.py b/flask_rest_api/blueprint.py index 38258616..9b485297 100644 --- a/flask_rest_api/blueprint.py +++ b/flask_rest_api/blueprint.py @@ -184,13 +184,20 @@ def _prepare_doc(operation, openapi_version): versions: the OpenAPI version is not known when the decorators are applied but only at registration time when this method is called. """ - if openapi_version.major >= 3: + if openapi_version.major < 3: if 'responses' in operation: for resp in operation['responses'].values(): - if 'schema' in resp: - resp['content'] = { - 'application/json': { - 'schema': resp.pop('schema')}} + if 'example' in resp: + resp['examples'] = { + 'application/json': resp.pop('example')} + else: + if 'responses' in operation: + for resp in operation['responses'].values(): + for field in ('schema', 'example', 'examples'): + if field in resp: + resp.setdefault('content', {}) + resp['content'].setdefault('application/json', {})[ + field] = resp.pop(field) if 'parameters' in operation: for param in operation['parameters']: if param['in'] == 'body': diff --git a/flask_rest_api/response.py b/flask_rest_api/response.py index 10122d51..28e0ddce 100644 --- a/flask_rest_api/response.py +++ b/flask_rest_api/response.py @@ -15,7 +15,10 @@ class ResponseMixin: """Extend Blueprint to add response handling""" - def response(self, schema=None, *, code=200, description=None): + def response( + self, schema=None, *, code=200, description=None, + example=None, examples=None, headers=None + ): """Decorator generating an endpoint response :param schema: :class:`Schema ` class or instance. @@ -23,6 +26,9 @@ def response(self, schema=None, *, code=200, description=None): :param int code: HTTP status code (default: 200). Used if none is returned from the view function. :param str description: Description of the response (default: None). + :param dict example: Example of response message. + :param list examples: Examples of response message. + :param dict headers: Headers returned by the response. The decorated function is expected to return the same types of value than a typical flask view function, except the body part may be an @@ -32,6 +38,12 @@ def response(self, schema=None, *, code=200, description=None): If the decorated function returns a ``Response`` object, the ``schema`` and ``code`` parameters are only used to document the resource. + The `example` and `examples` parameters are mutually exclusive. The + latter should only be used with OpenAPI 3. + + The `example`, `examples` and `headers` parameters are only used to + document the resource. + See :doc:`Response `. """ if isinstance(schema, type): @@ -46,6 +58,12 @@ def decorator(func): resp_doc['schema'] = doc_schema if description is not None: resp_doc['description'] = description + if example is not None: + resp_doc['example'] = example + if examples is not None: + resp_doc['examples'] = examples + if headers is not None: + resp_doc['headers'] = headers doc = {'responses': {str(code): resp_doc}} func._apidoc = deepupdate(getattr(func, '_apidoc', {}), doc) diff --git a/setup.py b/setup.py index cba1a8c1..66a142d4 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,6 @@ 'flask>=1.0', 'marshmallow>=2.15.2', 'webargs>=1.5.2', - 'apispec>=1.0.0', + 'apispec>=1.3.2', ], ) diff --git a/tests/test_blueprint.py b/tests/test_blueprint.py index ce93dd5f..d4f2e812 100644 --- a/tests/test_blueprint.py +++ b/tests/test_blueprint.py @@ -259,6 +259,68 @@ def func_2(): get_2 = api.spec.to_dict()['paths']['/test/route_2']['get'] assert get_2['responses']['200']['description'] == 'Test' + @pytest.mark.parametrize('openapi_version', ('2.0', '3.0.2')) + def test_blueprint_response_example(self, app, openapi_version): + app.config['OPENAPI_VERSION'] = openapi_version + api = Api(app) + blp = Blueprint('test', 'test', url_prefix='/test') + + example = {'name': 'One'} + + @blp.route('/') + @blp.response(example=example) + def func(): + pass + + api.register_blueprint(blp) + + get = api.spec.to_dict()['paths']['/test/']['get'] + if openapi_version == '2.0': + assert get['responses']['200']['examples'][ + 'application/json'] == example + else: + assert get['responses']['200']['content'][ + 'application/json']['example'] == example + + # This is only relevant to OAS3. + @pytest.mark.parametrize('openapi_version', ('3.0.2', )) + def test_blueprint_response_examples(self, app, openapi_version): + app.config['OPENAPI_VERSION'] = openapi_version + api = Api(app) + blp = Blueprint('test', 'test', url_prefix='/test') + + examples = { + 'example 1': {'summary': 'Example 1', 'value': {'name': 'One'}}, + 'example 2': {'summary': 'Example 2', 'value': {'name': 'Two'}}, + } + + @blp.route('/') + @blp.response(examples=examples) + def func(): + pass + + api.register_blueprint(blp) + + get = api.spec.to_dict()['paths']['/test/']['get'] + assert get['responses']['200']['content']['application/json'][ + 'examples'] == examples + + def test_blueprint_response_headers(self, app): + api = Api(app) + blp = Blueprint('test', 'test', url_prefix='/test') + + headers = {'X-Header': {'description': 'Custom header'}} + + @blp.route('/') + @blp.response(headers=headers) + def func(): + pass + + api.register_blueprint(blp) + + get = api.spec.to_dict()['paths']['/test/']['get'] + assert get['responses']['200']['headers'] == headers + @pytest.mark.parametrize('openapi_version', ('2.0', '3.0.2')) def test_blueprint_pagination(self, app, schemas, openapi_version): app.config['OPENAPI_VERSION'] = openapi_version