top | item 8808453

Things which aren't magic – Flask and app.route

160 points| StylifyYourBlog | 11 years ago |ains.co | reply

33 comments

order
[+] level09|11 years ago|reply
Flask is incredible. since I've made the transition from PHP-based CMS's I've never looked back.

if someone is interested in learning more about building larger scale/production apps with flask, I have a series of tutorials at medium to get you started: https://medium.com/@level09

Disclaimer: I'm the creator of enferno (http://enferno.io), A flask-based system pre-configured with caching/user auth/basic template/ORM/CRUD/task queue/mail functionality.

[+] manto|11 years ago|reply
Curious what made you go this route rather than say a more full featured framework (Django/Rails)? Just based on assumptions, was it the ability to specify each of your preferred components?
[+] josefdlange|11 years ago|reply
What's throwing me off -- and I'm sure it's something trivial I'm not seeing -- is how we add the functions to the routes dictionary. When is the decorator's code actually executed? Is that done at import time? My assumption is that its code is executed when its counterpart is called, though clearly that mustn't be the case.

Given my understanding from the article, there's a hole: how does serving route "/" know to call that function if (given my assumptions) the @app.route("/") decoration is not executed until the function it decorates is called?

[+] mquander|11 years ago|reply
app.route("/") is just a function call which will be run by Python on module import, like any other function that you call in the top level of your module.

The return value of the function is a decorator, so then the @ syntax applies it to the succeeding function. In the process of applying the decorator, it adds the route to the dictionary. That's also on module import.

[+] zrail|11 years ago|reply
Plain decorators work how you expect but generators are different. The decorator generator is called at import time, which is when the association between the route and the function is stored.
[+] josephlord|11 years ago|reply
Yes I had to think about it and test it (not done much Python recently). The decorating function is called as soon as the decorated function is declared.

These are the results of a simple test in the Python console.

  >>> def dec(f):
  ...   print "Decorator ran"
  ...   return f
  ... 
  >>> @dec
  ... def decorated(i):
  ...   print "Decorated function ran"
  ... 
  Decorator ran
  >>> decorated(5)
  Decorated function ran
  >>> decorated(5)
  Decorated function ran
[+] sfilipov|11 years ago|reply
The @ syntax is just syntactic sugar.

  @decorator
  def function():
      print("Hello World!")
is equivalent to:

  def function():
      print("Hello World!")
  function = decorator(function)
[+] untitaker_|11 years ago|reply
The term decorator generator is incredibly misleading, as it implies relation to Python generators. The term decorator factory (or just decorator with parameters) is preferrable.
[+] ainsej|11 years ago|reply
Author here, and good point! I was struggling a bit with the terminology to use, didn't think of decorator factory, it definitely would have been a bit clearer.
[+] hmsimha|11 years ago|reply
Came here to say this exact same thing
[+] sauere|11 years ago|reply
As a Python newbie that just recently started using things like Flask and Bottle: wow, that was easy.
[+] bigb9320|11 years ago|reply
Yeah I know, I worked with webapp2 on app engine and working with flask was just plain simple and comfortable.
[+] bigb9320|11 years ago|reply
Am I right in understanding that decorators are a form of closures as the decorator function is returning the function declared inside it ?
[+] alangpierce|11 years ago|reply
Decorators typically use closures (including in this case), but they're really just syntax sugar for transforming one function into a different one with the same name. This code:

  @d
  def foo():
      ...
is equivalent to this code:

  def foo():
      ...
  foo = d(foo)
(You should think of a Python "def" statement as an action that creates a function and assigns it to a variable, like "foo" in this case. Since functions are first-class values, they can be sent into other functions and assigned again, which is why this works.)

But yeah, if you're implementing a decorator (a function from function to function, like "d" above), you can declare an inner function and immediately return it, and that inner function will act as a closure (it will have access to variables in the outer scope). You can take that approach in other situations as well, not just with decorators.

[+] ekimekim|11 years ago|reply
As a followup to the sibling comment, note that closures aren't the only way to achieve the same effect, for example:

    class MyDecorator(object):
      def __init__(self, func):
        self.func = func
      def __call__(self, one, two, three):
        # do stuff
        return self.func(three, two, one)
ie. you can just as easily use a class instance to store your state, instead of a closure. I routinely use both methods, depending on which is more useful at the time.
[+] lmm|11 years ago|reply
Only in a trivial syntactic sense. They're better understood (IMO) as combinators, though even that isn't absolutely accurate.

Of course ultimately a word can mean different things; did you have a more specific question about what a decorator or a closure does?

[+] alangpierce|11 years ago|reply
Flask's route decorator gives a nice syntax, but it goes against some ideal best practices:

* Imports shouldn't have side-effects (like registering functions with flask).

* You shouldn't use globals (like the flask app).

* Objects (such as the flask app) should be immutable whenever possible.

None of these are hard-and-fast rules, and Python code has a tendency to give up purity in favor of syntax, so it's certainly justified for Flask to be designed this way, but it's still a bit unsettling, and can lead to bugs, especially in larger cases when your handlers are split up across many files. Some examples:

* You need to make sure that you import every file with a request handler, and those imports often end up unused (only imported for their side-effects), which confuses linters and other static analysis tools.

* It's also easy to accidentally import a new file through some other import chain, so someone rearranging imports later might accidentally disable part of your app by never importing it.

* It can break some "advanced" uses of modules/imports, such as the reload function.

* Test code and scripts that want access to your request handlers are forced to build a (partial) Flask app, even if they have no use for one.

At my job, I recently changed our Flask handlers to be registered with a different approach (but the same API) that avoids most of these issues. Rather than setting things up with side-effects, it makes the route details easy to introspect later. Here's what our implementation of @route() looks like now:

  def route(rule, **options):
      def route_decorator(func):
          # Attach the route rule to the request handler.
          func.func_dict.setdefault('_flask_routes', []).append((rule, options))
  
          # Add the request handler to this module's list of handlers.
          module = sys.modules[func.__module__]
          if not hasattr(module, '_FLASK_HANDLERS'):
              module. _FLASK_HANDLERS = {}
          module._FLASK_HANDLERS[func.__name__] = func
          return func
  
      return route_decorator
So if you have a module called user_routes.py, with 3 Flask request handlers, then user_routes._FLASK_HANDLERS is a list containing those three functions. If one of those handlers is user_routes.create_user, then you can access user_routes.create_user._flask_routes in order to see the names of all of the route strings (usually just one) registered for that request handler.

Then, in separate code, there's a list of all modules with request handlers, and we import and introspect all of them as part of a function that sets up and returns the Flask app. So outside code never has any way of accessing a partially-registered Flask app, imports of request handler modules are "pure", and request handlers can often be defined without depending on Flask at all.

[+] untitaker_|11 years ago|reply
>Imports shouldn't have side-effects (like registering functions with flask).

Imports don't have side-effects. Using Flask's route decorators has.

>You shouldn't use globals (like the flask app).

The Flask app is just as much a global as any other class instance in any OOP language. Whether you make it a module-level object or not is your choice.

>Objects (such as the flask app) should be immutable whenever possible.

They hardly are. This is a good rule which nobody follows, and I don't think you'd gain enough advantages through this.

>You need to make sure that you import every file with a request handler, and those imports often end up unused (only imported for their side-effects), which confuses linters and other static analysis tools.

The fact that your app has import side-effects is your fault, this pattern is not at all encouraged by Flask. You probably want to use blueprints.

[+] rev_bird|11 years ago|reply
That. Things _that_ aren't magic.

On a more relevant note, thanks for sharing this. It's always nice to read an explanation from somebody patient enough to not skip over a bunch of steps in the middle and avoid losing newbs like me.

[+] geofft|11 years ago|reply
"That" and "which" are apparently interchangeable on the other side of the pond, and the author is from Imperial College London.

On a more relevant note, I agree, this is a great article. :) (I'd already known in theory how decorators work, but this was a very clear presentation, and I hadn't quite thought through the part about only needing a reference to the unmodified function, so it was useful to see that trick spelled out.)

[+] phaemon|11 years ago|reply
> That. Things _that_ aren't magic.

"Then saith he unto them, Render therefore unto Caesar the things which are Caesar's; and unto God the things that are God's."

Clearly Flask is Caesar's ;-)