1Lazily Loading Views 2==================== 3 4Flask is usually used with the decorators. Decorators are simple and you 5have the URL right next to the function that is called for that specific 6URL. However there is a downside to this approach: it means all your code 7that uses decorators has to be imported upfront or Flask will never 8actually find your function. 9 10This can be a problem if your application has to import quick. It might 11have to do that on systems like Google's App Engine or other systems. So 12if you suddenly notice that your application outgrows this approach you 13can fall back to a centralized URL mapping. 14 15The system that enables having a central URL map is the 16:meth:`~flask.Flask.add_url_rule` function. Instead of using decorators, 17you have a file that sets up the application with all URLs. 18 19Converting to Centralized URL Map 20--------------------------------- 21 22Imagine the current application looks somewhat like this:: 23 24 from flask import Flask 25 app = Flask(__name__) 26 27 @app.route('/') 28 def index(): 29 pass 30 31 @app.route('/user/<username>') 32 def user(username): 33 pass 34 35Then, with the centralized approach you would have one file with the views 36(:file:`views.py`) but without any decorator:: 37 38 def index(): 39 pass 40 41 def user(username): 42 pass 43 44And then a file that sets up an application which maps the functions to 45URLs:: 46 47 from flask import Flask 48 from yourapplication import views 49 app = Flask(__name__) 50 app.add_url_rule('/', view_func=views.index) 51 app.add_url_rule('/user/<username>', view_func=views.user) 52 53Loading Late 54------------ 55 56So far we only split up the views and the routing, but the module is still 57loaded upfront. The trick is to actually load the view function as needed. 58This can be accomplished with a helper class that behaves just like a 59function but internally imports the real function on first use:: 60 61 from werkzeug.utils import import_string, cached_property 62 63 class LazyView(object): 64 65 def __init__(self, import_name): 66 self.__module__, self.__name__ = import_name.rsplit('.', 1) 67 self.import_name = import_name 68 69 @cached_property 70 def view(self): 71 return import_string(self.import_name) 72 73 def __call__(self, *args, **kwargs): 74 return self.view(*args, **kwargs) 75 76What's important here is is that `__module__` and `__name__` are properly 77set. This is used by Flask internally to figure out how to name the 78URL rules in case you don't provide a name for the rule yourself. 79 80Then you can define your central place to combine the views like this:: 81 82 from flask import Flask 83 from yourapplication.helpers import LazyView 84 app = Flask(__name__) 85 app.add_url_rule('/', 86 view_func=LazyView('yourapplication.views.index')) 87 app.add_url_rule('/user/<username>', 88 view_func=LazyView('yourapplication.views.user')) 89 90You can further optimize this in terms of amount of keystrokes needed to 91write this by having a function that calls into 92:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project 93name and a dot, and by wrapping `view_func` in a `LazyView` as needed. :: 94 95 def url(import_name, url_rules=[], **options): 96 view = LazyView(f"yourapplication.{import_name}") 97 for url_rule in url_rules: 98 app.add_url_rule(url_rule, view_func=view, **options) 99 100 # add a single route to the index view 101 url('views.index', ['/']) 102 103 # add two routes to a single function endpoint 104 url_rules = ['/user/','/user/<username>'] 105 url('views.user', url_rules) 106 107One thing to keep in mind is that before and after request handlers have 108to be in a file that is imported upfront to work properly on the first 109request. The same goes for any kind of remaining decorator. 110