Python Flask: error and exception handling

Python Flask: error and exception handling

Learn how to globally handle errors and exceptions in Flask that occur within endpoints, including custom exceptions.


Errors and exceptions are a common occurrence in any programming project,  you just need to ensure that they are properly handled so that they can be properly logged and an appropriate response can be returned to the user. Luckily Flask makes it easy for you to handle errors that occur within your end-points.


Basic error handling in Flask


When an error occurs within an endpoint in your Flask application, the usual response the user would receive is an error 500 with no additional details of what went wrong- maybe they passed the wrong arguments or maybe there's an issue with code- either way there's a much better way to handle such errors.


With Flask all you need to catch an error is to assign a function to the

errorhandler

decorator within your main class, this will allow you to catch the specified error and return a more helpful response. An example of basic usage of this functionality looks like this:


@app.errorhandler(Exception)          
def basic_error(e):          
    return "an error occured: " + str(e)


When an error occurs in any of the endpoints within your application it will be caught by this function and will return a specified string to the user. The above function will catch any errors with the base class 'Exception' which is essentially

all

errors, you can narrow this down to specific errors such as 'ValueError' but more on that later.


As an example let's create a small application with 2 endpoints, each will cause a different error to occur that will be caught by our error handler.


from flask import Flask          
          
app = Flask(__name__)          
          
@app.route('/test1')          
def test1():          
    arr = ["apples", "bananas"]          
    fruit = arr[2]          
    return "hello world - " + fruit          
          
@app.route('/test2')          
def test2():          
    num = 2 / 0          
    return "hello world - " + str(num)          
          
@app.errorhandler(Exception)          
def basic_error(e):          
    return "an error occured: " + str(e)          
          
if __name__ == "__main__":          
    app.run()          
          


In this application our first endpoint has a list with 2 values but is trying to access the third, this will cause an 'IndexError'. Our second endpoint suffers from a division by 0 which will cause a 'ZeroDivisionError', both of these errors inherit the base class 'Exception' and will both be caught by our error handler.


If we try to access

/test1

we will receive the following message:


an error occured: list index out of range


If we try to access

/test2

we will receive the following message:


an error occured: division by zero


Handling only specific errors


Using the errorhandler decorator that Flask provides, you can declare an error handling function for different types of errors instead of just handling all errors in one function. This allows you to handle different errors in different ways which is often necessary.


To specify a certain Error that you want to handle, all you need to do is pass it as an argument in your error handling function like so:


@app.errorhandler(NameError)          
def name_error(e):          
    return "A name error occurred!"


The above function will catch only NameError exceptions, all other errors will behave as usual, returning an error 500 to the user when they occur.


As an example we can create an application with 3 endpoints, each of which will cause a unique error. We will include error handlers for 2 of these unique errors.


from flask import Flask        
        
app = Flask(__name__)        
        
@app.route('/test1')        
def test1():        
    arr = ["apples", "bananas"]        
    fruit = arr[2]        
    return "hello world - " + fruit        
        
@app.route('/test2')        
def test2():        
    num = 2 / 0        
    return "hello world - " + str(num)        
        
@app.route('/test3')        
def test3():        
    return "hello world - " + undeclared        
        
@app.errorhandler(ZeroDivisionError)        
def zero_division_error(e):        
    return "Someone divided by zero!"        
        
@app.errorhandler(IndexError)        
def index_error(e):        
    return "An index error occurred!"        
        
if __name__ == "__main__":        
    app.run()


In this application we have 2 endpoints that are identical to those in the first example, one raising an IndexError and the other raising a ZeroDivisionError, however this time we've added another endpoint that will raise a NameError due to a call to the undefined variable 'undeclared'.


The errors caused by the first 2 endpoints will be caught and handled properly as error handlers exist for the specific errors raised within them. The 3rd function will raise an unhandled exception and will return an error 500 to the user.


If we try to access

/test1

we will receive the following message:


An index error occurred!


If we try to access

/test2

we will receive the following message:


Someone divided by zero!


If we try to access

/test3

we will receive the following message:


Internal Server Error        
The server encountered an internal error and was unable to complete your request.         
Either the server is overloaded or there is an error in the application.        


Error handling within molds


For larger web-applications it can often be necessary to split up your endpoints in to separate molds with different logical components, this also allows for more fine grained control over errors for those components.


Error handling within a Flask mold, works much the same as it does within the main module, you declare a function to handle a given error using a decorator, however molds allow you to declare an error handler as either global to your application, or mold-specific.


Mold-specific error handling


Using the errorhandler decorator that we used in the previous examples, we can create an error handling function that is local to a specific mold. If you declare an error handler in the same way as you would in your main class, it will only catch errors from endpoints contained within that mold.


As an example, let's create a main module to start our Flask application and a mold. Each module we will have 2 identical endpoints, but only the mold will contain error handlers meaning errors that occur from within the mold will be caught, but errors within the main module will not.


Main.py


from flask import Flask     
from ErrorHandler import ErrorHandlerMold     
     
app = Flask(__name__)     
     
app.register_blueprint(ErrorHandlerMold, url_prefix='/errors')     
     
@app.route('/test1')     
def test1():     
    arr = ["apples", "bananas"]     
    fruit = arr[2]     
    return "hello world - " + fruit     
     
@app.route('/test2')     
def test2():     
    num = 2 / 0     
    return "hello world - " + str(num)     
     
if __name__ == "__main__":     
    app.run()


ErrorHandler.py


from flask import Blueprint     
     
ErrorHandlerMold = Blueprint('ErrorHandler', __name__)     
     
@ErrorHandlerMold.errorhandler(Exception)     
def basic_error(e):     
    return "An error occurred: " + str(e)     
     
@ErrorHandlerMold.route('/test1')     
def test1():     
    arr = ["apples", "bananas"]     
    fruit = arr[2]     
    return "hello world - " + fruit     
     
@ErrorHandlerMold.route('/test2')     
def test2():     
    num = 2 / 0     
    return "hello world - " + str(num)


In the above application, if you call any of the two endpoints from the main module the user will be given an error 500 which will not be caught by our error handler.


If you call any of the endpoints in the mold (ie. /errors/test1)  an error will occur which will be caught by the error handler and the user will receive the message specified in the function.


Global mold error handling


If you want to handle certain errors that occur within any endpoint throughout your Flask application using error handlers in a mold, you can make use of the app_errorhandler decorator that Flask provides to create global error handling functions for given errors.


The app_errorhandler decorator can be used exactly the same way as the errorhandler decorator we used in the previous example, simply supply the error you want the function to handle and return a string or bytes value as an output. The only difference between the two decorators is that app_errorhandler creates an error handler that is global to the entire application.


As an example let's create a similar application to the one we created in the previous example, except we will be using a global error handler in our mold.


Main.py


from flask import Flask    
from ErrorHandler import ErrorHandlerMold    
    
app = Flask(__name__)    
    
app.register_blueprint(ErrorHandlerMold, url_prefix='/errors')    
    
@app.route('/test1')    
def test1():    
    arr = ["apples", "bananas"]    
    fruit = arr[2]    
    return "hello world - " + fruit    
    
@app.route('/test2')    
def test2():    
    num = 2 / 0    
    return "hello world - " + str(num)    
    
if __name__ == "__main__":    
    app.run()


ErrorHandler.py


from flask import Blueprint    
    
ErrorHandlerMold = Blueprint('ErrorHandler', __name__)    
    
@ErrorHandlerMold.app_errorhandler(Exception)    
def basic_error(e):    
    return "An error occurred: " + str(e)    
    
@ErrorHandlerMold.route('/test1')    
def test1():    
    arr = ["apples", "bananas"]    
    fruit = arr[2]    
    return "hello world - " + fruit    
    
@ErrorHandlerMold.route('/test2')    
def test2():    
    num = 2 / 0    
    return "hello world - " + str(num)


In the above application, no matter which endpoint we call from either the main module or the mold, an error will occur which will be handler by our error handling function. This error handler will also affect any other endpoints contained in all other molds within the application


Local or global error handling, which one should you use?


As with any issue regarding scope in applications, it all depends on the situation, the choice of global and local functions exists to provide structure and organization to your application.


If you want to standardize your error handling for common errors across your application- such as page not found errors for example- global errors are probably the way to go for you.


If you have multiple molds each performing more specific tasks, it may be useful to handle the same errors differently for each of them, making local error handling more suitable.


Using the request context within an error handler


When an error occurs within your application, you'll want to gather as much useful information about the event as possible in order to easily debug potentials faults, log suspicious activity or just to ensure endpoints are being called correctly.


The request context in Flask contains a lot of useful data about individual requests such as the user's IP, user agent, requested URL and much more, and is available within endpoints. The request context can also be used within error handlers (as well as other functions declared with

Flask decorators such as before_request and after_request

) to gather data.


To make use of Flask's request context, simply import the request object into the module you wish to use it in. Once imported you can access its properties.


from flask import Flask, request 
 
app = Flask(__name__) 
 
@app.route('/test1') 
def test1(): 
    arr = ["apples", "bananas"] 
    fruit = arr[2] 
    return "hello world - " + fruit 
 
@app.route('/test2') 
def test2(): 
    num = 2 / 0 
    return "hello world - " + str(num) 
 
@app.errorhandler(Exception) 
def basic_error(e): 
    # fetch some info about the user from the request object 
    user_ip = request.remote_addr 
    requested_path = request.path 
 
    print("User with IP %s tried to access endpoint: %s" % (user_ip , requested_path)) 
    return "An error occurred: " + str(e) 
 
if __name__ == "__main__": 
    app.run()


In the above example we have a simple application with two problematic endpoints each of which will cause an error. Within our error handler we make use of the request context to store the user's IP and the endpoint they requested in variables which we then print in a message to the console. If we try to access /test1 on our local server, the following message will be printed to the server's console:


User with IP 127.0.0.1 tried to access endpoint: /test1


Flask request object contains a lot more useful properties which you can use for debugging purposes, all of which can be found on

Flask's documentation for incoming requests

.


If you have any questions, have anything to add or found this tutorial helpful, please leave a comment or rating below!



Christopher Thornton@Instructobit 5 years ago
or