Lazily Loading Views

Flask is usually used with the decorators. Decorators are simple and youhave the URL right next to the function that is called for that specificURL. However there is a downside to this approach: it means all your codethat uses decorators has to be imported upfront or Flask will neveractually find your function.

This can be a problem if your application has to import quick. It mighthave to do that on systems like Google’s App Engine or other systems. Soif you suddenly notice that your application outgrows this approach youcan fall back to a centralized URL mapping.

The system that enables having a central URL map is theadd_url_rule() function. Instead of using decorators,you have a file that sets up the application with all URLs.

Converting to Centralized URL Map

Imagine the current application looks somewhat like this:

  1. from flask import Flask
  2. app = Flask(__name__)
  3.  
  4. @app.route('/')
  5. def index():
  6. pass
  7.  
  8. @app.route('/user/<username>')
  9. def user(username):
  10. pass

Then, with the centralized approach you would have one file with the views(views.py) but without any decorator:

  1. def index():
  2. pass
  3.  
  4. def user(username):
  5. pass

And then a file that sets up an application which maps the functions toURLs:

  1. from flask import Flask
  2. from yourapplication import views
  3. app = Flask(__name__)
  4. app.add_url_rule('/', view_func=views.index)
  5. app.add_url_rule('/user/<username>', view_func=views.user)

Loading Late

So far we only split up the views and the routing, but the module is stillloaded upfront. The trick is to actually load the view function as needed.This can be accomplished with a helper class that behaves just like afunction but internally imports the real function on first use:

  1. from werkzeug import import_string, cached_property
  2.  
  3. class LazyView(object):
  4.  
  5. def __init__(self, import_name):
  6. self.__module__, self.__name__ = import_name.rsplit('.', 1)
  7. self.import_name = import_name
  8.  
  9. @cached_property
  10. def view(self):
  11. return import_string(self.import_name)
  12.  
  13. def __call__(self, *args, **kwargs):
  14. return self.view(*args, **kwargs)

What’s important here is is that module and name are properlyset. This is used by Flask internally to figure out how to name theURL rules in case you don’t provide a name for the rule yourself.

Then you can define your central place to combine the views like this:

  1. from flask import Flask
  2. from yourapplication.helpers import LazyView
  3. app = Flask(__name__)
  4. app.add_url_rule('/',
  5. view_func=LazyView('yourapplication.views.index'))
  6. app.add_url_rule('/user/<username>',
  7. view_func=LazyView('yourapplication.views.user'))

You can further optimize this in terms of amount of keystrokes needed towrite this by having a function that calls intoadd_url_rule() by prefixing a string with the projectname and a dot, and by wrapping view_func in a LazyView as needed.

  1. def url(import_name, url_rules=[], **options):
  2. view = LazyView('yourapplication.' + import_name)
  3. for url_rule in url_rules:
  4. app.add_url_rule(url_rule, view_func=view, **options)
  5.  
  6. # add a single route to the index view
  7. url('views.index', ['/'])
  8.  
  9. # add two routes to a single function endpoint
  10. url_rules = ['/user/','/user/<username>']
  11. url('views.user', url_rules)

One thing to keep in mind is that before and after request handlers haveto be in a file that is imported upfront to work properly on the firstrequest. The same goes for any kind of remaining decorator.