Python Decorator and Flask
Tags: python, decorator, flask, tutorial
Published: 2019-07-22 21:53:40

**Decorator** is one of my favorite Python features. While seemingly confused at first, it is just a function that takes another function as a parameter and returns another function. 

`decorator: Func -> Func`

This is possible because, in Python, `function` is a first-class citizen, meaning that function can be used as a parameter, a return value and can be assigned to a variable.

Once understood, `decorator` can be used in various situations and some of which will be discussed below.

By definition,

> a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it


Concretely the below code:

```python
@decorator
def f(argument):
    ...
```

will replace `f` by `decorator(f)`: calling `f(argument)` is then equivalent to `decorator(f)(argument)`.

Flask, a popular and lightweight web framework uses decorator extensively that results in an elegant and intuitive code. Decorator uses can be found throughout the framework, from defining routes to adding hooks to application/request lifecycle. 

In this small post, some use cases of decorators in Flask will be discussed. Please note that this post is intended for users who already have experiences with Flask and decorators. The code examples are also mostly pseudocode to illustrate ideas rather than be immediately usable. If you want to learn about Flask or decorator, you can find some resources at the end of the post.

---
## First thing first, how @app.route is implemented?


The first example of Flask usually begins with something like:

```python
@app.route(“/”)
def index():
    return "Hello world"
```

By having `app.route` as decorator, the function `index` is registered for the route `/` so that when that route is requested, `index` is called and its result "Hello world" is returned to the client (be it a web browser, curl, etc).

Have you ever wondered how the ubiquitous `@app.route` is implemented? As the decorator is usually used to modify a function behavior, its effect can normally be seen when this function is called. Whereas `index` is not called anywhere in the code.

An unpopular fact about decorator is that it is **definition-time and not runtime**, that is `index` will be replaced by `app.route('/')(index)` when the module is **imported**. The fact that this registering is done in definition time is important here as if it wasn’t the case, Flask would have no way to know of index existence.

The implementation logic of `@app.route` is actually quite simple, basically `index` is added into a global url-function mapping variable that will be searched upon when a request comes in:

```python
class App:
    route_functions = {}
    def route(url_pattern):
        def wrap(f):
            route_functions[url_pattern] = f
            return f
        return wrap
```

so when replacing `index` by `app.route(“/”)(index)`, `index` is added into the `route_functions` that contains route-function matchings.

---
## Login decorator


Let’s say you have a Flask application that allows a user to add tweets. Each tweet belongs to a user and only logged in user can add tweet. Therefore you want to limit some routes to only logged in users and if a user is not logged in, they will be redirected to the login page. 

Adding the login check for every route is quite cumbersome and violates the **DRY** principle. It would be very handy if we can just decorate such routes with @login_required and all the login checks will be done automatically.

```python
@app.route("/add_tweet")
@login_required
def add_tweet():
   ...
```

Suppose that the user verification uses cookie, `login_required` first checks the cookie, if the cookie is not valid, redirect user to login page. Otherwise call `add_tweet`:

```python
from functools import wraps
def login_required(f):
    @wraps(f)
    def wrap(*args, **kwargs):
        # if user is not logged in, redirect to login page      
        if not request.headers["authorization"]:
            return redirect("login page")
        # get user via some ORM system
        user = User.get(request.headers["authorization"])
        # make user available down the pipeline via flask.g
        g.user = user
        # finally call f. f() now haves access to g.user
        return f(*args, **kwargs)
   
    return wrap
```

Note that `login_required` needs to be placed after `app.route` so `login_required(add_tweet)` will be called and not just `add_tweet` when the route matches. Otherwise, if `login_required` is placed before `app.route`, only `add_tweet` is called when the route is requested and therefore the login requirement check has no effect.

---
## Roles based decorator

The previous login decorator can be extended to take into account various user roles. Let’s say we have admin users that can call routes that other users cannot. It would be nice to have a decorator `admin_login_required`, that, when used with `login_required` marks a route only available for admin users.

```python
def admin_login_required():
    def wrap(*args, **kwargs):
        # user is available from @login_required
        if not g.user.is_admin:
            return "you need to be admin", 401
return f(*args, **kwargs)
```

So to decorate a route that requires admin access, one can just add these 2 decorators:


```python
@app.route("/admin/delete_user")
@login_required
@admin_login_required
def admin_delete_user():
    ...
```

The order here is again important: `admin_login_required` must be placed after `login_required` to benefit from `g.user` set in `login_required`.

---
## Other uses

Making use of decorator can elegantly resolve various DRY problems:
* Get input from request query, JSON, form with type checking, so that if the input is missing or not correctly-typed, return 400
* Add elapsed time logging to know what routes is slow
* Page caching based on URL parameters
* Etc

---
### Some excellent resources IMO to learn Flask and decorators:


https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world

http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/

https://www.thecodeship.com/patterns/guide-to-python-function-decorators/