Suppose you had a Flask app with a number of views for which you wanted to perform some logic before presenting the view to the user. A common case is checking that a user is logged in before being able to access views which should be protected, and if they’re not, redirecting them to the login page. Another use case may be CloudFlare’s IP Geo Location feature, which adds a HTTP header called CF-IPCountry and a corresponding 2 character country code. There may be views which you wish to alter depending on the content of this header.
One way to accomplish this is to use decorators. A decorator essentially allows us to wrap one function (the view) in another (the decorator), allowing the decorator to extend the functionality of the view without directly modifying it. A more accurate definition is that a decorator is a function that returns another function, but this explanation never helped me understand the practical use of decorators. The advantage of using decorators is that a single decorator function can be written and used with multiple views.
It’s also worth noting that decorators are not a Flask specific feature, but rather a part of Python - though the focus of this post is on the Flask implementation. In this example a decorator will be used to detect the country of origin using CloudFlare’s CF-IPCountry header, and show a HTTP 418 error if the user is from Australia.
from functools import wraps def check_country(f): @wraps(f) def func_wrapper(*args, **kwargs): blocked_countries = ['AU'] if any(country in request.headers.get('CF-IPCountry').upper() for country in blocked_countries): abort(418) return f(*args, **kwargs) return func_wrapper
This decorator can now be added to as many views as needed.
@app.route('/') @check_country def index(): # code
We may want to have similar functionality added to every view, or on every
request. Flask has a function to achieve this without having to add the
decorator to each function manually,
@app.before_request def before_request(): blocked_countries = ['AU'] if any(country in request.headers.get('CF-IPCountry').upper() for country in blocked_countries): abort(418)
With the above, the check_country function is not required, and the before_request function is applied to all views under our app.