Snake_Byte #9: Exceptional Exceptions in Flask

You wake up, and realize that you are 10 minutes late for an interview with a potential employer at the new coffee shop 4 blocks away that you never had the time to check out. Your phone is dead, so your alarm didn’t wake you up, and you left your watch at home this morning while rushing out the door. You are on your way, but you just can’t seem to find the coffee shop. So you find someone on the street, and you frantically ask them exactly how to get there…

“500 INTERNAL SERVER ERROR”, they reply.

“What!? … Why!?”, you yell and move right on past that person who clearly doesn’t know how to communicate.

When someone asks you a question, it is important to effectively and efficiently communicate the answer to that question. But what if you don’t know the answer, or if you don’t understand the question? The request-response architecture of an API means that engineers have to deal with these problems every day, as the world does not always return a 200 (OK) status code.

The Flask Library can enable you to make a platform of APIs with relative ease using Python. More importantly, Flask has an exceptional capability to handle any exception you can throw at it… that is, if you can implement it in the right way.

The first step to excellent exception handling is defining a base exception class for your app. You can then make exception classes that inherit from your BasePlatformException and offer more specific error responses across the wide range of HTTP error codes.

In order for your exception handlers to manage the full domain of possible errors that your app can produce, you will want to use and raise these custom exceptions anytime that an error may occur anywhere within your app.

In this example we have defined 3 constants that every exception class you define should have: STATUS_CODE, CATEGORY, and DEFAULT_MESSAGE. The status code constant is intended as a reference to the handler, so that all requests that encounter this exception should return a response with that status code. The category constant provides context about your error message to your user, and the default message constant is the message that the user will receive if you fail to provide a more specific error message.

Any exception class that inherits from BasePlatformException, such as PlatformValidationException, needs only to override the status code, category, and default message constants that define that exception, as BasePlatformException handles the error message creation via its __init__ method.

Onward to actually handling the exceptions across your Flask app:  you can create an ExceptionHandling class.

If your Flask app utilizes multiple blueprints you can register error handling to a specific blueprint by using the, exception, handler) method.

Here, exception handlers are registered to the app and blueprint in the __init__ method, so that simply instantiating this class covers your exceptions.

By registering these exception handlers, Flask creates a list of exception handling rules that it persists in its app object. When an exception occurs, Flask iterates through its list of rules and finds the exception closest in the inheritance hierarchy to the current exception. This means that you can handle every exception that inherits from BasePlatformException with one exception handler method, and they will produce error messages specific to the problem. In this example, this is done with the handle_base_platform_exception(exception_instance) method.

As an instance of the exception is passed to the handler when the exception occurs at runtime, you can use the instance variables therein to return a descriptive message to user.

Also included in this example is the handle_generic_exception (exception_instance) handler which serves as a catch-all for any exception that may occur on your app that does not inherit from your BasePlatformException. If this handler is utilized you may have a problem, as you are not catching all the exceptions you should. In that event, here a generic BasePlatformException is instantiated in order to obscure, from your users, that you most likely have a bug.

The final step in the path to exceptional exception handling in Flask is to initialize your ExceptionHandling class.

Here, our app’s exceptions become well-handled when the ExceptionHandling class is instantiated with the app instance and blueprint string name passed in as arguments, ExceptionHandling(app, ‘api’).

Using this strategy to handle exceptions within your app saves you a lot of misery. Exceptions must be handled in some way, but instead of having to create request bottlenecks that you wrap in massive try-except blocks to handle every different little exception that can occur, you can do it the simple and correct way.

Let’s go back to the original example of a desperate person just asking for directions. In the same way that it is not acceptable for a human being to respond to a legitimate question with “500 INTERNAL SERVER ERROR”, I do not believe that it is acceptable for an API to provide someone with such a meaningless response. Using and expanding upon the strategy provided here, your API can help people more often than it will frustrate them. This means that now if someone asks your app how to find that coffee shop, it can reply to them that it needs to know the name of coffee shop first, rather than replying with a useless and infuriating “DOES NOT COMPUTE”.