From bfd67764fb48311e6a7ddcc85244726d70af677b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 7 Jun 2011 15:32:44 +0200 Subject: [PATCH] Started documentation for blueprints --- docs/blueprints.rst | 43 ++++++++++++++++++++++++++++++++ docs/contents.rst.inc | 1 + flask/blueprints.py | 53 +++++++++++++++++++++++++++------------- scripts/testproj/test.py | 27 ++++++++++---------- 4 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 docs/blueprints.rst diff --git a/docs/blueprints.rst b/docs/blueprints.rst new file mode 100644 index 00000000..016a2369 --- /dev/null +++ b/docs/blueprints.rst @@ -0,0 +1,43 @@ +.. _blueprints: + +Modular Applications with Blueprints +==================================== + +.. versionadded:: 0.7 + +Flask knows a concept known as “blueprints” which can greatly simplify how +large applications work. A blueprint is an object works similar to an +actual :class:`Flask` application object, but it is not actually an +application. Rather it is the blueprint of how to create an application. +Think of it like that: you might want to have an application that has a +wiki. So what you can do is creating the blueprint for a wiki and then +let the application assemble the wiki on the application object. + +Why Blueprints? +--------------- + +Why have blueprints and not multiple application objects? The utopia of +pluggable applications are different WSGI applications and merging them +together somehow. You can do that (see :ref:`app-dispatch`) but it's not +the right tool for every case. Having different applications means having +different configs. Applications are also separated on the WSGI layer +which is a lot lower level than the level that Flask usually operates on +where you have request and response objects. + +Blueprints do not necessarily have to implement applications. They could +only provide filters for templates, static files, templates or similar +things. They share the same config as the application and can change the +application as necessary when being registered. + +The downside is that you cannot unregister a blueprint once application +without having to destroy the whole application object. + +The Concept of Blueprints +------------------------- + +The basic concept of blueprints is that they record operations that should +be executed when the blueprint is registered on the application. However +additionally each time a request gets dispatched to a view that was +declared to a blueprint Flask will remember that the request was +dispatched to that blueprint. That way it's easier to generate URLs from +one endpoint to another in the same module. diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc index 2689b4b5..6c49a878 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -18,6 +18,7 @@ instructions for web development with Flask. config signals reqcontext + blueprints shell patterns/index deploying/index diff --git a/flask/blueprints.py b/flask/blueprints.py index 1fed639d..41d5ef95 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -52,6 +52,9 @@ class Blueprint(_PackageBoundObject): .. versionadded:: 0.7 """ + warn_on_modifications = False + _got_registered_once = False + def __init__(self, name, import_name, static_folder=None, static_url_path=None, url_prefix=None, subdomain=None): @@ -64,14 +67,29 @@ class Blueprint(_PackageBoundObject): self.deferred_functions = [] self.view_functions = {} - def _record(self, func): + def record(self, func): + """Registers a function that is called when the blueprint is + registered on the application. This function is called with the + state as argument as returned by the :meth:`make_setup_state` + method. + """ + if self._got_registered_once and self.warn_on_modifications: + from warnings import warn + warn(Warning('The blueprint was already registered once ' + 'but is getting modified now. These changes ' + 'will not show up.')) self.deferred_functions.append(func) - def _record_once(self, func): + def record_once(self, func): + """Works like :meth:`record` but wraps the function in another + function that will ensure the function is only called once. If the + blueprint is registered a second time on the application, the + function passed is not called. + """ def wrapper(state): if state.first_registration: func(state) - return self._record(update_wrapper(wrapper, func)) + return self.record(update_wrapper(wrapper, func)) def make_setup_state(self, app, options, first_registration=False): return BlueprintSetupState(self, app, options, first_registration) @@ -83,6 +101,7 @@ class Blueprint(_PackageBoundObject): :func:`~flask.Flask.register_blueprint` are directly forwarded to this method in the `options` dictionary. """ + self._got_registered_once = True state = self.make_setup_state(app, options, first_registration) if self.has_static_folder: state.add_url_rule(self.static_url_path + '/', @@ -105,7 +124,7 @@ class Blueprint(_PackageBoundObject): """Like :meth:`Flask.add_url_rule` but for a module. The endpoint for the :func:`url_for` function is prefixed with the name of the module. """ - self._record(lambda s: + self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options)) def endpoint(self, endpoint): @@ -118,7 +137,7 @@ class Blueprint(_PackageBoundObject): def decorator(f): def register_endpoint(state): state.app.view_functions[endpoint] = f - self._record_once(register_endpoint) + self.record_once(register_endpoint) return f return decorator @@ -127,7 +146,7 @@ class Blueprint(_PackageBoundObject): is only executed before each request that is handled by a function of that module. """ - self._record_once(lambda s: s.app.before_request_funcs + self.record_once(lambda s: s.app.before_request_funcs .setdefault(self.name, []).append(f)) return f @@ -135,7 +154,7 @@ class Blueprint(_PackageBoundObject): """Like :meth:`Flask.before_request`. Such a function is executed before each request, even if outside of a module. """ - self._record_once(lambda s: s.app.before_request_funcs + self.record_once(lambda s: s.app.before_request_funcs .setdefault(None, []).append(f)) return f @@ -144,7 +163,7 @@ class Blueprint(_PackageBoundObject): is only executed after each request that is handled by a function of that module. """ - self._record_once(lambda s: s.app.after_request_funcs + self.record_once(lambda s: s.app.after_request_funcs .setdefault(self.name, []).append(f)) return f @@ -152,7 +171,7 @@ class Blueprint(_PackageBoundObject): """Like :meth:`Flask.after_request` but for a module. Such a function is executed after each request, even if outside of the module. """ - self._record_once(lambda s: s.app.after_request_funcs + self.record_once(lambda s: s.app.after_request_funcs .setdefault(None, []).append(f)) return f @@ -160,7 +179,7 @@ class Blueprint(_PackageBoundObject): """Like :meth:`Flask.context_processor` but for a module. This function is only executed for requests handled by a module. """ - self._record_once(lambda s: s.app.template_context_processors + self.record_once(lambda s: s.app.template_context_processors .setdefault(self.name, []).append(f)) return f @@ -168,7 +187,7 @@ class Blueprint(_PackageBoundObject): """Like :meth:`Flask.context_processor` but for a module. Such a function is executed each request, even if outside of the module. """ - self._record_once(lambda s: s.app.template_context_processors + self.record_once(lambda s: s.app.template_context_processors .setdefault(None, []).append(f)) return f @@ -177,7 +196,7 @@ class Blueprint(_PackageBoundObject): handler is used for all requests, even if outside of the module. """ def decorator(f): - self._record_once(lambda s: s.app.errorhandler(code)(f)) + self.record_once(lambda s: s.app.errorhandler(code)(f)) return f return decorator @@ -186,7 +205,7 @@ class Blueprint(_PackageBoundObject): blueprint. It's called before the view functions are called and can modify the url values provided. """ - self._record_once(lambda s: s.app.url_value_preprocessors + self.record_once(lambda s: s.app.url_value_preprocessors .setdefault(self.name, []).append(f)) return f @@ -195,21 +214,21 @@ class Blueprint(_PackageBoundObject): with the endpoint and values and should update the values passed in place. """ - self._record_once(lambda s: s.app.url_default_functions + self.record_once(lambda s: s.app.url_default_functions .setdefault(self.name, []).append(f)) return f def app_url_value_preprocessor(self, f): """Same as :meth:`url_value_preprocessor` but application wide. """ - self._record_once(lambda s: s.app.url_value_preprocessor + self.record_once(lambda s: s.app.url_value_preprocessor .setdefault(self.name, []).append(f)) return f def app_url_defaults(self, f): """Same as :meth:`url_defaults` but application wide. """ - self._record_once(lambda s: s.app.url_default_functions + self.record_once(lambda s: s.app.url_default_functions .setdefault(None, []).append(f)) return f @@ -227,7 +246,7 @@ class Blueprint(_PackageBoundObject): .. versionadded:: 0.7 """ def decorator(f): - self._record_once(lambda s: s.app._register_error_handler( + self.record_once(lambda s: s.app._register_error_handler( self.name, code_or_exception, f)) return f return decorator diff --git a/scripts/testproj/test.py b/scripts/testproj/test.py index 8073f3c8..e7b68eaf 100644 --- a/scripts/testproj/test.py +++ b/scripts/testproj/test.py @@ -1,19 +1,10 @@ -from flask import Flask, Module +from flask import Flask, Module, render_template mod = Module(__name__) mod2 = Module(__name__, 'testmod2') mod3 = Module(__name__, name='somemod', subdomain='meh') -app = Flask(__name__) -app.register_module(mod) -app.register_module(mod2) - - -def handle_404(error): - return 'Testing', 404 -app.error_handlers[404] = handle_404 - @app.after_request def after_request(response): @@ -27,10 +18,20 @@ def index(): @mod2.route('/') -def index(): +def mod2_index(): return render_template('testmod2/index.html') -@mod2.route('/') -def index(): +@mod3.route('/') +def mod3_index(): return render_template('something-else/index.html') + + +app = Flask(__name__) +app.register_module(mod) +app.register_module(mod2) + + +def handle_404(error): + return 'Testing', 404 +app.error_handlers[404] = handle_404