上文提到 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
继承自 AppGroup
,AppGroup
继承自 click.Group
。help 信息给出了帮助的提示,传入到 click 三方依赖中实际生效。
根据 os.name
来区分当前 OS 以适配,这里区分了 WIN 和非 WIN 的环境,os.name 是 posix
则认为是非 WIN 环境,提示使用 export FLASK_APP=hello.py
来设置全局变量,运行提示符使用 $
,WIN 环境配置上面的源码有类似信息。
在 python 中,有 3 种方式获取当前 OS 信息:
- 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 信息。
- sys.platform 是在编译期获取信息,在需要获取系统信息时使用,包括 linux, cygwin, darwin, atheos 等。
判断操作系统信息的时候,好的习惯是用
startswith
来匹配,因为不同的 python 版本对副版本号的处理不同,例如 3.3 版本后 sys.platform 的 linux 不在包含副版本号,返回 linux 来替代之前的 linux2, linux3
- 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
重载了 command
和 group
操作,实现了定制化功能。
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
只是缺省指定了 cls
为 AppGroup
,隔离对于 click.Group
的继承,使用 cls
的 dot 操作的时候确保优先在 AppGroup
中查找。
Take Away points
- 在主入口处限制三方依赖的版本,通过
__version__
的主/副版本号限制; - 通过
os.name
或sys.platform
在编译期获取 OS 信息,或者platform.system()
在运行时获取 OS 精确信息; - 通过重载第三方方法的方式来定制功能;
源码进度
__init__.py # 模块运行
__main__.py # 包运行
cli.py # 命令行运行
main() # 主入口
cli = FlaskGroup(...) # 全局对象化实例
FlaskGroup(AppGroup) # flask 初始化设置
AppGroup(click.Group) # 重载 click.Group 的命令操作
网友评论