python click 参数检测与子命令

目录

参数检测

click 在设置参数时可以使用 type 指定参数类型,可以使用 python 内置类型和 click 内置的类型,例如

@click.command()
@click.option('--pos', nargs=2, type=float)
def findme(pos):
    click.echo('%s / %s' % pos)

为了更精确地控制参数格式,click 也支持自定义参数类型。用户可以继承 click.ParamType 类,并实现 convert 方法。

class StartTimeParamType(click.ParamType):
    name = "start_time"
    def convert(self, value, param, ctx):
        try:
            if len(value) != 10:
                self.fail("length of start_time must be 10".format(value=value))
            start_date_time = datetime.datetime.strptime(value, "%Y%m%d%H")
            return start_date_time
        except ValueError:
            self.fail("{value} is not a valid start_time".format(value=value)

子命令

通常有两种形式实现子命令:

  1. 类似 git 命令,通过第二个关键词区分不同的命令
git clone
git add 
  1. 类似 ecflow_client 命令,不同的参数实现不同的功能
ecflow_client --load
ecflow_client --help  

click 中的 group command 可以很方便地实现第一种形式的命令。
对于第二种方式,可以使用 click 中的 eager options 折中实现。
eager option 比一般option具有更高的优先级,解析命令时,click 会首先处理 eager 参数。我们可以为 eager 参数设置 callback,在处理该参数时会调用 callback 函数。如果在callback 函数中退出命令,就可以实现子命令功能。

def show_find_local_file_types(ctx, param, value):
    if not value:
        return
    if 'config_dir' in ctx.params:
        config_dir = ctx.params['config_dir']
        config_dir = Path(config_dir).absolute()
    else:
        config_dir = get_default_local_config_path()
    click.echo("config dir:{config_dir}".format(config_dir=str(config_dir)))
    click.echo("data types:")
    for a_config_file in sorted(config_dir.rglob("*.yml")):
        click.echo(a_config_file.relative_to(config_dir).with_suffix(''))
    ctx.exit(0)

@click.command(short_help="Find local data path.")
@click.option("--config-dir", is_eager=True,
              help="Config dir, default: {file_path}".format(file_path=str(get_default_local_config_path())))
@click.option("--data-type", required=True,
              help="Data type used to locate config file path in config dir.")
@click.option("--show-types", is_flag=True, default=False,
              is_eager=True, callback=show_find_local_file_types,
              help="Show supported data types defined in config dir and exit.")
@click.argument("start_time", metavar='<start_time>', type=StartTimeParamType())
@click.argument("forecast_time", metavar='<forecast_time>', type=ForecastTimeParamType())
def find_local_file(config_dir, show_types, data_type, start_time, forecast_time):
    """Find local data path using config files in config dir.
    start_time: YYYYMMDDHH, such as 2018080100
    forecast_time: FFF, such as 000
    """
    if config_dir is None:
        config_dir = get_default_local_config_path()
    config_file_path = find_config(config_dir, data_type)
    if config_file_path is None:
        click.echo('model data type config not found.', err=True)
        click.get_current_context().exit(10)
    config = load_config(config_file_path)
    file_path = find_file(config, start_time, forecast_time)
    click.echo(file_path)

上述示例在一般情况下会执行 find_local_file 函数,但当用户设置了–show-types参数后,会首先执行该参数的 callback 函数 show_find_local_file_types,而该函数中调用 ctx.exit(0) 退出程序,因此 find_local_file 不会被执行。这样就可以通过指定不同的参数改变程序的运行流程。