View Decorators

Python has a really interesting feature called function decorators. Thisallows some really neat things for web applications. Because each view inFlask is a function, decorators can be used to inject additionalfunctionality to one or more functions. The route()decorator is the one you probably used already. But there are use casesfor implementing your own decorator. For instance, imagine you have aview that should only be used by people that are logged in. If a usergoes to the site and is not logged in, they should be redirected to thelogin page. This is a good example of a use case where a decorator is anexcellent solution.

Login Required Decorator

So let’s implement such a decorator. A decorator is a function thatwraps and replaces another function. Since the original function isreplaced, you need to remember to copy the original function’s informationto the new function. Use functools.wraps() to handle this for you.

This example assumes that the login page is called 'login' and thatthe current user is stored in g.user and is None if there is no-onelogged in.

  1. from functools import wraps
  2. from flask import g, request, redirect, url_for
  3.  
  4. def login_required(f):
  5. @wraps(f)
  6. def decorated_function(*args, **kwargs):
  7. if g.user is None:
  8. return redirect(url_for('login', next=request.url))
  9. return f(*args, **kwargs)
  10. return decorated_function

To use the decorator, apply it as innermost decorator to a view function.When applying further decorators, always rememberthat the route() decorator is the outermost.

  1. @app.route('/secret_page')@login_requireddef secret_page(): pass

Note

The next value will exist in request.args after a GET request forthe login page. You’ll have to pass it along when sending the POST requestfrom the login form. You can do this with a hidden input tag, then retrieve itfrom request.form when logging the user in.

  1. <input type="hidden" value="{{ request.args.get('next', '') }}"/>

Caching Decorator

Imagine you have a view function that does an expensive calculation andbecause of that you would like to cache the generated results for acertain amount of time. A decorator would be nice for that. We’reassuming you have set up a cache like mentioned in Caching.

Here is an example cache function. It generates the cache key from aspecific prefix (actually a format string) and the current path of therequest. Notice that we are using a function that first creates thedecorator that then decorates the function. Sounds awful? Unfortunatelyit is a little bit more complex, but the code should still bestraightforward to read.

The decorated function will then work as follows

  • get the unique cache key for the current request base on the currentpath.

  • get the value for that key from the cache. If the cache returnedsomething we will return that value.

  • otherwise the original function is called and the return value isstored in the cache for the timeout provided (by default 5 minutes).

Here the code:

  1. from functools import wraps
  2. from flask import request
  3.  
  4. def cached(timeout=5 * 60, key='view/%s'):
  5. def decorator(f):
  6. @wraps(f)
  7. def decorated_function(*args, **kwargs):
  8. cache_key = key % request.path
  9. rv = cache.get(cache_key)
  10. if rv is not None:
  11. return rv
  12. rv = f(*args, **kwargs)
  13. cache.set(cache_key, rv, timeout=timeout)
  14. return rv
  15. return decorated_function
  16. return decorator

Notice that this assumes an instantiated cache object is available, seeCaching for more information.

Templating Decorator

A common pattern invented by the TurboGears guys a while back is atemplating decorator. The idea of that decorator is that you return adictionary with the values passed to the template from the view functionand the template is automatically rendered. With that, the followingthree examples do exactly the same:

  1. @app.route('/')def index(): return render_template('index.html', value=42)

  2. @app.route('/')@templated('index.html')def index(): return dict(value=42)

  3. @app.route('/')@templated()def index(): return dict(value=42)

As you can see, if no template name is provided it will use the endpointof the URL map with dots converted to slashes + '.html'. Otherwisethe provided template name is used. When the decorated function returns,the dictionary returned is passed to the template rendering function. IfNone is returned, an empty dictionary is assumed, if something else thana dictionary is returned we return it from the function unchanged. Thatway you can still use the redirect function or return simple strings.

Here is the code for that decorator:

  1. from functools import wraps
  2. from flask import request, render_template
  3.  
  4. def templated(template=None):
  5. def decorator(f):
  6. @wraps(f)
  7. def decorated_function(*args, **kwargs):
  8. template_name = template
  9. if template_name is None:
  10. template_name = request.endpoint \
  11. .replace('.', '/') + '.html'
  12. ctx = f(*args, **kwargs)
  13. if ctx is None:
  14. ctx = {}
  15. elif not isinstance(ctx, dict):
  16. return ctx
  17. return render_template(template_name, **ctx)
  18. return decorated_function
  19. return decorator

Endpoint Decorator

When you want to use the werkzeug routing system for more flexibility youneed to map the endpoint as defined in the Ruleto a view function. This is possible with this decorator. For example:

  1. from flask import Flask
  2. from werkzeug.routing import Rule
  3.  
  4. app = Flask(__name__)
  5. app.url_map.add(Rule('/', endpoint='index'))
  6.  
  7. @app.endpoint('index')
  8. def my_index():
  9. return "Hello world"