Flask 源码解析-2.CLI 模块

2019-07-28  本文已影响0人  Sisyphus235

0. 上文

上一篇在 setup() 配置的最后提到了控制台入口函数 flask.cli:main

entry_points={"console_scripts": ["flask = flask.cli:main"]},

本文继续探索 flask 的 cli 源码 (command line interface),即通过命令行动态交互使用 flask,而不是启动 flask 直接运行程序。

1. main 函数

通过入口函数找到对应程序,在源码 src/flask/cli 中的 main 函数。

def main(as_module=False):
    # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed
    cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None)

main 函数只有一个参数 as_module 表明是否是模块化启动,默认值是否。程序中只有一行,通过 cli 来启动,cli 是 FlaskGroup 的一个 instance,main 是 FlaskGroup 的一个重要 method,这些内容后文展开。
在启动过程中传入了两个参数,args 和 prog_name。args 调用了内置系统函数获得命令行中传入的参数,argv[1:] 的含义是命令行第一个空格后的参数,例如 python -m flask 0 代表的是 python,args[1:] 获得了 -m 和 flask。prog_name 根据 as_module 设置,如果 as_module 为真则设置为 python -m flask,否则为 None。

2. cli instance

上文提到 cli 是 FlaskGroup 的一个 instance,接下来要探讨 cli 的实例化和 FlaskGroup 类。

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 "> ",
    )
)

help 参数用来提示开发者如何使用交互界面,显示内容根据 OS 还进行了调整,"posix" 的使用 export 来设置环境变量,$ 作为提示符,其他使用 set 来设置环境变量,> 作为提示符。

image
触发 help 显示,提示开发者如何使用参数。
image
根据提示加入命令行参数,这里查询了 flask version。

3. FlaskGroup 初始化

FlaskGroup 继承于 AppGroup,主要作用是补充 command line 设置参数,通常开发者不会使用这个类,大部分程序执行的是 AppGroup。

class FlaskGroup(AppGroup):
        def __init__(
        self,
        add_default_commands=True,
        create_app=None,
        add_version_option=True,
        load_dotenv=True,
        set_debug_flag=True,
        **extra
    ):
        params = list(extra.pop("params", None) or ())

        if add_version_option:
            params.append(version_option)

        AppGroup.__init__(self, params=params, **extra)
        self.create_app = create_app
        self.load_dotenv = load_dotenv
        self.set_debug_flag = set_debug_flag

        if add_default_commands:
            self.add_command(run_command)
            self.add_command(shell_command)
            self.add_command(routes_command)

        self._loaded_plugin_commands = False

上一节中的 help 参数是通过 FlaskGroup init 方法的 **extra 传入的。

初始化的过程是:

  1. 先从 **extra 获取 params 参数;
  2. 如果 add_version_option 为真,则添加设置 version 信息;
  3. 从 AppGroup 实例化程序,传入 paramsextra 参数;
  4. 设置 create_app, load_dotenv, set_debug_flag
  5. 如果 add_default_commands 为真,则添加三个方法,run_command, shell_command, routes_command,添加方法的实现是 python 源码,这里不做探讨;
  6. 设置 _loaded_plugin_commands 为假

4. AppGroup

上节提到 FlaskGroup 继承自 AppGroup,AppGroup 继承自 click.Group,这是 python 源码,这里不做探讨。

class AppGroup(click.Group):
    def command(self, *args, **kwargs):
        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):
        kwargs.setdefault("cls", AppGroup)
        return click.Group.group(self, *args, **kwargs)

AppGroup 重写了 click.Group 的 commandgroup 方法。

5.FlaskGroup 方法

FlaskGroup 同时定义了四种方法:
_load_plugin_commands 实现从插件加载指令;
get_command 重写了 python 中 click.Group 的 get_command 方法,加入了命令行传入的参数;
list_command 重写了 python 中 click.Group 的 list_command 方法,实现对 application 和 built-in 命令的列表;
main 方法是主要逻辑控制方法,全局设置程序是从 CLI 启动的,所有方法实现 CLI 预设方式,忽略一些错误报警。

class FlaskGroup(AppGroup):
    def main(self, *args, **kwargs):
        os.environ["FLASK_RUN_FROM_CLI"] = "true"
        if get_load_dotenv(self.load_dotenv):
            load_dotenv()

        obj = kwargs.get("obj")

        if obj is None:
            obj = ScriptInfo(
                create_app=self.create_app, set_debug_flag=self.set_debug_flag
            )

        kwargs["obj"] = obj
        kwargs.setdefault("auto_envvar_prefix", "FLASK")
        return super(FlaskGroup, self).main(*args, **kwargs)

先设置全局变量 FLASK_RUN_FROM_CLI 为真,如果需要从 .env 或者 .flaskenv 加载环境变量则加载。获取传入参数中的 obj,如果存在,则增加 create_app, set_debug_flag 控制,将所有环境变量的前缀默认设置为 FLASK,最后调用父类方法实例化。

6.总结

最后使用一种脑图总结 CLI 模块的全部逻辑关系

image

7.相关文章

Flask 源码解析-1.安装配置

上一篇下一篇

猜你喜欢

热点阅读