Explore Flask

Chapter 6. Advanced patterns for views and routing

6.1. View decorators

Python decorators are functions that are used to transform other functions. When a decorated function is called, the decorator is called instead. The decorator can then take action, modify the arguments, halt execution or call the original function. We can use decorators to wrap views with code we'd like to run before they are executed.

@decorator_function
def decorated():
    pass

If you've gone through the Flask tutorial, the syntax in this code block might look familiar to you. @app.route is a decorator used to match URLs to view functions in Flask apps.

Let's take a look at some other decorators you can use in your Flask apps.

6.1.1. Authentication

The Flask-Login extension makes it easy to implement a login system. In addition to handling the details of user authentication, Flask-Login gives us a decorator to restrict certain views to authenticated users: @login_required.

# app.py

from flask import render_template
from flask.ext.login import login_required, current_user


@app.route('/')
def index():
    return render_template("index.html")

@app.route('/dashboard')
@login_required
def account():
    return render_template("account.html")

Caution @app.route should always be the outermost view decorator.

Only an authenticated user will be able to access the /dashboard route. We can configure Flask-Login to redirect unauthenticated users to a login page, return an HTTP 401 status or anything else we'd like it to do with them.

Note Read more about using Flask-Login in the official docs.

6.1.2. Caching

Imagine that an article mentioning our application just appeared on CNN and some other news sites. We're getting thousands of requests per second. Our homepage makes several trips to the database for each request, so all of this attention is slowing things down to a crawl. How can we speed things up quickly, so all of these visitors don't miss out on our site?

There are a lot of good answers, but this section is about caching, so we'll talk about that. Specifically, we're going to use the Flask-Cache extension. This extension provides us with a decorator that we can use on our index view to cache the response for some period of time.

Flask-Cache can be configured to work with a bunch of different caching backends. A popular choice is Redis, which is easy to set-up and use. Assuming Flask-Cache is already configured, this code block shows what our decorated view would look like.

# app.py

from flask.ext.cache import Cache
from flask import Flask

app = Flask()

# We'd normally include configuration settings in this call
cache = Cache(app)

@app.route('/')
@cache.cached(timeout=60)
def index():
    [...] # Make a few database calls to get the information we need
    return render_template(
        'index.html',
        latest_posts=latest_posts, 
        recent_users=recent_users,
        recent_photos=recent_photos
    )

Now the function will only be run once every 60 seconds, when the cache expires. The response will be saved in our cache and pulled from there for any intervening requests.

Note Flask-Cache also lets us memoize functions — or cache the result of a function being called with certain arguments. You can even cache computationally expensive Jinja2 template snippets.

6.1.3. Custom decorators

For this section, let's imagine we have an application that charges users each month. If a user's account is expired, we'll redirect them to the billing page and tell them to upgrade.

# myapp/util.py

from functools import wraps
from datetime import datetime

from flask import flash, redirect, url_for

from flask.ext.login import current_user

def check_expired(func):
    @wraps(func)
    def decorated_function(*args, **kwargs):
        if datetime.utcnow() > current_user.account_expires:
            flash("Your account has expired. Update your billing info.")
            return redirect(url_for('account_billing'))
        return func(*args, **kwargs)

    return decorated_function
  • Line 10: When a function is decorated with @check_expired, check_expired() is called and the decorated function is passed as a parameter.
  • Line 11: @wraps is a decorator that does some bookkeeping so that decorated_function() appears as func() for the purposes of documentation and debugging. This makes the behavior of the functions a little more natural.
  • Line 12: decorated_function will get all of the args and kwargs that were passed to the original view function func(). This is where we check if the user's account is expired. If it is, we'll flash a message and redirect them to the billing page.
  • Line 16: Now that we've done what we wanted to do, we run the decorated view function func() with its original arguments.

When we stack decorators, the topmost decorator will run first, then call the next function in line: either the view function or the next decorator. The decorator syntax is just a little syntactic sugar.

# This code:
@foo
@bar
def one():
    pass

r1 = one()

# is the same as this code:
def two():
    pass

two = foo(bar(two))
r2 = two()

r1 == r2 # True

This code block shows an example using our custom decorator and the @login_required decorator from the Flask-Login extension. We can use multiple decorators by stacking them.

# myapp/views.py

from flask import render_template

from flask.ext.login import login_required

from . import app
from .util import check_expired

@app.route('/use_app')
@login_required
@check_expired
def use_app():
    """Use our amazing app."""
    # [...]
    return render_template('use_app.html')

@app.route('/account/billing')
@login_required
def account_billing():
    """Update your billing info."""
    # [...]
    return render_template('account/billing.html')

Now when a user tries to access use_app, check_expired() will make sure that their account hasn't expired before running the view function.

Note Read more about what the wraps() function does in the Python docs.