|
|
|
|
@ -0,0 +1,268 @@
|
|
|
|
|
==================================================
|
|
|
|
|
1. 模块级前置工具
|
|
|
|
|
==================================================
|
|
|
|
|
1.1 _complete_visible_commands
|
|
|
|
|
位置:文件头部,独立函数
|
|
|
|
|
作用:在 shell 补全场景里,把 Group 下属“非隐藏且名字匹配前缀”的子命令一次性枚举出来。
|
|
|
|
|
关键点:
|
|
|
|
|
- 把 ctx.command 强转成 Group(t.cast),保证类型检查通过;
|
|
|
|
|
- 用 list_commands(ctx) 而不是直接读 commands 字典,目的是让“插件式”命令(lazy loading)有机会被触发注册。
|
|
|
|
|
|
|
|
|
|
1.2 iter_params_for_processing
|
|
|
|
|
位置:紧接上面的独立函数
|
|
|
|
|
作用:解决“用户输入顺序”与“参数声明顺序”冲突时的回调执行次序。
|
|
|
|
|
算法:
|
|
|
|
|
- 以“用户实际输入顺序”为主键(找不到就置 inf),再以“是否 eager”为次要键;
|
|
|
|
|
- 返回排序后的列表,保证:
|
|
|
|
|
① 用户先写的选项先跑;
|
|
|
|
|
② eager 选项(如 --help)无论写在哪都优先跑。
|
|
|
|
|
后期版本演进:8.0 之后 eager 概念被提到参数层面,而非全局钩子,因此该函数成为唯一排序入口。
|
|
|
|
|
|
|
|
|
|
==================================================
|
|
|
|
|
2. Context 类——“一次命令执行”的生命周期
|
|
|
|
|
==================================================
|
|
|
|
|
2.1 字段初始化(__init__)
|
|
|
|
|
- parent / command / info_name:显式传入,不再解释。
|
|
|
|
|
- params:dict[str, Any]
|
|
|
|
|
存放“已经解析完成、且 expose_value=True”的参数终值;
|
|
|
|
|
注意:解析过程中可能先出现 UNSET,后期统一再刷成 None(见 2.4)。
|
|
|
|
|
- _parameter_source:dict[str, ParameterSource]
|
|
|
|
|
8.0 新增,记录每个值到底从哪来;
|
|
|
|
|
解决“用户碰巧写出与默认值相同内容”时,程序无法区分的问题。
|
|
|
|
|
- _exit_stack:ExitStack
|
|
|
|
|
8.0 新增,让 Click 也能用“with 语法”管理资源;
|
|
|
|
|
与 scope()/with_resource()/call_on_close() 配套。
|
|
|
|
|
|
|
|
|
|
2.2 参数继承链
|
|
|
|
|
代码片段:
|
|
|
|
|
if obj is None and parent is not None:
|
|
|
|
|
obj = parent.obj
|
|
|
|
|
if default_map is None and info_name is not None and parent …:
|
|
|
|
|
default_map = parent.default_map.get(info_name)
|
|
|
|
|
逻辑:
|
|
|
|
|
- obj 沿继承链向下穿透,最常用场景是把“数据库连接”挂在根 Context,子命令直接复用;
|
|
|
|
|
- default_map 支持“按命令路径分段”覆盖,例如:
|
|
|
|
|
{“subcmd”: {“host”: “127.0.0.1”}} 只对 subcmd 生效。
|
|
|
|
|
|
|
|
|
|
2.3 make_context 类方法
|
|
|
|
|
执行时序:
|
|
|
|
|
1. 把 Command.context_settings 先刷进 extra,确保“装饰器里写的配置”优先级最高;
|
|
|
|
|
2. 实例化 Context → 调用 ctx.scope(cleanup=False) → 调用 command.parse_args();
|
|
|
|
|
3. 返回 ctx,但尚未 invoke;
|
|
|
|
|
设计目的:
|
|
|
|
|
- 让“参数解析”与“回调执行”彻底分离,方便测试、文档生成、补全逻辑复用同一套解析结果。
|
|
|
|
|
|
|
|
|
|
2.4 parse_args 细节
|
|
|
|
|
- 空参数且 no_args_is_help=True 时,直接抛 NoArgsIsHelpError,最外层捕获后打印 help;
|
|
|
|
|
- 循环调用 param.handle_parse_result() 之前,先用 iter_params_for_processing 排序;
|
|
|
|
|
- 解析完成后统一把 ctx.params 中的 UNSET 刷成 None:
|
|
|
|
|
目的:防止下游用户代码意外拿到内部哨兵;
|
|
|
|
|
时机:必须等“所有参数都处理完”再刷,否则多值参数(nargs>1, multiple=True)无法判断“缺值”与“空列表”区别。
|
|
|
|
|
|
|
|
|
|
2.5 invoke 两套重载
|
|
|
|
|
重载 1:ctx.invoke(普通函数, **kwargs)
|
|
|
|
|
直接在当前上下文里跑回调,kwargs 就是终值;
|
|
|
|
|
重载 2:ctx.invoke(另一个 Command, **kwargs)
|
|
|
|
|
- 先给子命令生成 _make_sub_context();
|
|
|
|
|
- 对子命令所有“缺省且未在 kwargs 出现”的参数,调 param.get_default() 补全;
|
|
|
|
|
- 把 kwargs 全部写进 sub_ctx.params,因此后续再 forward() 也能继续向下传递;
|
|
|
|
|
- 用 augment_usage_errors 包一层,保证子命令里的 BadParameter 能带上父命令的 ctx。
|
|
|
|
|
|
|
|
|
|
2.6 ExitStack 集成
|
|
|
|
|
with_resource():
|
|
|
|
|
返回 __enter__ 结果,并把 __exit__ 注册到 _exit_stack;
|
|
|
|
|
call_on_close():
|
|
|
|
|
注册普通回调,内部就是 exit_stack.callback();
|
|
|
|
|
close():
|
|
|
|
|
在 ctx.__exit__() 且 _depth==0 时触发,保证:
|
|
|
|
|
- 补全场景只解析不执行,也能回收资源;
|
|
|
|
|
- 多层嵌套 Group 中,只有最外层退出时才统一清理。
|
|
|
|
|
|
|
|
|
|
==================================================
|
|
|
|
|
3. Command 类——“命令元数据 + 回调”
|
|
|
|
|
==================================================
|
|
|
|
|
3.1 get_params() 的“重复选项”警告
|
|
|
|
|
实现:
|
|
|
|
|
opts = [opt for param in params for opt in param.opts]
|
|
|
|
|
duplicate_opts = (opt for opt, count in Counter(opts).items() if count > 1)
|
|
|
|
|
for opt in duplicate_opts: warnings.warn(...)
|
|
|
|
|
触发场景:
|
|
|
|
|
@click.option('-f', '--file')
|
|
|
|
|
@click.option('-f', '--config') # 重复 -f,运行即提示
|
|
|
|
|
版本演进:
|
|
|
|
|
8.2 之前静默允许,8.2 发 UserWarning,9.0 计划抛 Error。
|
|
|
|
|
|
|
|
|
|
3.2 parse_args() 的“剩余参数”检查
|
|
|
|
|
代码:
|
|
|
|
|
if args and not ctx.allow_extra_args and not ctx.resilient_parsing:
|
|
|
|
|
ctx.fail(ngettext(...))
|
|
|
|
|
注意:
|
|
|
|
|
- resilient_parsing=True(补全场景)时强制放过,防止补全脚本因多余单词而失败;
|
|
|
|
|
- allow_extra_args 默认 False,需要显式 `@click.command(..., allow_extra_args=True)` 打开。
|
|
|
|
|
|
|
|
|
|
3.3 invoke() 的 deprecated 警告
|
|
|
|
|
实现:
|
|
|
|
|
if self.deprecated:
|
|
|
|
|
echo(style(message, fg="red"), err=True)
|
|
|
|
|
特点:
|
|
|
|
|
- 警告走 stderr,且带 ANSI 红色;
|
|
|
|
|
- 先于 callback 执行,确保即使用户回调里抛异常也能看到。
|
|
|
|
|
|
|
|
|
|
3.4 main() 的“管道破裂”特殊处理
|
|
|
|
|
代码:
|
|
|
|
|
except OSError as e:
|
|
|
|
|
if e.errno == errno.EPIPE:
|
|
|
|
|
sys.stdout = PacifyFlushWrapper(sys.stdout)
|
|
|
|
|
sys.stderr = PacifyFlushWrapper(sys.stderr)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
背景:
|
|
|
|
|
Linux 下常见 `cli | head` 场景,head 提前关闭管道,Python 会抛 BrokenPipeError;
|
|
|
|
|
Click 把它静默成 1 号退出码,避免 traceback 污染用户终端。
|
|
|
|
|
|
|
|
|
|
==================================================
|
|
|
|
|
4. Group 类——命令树与链式调用
|
|
|
|
|
==================================================
|
|
|
|
|
4.1 chain=True 时的参数限制
|
|
|
|
|
代码:
|
|
|
|
|
if self.chain:
|
|
|
|
|
for param in self.params:
|
|
|
|
|
if isinstance(param, Argument) and not param.required:
|
|
|
|
|
raise RuntimeError(...)
|
|
|
|
|
原因:
|
|
|
|
|
链式模式下,剩余参数会被不断“切片”给后续子命令,无法判断“可选参数”到底该在哪一段消费;
|
|
|
|
|
因此强制“链式 Group 只能有必填 Argument”。
|
|
|
|
|
|
|
|
|
|
4.2 invoke() 的两条分支
|
|
|
|
|
分支 A:非链式
|
|
|
|
|
- 只解析第一个子命令;
|
|
|
|
|
- 用 resolve_command() 取出 cmd_name, cmd, new_args;
|
|
|
|
|
- 子命令跑完后,把返回值交给 Group 的 result_callback。
|
|
|
|
|
|
|
|
|
|
分支 B:链式
|
|
|
|
|
- 先跑 Group 自身回调,返回值丢弃(除非 invoke_without_command);
|
|
|
|
|
- 循环 parse 剩余 args,每段都 make_context → invoke,结果收集到列表;
|
|
|
|
|
- 最后列表一次性传给 result_callback。
|
|
|
|
|
|
|
|
|
|
4.3 result_callback 的装饰器实现
|
|
|
|
|
代码:
|
|
|
|
|
def result_callback(self, replace=False):
|
|
|
|
|
def decorator(f):
|
|
|
|
|
old = self._result_callback
|
|
|
|
|
if old is None or replace:
|
|
|
|
|
self._result_callback = f
|
|
|
|
|
return f
|
|
|
|
|
def chained(value, /, *a, **k):
|
|
|
|
|
inner = old(value, *a, **k)
|
|
|
|
|
return f(inner, *a, **k)
|
|
|
|
|
self._result_callback = update_wrapper(chained, f)
|
|
|
|
|
return decorator
|
|
|
|
|
特点:
|
|
|
|
|
- 支持多次装饰,自动链式调用;
|
|
|
|
|
- 签名固定为 (value, *args, **kwargs),其中 value 是“子命令返回值”或“子命令列表”。
|
|
|
|
|
|
|
|
|
|
==================================================
|
|
|
|
|
5. Parameter 基类——“选项 + 参数”的公共抽象
|
|
|
|
|
==================================================
|
|
|
|
|
5.1 consume_value() 的优先级链
|
|
|
|
|
返回 (value, source) 二元组,顺序:
|
|
|
|
|
1. 命令行(opts 字典里能查到 self.name)
|
|
|
|
|
2. 环境变量(resolve_envvar_value)
|
|
|
|
|
3. default_map(ctx.lookup_default)
|
|
|
|
|
4. 参数自身 default(get_default)
|
|
|
|
|
5. 以上全 miss → 返回 UNSET
|
|
|
|
|
|
|
|
|
|
source 枚举值:
|
|
|
|
|
COMMANDLINE / ENVIRONMENT / DEFAULT_MAP / DEFAULT / PROMPT
|
|
|
|
|
用于后期“用户是否显式填写”判断,例如 deprecated 警告只在前两种场景下触发。
|
|
|
|
|
|
|
|
|
|
5.2 type_cast_value() 的 nargs 矩阵
|
|
|
|
|
- nargs == 1 或 composite type → 单值直接转;
|
|
|
|
|
- nargs == -1 → 剩余全部收集成 tuple;
|
|
|
|
|
- nargs > 1 → 严格检查长度,不足就抛 BadParameter;
|
|
|
|
|
- multiple=True → 在最外层再包一层 tuple,形成“列表里每项都是 tuple”的嵌套结构。
|
|
|
|
|
|
|
|
|
|
5.3 process_value() 的“UNSET 特殊处理”
|
|
|
|
|
代码:
|
|
|
|
|
if value is UNSET:
|
|
|
|
|
if self.multiple or self.nargs == -1:
|
|
|
|
|
value = ()
|
|
|
|
|
else:
|
|
|
|
|
value = self.type_cast_value(ctx, value)
|
|
|
|
|
目的:
|
|
|
|
|
- 让“缺值”与“空列表”在后续 value_is_missing() 里可区分;
|
|
|
|
|
- 同时保证类型转换层永远看不到 UNSET,简化自定义 ParamType 的实现。
|
|
|
|
|
|
|
|
|
|
5.4 callback 调用时的“临时上下文”
|
|
|
|
|
8.2 新增逻辑:
|
|
|
|
|
如果 ctx.params 里还有兄弟参数是 UNSET,会临时:
|
|
|
|
|
a) 把 UNSET 全部刷成 None;
|
|
|
|
|
b) 调用 callback;
|
|
|
|
|
c) 再把 None 恢复成 UNSET(仅当回调没改动的字段)。
|
|
|
|
|
解决:
|
|
|
|
|
旧代码写的 callback 可能用 `if value is None` 判断“用户没给”,
|
|
|
|
|
引入 UNSET 后不能破坏这一习惯,因此提供“伪 None”环境。
|
|
|
|
|
|
|
|
|
|
==================================================
|
|
|
|
|
6. Option 类——“可选参数”的专属逻辑
|
|
|
|
|
==================================================
|
|
|
|
|
6.1 _parse_decls() 的“/”分片语法
|
|
|
|
|
支持两种写法:
|
|
|
|
|
- `--verbose/--quiet` → secondary_opts = ["--quiet"]
|
|
|
|
|
- `/-v` → secondary_opts = ["-v"]
|
|
|
|
|
约束:
|
|
|
|
|
- bool flag 以外禁止出现 secondary_opts;
|
|
|
|
|
- 同一名字不能同时出现在 opts 与 secondary_opts(会抛 ValueError)。
|
|
|
|
|
|
|
|
|
|
6.2 flag_value 与 default 对齐
|
|
|
|
|
代码:
|
|
|
|
|
if self.default is True and self.flag_value is not UNSET:
|
|
|
|
|
self.default = self.flag_value
|
|
|
|
|
场景:
|
|
|
|
|
@click.option("--feature/--no-feature", default=True, flag_value=False)
|
|
|
|
|
期望“默认开,--no-feature 关”,此时把 default 统一成 flag_value,
|
|
|
|
|
后续 prompt/帮助/环境变量解析都能拿到一致值。
|
|
|
|
|
|
|
|
|
|
6.3 value_from_envvar() 的“非 bool flag”处理
|
|
|
|
|
- 非 bool flag(如 --format=json)会把 envvar 原串与 flag_value 比对,
|
|
|
|
|
相等则返回 flag_value,否则走 BoolParamType.str_to_bool() 判断“是否激活”;
|
|
|
|
|
- 激活后真正存的仍是 flag_value,保证“环境变量与命令行”语义一致。
|
|
|
|
|
|
|
|
|
|
6.4 prompt 分支
|
|
|
|
|
- bool flag 用 confirm(),支持 [Y/n] 反向默认;
|
|
|
|
|
- 普通 option 用 prompt(),并把 process_value 作为 value_proc 传入,
|
|
|
|
|
用户键盘输入即时做类型转换,失败可循环提示,直到拿到合法值。
|
|
|
|
|
|
|
|
|
|
==================================================
|
|
|
|
|
7. Argument 类——“位置参数”的专属逻辑
|
|
|
|
|
==================================================
|
|
|
|
|
7.1 required 自动推断
|
|
|
|
|
规则:
|
|
|
|
|
- 用户显式传了 required → 用用户的;
|
|
|
|
|
- 没传时:
|
|
|
|
|
– 只要“无默认值”且“nargs != 0”就视为必填;
|
|
|
|
|
– 有默认值 → 视为可选。
|
|
|
|
|
目的:
|
|
|
|
|
让 `@click.argument('filename')` 默认就是必填,
|
|
|
|
|
`@click.argument('filename', default='-')` 默认就是可选,减少样板。
|
|
|
|
|
|
|
|
|
|
7.2 make_metavar() 的修饰符号
|
|
|
|
|
- nargs != 1 → 加 “…”
|
|
|
|
|
- 非 required → 外套 “[ ]”
|
|
|
|
|
- deprecated → 加 “!”
|
|
|
|
|
示例:
|
|
|
|
|
ARGUMENT [...]! # 可选、可变长、已弃用
|
|
|
|
|
|
|
|
|
|
==================================================
|
|
|
|
|
8. 资源清理与上下文栈(再总结)
|
|
|
|
|
==================================================
|
|
|
|
|
8.1 ExitStack 生命周期
|
|
|
|
|
- Context.__init__() 实例化 ExitStack;
|
|
|
|
|
- 每进一层嵌套命令(Group/chain)_depth++,但 ExitStack 只有一份;
|
|
|
|
|
- 最外层 __exit__() 时 _depth==0,才真正调用 _exit_stack.__exit__();
|
|
|
|
|
- 异常信息(exc_type/value/tb)原样传进去,保证上下文管理器能正常 suppress 异常。
|
|
|
|
|
|
|
|
|
|
8.2 with_resource() 与 call_on_close() 的差异
|
|
|
|
|
- with_resource() 必须传入“上下文管理器”,返回的是 __enter__ 结果;
|
|
|
|
|
- call_on_close() 只注册一个回调,适合“非上下文管理器”场景,例如:
|
|
|
|
|
def cleanup(): os.unlink(tmpfile)
|
|
|
|
|
ctx.call_on_close(cleanup)
|