日常项目中使用蓝图来分组用户侧和管理侧的api,使用不同的前缀。今天就来一探蓝图背后的原理。
先看使用蓝图的一个例子。
from flask import Flask, Blueprint
# 实例化
app = Flask(__name__)
# 构建蓝图对象
some_blueprint = Blueprint('some', __name__)
# 使用蓝图对象的方法装饰view_function
@some_blueprint.route("/a")
def hello_world():
return "<p>Hello, World!</p>"
# 将蓝图注册到 flask_app
app.register_blueprint(some_blueprint, url_prefix='/pre')
之前的系列我们已经搞清楚了Flask 将url_rule和view_function 分别保存到 url_map和view_functions 属性中。蓝图只是一种新的给路由分组管理的形式,最终蓝图中的信息肯定还是要以同样的方式保存到同样的地方,我们来研究下其中的过程吧。
先来看下 @some_blueprint.route(“/a”)
class Blueprint(_PackageBoundObject):
def route(self, rule, **options):
# 和flask_app.route()长得一毛一样
def decorator(f):
# endpoint 默认函数名称
endpoint = options.pop("endpoint", f.__name__)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
# 参数是一个 函数!
# 这个函数闭包了 路由和函数相关的信息
self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))
def record(self, func):
# self.deferred_functions 是个 []
# 此处就是简单的把这个函数保存到蓝图对象中
self.deferred_functions.append(func)
由上可见,蓝图 简单的将 路由和函数等信息保存了起来,只不过保存的方式比较特别,保存的是这个匿名函数 lambda s: s.add_url_rule(rule, endpoint, view_func, **options) ,后面会看到在将蓝图注册到flask_app的时候,会调用这个函数。
下面来看下 app.register_blueprint(some_blueprint, url_prefix=‘/pre’)
class Flask(_PackageBoundObject):
@setupmethod
def register_blueprint(self, blueprint, **options):
first_registration = False
# 调用buleprint的方法
# 第一个参数为 flask_app
blueprint.register(self, options, first_registration)
class Blueprint(_PackageBoundObject):
def make_setup_state(self, app, options, first_registration=False):
# 第一个参数 Blueprint实例
# 第二个参数 flask_app实例
return BlueprintSetupState(self, app, options, first_registration)
def register(self, app, options, first_registration=False):
self._got_registered_once = True
# state 是一个 BlueprintSetupState实例 下面研究
state = self.make_setup_state(app, options, first_registration)
# 之前 Blueprint.route() 保存的函数列表 依次调用
for deferred in self.deferred_functions:
# 回忆下 deferred 函数是 lambda s: s.add_url_rule(rule, endpoint, view_func, **options)
# 也就是说 deferred(state) 会调用 state.add_url_rule(rule, endpoint, view_func, **options)
deferred(state)
cli_resolved_group = options.get("cli_group", self.cli_group)
看下 BlueprintSetupState 类
class BlueprintSetupState(object):
def __init__(self, blueprint, app, options, first_registration):
self.app = app
self.blueprint = blueprint
self.options = options
# 优先 使用 flask_app.register_blueprint() 时传入的 url_prefix
# 若没传 再使用Blueprint的 url_prefix
url_prefix = self.options.get("url_prefix")
if url_prefix is None:
url_prefix = self.blueprint.url_prefix
self.url_prefix = url_prefix
# 上面 deferred(state) 就是调用了此方法
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
if self.url_prefix is not None:
if rule:
# 既有 url_prefix 又有 url_rule的情况下 拼接两者
rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/")))
else:
rule = self.url_prefix
# endpoint 默认用函数名称
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
defaults = self.url_defaults
# 最终最终 走到了 flask_app.add_url_rule
# 注意下 endpoint为 {bulprint.name}.{endpoint} 很合理 应当使用全路径避免组内元素重复
self.app.add_url_rule(
rule,
"%s.%s" % (self.blueprint.name, endpoint),
view_func,
defaults=defaults,
**options
)
总结下:
- Blueprint 包含了一组路由的信息,可以使用统一的前缀。里面有个比较有意思的设计就是在 Blueprint.deferred_functions属性中保存的是个 匿名函数对象 lambda s: s.add_url_rule(rule, endpoint, view_func, **options),匿名函数闭包了路由等信息。
- flask.register_blueprint() 最终触发了Blueprint.deferred_functions 函数的依次调用,不过并没有直接传入 flask_app,而是使用 BlueprintSetupState 这个 holder class 进行了一些信息的记录和预处理,之后将其作为参数传入。最终,调用 BlueprintSetupState.add_url_rule(),其内部最终调用了 flask_app.add_url_rule()