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])
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)
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
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