# -*- coding: utf-8 -*-
import flask
import functools
import types
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from flask_apispec import ResourceMeta
from flask_apispec.apidoc import ViewConverter, ResourceConverter
[docs]class FlaskApiSpec(object):
"""Flask-apispec extension.
Usage:
.. code-block:: python
app = Flask(__name__)
app.config.update({
'APISPEC_SPEC': APISpec(
title='pets',
version='v1',
plugins=[MarshmallowPlugin()],
),
'APISPEC_SWAGGER_URL': '/swagger/',
})
docs = FlaskApiSpec(app)
@app.route('/pet/<pet_id>')
def get_pet(pet_id):
return Pet.query.filter(Pet.id == pet_id).one()
docs.register(get_pet)
:param Flask app: App associated with API documentation
:param APISpec spec: apispec specification associated with API documentation
"""
def __init__(self, app=None):
self._deferred = []
self.app = app
self.view_converter = None
self.resource_converter = None
self.spec = None
if app:
self.init_app(app)
def init_app(self, app):
self.app = app
self.spec = self.app.config.get('APISPEC_SPEC') or \
make_apispec(self.app.config.get('APISPEC_TITLE', 'flask-apispec'),
self.app.config.get('APISPEC_VERSION', 'v1'))
self.add_swagger_routes()
self.resource_converter = ResourceConverter(self.app, spec=self.spec)
self.view_converter = ViewConverter(app=self.app, spec=self.spec)
for deferred in self._deferred:
deferred()
def _defer(self, callable, *args, **kwargs):
bound = functools.partial(callable, *args, **kwargs)
self._deferred.append(bound)
if self.app:
bound()
def add_swagger_routes(self):
blueprint = flask.Blueprint(
'flask-apispec',
__name__,
static_folder='./static',
template_folder='./templates',
static_url_path='/flask-apispec/static',
)
json_url = self.app.config.get('APISPEC_SWAGGER_URL', '/swagger/')
if json_url:
blueprint.add_url_rule(json_url, 'swagger-json', self.swagger_json)
ui_url = self.app.config.get('APISPEC_SWAGGER_UI_URL', '/swagger-ui/')
if ui_url:
blueprint.add_url_rule(ui_url, 'swagger-ui', self.swagger_ui)
self.app.register_blueprint(blueprint)
def swagger_json(self):
return flask.jsonify(self.spec.to_dict())
def swagger_ui(self):
return flask.render_template('swagger-ui.html')
def register_existing_resources(self):
for name, rule in self.app.view_functions.items():
try:
blueprint_name, _ = name.split('.')
except ValueError:
blueprint_name = None
try:
self.register(rule, blueprint=blueprint_name)
except TypeError:
pass
[docs] def register(self, target, endpoint=None, blueprint=None,
resource_class_args=None, resource_class_kwargs=None):
"""Register a view.
:param target: view function or view class.
:param endpoint: (optional) endpoint name.
:param blueprint: (optional) blueprint name.
:param tuple resource_class_args: (optional) args to be forwarded to the
view class constructor.
:param dict resource_class_kwargs: (optional) kwargs to be forwarded to
the view class constructor.
"""
self._defer(self._register, target, endpoint, blueprint,
resource_class_args, resource_class_kwargs)
def _register(self, target, endpoint=None, blueprint=None,
resource_class_args=None, resource_class_kwargs=None):
"""Register a view.
:param target: view function or view class.
:param endpoint: (optional) endpoint name.
:param blueprint: (optional) blueprint name.
:param tuple resource_class_args: (optional) args to be forwarded to the
view class constructor.
:param dict resource_class_kwargs: (optional) kwargs to be forwarded to
the view class constructor.
"""
if isinstance(target, types.FunctionType):
paths = self.view_converter.convert(target, endpoint, blueprint)
elif isinstance(target, ResourceMeta):
paths = self.resource_converter.convert(
target,
endpoint,
blueprint,
resource_class_args=resource_class_args,
resource_class_kwargs=resource_class_kwargs,
)
else:
raise TypeError()
for path in paths:
self.spec.add_path(**path)
def make_apispec(title='flask-apispec', version='v1'):
return APISpec(
title=title,
version=version,
plugins=[MarshmallowPlugin()],
)