From 2de1b01f16ca74f58ea4260c7e265258ae20ad32 Mon Sep 17 00:00:00 2001 From: Henning Jacobs Date: Thu, 12 Jan 2017 15:32:21 +0100 Subject: [PATCH] #380 move Flask dependency from Api to framework --- connexion/api.py | 81 ++++---------------------------- connexion/app.py | 3 ++ connexion/frameworks/__init__.py | 13 +++++ connexion/frameworks/flask.py | 74 +++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 73 deletions(-) create mode 100644 connexion/frameworks/__init__.py create mode 100644 connexion/frameworks/flask.py diff --git a/connexion/api.py b/connexion/api.py index 51cdf4346..e7e8ab368 100644 --- a/connexion/api.py +++ b/connexion/api.py @@ -3,17 +3,12 @@ import pathlib import sys -import flask import jinja2 import six -import werkzeug.exceptions - import yaml from swagger_spec_validator.validator20 import validate_spec -from . import utils from .exceptions import ResolverError -from .handlers import AuthErrorHandler from .operation import Operation from .resolver import Resolver @@ -56,10 +51,10 @@ def canonical_base_url(base_path): class Api(object): """ - Single API that corresponds to a flask blueprint + Single API that corresponds to a framework blueprint """ - def __init__(self, specification, base_url=None, arguments=None, + def __init__(self, specification, framework, base_url=None, arguments=None, swagger_json=None, swagger_ui=None, swagger_path=None, swagger_url=None, validate_responses=False, strict_validation=False, resolver=None, auth_all_paths=False, debug=False, resolver_error_handler=None, validator_map=None): @@ -82,6 +77,7 @@ def __init__(self, specification, base_url=None, arguments=None, Operation used for handling ResolveErrors :type resolver_error_handler: callable | None """ + self.framework = framework self.debug = debug self.validator_map = validator_map self.resolver_error_handler = resolver_error_handler @@ -146,14 +142,14 @@ def __init__(self, specification, base_url=None, arguments=None, self.blueprint = self.create_blueprint() if swagger_json: - self.add_swagger_json() + self.framework.register_swagger_json() if swagger_ui: - self.add_swagger_ui() + self.framework.register_swagger_ui() self.add_paths() if auth_all_paths: - self.add_auth_on_not_found() + self.framework.register_auth_on_not_found() def add_operation(self, method, path, swagger_operation, path_parameters): """ @@ -187,7 +183,7 @@ def add_operation(self, method, path, swagger_operation, path_parameters): validator_map=self.validator_map, strict_validation=self.strict_validation, resolver=self.resolver) - self._add_operation_internal(method, path, operation) + self.framework.register_operation(method, path, operation) def _add_resolver_error_handler(self, method, path, err): """ @@ -208,14 +204,6 @@ def _add_resolver_error_handler(self, method, path, err): randomize_endpoint=RESOLVER_ERROR_ENDPOINT_RANDOM_DIGITS) self._add_operation_internal(method, path, operation) - def _add_operation_internal(self, method, path, operation): - operation_id = operation.operation_id - logger.debug('... Adding %s -> %s', method.upper(), operation_id, - extra=vars(operation)) - - flask_path = utils.flaskify_path(path, operation.get_path_parameter_types()) - self.blueprint.add_url_rule(flask_path, operation.endpoint_name, operation.function, methods=[method]) - def add_paths(self, paths=None): """ Adds the paths defined in the specification as endpoints @@ -237,7 +225,7 @@ def add_paths(self, paths=None): self.add_operation(method, path, endpoint, path_parameters) except ResolverError as err: # If we have an error handler for resolver errors, add it - # as an operation (but randomize the flask endpoint name). + # as an operation (but randomize the endpoint name). # Otherwise treat it as any other error. if self.resolver_error_handler is not None: self._add_resolver_error_handler(method, path, err) @@ -261,59 +249,6 @@ def _handle_add_operation_error(self, path, method, exc_info): logger.error(error_msg) six.reraise(*exc_info) - def add_auth_on_not_found(self): - """ - Adds a 404 error handler to authenticate and only expose the 404 status if the security validation pass. - """ - logger.debug('Adding path not found authentication') - not_found_error = AuthErrorHandler(werkzeug.exceptions.NotFound(), security=self.security, - security_definitions=self.security_definitions) - endpoint_name = "{name}_not_found".format(name=self.blueprint.name) - self.blueprint.add_url_rule('/', endpoint_name, not_found_error.function) - - def add_swagger_json(self): - """ - Adds swagger json to {base_url}/swagger.json - """ - logger.debug('Adding swagger.json: %s/swagger.json', self.base_url) - endpoint_name = "{name}_swagger_json".format(name=self.blueprint.name) - self.blueprint.add_url_rule('/swagger.json', - endpoint_name, - lambda: flask.jsonify(self.specification)) - - def add_swagger_ui(self): - """ - Adds swagger ui to {base_url}/ui/ - """ - logger.debug('Adding swagger-ui: %s/%s/', self.base_url, self.swagger_url) - static_endpoint_name = "{name}_swagger_ui_static".format(name=self.blueprint.name) - self.blueprint.add_url_rule('/{swagger_url}/'.format(swagger_url=self.swagger_url), - static_endpoint_name, self.swagger_ui_static) - index_endpoint_name = "{name}_swagger_ui_index".format(name=self.blueprint.name) - self.blueprint.add_url_rule('/{swagger_url}/'.format(swagger_url=self.swagger_url), - index_endpoint_name, self.swagger_ui_index) - - def create_blueprint(self, base_url=None): - """ - :type base_url: str | None - :rtype: flask.Blueprint - """ - base_url = base_url or self.base_url - logger.debug('Creating API blueprint: %s', base_url) - endpoint = utils.flaskify_endpoint(base_url) - blueprint = flask.Blueprint(endpoint, __name__, url_prefix=base_url, - template_folder=str(self.swagger_path)) - return blueprint - - def swagger_ui_index(self): - return flask.render_template('index.html', api_url=self.base_url) - - def swagger_ui_static(self, filename): - """ - :type filename: str - """ - return flask.send_from_directory(str(self.swagger_path), filename) - def load_spec_from_file(self, arguments, specification): arguments = arguments or {} diff --git a/connexion/app.py b/connexion/app.py index 853dbd8c9..c044017da 100644 --- a/connexion/app.py +++ b/connexion/app.py @@ -9,6 +9,7 @@ from .exceptions import ProblemException from .problem import problem from .resolver import Resolver +from .frameworks.flask import FlaskFramework logger = logging.getLogger('connexion.app') @@ -151,7 +152,9 @@ def add_api(self, specification, base_path=None, arguments=None, auth_all_paths= else: specification = self.specification_dir / specification + framework = FlaskFramework() api = Api(specification=specification, + framework=framework, base_url=base_path, arguments=arguments, swagger_json=swagger_json, swagger_ui=swagger_ui, diff --git a/connexion/frameworks/__init__.py b/connexion/frameworks/__init__.py new file mode 100644 index 000000000..b34bae400 --- /dev/null +++ b/connexion/frameworks/__init__.py @@ -0,0 +1,13 @@ + + +class Framework: + '''Generic interface for framework implementations''' + + def register_operation(self, method, path, operation): + raise NotImplementedError() + + def register_swagger_json(self): + raise NotImplementedError() + + def register_swagger_ui(): + raise NotImplementedError() diff --git a/connexion/frameworks/flask.py b/connexion/frameworks/flask.py new file mode 100644 index 000000000..685f2941b --- /dev/null +++ b/connexion/frameworks/flask.py @@ -0,0 +1,74 @@ +import logging + +import connexion.utils as utils +import flask +import werkzeug.exceptions +from connexion.handlers import AuthErrorHandler + +logger = logging.getLogger('connexion.frameworks.flask') + + +class FlaskFramework: + def __init__(self): + self.blueprint = self._create_blueprint() + + def register_operation(self, method, path, operation): + operation_id = operation.operation_id + logger.debug('... Adding %s -> %s', method.upper(), operation_id, + extra=vars(operation)) + + flask_path = utils.flaskify_path(path, operation.get_path_parameter_types()) + self.blueprint.add_url_rule(flask_path, operation.endpoint_name, operation.function, methods=[method]) + + def register_swagger_json(self): + """ + Adds swagger json to {base_url}/swagger.json + """ + logger.debug('Adding swagger.json: %s/swagger.json', self.base_url) + endpoint_name = "{name}_swagger_json".format(name=self.blueprint.name) + self.blueprint.add_url_rule('/swagger.json', + endpoint_name, + lambda: flask.jsonify(self.specification)) + + def register_swagger_ui(self): + """ + Adds swagger json to {base_url}/swagger.json + """ + logger.debug('Adding swagger-ui: %s/%s/', self.base_url, self.swagger_url) + static_endpoint_name = "{name}_swagger_ui_static".format(name=self.blueprint.name) + self.blueprint.add_url_rule('/{swagger_url}/'.format(swagger_url=self.swagger_url), + static_endpoint_name, self.swagger_ui_static) + index_endpoint_name = "{name}_swagger_ui_index".format(name=self.blueprint.name) + self.blueprint.add_url_rule('/{swagger_url}/'.format(swagger_url=self.swagger_url), + index_endpoint_name, self.swagger_ui_index) + + def register_auth_on_not_found(self): + """ + Adds a 404 error handler to authenticate and only expose the 404 status if the security validation pass. + """ + logger.debug('Adding path not found authentication') + not_found_error = AuthErrorHandler(werkzeug.exceptions.NotFound(), security=self.security, + security_definitions=self.security_definitions) + endpoint_name = "{name}_not_found".format(name=self.blueprint.name) + self.blueprint.add_url_rule('/', endpoint_name, not_found_error.function) + + def _create_blueprint(self, base_url=None): + """ + :type base_url: str | None + :rtype: flask.Blueprint + """ + base_url = base_url or self.base_url + logger.debug('Creating API blueprint: %s', base_url) + endpoint = utils.flaskify_endpoint(base_url) + blueprint = flask.Blueprint(endpoint, __name__, url_prefix=base_url, + template_folder=str(self.swagger_path)) + return blueprint + + def _swagger_ui_index(self): + return flask.render_template('index.html', api_url=self.base_url) + + def _swagger_ui_static(self, filename): + """ + :type filename: str + """ + return flask.send_from_directory(str(self.swagger_path), filename)