如何重写click.Option实现仅当配置文件无值时才提示输入
问题
我正在编写一个自定义类,希望通过Python的click option装饰器读取JSON配置文件,仅当配置文件中不存在对应值时才提示用户输入。首次运行时需要将用户输入的选项写入配置文件,后续运行则直接读取配置,不再提示。
现有用于读取config.json的CLIConfig类如下:
import json import pathlib class CLIConfig(): def __init__(self, source_app_name): self.source_app_name = source_app_name self.config_dict = {} def gitlab_access_token(self): if 'scm' in self.config_dict and 'accessToken' in self.config_dict['scm']: return self.config_dict['scm']['accessToken'] return None def group_id(self): if 'scm' in self.config_dict and 'groupId' in self.config_dict['scm']: return self.config_dict['scm']['groupId'] return None def microservice_path(self): if 'scm' in self.config_dict and 'microservice' in self.config_dict['scm']: return self.config_dict['scm']['microservice'].get('localPath', None) return None def terraform_path(self): if 'scm' in self.config_dict and 'terraform' in self.config_dict['scm']: return self.config_dict['scm']['terraform'].get('localPath', None) return None def __enter__(self): config_path = pathlib.Path.home().joinpath(f".{self.source_app_name}", "config.json") try: with open(config_path, mode='r') as f: self.config_dict = json.load(f) except (json.decoder.JSONDecodeError, FileNotFoundError): self.config_dict = {} return self def __exit__(self, exc_type, exc_value, tb): pass
现有CLI结构:
import click import sys import pathlib @click.group() @click.pass_context def cli(ctx): """Root level of CLI""" pass @click.command(name='setup', help='Setup CLI') @click.argument('source_app_name', default=pathlib.Path(sys.argv[0]).name) @click.option('--microservice_path', prompt='Enter the local repository location for microservices', type=click.Path()) @click.option('--terraform_path', prompt='Enter the local repository location for terraform', type=click.Path()) @click.option('--private_token', hide_input=True, prompt='Provide GitLab API Token') @click.pass_context def setup(ctx, microservice_path, terraform_path, private_token): # 待实现的逻辑 pass if __name__ == '__main__': cli()
需要增强CLIConfig以集成click.Option,实现预期需求。
解决方案
1. 改造CLIConfig类
优化CLIConfig,增加配置写入方法和通用的配置读取逻辑,减少重复代码:
import json import pathlib from functools import reduce import operator class CLIConfig(): def __init__(self, source_app_name): self.source_app_name = source_app_name self.config_path = pathlib.Path.home().joinpath(f".{self.source_app_name}", "config.json") self.config_dict = {} self._load_config() def _load_config(self): """加载配置文件,不存在则创建空配置""" self.config_path.parent.mkdir(exist_ok=True) try: with open(self.config_path, 'r') as f: self.config_dict = json.load(f) except (json.JSONDecodeError, FileNotFoundError): self.config_dict = {} def _get_value(self, key_path): """通过键路径(如'scm.accessToken')获取配置值""" keys = key_path.split('.') try: return reduce(operator.getitem, keys, self.config_dict) except (KeyError, TypeError): return None def _set_value(self, key_path, value): """通过键路径设置配置值""" keys = key_path.split('.') current = self.config_dict for key in keys[:-1]: current = current.setdefault(key, {}) current[keys[-1]] = value def save_config(self): """将配置写入文件""" with open(self.config_path, 'w') as f: json.dump(self.config_dict, f, indent=2) # 保留原有属性方法,或直接用_get_value替代 def gitlab_access_token(self): return self._get_value('scm.accessToken') def group_id(self): return self._get_value('scm.groupId') def microservice_path(self): return self._get_value('scm.microservice.localPath') def terraform_path(self): return self._get_value('scm.terraform.localPath')
2. 自定义Click Option子类
创建ConfigOption类,实现从配置读取默认值,仅当配置无值时触发用户提示:
import click class ConfigOption(click.Option): def __init__(self, *args, config_key=None, **kwargs): self.config_key = config_key super().__init__(*args, **kwargs) def get_default(self, ctx): # 从上下文获取CLIConfig实例 config = ctx.obj.get('config') if config and self.config_key: value = config._get_value(self.config_key) if value is not None: # 配置存在值时,取消提示并返回该值 self.prompt = None return value return super().get_default(ctx)
3. 集成到CLI命令中
修改CLI组命令,在上下文加载配置;同时改造setup命令,使用自定义ConfigOption并处理配置保存:
import click import sys import pathlib @click.group() @click.option('--source-app-name', default=pathlib.Path(sys.argv[0]).name, help='应用名称,用于区分配置文件') @click.pass_context def cli(ctx, source_app_name): """Root level of CLI""" # 初始化配置并放入上下文 ctx.ensure_object(dict) ctx.obj['config'] = CLIConfig(source_app_name) @click.command(name='setup', help='Setup CLI') @click.option('--microservice-path', cls=ConfigOption, config_key='scm.microservice.localPath', prompt='Enter the local repository location for microservices', type=click.Path()) @click.option('--terraform-path', cls=ConfigOption, config_key='scm.terraform.localPath', prompt='Enter the local repository location for terraform', type=click.Path()) @click.option('--private-token', cls=ConfigOption, config_key='scm.accessToken', hide_input=True, prompt='Provide GitLab API Token') @click.pass_context def setup(ctx, microservice_path, terraform_path, private_token): # 将用户输入写入配置 config = ctx.obj['config'] config._set_value('scm.microservice.localPath', microservice_path) config._set_value('scm.terraform.localPath', terraform_path) config._set_value('scm.accessToken', private_token) config.save_config() click.echo("配置已保存!") cli.add_command(setup) if __name__ == '__main__': cli()
效果说明
- 首次运行
setup命令时,会提示用户输入所有选项,输入完成后写入配置文件; - 后续运行
setup或其他需要这些配置的命令时,会直接读取配置文件中的值,不再提示用户输入; - 如果需要修改配置,可直接编辑
~/.{source_app_name}/config.json文件,或者重新运行setup命令覆盖现有配置。
内容的提问来源于stack exchange,提问作者Sarvesh
相关产品推荐
相关产品推荐

