forked from orbit-oss/flask
Started documentation for blueprints
This commit is contained in:
parent
b4ffa4d9ac
commit
bfd67764fb
4 changed files with 94 additions and 30 deletions
43
docs/blueprints.rst
Normal file
43
docs/blueprints.rst
Normal file
|
|
@ -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.
|
||||||
|
|
@ -18,6 +18,7 @@ instructions for web development with Flask.
|
||||||
config
|
config
|
||||||
signals
|
signals
|
||||||
reqcontext
|
reqcontext
|
||||||
|
blueprints
|
||||||
shell
|
shell
|
||||||
patterns/index
|
patterns/index
|
||||||
deploying/index
|
deploying/index
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,9 @@ class Blueprint(_PackageBoundObject):
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
warn_on_modifications = False
|
||||||
|
_got_registered_once = False
|
||||||
|
|
||||||
def __init__(self, name, import_name, static_folder=None,
|
def __init__(self, name, import_name, static_folder=None,
|
||||||
static_url_path=None, url_prefix=None,
|
static_url_path=None, url_prefix=None,
|
||||||
subdomain=None):
|
subdomain=None):
|
||||||
|
|
@ -64,14 +67,29 @@ class Blueprint(_PackageBoundObject):
|
||||||
self.deferred_functions = []
|
self.deferred_functions = []
|
||||||
self.view_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)
|
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):
|
def wrapper(state):
|
||||||
if state.first_registration:
|
if state.first_registration:
|
||||||
func(state)
|
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):
|
def make_setup_state(self, app, options, first_registration=False):
|
||||||
return BlueprintSetupState(self, app, options, first_registration)
|
return BlueprintSetupState(self, app, options, first_registration)
|
||||||
|
|
@ -83,6 +101,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
:func:`~flask.Flask.register_blueprint` are directly forwarded to this
|
:func:`~flask.Flask.register_blueprint` are directly forwarded to this
|
||||||
method in the `options` dictionary.
|
method in the `options` dictionary.
|
||||||
"""
|
"""
|
||||||
|
self._got_registered_once = True
|
||||||
state = self.make_setup_state(app, options, first_registration)
|
state = self.make_setup_state(app, options, first_registration)
|
||||||
if self.has_static_folder:
|
if self.has_static_folder:
|
||||||
state.add_url_rule(self.static_url_path + '/<path:filename>',
|
state.add_url_rule(self.static_url_path + '/<path:filename>',
|
||||||
|
|
@ -105,7 +124,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
"""Like :meth:`Flask.add_url_rule` but for a module. The endpoint for
|
"""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.
|
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))
|
s.add_url_rule(rule, endpoint, view_func, **options))
|
||||||
|
|
||||||
def endpoint(self, endpoint):
|
def endpoint(self, endpoint):
|
||||||
|
|
@ -118,7 +137,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
def register_endpoint(state):
|
def register_endpoint(state):
|
||||||
state.app.view_functions[endpoint] = f
|
state.app.view_functions[endpoint] = f
|
||||||
self._record_once(register_endpoint)
|
self.record_once(register_endpoint)
|
||||||
return f
|
return f
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
@ -127,7 +146,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
is only executed before each request that is handled by a function of
|
is only executed before each request that is handled by a function of
|
||||||
that module.
|
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))
|
.setdefault(self.name, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -135,7 +154,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
"""Like :meth:`Flask.before_request`. Such a function is executed
|
"""Like :meth:`Flask.before_request`. Such a function is executed
|
||||||
before each request, even if outside of a module.
|
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))
|
.setdefault(None, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -144,7 +163,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
is only executed after each request that is handled by a function of
|
is only executed after each request that is handled by a function of
|
||||||
that module.
|
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))
|
.setdefault(self.name, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -152,7 +171,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
"""Like :meth:`Flask.after_request` but for a module. Such a function
|
"""Like :meth:`Flask.after_request` but for a module. Such a function
|
||||||
is executed after each request, even if outside of the module.
|
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))
|
.setdefault(None, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -160,7 +179,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
"""Like :meth:`Flask.context_processor` but for a module. This
|
"""Like :meth:`Flask.context_processor` but for a module. This
|
||||||
function is only executed for requests handled by a module.
|
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))
|
.setdefault(self.name, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -168,7 +187,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
"""Like :meth:`Flask.context_processor` but for a module. Such a
|
"""Like :meth:`Flask.context_processor` but for a module. Such a
|
||||||
function is executed each request, even if outside of the module.
|
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))
|
.setdefault(None, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -177,7 +196,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
handler is used for all requests, even if outside of the module.
|
handler is used for all requests, even if outside of the module.
|
||||||
"""
|
"""
|
||||||
def decorator(f):
|
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 f
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
@ -186,7 +205,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
blueprint. It's called before the view functions are called and
|
blueprint. It's called before the view functions are called and
|
||||||
can modify the url values provided.
|
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))
|
.setdefault(self.name, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -195,21 +214,21 @@ class Blueprint(_PackageBoundObject):
|
||||||
with the endpoint and values and should update the values passed
|
with the endpoint and values and should update the values passed
|
||||||
in place.
|
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))
|
.setdefault(self.name, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def app_url_value_preprocessor(self, f):
|
def app_url_value_preprocessor(self, f):
|
||||||
"""Same as :meth:`url_value_preprocessor` but application wide.
|
"""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))
|
.setdefault(self.name, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def app_url_defaults(self, f):
|
def app_url_defaults(self, f):
|
||||||
"""Same as :meth:`url_defaults` but application wide.
|
"""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))
|
.setdefault(None, []).append(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -227,7 +246,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
def decorator(f):
|
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))
|
self.name, code_or_exception, f))
|
||||||
return f
|
return f
|
||||||
return decorator
|
return decorator
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,10 @@
|
||||||
from flask import Flask, Module
|
from flask import Flask, Module, render_template
|
||||||
|
|
||||||
|
|
||||||
mod = Module(__name__)
|
mod = Module(__name__)
|
||||||
mod2 = Module(__name__, 'testmod2')
|
mod2 = Module(__name__, 'testmod2')
|
||||||
mod3 = Module(__name__, name='somemod', subdomain='meh')
|
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
|
@app.after_request
|
||||||
def after_request(response):
|
def after_request(response):
|
||||||
|
|
@ -27,10 +18,20 @@ def index():
|
||||||
|
|
||||||
|
|
||||||
@mod2.route('/')
|
@mod2.route('/')
|
||||||
def index():
|
def mod2_index():
|
||||||
return render_template('testmod2/index.html')
|
return render_template('testmod2/index.html')
|
||||||
|
|
||||||
|
|
||||||
@mod2.route('/')
|
@mod3.route('/')
|
||||||
def index():
|
def mod3_index():
|
||||||
return render_template('something-else/index.html')
|
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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue