Live data from Hacker News

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

ains.co

11–20 of 35 posts

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

#12
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.

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

#15
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.

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

#16
post #12

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/OR…

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?

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

#17

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

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.

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

#20

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

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.
Post reply on HN