Source code for flask_redoc

"""flask_redoc Module."""
import copy
import json
import os

import yaml
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
from flask import Blueprint, render_template
from jsonmerge import merge

from .version import __version__


[docs]class Redoc(object): """Redoc Object.""" DEFAULT_CONFIG = { 'endpoint': 'docs', 'spec_route': '/docs', 'static_url_path': '/redoc_static', 'title': 'ReDoc', 'version': '1.0.0', 'openapi_version': '3.0.2', 'use_cdn': True, 'info':dict(), 'plugins': [FlaskPlugin()], 'marshmallow_schemas': list() } def __init__(self, app=None, spec_file=None, config=None): """Init the Redoc object. :param spec_file: spec file path :param app: Flask app :param config: dictionary with Redoc configuration """ self.app = app self.spec_file = spec_file self.config = config or self.DEFAULT_CONFIG.copy() self.spec = None self._is_first_request = True if app is not None: self.init_app(app)
[docs] def init_app(self, app): """Initialize the Redoc. :param app: Flask app """ self.app = app self.config.update(self.app.config.get('REDOC', {})) if len(self.config['marshmallow_schemas']) > 0: self.config['plugins'].append(MarshmallowPlugin()) if self.spec_file is not None: self.spec_file = self.load_spec_file(self.spec_file) self.config['title'] = self.spec_file['info']['title'] self.config['version'] = self.spec_file['info']['version'] self.config['openapi_version'] = self.spec_file['openapi'] self.config['info'] = self.spec_file['info'] self.spec = APISpec(title=self.config['title'], version=self.config['version'], openapi_version=self.config['openapi_version'], info=self.config['info'], plugins=self.config['plugins']) self.app.before_request(self.docstrings_to_openapi) bp = Blueprint(self.config.get('endpoint', 'redoc'), __name__, url_prefix=self.config.get('url_prefix', None), template_folder=self.config.get( 'template_folder', 'templates'), static_folder=self.config.get( 'static_folder', 'static'), static_url_path=self.config.get('static_url_path')) bp.add_url_rule(self.config.get('spec_route'), 'docs', view_func=self.docs_view) bp.add_url_rule(self.config.get('spec_route')+'/json', 'docs_json', view_func=self.docs_json) self.app.register_blueprint(bp)
[docs] def docs_view(self): """Render the docs.html template.""" return render_template('docs.html', spec_file=self.spec_file, endpoint=self.config.get('endpoint', 'redoc'), title=self.config.get('title', 'ReDoc'), use_cdn=self.config.get('use_cdn', True))
[docs] def docs_json(self): return self.spec_file
[docs] def load_spec_file(self, filename): """Load the spec file. :param filename: spec filename :return: spec as dict """ if not filename.startswith('/'): filename = os.path.join( self.app.root_path, filename ) with open(filename) as file: if filename.endswith(".yml") or filename.endswith(".yaml"): retval = yaml.safe_load(file) else: retval = json.load(file) return retval
[docs] def docstrings_to_openapi(self): """Transform Flask docstring documentation to openapi spec. """ if not self._is_first_request: return for schema in self.config['marshmallow_schemas']: self.spec.components.schema(schema.__name__, schema=schema) for view_name, view_func in self.app.view_functions.items(): if view_func.__doc__ is not None: self.spec.path(view=view_func) self.spec_file = strip_empties_from_dict(merge(self.spec_file, self.spec.to_dict())) self._is_first_request = False
[docs]def strip_empties_from_list(data): """Strip empty list """ new_data = [] for v in data: if isinstance(v, dict): v = strip_empties_from_dict(v) elif isinstance(v, list): v = strip_empties_from_list(v) if v not in (None, str(), list(), dict(),): new_data.append(v) return new_data
[docs]def strip_empties_from_dict(data): """Strip empty dict """ new_data = {} for k, v in data.items(): if isinstance(v, dict): v = strip_empties_from_dict(v) elif isinstance(v, list): v = strip_empties_from_list(v) if v not in (None, str(), list(), dict(),): new_data[k] = v return new_data