mirror of https://github.com/pallets/flask.git
				
				
				
			
		
			
				
	
	
		
			110 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
			
		
		
	
	
			110 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
Lazily Loading Views
 | 
						|
====================
 | 
						|
 | 
						|
Flask is usually used with the decorators.  Decorators are simple and you
 | 
						|
have the URL right next to the function that is called for that specific
 | 
						|
URL.  However there is a downside to this approach: it means all your code
 | 
						|
that uses decorators has to be imported upfront or Flask will never
 | 
						|
actually find your function.
 | 
						|
 | 
						|
This can be a problem if your application has to import quick.  It might
 | 
						|
have to do that on systems like Google's App Engine or other systems.  So
 | 
						|
if you suddenly notice that your application outgrows this approach you
 | 
						|
can fall back to a centralized URL mapping.
 | 
						|
 | 
						|
The system that enables having a central URL map is the
 | 
						|
:meth:`~flask.Flask.add_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::
 | 
						|
 | 
						|
    from flask import Flask
 | 
						|
    app = Flask(__name__)
 | 
						|
 | 
						|
    @app.route('/')
 | 
						|
    def index():
 | 
						|
        pass
 | 
						|
 | 
						|
    @app.route('/user/<username>')
 | 
						|
    def user(username):
 | 
						|
        pass
 | 
						|
 | 
						|
Then, with the centralized approach you would have one file with the views
 | 
						|
(:file:`views.py`) but without any decorator::
 | 
						|
 | 
						|
    def index():
 | 
						|
        pass
 | 
						|
 | 
						|
    def user(username):
 | 
						|
        pass
 | 
						|
 | 
						|
And then a file that sets up an application which maps the functions to
 | 
						|
URLs::
 | 
						|
 | 
						|
    from flask import Flask
 | 
						|
    from yourapplication import views
 | 
						|
    app = Flask(__name__)
 | 
						|
    app.add_url_rule('/', view_func=views.index)
 | 
						|
    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 still
 | 
						|
loaded upfront.  The trick is to actually load the view function as needed.
 | 
						|
This can be accomplished with a helper class that behaves just like a
 | 
						|
function but internally imports the real function on first use::
 | 
						|
 | 
						|
    from werkzeug.utils import import_string, cached_property
 | 
						|
 | 
						|
    class LazyView(object):
 | 
						|
 | 
						|
        def __init__(self, import_name):
 | 
						|
            self.__module__, self.__name__ = import_name.rsplit('.', 1)
 | 
						|
            self.import_name = import_name
 | 
						|
 | 
						|
        @cached_property
 | 
						|
        def view(self):
 | 
						|
            return import_string(self.import_name)
 | 
						|
 | 
						|
        def __call__(self, *args, **kwargs):
 | 
						|
            return self.view(*args, **kwargs)
 | 
						|
 | 
						|
What's important here is is that `__module__` and `__name__` are properly
 | 
						|
set.  This is used by Flask internally to figure out how to name the
 | 
						|
URL 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::
 | 
						|
 | 
						|
    from flask import Flask
 | 
						|
    from yourapplication.helpers import LazyView
 | 
						|
    app = Flask(__name__)
 | 
						|
    app.add_url_rule('/',
 | 
						|
                     view_func=LazyView('yourapplication.views.index'))
 | 
						|
    app.add_url_rule('/user/<username>',
 | 
						|
                     view_func=LazyView('yourapplication.views.user'))
 | 
						|
 | 
						|
You can further optimize this in terms of amount of keystrokes needed to
 | 
						|
write this by having a function that calls into
 | 
						|
:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project
 | 
						|
name and a dot, and by wrapping `view_func` in a `LazyView` as needed.  ::
 | 
						|
 | 
						|
    def url(import_name, url_rules=[], **options):
 | 
						|
        view = LazyView(f"yourapplication.{import_name}")
 | 
						|
        for url_rule in url_rules:
 | 
						|
            app.add_url_rule(url_rule, view_func=view, **options)
 | 
						|
 | 
						|
    # add a single route to the index view
 | 
						|
    url('views.index', ['/'])
 | 
						|
 | 
						|
    # add two routes to a single function endpoint
 | 
						|
    url_rules = ['/user/','/user/<username>']
 | 
						|
    url('views.user', url_rules)
 | 
						|
 | 
						|
One thing to keep in mind is that before and after request handlers have
 | 
						|
to be in a file that is imported upfront to work properly on the first
 | 
						|
request.  The same goes for any kind of remaining decorator.
 |