

Things which aren't magic – Flask and app.route - StylifyYourBlog
http://ains.co/blog/things-which-arent-magic-flask-part-1.html

======
level09
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](https://medium.com/@level09)

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

~~~
manto
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?

~~~
level09
The short answer is, faster development, easier to maintain and extend, and
even more efficiency which is exactly what I was looking for.

the long answer is, a combination of many things: \- flask is simpler, has
almost no learning curve

\- Django's ORM is not great, and I preferred nosql where things can be done
faster and no schema migration was needed

\- Jinja2 seemed better and the template structure was simpler, and the static
file serving seemed more straight forward in flask

\- settings management, urls and routing (with decorators), blueprints and
many other things ..

I really believe that Flask represents the best way to design any web
framework.

------
josefdlange
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?

~~~
zrail
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.

~~~
ekimekim
Well, no. The decorator generator is called first, then the decorator is
called, all at the time when the line "@app.route()" is first reached during
import. You could just as easily write @foo.bar[baz]('abc').hello and it would
still work - the code after the @ is just an expression that should return a
callable.

------
untitaker_
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
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.

------
RubyPinch
Something a bit more on the "magic" side, that some might find relevant
[https://gist.github.com/Socialery/40141aa2c2d70bd065e8](https://gist.github.com/Socialery/40141aa2c2d70bd065e8)

~~~
zifnab06
I never knew about q - looks very useful

------
sauere
As a Python newbie that just recently started using things like Flask and
Bottle: wow, that was easy.

~~~
bigb9320
Yeah I know, I worked with webapp2 on app engine and working with flask was
just plain simple and comfortable.

------
marvatu
I did something similar when playing around trying to make a wsgi
[http://jibreel.me/blog/2/](http://jibreel.me/blog/2/)

------
bigb9320
Am I right in understanding that decorators are a form of closures as the
decorator function is returning the function declared inside it ?

~~~
alangpierce
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.

------
alangpierce
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_
>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.

~~~
alangpierce
You're right, I hadn't seen blueprints, and they do seem to address my
concerns pretty nicely.

All of the examples that I've seen (including the Flask quickstart guide, the
linked post, and everything I could find on
[http://flask.pocoo.org/community/poweredby/](http://flask.pocoo.org/community/poweredby/)
) work by assigning the Flask app to a module-level variable (i.e. a global),
then using that global at import time for all @app.route usages, so my
assumption has been that that's the encouraged style. It at least seems to be
pretty common. But I guess none of those examples are very big (only a few
split the request handlers across multiple files), so they didn't get to a
point where blueprints would be especially useful.

(Also, to be clear, when I say "imports shouldn't have side-effects", what I
mean is that top-level code (which runs at import time) should ideally have no
side effects.)

------
rev_bird
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
"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.)

~~~
rev_bird
Huh. And now I've learned about British that/which! Thank you.

