flask系列(4-1)-蓝图原理
在引入蓝图概念之前,先分析app注册路由的原理
app注册路由的基本原理
## demo.py
from flask import Flask
app = Flask(__name__)
@app.route("/") # 调用app.route方法
def index():
return 'pass'
if __name__ == '__main__':
app.run(debug=True)
## scaffold.py 上面的Flask其实继承于Scaffold
class Scaffold:
def route(self, rule: str, **options: t.Any) -> t.Callable[[F], F]:
def decorator(f: F) -> F:
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options) # 这里的self指的是app对象,调用Flask类的add_url_rule方法
return f
return decorator
@setupmethod
def add_url_rule(
self,
rule: str,
endpoint: t.Optional[str] = None,
view_func: t.Optional[t.Callable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
## app.py
@setupmethod
def add_url_rule(
self,
rule: str,
endpoint: t.Optional[str] = None,
view_func: t.Optional[t.Callable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # type: ignore
options["endpoint"] = endpoint
methods = options.pop("methods", None)
# if the methods are not given and the view_func object knows its
# methods we can use that instead. If neither exists, we go with
# a tuple of only ``GET`` as default.
if methods is None:
methods = getattr(view_func, "methods", None) or ("GET",)
if isinstance(methods, str):
raise TypeError(
"Allowed methods must be a list of strings, for"
' example: @app.route(..., methods=["POST"])'
)
methods = {item.upper() for item in methods}
# Methods that should always be added
required_methods = set(getattr(view_func, "required_methods", ()))
# starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling.
if provide_automatic_options is None:
provide_automatic_options = getattr(
view_func, "provide_automatic_options", None
)
if provide_automatic_options is None:
if "OPTIONS" not in methods:
provide_automatic_options = True
required_methods.add("OPTIONS")
else:
provide_automatic_options = False
# Add the required methods now.
methods |= required_methods
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options # type: ignore
self.url_map.add(rule) # 将规则增加到url_map里面去,到这里路由函数的映射关系就结束了
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError(
"View function mapping is overwriting an existing"
f" endpoint function: {endpoint}"
)
self.view_functions[endpoint] = view_func
Blueprint概念
简单来说,Blueprint 是一个存储操作方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。
Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:
一个应用可以具有多个Blueprint
可以将一个Blueprint注册到任何一个未使用的URL下比如 “/”、“/sample”或者子域名
在一个应用中,一个模块可以注册多次
Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
在一个应用初始化时,就应该要注册需要使用的Blueprint
但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
1,创建一个蓝图对象
admin=Blueprint('admin',__name__)
2,在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器
@admin.route('/')
def admin_home():
return 'admin_home'
3,在应用对象上注册这个蓝图对象
app.register_blueprint(admin,url\_prefix='/admin')
当这个应用启动后,通过/admin/可以访问到蓝图中定义的视图函数
运行机制
蓝图是保存了一组将来可以在应用对象上执行的操作,注册路由就是一种操作
当在应用对象上调用 route 装饰器注册路由时,这个操作将修改对象的url_map路由表
然而,蓝图对象根本没有路由表,当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项
当执行应用对象的 register_blueprint() 方法时,应用对象将从蓝图对象的 defered_functions 列表中取出每一项,并以自身作为参数执行该匿名函数,即调用应用对象的 add_url_rule() 方法,这将真正的修改应用对象的路由表
from flask import Flask
app = Flask(__name__)
from flask import Blueprint
order_blu = Blueprint('orders', __name__, static_folder='static', template_folder="templates") # 初始化蓝图对象,这里的Blueprint类也是继承于Scaffold
@order_blu.route("/order/list") # 调用蓝图对象的route方法,实际上调用的Scaffol类的route方法
def orders():
return 'pass'
app.register_blueprint(order_blu)
if __name__ == '__main__':
print(app.url_map)
app.run(debug=True)
## scaffold.py 上面的其实Blueprint继承于Scaffold
class Scaffold:
def route(self, rule: str, **options: t.Any) -> t.Callable[[F], F]:
def decorator(f: F) -> F:
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options) # 这里的self指的是蓝图对象,调用Blueprint类的add_url_rule方法
return f
return decorator
@setupmethod
def add_url_rule(
self,
rule: str,
endpoint: t.Optional[str] = None,
view_func: t.Optional[t.Callable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
## bluerints.py
class Blueprint(Scaffold):
self.deferred_functions: t.List[DeferredSetupFunction] = [] # 定义了空列表,用户存放下面lambda表达式的返回值,lambda的返回值就是函数的引用
def add_url_rule(
self,
rule: str,
endpoint: t.Optional[str] = None,
view_func: t.Optional[t.Callable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
"""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 and "." in endpoint:
raise ValueError("'endpoint' may not contain a dot '.' character.")
if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
raise ValueError("'view_func' name may not contain a dot '.' character.")
## self.record() 调用蓝图对象的record方法,里面参数是lambda表达式,返回值是匿名函数的引用,入参s,调用s.add_url_rule方法
## 主要看s是谁,调用这个匿名函数时就调用谁的add_url_rule
self.record(
lambda s: s.add_url_rule(
rule,
endpoint,
view_func,
provide_automatic_options=provide_automatic_options,
**options,
)
)
def record(self, func: t.Callable) -> None:
"""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)
接下来看app.register_blueprint(order_blu),调用app的register_blueprint方法
## app.py 上面的Flask其实继承于Scaffold
class Flask(Scaffold):
def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None:
blueprint.register(self, options) # 这里blueprint指的是蓝图对象order_blu,调用蓝图对象的register方法
## blueprints.py
class Blueprint(Scaffold)
def register(self, app: "Flask", options: dict) -> None:
state = self.make_setup_state(app, options, first_bp_registration) ## 这里的state返回为BlueprintSetupState实例对象
for deferred in self.deferred_functions: ## 依次遍历匿名函数列表
deferred(state) ## 调用上面的匿名函数,BlueprintSetupState为参数,所以执行上面的匿名函数就是执行BlueprintSetupState实例对象的add_url_rule方法,如下面
def add_url_rule(
self,
rule: str,
endpoint: t.Optional[str] = None,
view_func: t.Optional[t.Callable] = None,
**options: t.Any,
) -> None:
## 调用app.add_url_rule方法
self.app.add_url_rule(
rule,
f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
view_func,
defaults=defaults,
**options,
)
## app.py
class Flask(Scaffold):
@setupmethod
def add_url_rule(
self,
rule: str,
endpoint: t.Optional[str] = None,
view_func: t.Optional[t.Callable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
self.url_map.add(rule) ## 看到这里终于结束了,将规则添加到app.url_map里面去了
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError(
"View function mapping is overwriting an existing"
f" endpoint function: {endpoint}"
)
self.view_functions[endpoint] = view_func