From 9d19b77acf413de77b39ed1c6d972fb1e5fef1c3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 26 May 2010 14:49:01 +0200 Subject: [PATCH] Added lazyloading pattern and explicit chapter links in tutorial. This fixes #49. --- docs/_themes | 2 +- docs/conf.py | 4 +- docs/patterns/index.rst | 1 + docs/patterns/lazyloading.rst | 104 +++++++++++++++++++++++++++++++++ docs/tutorial/css.rst | 4 ++ docs/tutorial/dbcon.rst | 4 ++ docs/tutorial/dbinit.rst | 4 ++ docs/tutorial/folders.rst | 4 ++ docs/tutorial/introduction.rst | 4 ++ docs/tutorial/schema.rst | 4 ++ docs/tutorial/setup.rst | 4 ++ docs/tutorial/templates.rst | 4 ++ docs/tutorial/testing.rst | 2 + docs/tutorial/views.rst | 4 ++ examples/minitwit/minitwit.py | 1 + 15 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 docs/patterns/lazyloading.rst diff --git a/docs/_themes b/docs/_themes index 09eeca52..91eee537 160000 --- a/docs/_themes +++ b/docs/_themes @@ -1 +1 @@ -Subproject commit 09eeca526b2b5675cc29f45917f5d0f795395035 +Subproject commit 91eee537e91594f752224a5847719f6d4fb38c2d diff --git a/docs/conf.py b/docs/conf.py index f9f7867e..18e8f309 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -140,7 +140,7 @@ html_sidebars = { #html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +html_use_modindex = False # If false, no index is generated. #html_use_index = True @@ -152,7 +152,7 @@ html_sidebars = { #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +html_show_sphinx = False # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index 61162372..fcd482d7 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -28,3 +28,4 @@ Snippet Archives `_. flashing jquery errorpages + lazyloading diff --git a/docs/patterns/lazyloading.rst b/docs/patterns/lazyloading.rst new file mode 100644 index 00000000..2c331ca9 --- /dev/null +++ b/docs/patterns/lazyloading.rst @@ -0,0 +1,104 @@ +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 AppEngine 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/') + def user(username): + pass + +Then the centralized approach you would have one file with the views +(`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/', 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 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 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 do 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/', + 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(url_rule, import_name, **options): + view = LazyView('yourapplication.' + import_name) + app.add_url_rule(url_rule, view_func=view, **options) + + url('/', 'views.index') + url('/user/', 'views.user') + +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 propery on the first +request. The same goes for any kind of remaining decorator. diff --git a/docs/tutorial/css.rst b/docs/tutorial/css.rst index c2a6ba5b..76265a65 100644 --- a/docs/tutorial/css.rst +++ b/docs/tutorial/css.rst @@ -1,3 +1,5 @@ +.. _tutorial-css: + Step 7: Adding Style ==================== @@ -25,3 +27,5 @@ folder we created before: .flash { background: #CEE5F5; padding: 0.5em; border: 1px solid #AACBE2; } .error { background: #F0D6D6; padding: 0.5em; } + +Continue with :ref:`tutorial-testing`. diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index 9741dabb..50aba04d 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -1,3 +1,5 @@ +.. _tutorial-dbcon: + Step 4: Request Database Connections ------------------------------------ @@ -31,3 +33,5 @@ request only and is available from within each function. Never store such things on other objects because this would not work with threaded environments. That special :data:`~flask.g` object does some magic behind the scenes to ensure it does the right thing. + +Continue to :ref:`tutorial-views`. diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index 0dc87d58..602b999d 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -1,3 +1,5 @@ +.. _tutorial-dbinit: + Step 3: Creating The Database ============================= @@ -61,3 +63,5 @@ importing and calling that function:: If you get an exception later that a table cannot be found check that you did call the `init_db` function and that your table names are correct (singular vs. plural for example). + +Continue with :ref:`tutorial-dbcon` diff --git a/docs/tutorial/folders.rst b/docs/tutorial/folders.rst index 80697a94..5e685c7e 100644 --- a/docs/tutorial/folders.rst +++ b/docs/tutorial/folders.rst @@ -1,3 +1,5 @@ +.. _tutorial-folders: + Step 0: Creating The Folders ============================ @@ -16,4 +18,6 @@ This is the place where css and javascript files go. Inside the `templates` folder Flask will look for `Jinja2`_ templates. Drop all the templates there. +Continue with :ref:`tutorial-schema`. + .. _Jinja2: http://jinja.pocoo.org/2/ diff --git a/docs/tutorial/introduction.rst b/docs/tutorial/introduction.rst index 04396a9d..ff7cdcab 100644 --- a/docs/tutorial/introduction.rst +++ b/docs/tutorial/introduction.rst @@ -1,3 +1,5 @@ +.. _tutorial-introduction: + Introducing Flaskr ================== @@ -26,4 +28,6 @@ Here a screenshot from the final application: :class: screenshot :alt: screenshot of the final application +Continue with :ref:`tutorial-folders`. + .. _SQLAlchemy: http://www.sqlalchemy.org/ diff --git a/docs/tutorial/schema.rst b/docs/tutorial/schema.rst index ed329539..c078667e 100644 --- a/docs/tutorial/schema.rst +++ b/docs/tutorial/schema.rst @@ -1,3 +1,5 @@ +.. _tutorial-schema: + Step 1: Database Schema ======================= @@ -19,3 +21,5 @@ This schema consists of a single table called `entries` and each row in this table has an `id`, a `title` and a `text`. The `id` is an automatically incrementing integer and a primary key, the other two are strings that must not be null. + +Continue with :ref:`tutorial-setup`. diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index 1214c22f..f9a0b302 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -1,3 +1,5 @@ +.. _tutorial-setup: + Step 2: Application Setup Code ============================== @@ -62,3 +64,5 @@ focus on that a little later. First we should get the database working. Want your server to be publically available? Check out the :ref:`externally visible server ` section for more information. + +Continue with :ref:`tutorial-dbinit`. diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index 66b1dec6..5ec5584d 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -1,3 +1,5 @@ +.. _tutorial-templates: + Step 6: The Templates ===================== @@ -105,3 +107,5 @@ the user to login: {% endblock %} + +Continue with :ref:`tutorial-css`. diff --git a/docs/tutorial/testing.rst b/docs/tutorial/testing.rst index c3075e3a..051e915a 100644 --- a/docs/tutorial/testing.rst +++ b/docs/tutorial/testing.rst @@ -1,3 +1,5 @@ +.. _tutorial-testing: + Bonus: Testing the Application =============================== diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index 29be65fa..03c7709b 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -1,3 +1,5 @@ +.. _tutorial-views: + Step 5: The View Functions ========================== @@ -85,3 +87,5 @@ that case if the user was logged in. session.pop('logged_in', None) flash('You were logged out') return redirect(url_for('show_entries')) + +Continue with :ref:`tutorial-templates`. diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index 07ffe4c7..4e01cecb 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -127,6 +127,7 @@ def user_timeline(username): follower.who_id = ? and follower.whom_id = ?''', [session['user_id'], profile_user['user_id']], one=True) is not None + broken_just_for_djangocon return render_template('timeline.html', messages=query_db(''' select message.*, user.* from message, user where user.user_id = message.author_id and user.user_id = ?