Click 按 Tab 补全时执行了参数回调,怎样避免副作用
Click 8.5.0 补全需要构造解析上下文,参数 callback 仍可能运行。用 ctx.resilient_parsing 跳过副作用,并把真正业务操作移到命令执行阶段。
终端里只按了一次 Tab,参数 callback 的日志就出现了,甚至执行了原本打算在命令运行时才做的动作。Click 的补全需要解析已输入的参数;不能把“主命令函数还没执行”理解成“参数回调一定没有运行”。
Click 8.5.0 的参数处理仍会调用注册的回调。补全等容错解析场景会设置 ctx.resilient_parsing,应用回调应在这个阶段避免打印、退出、发请求或写入数据。
在回调入口识别容错解析
下面的例子在补全过程直接返回原值,正常执行时保留空白值校验:
import click
def check_name(ctx, param, value):
if ctx.resilient_parsing:
return value
if value is not None and not value.strip():
raise click.BadParameter("名称不能只包含空白")
return value
@click.command()
@click.option("--name", callback=check_name)
def cli(name):
click.echo(name)
这里没有在回调中执行业务写入。return value 保留了参数流,不应为了避开补全随意改成 None,导致后续补全得到不同的上下文。也不要在回调里把 ctx.resilient_parsing 改成 False;它描述当前解析方式,不是需要应用关闭的错误开关。
有些回调用于立即显示版本、打印信息后退出,尤其需要在动作之前做这项判断。仅把打印改到标准错误,仍会污染补全交互,也没有阻止其他副作用。
业务动作放到真正执行命令的阶段
如果回调负责创建记录、刷新令牌、打开连接或触发远端作业,应重新划清职责。参数回调适合转换和检查参数;业务动作应在命令函数里、输入与权限条件确认后执行。容错解析保护只是其中一道边界,不能让一个本来不应在解析阶段运行的写入流程继续隐藏在回调里。
自定义 shell_complete 函数也要单独检查。它本来就会在补全时运行,callback 的保护不会自动约束它。为补全查询远端数据时,应沿用应用现有的只读范围、超时与输出约定,不能顺便提交业务变更。
复核时比较普通执行、输入到一半的补全、缺参数及非法值。补全不应打印业务日志或改变数据,正常执行仍应完成原有校验;不要用全局关闭 shell completion 掩盖回调的职责问题。


