apispec: Flask extension does not support blueprints

I have organized my views with Flask blueprints, but am unable to document them in the view file, because it is not executed in the application context.

# app/views/example.py

from flask import Blueprint
example_view = Blueprint('example_view', __name__)

from app.spec import spec

@bp.route('example', methods=['GET'])
def get_example():
    """An example.
    ---
    get:
        description: Get an example
        responses:
            200:
                description: An example
                schema: ExampleSchema
    """
    return 'example', 200

spec.add_path(view=get_example)
  ...
  File "/usr/local/lib/python2.7/site-packages/apispec/core.py", line 170, in add_path
    self, path=path, operations=operations, **kwargs
  File "/usr/local/lib/python2.7/site-packages/apispec/ext/flask.py", line 62, in path_from_view
    rule = _rule_for_view(view)
  File "/usr/local/lib/python2.7/site-packages/apispec/ext/flask.py", line 38, in _rule_for_view
    view_funcs = current_app.view_functions
  File "/usr/local/lib/python2.7/site-packages/werkzeug/local.py", line 343, in __getattr__
    return getattr(self._get_current_object(), name)
  File "/usr/local/lib/python2.7/site-packages/werkzeug/local.py", line 302, in _get_current_object
    return self.__local()
  File "/usr/local/lib/python2.7/site-packages/flask/globals.py", line 34, in _find_app
    raise RuntimeError('working outside of application context')
RuntimeError: working outside of application context

Is there a way to keep the spec declarations with the blueprint? (It seems like there might not be.)

Do you think it would be useful to add the ability to add all the views from a blueprint at once?

I noticed that the flask extension seems to acknowledge that a view could contain multiple paths, but assumes it only contains one. apispec/ext/flask.py#L46

Maybe something like spec.add_paths() could be added to handle compound view objects?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 3
  • Comments: 19 (12 by maintainers)

Most upvoted comments

A flask app with blueprints starts out looking something like:

from flask import Flask
from my_app.views.blueprint1 import blueprint1

app = Flask(__name__)

app.register_blueprint(blueprint1)

Blueprints are not defined in the flask application context, so the paths must be register to apispec in the flask app context:

from flask import Flask
from apispec import APISpec
from views.blueprint1 import blueprint1, view1, view2, view3, ...

spec = APISpec( ... )
app = Flask(__name__)

app.register_blueprint(blueprint1)

spec.add_path(view=view1)
spec.add_path(view=view2)
spec.add_path(view=view3)
...

This defeats the benefit of blueprints encapsulating view specific details away from the core application logic.

My current alternative is to import the app into a spec file and explicitly register the views within the app context. My core app logic is no longer aware of the blueprints implementation details, but I am still leaking those details outside of the blueprint.

Having the flask extension be able to register blueprints would correct the app blueprint encapsulation issue:

from flask import Flask
from apispec import APISpec
from views.blueprint1 import blueprint1

spec = APISpec( ... )
app = Flask(__name__)

app.register_blueprint(blueprint1)
spec.add_paths(views=blueprint1)

Having the flask extension add all the paths in an application at once would be icing on the cake.

from flask import Flask
from apispec import APISpec
from views.blueprint1 import blueprint1
from views.blueprint2 import blueprint2
from views.blueprint3 import blueprint3
...

spec = APISpec( ... )
app = Flask(__name__)

app.register_blueprint(blueprint1)
app.register_blueprint(blueprint2)
app.register_blueprint(blueprint3)
...

spec.add_paths(views=app)

This is really about providing a blessed pattern for large apps that have turned to blueprints for organization, then find the apispec docs no longer apply.

Maybe add_paths() is outside the scope of this project. An alternative is a wrapper library like apispec-flask-blueprints (following the example of marshmallow-jsonapi).

Hello, so what’s the best approach for this issue? @sloria I would like to use blueprints.

I’ve come here because I try to add apispec to document an API that I have in a blueprint, I use flask view functions and not MethodViews, and ends up with a solution that believe interesting in this discussion.

As the url paths on flask are defined as Rules on Flask.url_map I define a helper based on path_from_view() that loads the path from a Rule object:

from apispec import Path, utils
from apispec.ext.flask import flaskpath2swagger

def path_from_rule(spec, rule, **kwargs):
    """Path helper that allows passing a Flask url Rule object."""
    path = flaskpath2swagger(rule.rule)
    view = current_app.view_functions.get(rule.endpoint)
    # Get operations from view function docstring
    operations = utils.load_operations_from_docstring(view.__doc__)
    return Path(path=path, operations=operations)

spec.register_path_helper(path_from_rule)

I ignored the current_app.config['APPLICATION_ROOT'] in path_from_view() as I couldn’t find a standard explanation about it’s use.

Then I created a function that iter the rules on url_map and add the path for the matching rules:

def add_paths_for_blueprint(spec, blueprint, exclude=()):
    bp_name = blueprint.name
    for r in current_app.url_map.iter_rules():
        ep = r.endpoint.split('.')
        if len(ep) == 1:  # App endpoint, not processed here
            break
        elif len(ep) == 2:  # Blueprint endpoint
            prefix, endpoint = ep[0], ep[1]
            if prefix == bp_name and endpoint not in exclude:
                spec.add_path(rule=r)
        else:
            raise ValueError("Not valid endpoint?", r.endpoint)

@bp.route('/swagger.json')
def get_swagger_json():
    spec = APISpec(...)  # Edited for clarity
    ...  # do other operation to spec, add definitions, etc.
    add_paths_for_blueprint(spec, bp, exclude=['get_swagger_json'])
    return jsonify(spec.to_dict()), 200

And that’s it, all the paths in my blueprint documented.

@deckar01:

app.register_blueprint(blueprint1) spec.add_paths(views=blueprint1)

Notice a blueprint can be registered with a custom url_prefix:

app.register_blueprint(simple_page, url_prefix='/pages')

So you may need to do:

app.register_blueprint(blueprint1, url_prefix=‘/pages’) spec.add_paths(views=blueprint1, url_prefix=‘/pages’)

Ah, I see; the benefit would be that plugins could implement a paths_helper that would abstract extracting paths. Thanks for the clarification.

An add_paths method is within scope of apispec. The question is whether is worth the increased API surface area. You presented a valid use case, and I would gladly review and merge a PR for it.

@andho If I remember well I had all my API inside a blueprint, sharing the Flask App with ‘normal’ web endpoints in other blueprints, but only one blueprint having the whole API.

Nothing avoids you to create the spec at the app level and use the add_paths_for_blueprint function to add the paths from different blueprints to the spec.

@tinproject Tried this out for myself almost worked perfectly, except for me the len(ep) == 1 condition should have resulted in a continue and not a break because there was an app route in between my blueprint routes. Not sure why that was the case but none the less thank you for this contribution!