Compare commits

..

1 Commits
zsy ... main

Author SHA1 Message Date
psfa6oquc 2cde564420 ADD file via upload
2 weeks ago

@ -0,0 +1,268 @@
==================================================
1. 模块级前置工具
==================================================
1.1 _complete_visible_commands
位置:文件头部,独立函数
作用:在 shell 补全场景里,把 Group 下属“非隐藏且名字匹配前缀”的子命令一次性枚举出来。
关键点:
- 把 ctx.command 强转成 Groupt.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显式传入不再解释。
- paramsdict[str, Any]
存放“已经解析完成、且 expose_value=True”的参数终值
注意:解析过程中可能先出现 UNSET后期统一再刷成 None见 2.4)。
- _parameter_sourcedict[str, ParameterSource]
8.0 新增,记录每个值到底从哪来;
解决“用户碰巧写出与默认值相同内容”时,程序无法区分的问题。
- _exit_stackExitStack
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 两套重载
重载 1ctx.invoke(普通函数, **kwargs)
直接在当前上下文里跑回调kwargs 就是终值;
重载 2ctx.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 发 UserWarning9.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_mapctx.lookup_default
4. 参数自身 defaultget_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)

Binary file not shown.
Loading…
Cancel
Save