""" Response. (taken from github.com/xethorn/sukimu) The response is an object that helps communication between our different layers. The response contains 3 fields: message (data), errors (any errors found), and a status. The success property of the object determines whether or not the method was able (or not) to perform the operation. The operation is considered as successful only in the case of a status 200 and absense of errors:: resp = Response(errors='Something', status=200) bool(resp) # False resp.status # 400 resp = Response() bool(resp) # True resp.status # 200 """ import json import flask from deliveryhistory.connectors import sentry ERROR_CODE_NOT_FOUND = 'not_found_error' ERROR_CODE_INTERNAL_ERROR = 'internal_error' # TODO: replace with oto.response class Response: """Response class for passing the data between layers.""" def __init__(self, message=None, errors=None, status=200): """Create a response object. Args: message: the message object (it could be any type of object.) errors: the errors to attach (it could be any type of object.) status (int): the status of the response. Errors should use the status that is the most appropriate. System failures should set a 500. """ self.status = status self.message = message self.errors = errors if self.errors and self.status == 200: self.status = 400 def __bool__(self): """If the request has been successful. Returns: boolean: if the response is considered successful. """ return 200 <= self.status < 300 and not self.errors def flaskify(response, headers=None, encoder=None): """Format the response to be consumeable by flask. The api returns mostly JSON responses. The format method converts the dicts into a json object (as a string), and the right response is returned (with the valid mimetype, charset and status.) Args: response (Response): The dictionary object to convert into a json object. If the value is a string, a dictionary is created with the key "message". headers (dict): optional headers for the flask response. encoder (Class): The class of the encoder (if any). Returns: flask.Response: The flask response with formatted data, headers, and mimetype. """ status_code = response.status data = response.errors or response.message mimetype = 'text/plain' if isinstance(data, list) or isinstance(data, dict): mimetype = 'application/json' data = json.dumps(data, cls=encoder) return flask.Response( response=data, status=status_code, headers=headers, mimetype=mimetype) def send_to_sentry(response, sentry_message): """Send a Response object to Sentry. This method should be used to send a Response object & message to Sentry when an error occurs that we should be alerted about. ex. error_response = response.create_fatal_response('Something bad') response.send_to_sentry(error_response, response.errors['code']) Args: response (Response): Response object to send to Sentry. sentry_message (str): Message to be displayed in Sentry. Best practice for errors is for the sentry_message to be the error code so that all errors of the same type are grouped together. """ sentry.sentry_client.captureMessage( message=sentry_message, stack=True, extra={ 'message': response.message, 'errors': response.errors, 'status': response.status}) def create_fatal_response(message=None): """Create a fatal response. Args: message: the error to add (it could be any type of object, from string to dict.) Returns: Response: the fatal error response object. """ return create_error_response( ERROR_CODE_INTERNAL_ERROR, message, status=500) def create_error_response(code, message, status=400): """Create a fail response. Args: code (str): the code of the error. The title should be lowercase and underscore separated. message (dict, list, str): the message of the error. This can be a list, dictionary or simple string. status (int): the status code. Defaults to 400. Returns: Response: the response with the error. The format of the error is the following: code and message. The code could be `user_error` or `internal_error`. The message contains either a string, or a list or a dictionary. If not specify, the status will be a 400. """ errors = dict(code=code, message=message) return Response(errors=errors, status=status) def create_not_found_response(message=None): """Create a not found response. Args: message: The errors to add (it could be any type of object, from strings to dict.) Returns: Response: the “not found” response object. """ return create_error_response(ERROR_CODE_NOT_FOUND, message, status=404)