How do function decorators work and how to use them for Flask routes

How do you limit routes so they can be accessed if and only if certain condition are met? For example, limiting a route for users who are both logged in and have the admin role:

@login_required
@roles_required(["admin"])
def admin():
return render_template("admin.html")

1. What are decorators?

Python decorators are syntactic sugar for functions that return functions. Let’s take a look at a simple example of what this means.

def my_function():
print("Inside my_function")

def decorator_func(func_to_decorate):
def wrapper_func():
print("Runs before func_to_decorate")
func_to_decorate()
print("Runs after func_to_decorate")
return wrapper_func

my_function_decorated = decorator_func(my_function)

my_function_decorated()
# Output:
# "Runs before func_to_decorate"
# "Inside my_function"
# "Runs after func_to_decorate"

We can see that the decorator_func returns the function wrapper_func, which simply calls the function that we pass into decorator_func. Now let’s do the same thing but using the decorator syntax.

@decorator_func
def another_function():
print("Inside another_function")

another_function()
# Outputs:
# "Runs before func_to_decorate"
# "Inside another_function"
# "Runs after func_to_decorate"

So we can see that @decorator is just a shortcut for my_function_decorated = decorator_func(my_function)

2. How to create a login_required decorator?

Now let’s look at how to create a login_required decorator for our routes. The flask documentation has a good example for it.

from functools import wraps
from flask import g, request, redirect, url_for

def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user is None:
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function

Used like so:

@login_required
def login_route():
# ...

It looks similar to our decorator_func above, except that it does a check first to see if the g object has a user, and if it doesn’t, redirects the person to the login page. If the user does exist, then it returns the f function, passing in all args and kwargs.

3. Why should we use @wraps?

The other noticeable difference is @wraps, imported from functools. The main purpose of using @wraps is so that the metadata of the returned wrapper function will point to the decorated function. An example will make this clear:

from functools import wraps

def decorator_func(func_to_decorate):
@wraps(func_to_decorate)
def wrapper_func():
func_to_decorate()
return wrapper_func

def decorator_func_without_wraps(func_to_decorate):
def wrapper_func():
func_to_decorate()
return wrapper_func

@decorator_func
def my_function():
print("Inside my_function")

@decorator_func_without_wraps
def another_function():
print("Inside another_function")

my_function.__name__
# 'my_function'

another_function.__name__
# 'wrapper_func'

Here we have two decorators, one which uses @wraps, and another which doesn’t. When we call __name__ on the decorated function, we see that the one that uses @wraps correctly returns the name of the decorated function. But the other decorated function returns the __name__ of the wrapper function, which is not useful. That’s why we use @wraps.

4. How can we create a decorator that accepts arguments?

Next, we want to look at how we can create a decorator function that accepts arguments like @roles_required(["admin"]).

from functools import wraps
from flask_login import current_user

def roles_required(roles):
def decorator_function(f):
@wraps(f)
def wrapper(*args, **kwargs):
if not set(roles).issubset({r.name for r in current_user.roles}):
return { error: 'Not an admin' }
return f(*args, **kwargs)

return wrapper

return decorator_function

It looks similar except we wrap the decorator function inside roles_required, so that it can accept arguments.

  • roles_required takes in the roles argument and when it’s called it returns decorator_function
  • decorator_function returns wrapper function
  • wrapper function returns f, which is the decorated function

5. Using multiple decorators together

Let’s go back to our first example:

@login_required
@roles_required(["admin"])
def admin():
return render_template("admin.html")

Now we understand that linking two decorators together is just the same as:

login_required(roles_required(["admin"])(admin))

The general formula for creating a decorator function is:

  • create the decorator function that accepts your_function
  • create a wrapper function inside the decorator function (and use @wraps to decorate the wrapper function)
  • have the wrapper function return your_function with all the arguments passed in
  • have the decorator function return the wrapper function

Acknowledgement: Some code snippets from a great answer on SOF


Want to learn how to create a modern Flask app using a services oriented structure? Take a look at the github repo or