DEBUG.WIKI
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
OnlineSIM:在线接收短信,虚拟号码租用
RackNerd;$21.99/年 · 1GB KVM

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 掩盖回调的职责问题。