Search code examples
pythonmongodbflaskflask-restful

Flask API Routes not catching exceptions


May be a little confused on how flask handles errors. Working on adding error handling to a DB backend. I essentially have a worker script, a router, and error handling script.

The error handling is pretty basic. I have a separate module with the following classes. They inherit from exception so there is not much added to them

class EmptyQueryError(Exception):
    pass

class BadURLError(Exception):
    pass

My main route method is as follows, and calls a method which takes care of post process/etc.

def queryJobData(projectId, jobId): 
    try:
        dbv.jobData(projectId, jobId)
    except EmptyQueryError as e:
        abort(400, "An unexpected Exception Occurred: {}".format(e))
    except Exception as e:
        abort(400, "An unexpected Exception Occurred: {}".format(e))

Lastly, the main driver function is contained in the dbv object from about. The flow of this works properly, as for when I pass in valued project and job ids, the query is successful and it returns a document as I aim to. However, when I purposely insert errors to get an exception to raise, that's where issues occur. Here is the handler function:

def jobData(self, projectId, jobId):
    try:
        print("[Querying the job data]")
        if (request.method == "GET"):
            qData = [x for x in self.collection.find({"projectId": projectId, "jobId": jobId})]
            # input(qData)
            if (len(qData) == 0):
                raise EmptyQueryError("EmptyQueryError: The url you attempted to query did not return any data")
            pp.data = qData
            unrolled_data = pp.unrollData()
            df = pd.DataFrame(unrolled_data)

            pps = PostProcessSummary(df)
            table_test = pps.pivot_summary()
            return dumps(table_test)


    except Exception as e:
        print(e)
    finally:
        pass

I purposely did not import the "request" module so it raises an error. I can see it gets caught by "jobData":

enter image description here

However, it never enters one of the exception blocks in "queryJobData", where the handle "jobData" is called.

To say the least this has thrown me a for a loop. Almost all other pieces of software I've built would handle this exception accordingly. (ie it follows the pattern where if one exception is raised elsewhere, it should be handled by the parent calling the child generating the exception). First time using Flask so I imagine I'm missing something obvious I can't find in documentation.

edit: The exception in jobData() gets caught and it exists back into queryJobData() as if nothing happens. For instance in this block it goes directly to the return and not to handle the raised exception

try:
    dbv_ret = dbv.jobData(projectId, jobId)
    return dbv_ret
except TypeError:
    abort(400, "TypeError Occurred")
except EmptyQueryError:
    print("[ARE WE HERE?!]")
    abort(404, "Empty Query")
except BadURLError:
    abort(404, "Bad URL")
except Exception as e:
    abort(404, "An unexptec exception has occurred: {}".format(e))

Solution

  • You a catching all exceptions in you function, so anything that happens inside the try/except block will be caught inside your except block, and then your finally block will be executed.

    If you want to pass the EmptyQueryError and BadURLError exceptions to the calling function, raise it outside the try/except block. Or, if you want, re-raise it inside your except block

    class EmptyQueryError(Exception):
        pass
    
    class BadURLError(Exception):
        pass
    
    
    def jobData():
        try:
            print("[Querying the job data]")
            # this will be caught by the except block
            raise EmptyQueryError("EmptyQueryError: The url you attempted to query did not return any data")
    
    
        except Exception as e:
            # catching all exceptions. Including the ones you are raising.
            # if you don't re-raise the exception here, no error will be passed
            print("Wow, exception")
            print(e)
        finally:
            print("I'm in finally block ;)")
            # not returning anything
            pass
    
    
    if __name__ == "__main__":
        try:
            returned_value = jobData()
            # will print None
            print(returned_value)
        except EmptyQueryError as query_err:
            # will never be caught
            print("Got a EmptyQueryError")
        except BadURLError as url_err:
            # will never be caught
            print("Got a BadURLError")
    

    The sequence of prints will be:

    [Querying the job data]
    Wow, exception
    EmptyQueryError: The url you attempted to query did not return any data
    I'm in finally block ;)
    None
    

    You can do something like:

    def jobData(data):
        if len(data) == 0:
            raise EmptyQueryError("EmptyQueryError: The url you attempted to query did not return any data")
    
        try:
            # do your thing here
            pass
        except Exception as e:
            print(e)
        finally:
            print("I'm in finally block ;)")
            # not returning anything
            pass
    

    Now the EmptyQueryError will be caught by the calling function.