Create a custom Tornado decorator with functools

Decorators are neat.  They let you do things in much the same way javascript’s anonymous functions work.

So why use a decorator? Why not create an override (or overload, depending on your vernacular) to accomplish the same sort of goal?

I had a problem today where I needed to ensure a specific variable was coming over in a query string argument in the wonderful tornado framework.  I didn’t want to have to reinvent the wheel, or have to overwrite the concept of simplicity in the Web class in the framework.  Programmers in general are lazy people, and like doing a lot with a little, so I decided to see how tornado handled it’s decorators.

 

Before I dig into the code, a few notes:

 A decorator is the name used for a software design pattern. Decorators dynamically alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated.

Make sense? It wasn’t really clear to me either.  But I’ve used anonymous functions in javascript to alter a function without interfering with the global object, so why can’t we do something similar in Python?  Well- It turns out; We can.

Here’s what tornado does to handle their @authenticated decorator. (for authenticating a user before executing the route)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def authenticated(method):
    """Decorate methods with this to require that the user be logged in."""
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        if not self.current_user:
            if self.request.method in ("GET", "HEAD"):
                url = self.get_login_url()
                if "?" not in url:
                    if urlparse.urlsplit(url).scheme:
                        # if login url is absolute, make next absolute too
                        next_url = self.request.full_url()
                    else:
                        next_url = self.request.uri
                    url += "?" + urllib.urlencode(dict(next=next_url))
                self.redirect(url)
                return
            raise HTTPError(403)
        return method(self, *args, **kwargs)
    return wrapper

Whoa. tons of stuff going on here. Lets break it down.

First, it’s important to note that this lives OUTSIDE of any particular class.

1
2
3
4
5
6
7
def authenticated(method):
    """Decorate methods with this to require that the user be logged in."""
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        implementation_details()
        return method(self, *args, **kwargs)
    return wrapper

Wait, what? @functools?

The functools module is for higher-order functions: functions that act on or return other functions. In general, any callable object can be treated as a function for the purposes of this module.

Get that?  for any javascript developers, this may seem familar.  This is somewhat similar to setting up an anonymous function, right?

The most important thing to remember here is that .wraps() is being fed another method and spitting out that method if conditions or whatever is met during the wrappers execution. You don’t necessarily have to return the function (as explained below) but you probably should.

In the implementation I built for my app, I needed an easy way to throw an error when a condition wasn’t met during the request’s execution. Since tornado kind of already does this through it’s @authenticated decorator, it makes sense to reuse that bit of magic for my own purposes.

Lets go over some boilerplate to defining our own methods for use in tornado.

For this example, I’ve built a simple request blocker that returns some json if a user isn’t allowed to hit an API call.

1
2
3
4
5
6
7
8
9
10
def block_jerks(method):
    """The black knight always triumphs!"""
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        should_i_block_this_request = self.check_if_i_should()
        if should_i_block_this_request:
            return self.you_have_been_blocked()
        else:
            return method(self, *args, **kwargs)
    return wrapper

Then later on, when i want to actually use this decorator, I do the following:

1
2
3
4
class SomeHandler(WebRequest):
  @block_jerks
  def get(self, param):
    self.write('tis but a scratch')

Of course, this assumes that you have a global subclass of tornado.web.RequestHandler (WebRequest above) somewhere in your project. You should probably do that to avoid violating DRY. When you have something of that sort, just import the decorator like you would a class in your file and you’re good to go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import functools
import tornado.web

def block_jerks(method):
    """The black knight always triumphs!"""
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        should_i_block_this_request = self.check_if_i_should()
        if should_i_block_this_request:
            return self.you_have_been_blocked()
        else:
            return method(self, *args, **kwargs)
    return wrapper
   
class WebRequest(tornado.web.RequestHandler):
  pass
 
class SomeHandler(WebRequest):
  @block_jerks
  def get(self, param):
    self.write('tis but a scratch')

That’s really it. We now have a decorator in place that we can use anywhere in our routes. (because we’re using a project-wide subclass of RequestHandler, right?)

 

About the author

Written by @landongn

Landon is the lead developer over at TVplus. He works primarily with Javascript, Python, and HTML5. In his spare time, He usually dabbles in Game Design and Programming, as well as Mobile Apps. You can follow him on twitter here.

Comment on this post

Things I’m tweetingAll tweets

Please wait while our tweets load indicator

Recent Comments