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
posted @ 2022-06-21 00:11  我是小菜鸡丫丫  阅读(222)  评论(0编辑  收藏  举报