-1

Flask==1.1.2 Python==3.8

I am building a restAPI that is serving machine learning model. My co-worker who will be sending request to my restAPI and use the outcome to send it to users wants me to send him appropriate error message as well with status_code.

I've did a lot of searching on how to properly handle errors in Python Flask however I am still stuck on what would be a best practice for scalable and maintainable restAPI.

Currently whenever some error occurs I simply return dictionary with message and status code. There are some problems with this method that I want to mitigate:

  1. If error occurs inside a function it has to return dictionary containing error messages to where the function was called and at need to check if it was actually an error, if yes then return error message

    Example:

     def add_data(x,y):
         """return addition of x,y. They both need to be integers"""
         if type(x) != int:
             return "x has wrong datatype"
         if type(y) != int:
             return "y has wrong datatype"
         return x+y
    
     @app.route("/predict", methods=["POST"])
     def predict():
         data = request.get_json()
         result = add_data(data["x"], data["y"])
         if type(result) == str:
             return {"message":"input error", "status":222}
    
  2. Cannot break code inside a function.

    following some references

    I've changed my code to following:

     class InputError(Exception):
         status_code = 400
         def __init__(self, message, status_code=None):
             Exception.__init__(self)
             self.message = message
             if status_code is not None:
                 self.status_code = status_code
    
         def __str__(self):
             return repr(self.status_code)
    
    
     def add_data(x,y):
         if type(x) != int:
             raise InputError("x has wrong datatype", status_code=222)
         if type(y) != int:
             raise InputError("y has wrong datatype", status_code=222)
         return x+y
    

    This does break the code where error is found however I cannot find out how to return dictionary just like before.

How can I do this and which practice is considered a best practice?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
haneulkim
  • 4,406
  • 9
  • 38
  • 80
  • 1
    You're mangling the business and transport logic together. `add_data` shouldn't be deciding what the appropriate HTTP status code to represent the outcome is (and it certainly shouldn't be deciding that it's 222, a code with no defined meaning in the _success_ class). `add_data` should just throw a `TypeError`, then `predict` can catch that and respond appropriately. Also you should use `isinstance` for type comparisons. – jonrsharpe May 03 '21 at 07:31
  • What if I use status codes that have no defined meaning like 1,2,3,4,5 etc...? So if I don't want to mangle business and transport logic together I shouldn't output status code? – haneulkim May 03 '21 at 07:40
  • 1
    You mean have your own internal status codes that the transport layer turns into HTTP status codes? I suppose that would be OK, you could use an enum to represent them so they're names rather than magic numbers: https://docs.python.org/3/library/enum.html. But then how's that much different to throwing your own business-related errors? – jonrsharpe May 03 '21 at 07:41

1 Answers1

1

The solution is to use error handlers https://flask.palletsprojects.com/en/1.1.x/errorhandling/

In your case:

@app.errorhandler(InputError)
def handle_input_error(e):
    return {"message": e["message"], "status": e["status"]}

Now whenever you raise InputError somewhere in the code, flask will know to call this method and return this response

If you have more types of errors I would switch to something more general

class MyErrors(Exception):
    status_code: int
    
    def __init__(self, message):
        super(MyErrors, self).__init__(message)
        self.message = message
        
    def response(self):
        return {"message": self.message, "status": self.status_code}
    
class InputError(MyErrors):
    status_code = 222
    
class SomeOtherError(MyErrors):
    status_code = 123

@app.errorhandler(MyErrors)
def handle_errors(e):
    return e.response()
Ron Serruya
  • 3,988
  • 1
  • 16
  • 26
  • I still wouldn't put the status code in the business error; put it in the error handler itself, which belongs to the transport domain. – jonrsharpe May 03 '21 at 07:35
  • Any guide on how to architect them? script you've written should be inside app.py? – haneulkim May 03 '21 at 07:43
  • @haneulkim I would pull all the exceptions in a separate file, the error handler in its own file as well in case you would add more in the future – Ron Serruya May 03 '21 at 08:41