Skip to content

Commit

Permalink
Merge manual doc added with @doc after the call to _prepare_doc
Browse files Browse the repository at this point in the history
  • Loading branch information
lafrech committed Nov 17, 2018
1 parent 46fd81a commit 7e0a7d7
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 15 deletions.
46 changes: 31 additions & 15 deletions flask_rest_api/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,27 @@
- At import time
- When a MethodView or a function is decorated, relevant information
is added to the object's `_apispec` attribute.
- When a MethodView or a view function is decorated, relevant information
is automatically added to the object's ``_apidoc`` attribute.
- The `route` decorator registers the endpoint in the Blueprint and gathers
all information about the endpoint in `Blueprint._docs[endpoint]`
- The ``Blueprint.doc`` decorator stores additional information in a separate
``_api_manual_doc``. It allows the user to specify documentation
information that flask-rest-api can not - or does not yet - infer from the
code.
- The ``Blueprint.route`` decorator registers the endpoint in the Blueprint
and gathers all information about the endpoint in
``Blueprint._docs[endpoint]``.
- At initialization time
- Schema instances are replaced either by their reference in the `definition`
section of the spec if applicable, otherwise by their json representation.
- Endpoints documentation is registered in the APISpec object
- Automatic documentation is adapted to OpenAPI version and deep-merged with
manual documentation.
- Endpoints documentation is registered in the APISpec object.
"""

from collections import OrderedDict
Expand Down Expand Up @@ -103,13 +112,15 @@ def _store_endpoint_docs(self, endpoint, obj, **kwargs):
endpoint_doc = self._docs.setdefault(endpoint, OrderedDict())

def store_method_docs(method, function):
"""Add auto and manual doc to table for later registration"""
# Get summary/description from docstring
docstring = function.__doc__
doc = load_info_from_docstring(docstring) if docstring else {}
# Update doc with description from @doc decorator
doc.update(getattr(function, '_apidoc', {}))
# Add function doc to table for later registration
endpoint_doc[method.lower()] = doc
autodoc = load_info_from_docstring(docstring) if docstring else {}
# Update doc with auto documentation from decorators
autodoc.update(getattr(function, '_apidoc', {}))
manual_doc = getattr(function, '_api_manual_doc', {})
# Add function auto and manual doc to table for later registration
endpoint_doc[method.lower()] = autodoc, manual_doc

# MethodView (class)
if isinstance(obj, MethodViewType):
Expand Down Expand Up @@ -142,11 +153,13 @@ def register_views_in_doc(self, app, spec):
# Prepend Blueprint name to endpoint
endpoint = '.'.join((self.name, endpoint))

# Tag all operations with Blueprint name
# Format operations documentation in OpenAPI structure
for operation in doc.values():
operation['tags'] = [self.name]
self._prepare_doc(operation, spec.openapi_version)
# Tag all operations with Blueprint name
# Merge manual doc
for key, (auto_doc, manual_doc) in doc.items():
self._prepare_doc(auto_doc, spec.openapi_version)
auto_doc['tags'] = [self.name]
doc[key] = deepupdate(auto_doc, manual_doc)

# Thanks to self.route, there can only be one rule per endpoint
rule = next(app.url_map.iter_rules(endpoint))
Expand Down Expand Up @@ -217,6 +230,9 @@ def get(...):
...
"""
def decorator(func):
func._apidoc = deepupdate(getattr(func, '_apidoc', {}), kwargs)
# Don't merge manual doc with auto-documentation right now.
# Store it in a separate attribute to merge it after .
func._api_manual_doc = deepupdate(
getattr(func, '_api_manual_doc', {}), kwargs)
return func
return decorator
48 changes: 48 additions & 0 deletions tests/test_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import json
import pytest

import marshmallow as ma

from flask import jsonify
from flask.views import MethodView

Expand Down Expand Up @@ -282,6 +284,52 @@ def patch(self):
assert path[method]['summary'] == 'Dummy {}'.format(method)
assert path[method]['description'] == 'Do dummy {}'.format(method)

def test_blueprint_doc_called_twice(self, app):
api = Api(app)
blp = Blueprint('test', __name__, url_prefix='/test')

@blp.route('/')
@blp.doc(summary='Dummy func')
@blp.doc(description='Do dummy stuff')
def view_func():
pass

api.register_blueprint(blp)
spec = api.spec.to_dict()
path = spec['paths']['/test/']
assert path['get']['summary'] == 'Dummy func'
assert path['get']['description'] == 'Do dummy stuff'

# Regression test for https://github.com/Nobatek/flask-rest-api/issues/19
def test_blueprint_doc_merged_after_prepare_doc(self, app):
app.config['OPENAPI_VERSION'] = '3.0.1'
api = Api(app)
blp = Blueprint('test', __name__, url_prefix='/test')

doc_example = {
'content': {'application/json': {'example': {'test': 123}}}}

class ItemSchema(ma.Schema):
test = ma.fields.Int()

@blp.route('/')
class Resource(MethodView):

@blp.doc(**{'requestBody': doc_example})
@blp.doc(**{'responses': {200: doc_example}})
@blp.arguments(ItemSchema)
@blp.response(ItemSchema)
def get(self):
pass

api.register_blueprint(blp)
spec = api.spec.to_dict()
get = spec['paths']['/test/']['get']
assert get['requestBody']['content']['application/json'][
'example'] == {'test': 123}
assert get['responses'][200]['content']['application/json'][
'example'] == {'test': 123}

def test_blueprint_doc_info_from_docstring(self, app):
api = Api(app)
blp = Blueprint('test', __name__, url_prefix='/test')
Expand Down

0 comments on commit 7e0a7d7

Please sign in to comment.