예제 #1
0
    def mycallback(endpoint):
        handler_func = get_function(endpoint.handler_server)

        # Generate api endpoint around that handler
        handler_wrapper = _generate_handler_wrapper(api_name, api_spec, endpoint, handler_func, error_callback, decorator)

        # Bind handler to the API path
        log.info("Binding %s %s ==> %s" % (endpoint.method, endpoint.path, endpoint.handler_server))
        endpoint_name = '_'.join([endpoint.method, endpoint.path]).replace('/', '_')
        app.add_url_rule(endpoint.path, endpoint_name, handler_wrapper, methods=[endpoint.method])
예제 #2
0
    def _make_persistent(self, model_name, pkg_name):
        """Monkey-patch object persistence (ex: to/from database) into a
        bravado-core model class"""

        # Load class at path pkg_name
        c = get_function(pkg_name)
        for name in ('load_from_db', 'save_to_db'):
            if not hasattr(c, name):
                raise KlueException("Class %s has no static method '%s'" % (pkg_name, name))

        log.info("Making %s persistent via %s" % (model_name, pkg_name))

        # Replace model generator with one that adds 'save_to_db' to every instance
        model = getattr(self.model, model_name)
        n = self._wrap_bravado_model_generator(model, c.save_to_db)
        setattr(self.model, model_name, n)

        # Add class method load_from_db to model generator
        model = getattr(self.model, model_name)
        setattr(model, 'load_from_db', c.load_from_db)
예제 #3
0
def _generate_handler_wrapper(api_name, api_spec, endpoint, handler_func, error_callback, global_decorator):
    """Generate a handler method for the given url method+path and operation"""

    # Decorate the handler function, if Swagger spec tells us to
    if endpoint.decorate_server:
        endpoint_decorator = get_function(endpoint.decorate_server)
        handler_func = endpoint_decorator(handler_func)

    @wraps(handler_func)
    def handler_wrapper(**path_params):
        log.info("=> INCOMING REQUEST %s %s -> %s" %
                 (endpoint.method, endpoint.path, handler_func.__name__))

        # Get caller's klue-call-id or generate one
        call_id = request.headers.get('KlueCallID', None)
        if not call_id:
            call_id = str(uuid.uuid4())
        stack.top.call_id = call_id

        # Append current server to call path, or start one
        call_path = request.headers.get('KlueCallPath', None)
        if call_path:
            call_path = "%s.%s" % (call_path, api_name)
        else:
            call_path = api_name
        stack.top.call_path = call_path

        if endpoint.param_in_body or endpoint.param_in_query:
            # Turn the flask request into something bravado-core can process...
            try:
                req = FlaskRequestProxy(request, endpoint.param_in_body)
            except BadRequest:
                ee = error_callback(ValidationError("Cannot parse json data: have you set 'Content-Type' to 'application/json'?"))
                return _responsify(api_spec, ee, 400)

            try:
                # Note: unmarshall validates parameters but does not fail
                # if extra unknown parameters are submitted
                parameters = unmarshal_request(req, endpoint.operation)
                # Example of parameters: {'body': RegisterCredentials()}
            except jsonschema.exceptions.ValidationError as e:
                ee = error_callback(ValidationError(str(e)))
                return _responsify(api_spec, ee, 400)

        # Call the endpoint, with proper parameters depending on whether
        # parameters are in body, query or url
        args = []
        kwargs = {}

        if endpoint.param_in_path:
            kwargs = path_params

        if endpoint.param_in_body:
            # Remove the parameters already defined in path_params
            for k in path_params.keys():
                del parameters[k]
            l = list(parameters.values())
            assert len(l) == 1
            args.append(l[0])

        if endpoint.param_in_query:
            kwargs.update(parameters)

        result = handler_func(*args, **kwargs)

        if not result:
            e = error_callback(KlueException("Have nothing to send in response"))
            return _responsify(api_spec, e, 500)

        # Did we get the expected response?
        if endpoint.produces_html:
            if type(result) is not tuple:
                e = error_callback(KlueException("Method %s should return %s but returned %s" %
                                                 (endpoint.handler_server, endpoint.produces, type(result))))
                return _responsify(api_spec, e, 500)

            # Return an html page
            return result

        elif endpoint.produces_json:
            if not hasattr(result, '__module__') or not hasattr(result, '__class__'):
                e = error_callback(KlueException("Method %s did not return a class instance but a %s" %
                                                 (endpoint.handler_server, type(result))))
                return _responsify(api_spec, e, 500)

            # If it's already a flask Response, just pass it through.
            # Errors in particular may be either passed back as flask Responses, or
            # raised as exceptions to be caught and formatted by the error_callback
            result_type = result.__module__ + "." + result.__class__.__name__
            if result_type == 'flask.wrappers.Response':
                return result

            # Otherwise, assume no error occured and make a flask Response out of
            # the result.

            # TODO: check that result is an instance of a model expected as response from this endpoint
            result_json = api_spec.model_to_json(result)

            # Send a Flask Response with code 200 and result_json
            r = jsonify(result_json)
            r.status_code = 200
            return r

    handler_wrapper = cross_origin(headers=['Content-Type', 'Authorization'])(handler_wrapper)

    # And encapsulate all in a global decorator, if given one
    if global_decorator:
        handler_wrapper = global_decorator(handler_wrapper)

    return handler_wrapper
예제 #4
0
def _generate_client_caller(spec, endpoint, timeout, error_callback):

    url = "%s://%s:%s/%s" % (spec.protocol,
                             spec.host,
                             spec.port,
                             endpoint.path.lstrip('/'))

    decorator = None
    if endpoint.decorate_request:
        decorator = get_function(endpoint.decorate_request)

    method = endpoint.method.lower()
    if method not in ('get', 'post', 'patch', 'put', 'delete'):
        raise KlueException("BUG: method %s for %s is not supported. Only get and post are." %
                            (endpoint.method, endpoint.path))

    requests_method = getattr(requests, method)
    if decorator:
        requests_method = decorator(requests_method)

    def client(*args, **kwargs):
        """Call the server endpoint and handle marshaling/unmarshaling of parameters/result.

        client takes either a dict of query parameters, or an object representing the unique
        body parameter.
        """

        # Extract custom parameters from **kwargs
        max_attempts = 3
        read_timeout = timeout
        connect_timeout = timeout

        if 'max_attempts' in kwargs:
            max_attempts = kwargs['max_attempts']
            del kwargs['max_attempts']
        if 'read_timeout' in kwargs:
            read_timeout = kwargs['read_timeout']
            del kwargs['read_timeout']
        if 'connect_timeout' in kwargs:
            connect_timeout = kwargs['connect_timeout']
            del kwargs['connect_timeout']

        # Prepare (g)requests arguments
        headers = {'Content-Type': 'application/json'}
        data = None
        params = None

        custom_url = url

        if hasattr(stack.top, 'call_id'):
            headers['KlueCallID'] = stack.top.call_id
        if hasattr(stack.top, 'call_path'):
            headers['KlueCallPath'] = stack.top.call_path

        if endpoint.param_in_path:
            # Fill url with values from kwargs, and remove those params from kwargs
            custom_url = _format_flask_url(url, kwargs)
            if '<' in custom_url:
                # Some arguments were missing
                return error_callback(ValidationError("Missing some arguments to format url: %s" % custom_url))

        if endpoint.param_in_query:
            # The query parameters are contained in **kwargs
            params = kwargs
            # TODO: validate params? or let the server do that...
        elif endpoint.param_in_body:
            # The body parameter is the first elem in *args
            if len(args) != 1:
                return error_callback(ValidationError("%s expects exactly 1 parameter" % endpoint.handler_client))
            data = json.dumps(spec.model_to_json(args[0]))

        # TODO: refactor this left-over from the time of asyn/grequests support and simplify!
        return ClientCaller(requests_method, custom_url, data, params, headers, read_timeout, connect_timeout, endpoint.operation, endpoint.method, error_callback, max_attempts).call()

    return client