Python Click可变选项:如何在装饰器内获取上下文动态生成选项
问题描述
需要基于前序命令参数feature_name动态生成Python Click命令update的选项:根据feature_name从预设字典features中匹配对应选项列表,添加到update命令中。例如执行cli do feature1 update --option11时,需加载feature1对应的option11、option77选项。
当前硬编码feature_name为"feature1"时功能正常,但在variable_options装饰器内尝试通过click.get_current_context()或@click.pass_context获取上下文以动态获取feature_name时,会触发“上下文不存在”的运行时错误。要求不能使用allow_extra_args,因为需要在帮助信息中展示所有可用选项。
原始代码:
import click import functools features = { "feature1": { "option11", "option77" }, "feature2": { "option33", "option44" }, } def wrapper(f, opt): @click.option(f"--{opt}") @functools.wraps(f) def inner(*args, **kwargs): return f(*args, **kwargs) return inner def variable_options(f): # ctx = click.get_current_context() <-- 此处无法获取上下文 # arg = ctx.parent.params.get("feature_name") <-- 需要获取该参数 arg = "feature1" # 硬编码值 opts = features[arg] for opt in opts: f = wrapper(f, opt) return f @click.group() def cli(): pass @cli.group("do") @click.argument("feature_name") def do(feature_name): pass @do.command("update") @variable_options @click.pass_context def update(ctx, **kwargs): # 此处能获取到需要的feature_name,但装饰器内无法获取 print(ctx.parent.params.get("feature_name")) print(kwargs) exit(0)
解决方案
问题根源在于:装饰器是程序加载阶段执行的,此时Click上下文尚未创建,因此无法通过click.get_current_context()获取参数。正确的做法是在命令解析阶段(此时上下文已初始化,父级参数已解析)动态添加选项。
可以通过自定义click.Command子类,重写parse_args方法实现动态选项添加,同时保证帮助信息能正确展示对应选项:
import click features = { "feature1": { "option11", "option77" }, "feature2": { "option33", "option44" }, } class DynamicOptionCommand(click.Command): def __init__(self, name, callback, **kwargs): self.base_callback = callback super().__init__(name, **kwargs) def parse_args(self, ctx, args): # 从父上下文获取已解析的feature_name feature_name = ctx.parent.params.get("feature_name") if feature_name in features: # 动态添加对应选项(这里默认设为布尔型选项,可根据需求调整) for opt_name in features[feature_name]: option = click.Option( [f"--{opt_name}"], is_flag=True, help=f"Option for {feature_name}" ) self.params.append(option) # 调用父类方法完成参数解析 super().parse_args(ctx, args) def invoke(self, ctx): # 调用原始命令回调函数 return self.base_callback(ctx, **ctx.params) @click.group() def cli(): pass @cli.group("do") @click.argument("feature_name") def do(feature_name): pass # 定义update命令的核心逻辑 def update(ctx, **kwargs): print(f"Selected feature: {ctx.parent.params.get('feature_name')}") print(f"Received options: {kwargs}") # 使用自定义Command类注册update命令 do.add_command(DynamicOptionCommand("update", callback=update)) if __name__ == "__main__": cli()
效果验证
- 执行
cli do feature1 update --help,帮助信息会显示--option11和--option77选项 - 执行
cli do feature1 update --option11,会输出对应的feature名称和选项参数 - 执行
cli do feature2 update --option44,会加载feature2的选项并正常输出
内容的提问来源于stack exchange,提问作者David White
相关产品推荐
相关产品推荐

