From 47d694cd8823ea5c98290e34360a036a0e2e965c Mon Sep 17 00:00:00 2001 From: Henning Jacobs Date: Thu, 12 Jan 2017 16:45:18 +0100 Subject: [PATCH] #380 first hack (tests broken) works with helloworld --- connexion/api.py | 9 ++++----- connexion/app.py | 2 +- connexion/decorators/decorator.py | 7 +++++-- connexion/decorators/produces.py | 13 ++++++------- connexion/decorators/response.py | 10 +++++----- connexion/decorators/validation.py | 24 ++++++++++++------------ connexion/frameworks/__init__.py | 13 ++++++++++++- connexion/frameworks/flask.py | 26 +++++++++++++++++++------- connexion/operation.py | 8 +++++--- 9 files changed, 69 insertions(+), 43 deletions(-) diff --git a/connexion/api.py b/connexion/api.py index e7e8ab368..a04a121b4 100644 --- a/connexion/api.py +++ b/connexion/api.py @@ -110,6 +110,7 @@ def __init__(self, specification, framework, base_url=None, arguments=None, else: self.base_url = canonical_base_url(base_url) self.specification['basePath'] = base_url + self.framework.set_base_url(self.base_url) # A list of MIME types the APIs can produce. This is global to all APIs but can be overridden on specific # API calls. @@ -138,11 +139,8 @@ def __init__(self, specification, framework, base_url=None, arguments=None, logger.debug('Strict Request Validation: %s', str(validate_responses)) self.strict_validation = strict_validation - # Create blueprint and endpoints - self.blueprint = self.create_blueprint() - if swagger_json: - self.framework.register_swagger_json() + self.framework.register_swagger_json(self.specification) if swagger_ui: self.framework.register_swagger_ui() @@ -168,7 +166,8 @@ def add_operation(self, method, path, swagger_operation, path_parameters): :type path: str :type swagger_operation: dict """ - operation = Operation(method=method, + operation = Operation(framework=self.framework, + method=method, path=path, path_parameters=path_parameters, operation=swagger_operation, diff --git a/connexion/app.py b/connexion/app.py index c044017da..506a5be35 100644 --- a/connexion/app.py +++ b/connexion/app.py @@ -167,7 +167,7 @@ def add_api(self, specification, base_path=None, arguments=None, auth_all_paths= auth_all_paths=auth_all_paths, debug=self.debug, validator_map=self.validator_map) - self.app.register_blueprint(api.blueprint) + framework.register_app(self.app) return api def _resolver_error_handler(self, *args, **kwargs): diff --git a/connexion/decorators/decorator.py b/connexion/decorators/decorator.py index 94dc1b73e..b55358300 100644 --- a/connexion/decorators/decorator.py +++ b/connexion/decorators/decorator.py @@ -94,7 +94,7 @@ def __call__(self, function): :rtype: types.FunctionType """ @functools.wraps(function) - def wrapper(*args, **kwargs): + def wrapper(request, *args, **kwargs): operation_handler_result = function(*args, **kwargs) return self.get_response_container(operation_handler_result) @@ -107,6 +107,8 @@ class EndOfRequestLifecycleDecorator(BaseDecorator): Filter the ResponseContainer instance to return the corresponding flask.Response object. """ + def __init__(self, framework): + self.framework = framework def __call__(self, function): """ @@ -115,7 +117,8 @@ def __call__(self, function): """ @functools.wraps(function) def wrapper(*args, **kwargs): - response_container = function(*args, **kwargs) + request = self.framework.get_request(*args, **kwargs) + response_container = function(request, *args, **kwargs) return response_container.flask_response_object() return wrapper diff --git a/connexion/decorators/produces.py b/connexion/decorators/produces.py index 5536ea637..2d3903055 100644 --- a/connexion/decorators/produces.py +++ b/connexion/decorators/produces.py @@ -3,7 +3,6 @@ import functools import logging -import flask from flask import json from .decorator import BaseDecorator @@ -54,9 +53,9 @@ def __call__(self, function): """ @functools.wraps(function) - def wrapper(*args, **kwargs): - url = flask.request.url - response = function(*args, **kwargs) + def wrapper(request, *args, **kwargs): + url = request.url + response = function(request, *args, **kwargs) logger.debug('Returning %s', url, extra={'url': url, 'mimetype': self.mimetype}) return response @@ -85,13 +84,13 @@ def __call__(self, function): """ @functools.wraps(function) - def wrapper(*args, **kwargs): - url = flask.request.url + def wrapper(request, *args, **kwargs): + url = request.url logger.debug('Jsonifing %s', url, extra={'url': url, 'mimetype': self.mimetype}) - response = function(*args, **kwargs) + response = function(request, *args, **kwargs) if response.is_handler_response_object: logger.debug('Endpoint returned a Flask Response', diff --git a/connexion/decorators/response.py b/connexion/decorators/response.py index c04f71d7d..ce037995e 100644 --- a/connexion/decorators/response.py +++ b/connexion/decorators/response.py @@ -24,7 +24,7 @@ def __init__(self, operation, mimetype): self.operation = operation self.mimetype = mimetype - def validate_response(self, data, status_code, headers): + def validate_response(self, data, status_code, headers, url): """ Validates the Response object based on what has been declared in the specification. Ensures the response body matches the declated schema. @@ -47,7 +47,7 @@ def validate_response(self, data, status_code, headers): data = json.dumps(data) data = json.loads(data) - v.validate_schema(data) + v.validate_schema(data, url) except ValidationError as e: raise NonConformingResponseBody(message=str(e)) @@ -86,10 +86,10 @@ def __call__(self, function): :rtype: types.FunctionType """ @functools.wraps(function) - def wrapper(*args, **kwargs): - response = function(*args, **kwargs) + def wrapper(request, *args, **kwargs): + response = function(request, *args, **kwargs) try: - self.validate_response(response.get_data(), response.status_code, response.headers) + self.validate_response(response.get_data(), response.status_code, response.headers, request.url) except NonConformingResponseBody as e: return problem(500, e.reason, e.message) except NonConformingResponseHeaders as e: diff --git a/connexion/decorators/validation.py b/connexion/decorators/validation.py index 20f6e9454..6eb697e9f 100644 --- a/connexion/decorators/validation.py +++ b/connexion/decorators/validation.py @@ -102,21 +102,21 @@ def __call__(self, function): """ @functools.wraps(function) - def wrapper(*args, **kwargs): + def wrapper(request, *args, **kwargs): if all_json(self.consumes): - data = flask.request.json + data = request.json - logger.debug("%s validating schema...", flask.request.url) - error = self.validate_schema(data) + logger.debug("%s validating schema...", request.url) + error = self.validate_schema(data, request.url) if error and not self.has_default: return error - response = function(*args, **kwargs) + response = function(request, *args, **kwargs) return response return wrapper - def validate_schema(self, data): + def validate_schema(self, data, url): """ :type data: dict :rtype: flask.Response | None @@ -127,7 +127,7 @@ def validate_schema(self, data): try: self.validator.validate(data) except ValidationError as exception: - logger.error("{url} validation error: {error}".format(url=flask.request.url, + logger.error("{url} validation error: {error}".format(url=url, error=exception.message)) return problem(400, 'Bad Request', str(exception.message)) @@ -145,7 +145,7 @@ def __init__(self, schema, validator=None): ValidatorClass = validator or Draft4Validator self.validator = ValidatorClass(schema, format_checker=draft4_format_checker) - def validate_schema(self, data): + def validate_schema(self, data, url): """ :type data: dict :rtype: flask.Response | None @@ -153,7 +153,7 @@ def validate_schema(self, data): try: self.validator.validate(data) except ValidationError as exception: - logger.error("{url} validation error: {error}".format(url=flask.request.url, + logger.error("{url} validation error: {error}".format(url=url, error=exception)) six.reraise(*sys.exc_info()) @@ -253,8 +253,8 @@ def __call__(self, function): """ @functools.wraps(function) - def wrapper(*args, **kwargs): - logger.debug("%s validating parameters...", flask.request.url) + def wrapper(request, *args, **kwargs): + logger.debug("%s validating parameters...", request.url) if self.strict_validation: query_errors = self.validate_query_parameter_list() @@ -283,6 +283,6 @@ def wrapper(*args, **kwargs): if error: return problem(400, 'Bad Request', error) - return function(*args, **kwargs) + return function(request, *args, **kwargs) return wrapper diff --git a/connexion/frameworks/__init__.py b/connexion/frameworks/__init__.py index b34bae400..acaeeda94 100644 --- a/connexion/frameworks/__init__.py +++ b/connexion/frameworks/__init__.py @@ -1,7 +1,18 @@ class Framework: - '''Generic interface for framework implementations''' + '''Generic interface for framework implementations + + assumptions about request object: + + * headers: dict (case insensitive or not) + * data: Python object (e.g. JSON request body) + * args: + * form: + * files: + ''' + def __init__(self, base_url): + raise NotImplementedError() def register_operation(self, method, path, operation): raise NotImplementedError() diff --git a/connexion/frameworks/flask.py b/connexion/frameworks/flask.py index 685f2941b..2679637a7 100644 --- a/connexion/frameworks/flask.py +++ b/connexion/frameworks/flask.py @@ -1,5 +1,6 @@ import logging +import connexion.api import connexion.utils as utils import flask import werkzeug.exceptions @@ -10,7 +11,19 @@ class FlaskFramework: def __init__(self): - self.blueprint = self._create_blueprint() + self.swagger_path = connexion.api.SWAGGER_UI_PATH + self.swagger_url = connexion.api.SWAGGER_UI_URL + + def get_request(self, *args, **kwargs): + return flask.request + + def register_app(self, app): + self.app = app + self.app.register_blueprint(self.blueprint) + + def set_base_url(self, base_url): + self.base_url = base_url + self.blueprint = self._create_blueprint(base_url) def register_operation(self, method, path, operation): operation_id = operation.operation_id @@ -20,7 +33,7 @@ def register_operation(self, method, path, 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): + def register_swagger_json(self, specification): """ Adds swagger json to {base_url}/swagger.json """ @@ -28,7 +41,7 @@ def register_swagger_json(self): endpoint_name = "{name}_swagger_json".format(name=self.blueprint.name) self.blueprint.add_url_rule('/swagger.json', endpoint_name, - lambda: flask.jsonify(self.specification)) + lambda: flask.jsonify(specification)) def register_swagger_ui(self): """ @@ -37,10 +50,10 @@ def register_swagger_ui(self): 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) + 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) + index_endpoint_name, self._swagger_ui_index) def register_auth_on_not_found(self): """ @@ -52,12 +65,11 @@ def register_auth_on_not_found(self): 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): + def _create_blueprint(self, base_url): """ :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, diff --git a/connexion/operation.py b/connexion/operation.py index 9b8ed360b..89fff3c79 100644 --- a/connexion/operation.py +++ b/connexion/operation.py @@ -32,7 +32,7 @@ class SecureOperation(object): - def __init__(self, security, security_definitions): + def __init__(self, framework, security, security_definitions): """ :param security: list of security rules the application uses by default :type security: list @@ -40,6 +40,7 @@ def __init__(self, security, security_definitions): `_ :type security_definitions: dict """ + self.framework = framework self.security = security self.security_definitions = security_definitions @@ -120,7 +121,7 @@ def _request_end_lifecycle_decorator(self): :rtype: types.FunctionType """ - return EndOfRequestLifecycleDecorator() + return EndOfRequestLifecycleDecorator(self.framework) class Operation(SecureOperation): @@ -129,7 +130,7 @@ class Operation(SecureOperation): A single API operation on a path. """ - def __init__(self, method, path, operation, resolver, app_produces, app_consumes, + def __init__(self, framework, method, path, operation, resolver, app_produces, app_consumes, path_parameters=None, app_security=None, security_definitions=None, definitions=None, parameter_definitions=None, response_definitions=None, validate_responses=False, strict_validation=False, randomize_endpoint=None, @@ -179,6 +180,7 @@ def __init__(self, method, path, operation, resolver, app_produces, app_consumes :type strict_validation: bool """ + self.framework = framework self.method = method self.path = path self.validator_map = dict(VALIDATOR_MAP)