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 来设置环境变量,> 作为提示符。
触发 help 显示,提示开发者如何使用参数。
根据提示加入命令行参数,这里查询了 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 传入的。
-
add_default_commands
参数用来控制是否从命令行读取配置参数,所有的 params 都是从命令行读取通过 **extra 传入。params = list(extra.pop("params", None) or ())
, -
add_version_option
参数用来控制是否支持命令行--version
查询。如果支持,则在 params 中添加 version 信息。params.append(version_option)
,其中version_option
是一个全局变量
click 是 python 的内置函数,这里不深入探讨,这段程序的含义是配置了 version 信息。version_option = click.Option( ["--version"], help="Show the flask version", expose_value=False, callback=get_version, is_flag=True, is_eager=True, )
-
create_app
参数用来控制是否从命令行传入控制参数后回调 app。 -
load_dotenv
参数用来控制是否从最近的.env
或.flaskenv
文件读取设置环境变量,如果为真则会根据发现的第一个文件设置工作路径。 -
set_debug_flag
参数用来在交互环境下设置debug
模式。
初始化的过程是:
- 先从
**extra
获取params
参数; - 如果
add_version_option
为真,则添加设置 version 信息; - 从 AppGroup 实例化程序,传入
params
和extra
参数; - 设置
create_app
,load_dotenv
,set_debug_flag
; - 如果
add_default_commands
为真,则添加三个方法,run_command
,shell_command
,routes_command
,添加方法的实现是 python 源码,这里不做探讨; - 设置
_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 的 command
和 group
方法。
-
command
重写和原方法类似,不过加入了回调的装饰器with_appcontext
,这种写法是函数式编程,传入一个函数f
再返回一个函数decorator
,实现对内部函数的回调。def with_appcontext(f): @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)
实现了在脚本中加载程序应用的上下文环境,源码中 shell_command 和 routes_command 使用了该装饰器。
group
重写和原方法类似,只是指定了AppGroup
是group class
。
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 模块的全部逻辑关系
7.相关文章
Flask 源码解析-1.安装配置