Flask的Blueprint原理

蓝图可以有多个子应用,每个子应用拥有自己的视图函数和URL规则,有自己的模板等等,它们之间相互不影响,这大大地方便了我们日常代码的编写和维护,也便于后续扩展功能。当我们想扩展功能时,只需要创建一个子应用即可。而这些子应用都属于同一个应用。

1、使用示例

1、创建蓝图对象

article = Blueprint('article', name ) @article.route('/'): def hello(): pass

cost = Blueprint('cost',__name__)

2、app添加子应用

app = Flask( name ) app.register_blueprint(article,url_prefix='/article') app.register_blueprint(cost,url_prefix='/cost')

这样就可以访问' http://rootpat/article/'

2、Flask的蓝图原理:


@setupmethod
def register_blueprint(self, blueprint, **options):
    """Registers a blueprint on the application.
    """
    first_registration = False
    if blueprint.name in self.blueprints:
        assert self.blueprints[blueprint.name] is blueprint, \
            'A blueprint\'s name collision occurred between %r and ' \
            '%r.  Both share the same name "%s".  Blueprints that ' \
            'are created on the fly need unique names.' % \
            (blueprint, self.blueprints[blueprint.name], blueprint.name)
    else:
        self.blueprints[blueprint.name] = blueprint
        self._blueprint_order.append(blueprint)
        first_registration = True
    blueprint.register(self, options, first_registration)
如果某个蓝图已经注册过,那么就会抛出异常;如果第一次注册,就会设定局部变量first_registration为True,并执行blueprint.register函数,带有options,first_registration参数。options如url_prefix='/article'。


class Blueprint():
def register(self, app, options, first_registration=False):
    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 + '/',
                           view_func=self.send_static_file,
                           endpoint='static')

    for deferred in self.deferred_functions:
        deferred(state)

上述代码中state = self.make_setup_state(app, options, first_registration),返回一个BlueprintSetupState类。如果有静态文件,就会自动添加静态文件路由。
后有一个deferred(state)。其实刚开始我没有看懂,因为默认self.deferred_functions是空的,怎么执行的呢?后来我在Blueprint类中看到了一个method:


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会添加函数,那起初我猜想这应该是添加的是视图函数,这时就有点眉目了。问题来了,如果是添加视图函数,那应该是什么时候添加的呢。再看源码:

class Blueprint():
   .........
   def add_url_rule(self, rule, endpoint=None, view_func=None, options):
    """Like :meth:Flask.add_url_rule but for a blueprint.  The endpoint for
    the :func:url_for function is prefixed with the name of the blueprint.
    """
    if endpoint:
        assert '.' not in endpoint, "Blueprint endpoints should not contain dots"
    self.record(lambda s:
        s.add_url_rule(rule, endpoint, view_func, options))
看到这段代码,我豁然开朗,经过route装饰器修饰过的视图函数,会自动执行该method,self.recored的参数是一个lambda匿名函数,lambda s:s.add_url_rule(rule, endpoint, view_func, options)。再看上面的record方法,有一句self.deferred_functions.append(func),该列表添加了这个lambda函数,只是每个lambda函数有各自的view_func(视图函数)和options。
也就是说,目前为止,蓝图对象的所有视图函数都以特定的方式(lambda函数)添加到deferred_functions列表中。这时再看register函数下的语句: 《

 for deferred in self.deferred_functions:
        deferred(state)
这里的deferred就是上面说的lambda函数,state是上述讲的BlueprintSetupState。也就是说最后会执行 BlueprintSetupState.add_url_rule(rule, endpoint, view_func,
options)函数。
现在看一下BlueprintSetupState类的源代码:

class BlueprintSetupState(object):
    ""
    def init(self, blueprint, app, options, first_registration):
        #: a reference to the current application
        self.app = app

    #: a reference to the blueprint that created this setup state.
    self.blueprint = blueprint

    #: a dictionary with all options that were passed to the
    #: :meth:`~flask.Flask.register_blueprint` method.
    self.options = options
    self.first_registration = first_registration

    subdomain = self.options.get('subdomain')
    if subdomain is None:
        subdomain = self.blueprint.subdomain

    #: The subdomain that the blueprint should be active for, ``None``
    #: otherwise.
    self.subdomain = subdomain

    url_prefix = self.options.get('url_prefix')
    if url_prefix is None:
        url_prefix = self.blueprint.url_prefix

    #: The prefix that should be used for all URLs defined on the
    #: blueprint.
    self.url_prefix = url_prefix

    #: A dictionary with URL defaults that is added to each and every
    #: URL that was defined with the blueprint.
    self.url_defaults = dict(self.blueprint.url_values_defaults)
    self.url_defaults.update(self.options.get('url_defaults', ()))

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):

    if self.url_prefix:
        rule = self.url_prefix + rule
    options.setdefault('subdomain', self.subdomain)
    if endpoint is None:
        endpoint = _endpoint_from_view_func(view_func)
    defaults = self.url_defaults
    if 'defaults' in options:
        defaults = dict(defaults, **options.pop('defaults'))
    self.app.add_url_rule(rule, '%s.%s' % (self.blueprint.name, endpoint),
                          view_func, defaults=defaults, **options)

看add_url_rule源代码,rule = self.url_prefix + rule,是为你在蓝图中定义的rule添加一个前缀,如你在蓝图模块中定义:@article.route('/view/'),url_prefix='/artilce/',那么rule ='/article/view/'.有了这个rule,最后app执行self.app.add_url_rule(rule, '%s.%s' % (self.blueprint.name, endpoint),也就是最终调用app的添加路由规则函数。

至此,一个蓝图的视图函数的路由生成的整个流程已经完成。
再说一下蓝图的资源使用,蓝图可以有自己的静态文件和模板,可以在创建蓝图实例时添加。

3、蓝图资源文件夹

和普通应用一样,蓝图一般都放在一个文件夹中。虽然多个蓝图可以共存于同一个文件夹 中,但是最好不要这样做。
文件夹由 Blueprint 的第二个参数指定,通常为 name 。这个参数指定 与蓝图相关的逻辑 Python 模块或包。如果这个参数指向的是实际的 Python 包(文件 系统中的一个文件夹),那么它就是资源文件夹。如果是一个模块,那么这个模块包含的 包就是资源文件夹。可以通过 Blueprint.root_path 属性来查看蓝图的资源 文件夹:
article.root_path'/Users/username/TestProject/yourapplication'

静态文件

蓝图的第三个参数是 static_folder 。这个参数用以指定蓝图的静态文件所在的 文件夹,它可以是一个绝对路径也可以是相对路径。:
admin = Blueprint('admin', __name__, static_folder='static')
缺省情况下,路径最右端的部分是在 URL 中暴露的部分。上例中的文件夹为 static ,那么 URL 应该是蓝图加上 /static 。蓝图注册为 /admin ,那么 静态文件夹就是/admin/static 。
端点的名称是 blueprint_name.static ,因此你可以使用和应用中的文件夹一样的方法 来生成其 URL: url_for('admin.static', filename='style.css')

模板

如果你想使用蓝图来暴露模板,那么可以使用 Blueprint 的 template_folder 参数: admin = Blueprint('admin', __name__, template_folder='templates')
和静态文件一样,指向蓝图资源文件夹的路径可以是绝对的也可以是相对的。蓝图中的 模板文件夹会被添加到模板搜索路径中,但其优先级低于实际应用的模板文件夹。这样在 实际应用中可以方便地重载蓝图提供的模板。
假设你的蓝图便于 yourapplication/admin 中,要渲染的模板是 'admin/index.html', template_folder 参数值为 templates ,那么真正的 模板文件为:
yourapplication/admin/templates/admin/index.html 。

创建 URL

如果要创建页面链接,可以和通常一样使用 url_for() 函数,只是要把蓝图名称作为端点的前缀,并且用一个点( . )来 分隔: url_for('admin.index')
另外,如果在一个蓝图的视图函数或者被渲染的模板中需要链接同一个蓝图中的其他 端点,那么使用相对重定向,只使用一个点使用为前缀: url_for('.index')

总结 :在利用Flask开发时,最好要使用蓝图,这非常有利于功能的扩展和维护。

--------EOF---------
微信分享/微信扫码阅读