You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何重写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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 04:39:34