美文网首页源码分析
Flask 源码(2)

Flask 源码(2)

作者: Sisyphus235 | 来源:发表于2021-10-07 11:54 被阅读0次

    上文提到 flask 统一入口到 cli.py

    使用场景

    package 的常见入口是包引用和 cli 命令行使用,后端服务的常见入口是 WEB,移动端和 cli 命令行使用。不同的使用场景关注的内容是有差别的:

    • 包引用的场景希望用最简便的方式引入二方依赖,希望引入最少的概念,例如 flask 的 group, app;
    • cli 命令行希望用简单清晰的命令操作,同时能够灵活的传参控制内部核心参数;
    • WEB 和移动端场景关注接口的管控能力,包括安全、限流和并发度等;
      统一入口能够方便管理,重用核心逻辑功能,要注意的是在重用的设计中,要有足够的灵活度支持不同场景的特异化需求。

    三方依赖的版本控制

    def main() -> None:
        if int(click.__version__[0]) < 8:
            warnings.warn(
                "Using the `flask` cli with Click 7 is deprecated and"
                " will not be supported starting with Flask 2.1."
                " Please upgrade to Click 8 as soon as possible.",
                DeprecationWarning,
            )
        # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed
        cli.main(args=sys.argv[1:])
    

    cli.py 的入口函数 main() 中给出了一种对三方版本依赖的校检方法。引入三方包 click 后,通过 __version__ 获得包版本,这个管理方法和 flask 是相似的,结果是一个字符串,通过三位版本号的首位大版本限制了 <8 的版本,给出 python 的 DeprecationWarning 警告。
    args 的处理逻辑是为了暂时规避 click 三方版本的 bug。
    cli.main(...) 进入了 flask 内部逻辑。

    cli 实例

    cli = FlaskGroup(
        help="""\
    A general utility script for Flask applications.
    
    Provides commands from Flask, extensions, and the application. Loads the
    application defined in the FLASK_APP environment variable, or from a wsgi.py
    file. Setting the FLASK_ENV environment variable to 'development' will enable
    debug mode.
    
    \b
      {prefix}{cmd} FLASK_APP=hello.py
      {prefix}{cmd} FLASK_ENV=development
      {prefix}flask run
    """.format(
            cmd="export" if os.name == "posix" else "set",
            prefix="$ " if os.name == "posix" else "> ",
        )
    )
    

    cli 是一个 FlaskGroup 的对象化实例,实例化的时候定义了 help 信息,FlaskGroup 继承自 AppGroupAppGroup 继承自 click.Group。help 信息给出了帮助的提示,传入到 click 三方依赖中实际生效。
    根据 os.name 来区分当前 OS 以适配,这里区分了 WIN 和非 WIN 的环境,os.name 是 posix 则认为是非 WIN 环境,提示使用 export FLASK_APP=hello.py 来设置全局变量,运行提示符使用 $,WIN 环境配置上面的源码有类似信息。
    在 python 中,有 3 种方式获取当前 OS 信息:

    1. os.name 是在编译期获取信息,在需要获取系统模块时使用,包括 posix, nt, os2, ce, java, riscos 等,也是 flask 使用的方式。POSIX 是 Portable Operation System Interface,一种标准的系统调用 API,统一了 SYSV 建制派和 BSD 学院派,IX 结尾表达和 UNIX 的血缘关系。

    os.uname() 提供了更加完整的信息,包含 sysname, nodename, release, version, machine 信息。

    1. sys.platform 是在编译期获取信息,在需要获取系统信息时使用,包括 linux, cygwin, darwin, atheos 等。

    判断操作系统信息的时候,好的习惯是用 startswith 来匹配,因为不同的 python 版本对副版本号的处理不同,例如 3.3 版本后 sys.platform 的 linux 不在包含副版本号,返回 linux 来替代之前的 linux2, linux3

    1. platform.system() 是在运行时获取信息,在需要精确获取系统信息时使用,是通过运行 uname 和其他函数在运行时决定系统类型的。

    AppGroup 封装 with 操作

    class AppGroup(click.Group):
        """This works similar to a regular click :class:`~click.Group` but it
        changes the behavior of the :meth:`command` decorator so that it
        automatically wraps the functions in :func:`with_appcontext`.
    
        Not to be confused with :class:`FlaskGroup`.
        """
    
        def command(self, *args, **kwargs):
            """This works exactly like the method of the same name on a regular
            :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
            unless it's disabled by passing ``with_appcontext=False``.
            """
            wrap_for_ctx = kwargs.pop("with_appcontext", True)
    
            def decorator(f):
                if wrap_for_ctx:
                    f = with_appcontext(f)
                return click.Group.command(self, *args, **kwargs)(f)
    
            return decorator
    
        def group(self, *args, **kwargs):
            """This works exactly like the method of the same name on a regular
            :class:`click.Group` but it defaults the group class to
            :class:`AppGroup`.
            """
            kwargs.setdefault("cls", AppGroup)
            return click.Group.group(self, *args, **kwargs)
    

    AppGroup 重载了 commandgroup 操作,实现了定制化功能。
    command 识别了关键字 with_appcontext,缺省为 True,用户可以在传参中关闭。

    def with_appcontext(f):
        """Wraps a callback so that it's guaranteed to be executed with the
        script's application context.  If callbacks are registered directly
        to the ``app.cli`` object then they are wrapped with this function
        by default unless it's disabled.
        """
    
        @click.pass_context
        def decorator(__ctx, *args, **kwargs):
            with __ctx.ensure_object(ScriptInfo).load_app().app_context():
                return __ctx.invoke(f, *args, **kwargs)
    
        return update_wrapper(decorator, f)
    

    with_appcontext 包装了 click.pass_context,将 click 的回调写入 __ctx 的顶部。这里使用了 update_wrapper() 的方式实现,具体分析在下篇文章中完成。
    group 只是缺省指定了 clsAppGroup,隔离对于 click.Group 的继承,使用 cls 的 dot 操作的时候确保优先在 AppGroup 中查找。

    Take Away points

    1. 在主入口处限制三方依赖的版本,通过 __version__ 的主/副版本号限制;
    2. 通过 os.namesys.platform 在编译期获取 OS 信息,或者 platform.system() 在运行时获取 OS 精确信息;
    3. 通过重载第三方方法的方式来定制功能;

    源码进度

    __init__.py                  # 模块运行
    __main__.py                  # 包运行
    cli.py                       # 命令行运行
      main()                     # 主入口
      cli = FlaskGroup(...)      # 全局对象化实例
      FlaskGroup(AppGroup)       # flask 初始化设置
      AppGroup(click.Group)      # 重载 click.Group 的命令操作
    

    相关文章

      网友评论

        本文标题:Flask 源码(2)

        本文链接:https://www.haomeiwen.com/subject/mrzanltx.html