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