Skip to content

Commit

Permalink
#380 move Flask dependency from Api to framework
Browse files Browse the repository at this point in the history
  • Loading branch information
hjacobs committed Jan 12, 2017
1 parent 0eedc5d commit 2de1b01
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 73 deletions.
81 changes: 8 additions & 73 deletions connexion/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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('/<path:invalid_path>', 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}/<path:filename>'.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 {}

Expand Down
3 changes: 3 additions & 0 deletions connexion/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions connexion/frameworks/__init__.py
Original file line number Diff line number Diff line change
@@ -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()
74 changes: 74 additions & 0 deletions connexion/frameworks/flask.py
Original file line number Diff line number Diff line change
@@ -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}/<path:filename>'.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('/<path:invalid_path>', 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)

0 comments on commit 2de1b01

Please sign in to comment.