PeterE
PeterE

Reputation: 5855

python-click: Adding an option that modifies the behavior of other parameters

This question is about the click package:

Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It’s the “Command Line Interface Creation Kit”. It’s highly configurable but comes with sensible defaults out of the box.

It aims to make the process of writing command line tools quick and fun while also preventing any frustration caused by the inability to implement an intended CLI API.

I'd like to add a click.Option to my click.Command, which changes the behavior of the other parameters of that command. Consider the following example:

@click.option('--x', default=42, prompt=True)
@click.command
def cli_a(x):
  print(x)


@click.option('--x', default=42, prompt=False)
@click.command
def cli_b(x):
  print(x)

If cli_a is called without explicitly specifying x the user is prompted to provide a value (or confirm the default value with ENTER). If cli_b is called without specifying x the default value is used without prompting the user.

I'd now like to add a flag click.Option that allows the user to choose between one of the above variants (at runtime). So, calling cli_c --i would behave like cli_a and calling cli_c would behave like cli_b.

@click.option('--i', is_flag=True, default=False, expose_value=False)
@click.option('--x', default=42, prompt=False)
@click.command
def cli_c(x):
  print(x)

Is that doable with the current API? Is it feasible?

A similar use-case would be something like an anwser-all-confimation-prompts-with-yes flag. Usually this comes up if the cli tool is supposed to be usable interactively by the user and in automated mode via a script or some such thing.

Upvotes: 7

Views: 3027

Answers (1)

PeterE
PeterE

Reputation: 5855

I have come up with following code, which produces the desired behavior:

def deactivate_prompts(ctx, param, value):
    if not value:
        click.echo("entering batch mode, deactivating all prompts ...")
        for p in ctx.command.params:
            if isinstance(p, click.Option) and p.prompt is not None:
                p.prompt = None
    return value

@click.option('--i/--b', default=True, is_eager=True, expose_value=False, callback=deactivate_prompts)
@click.option('--x', default=42, prompt=True)
@click.command
def cli_c(x):
  print(x)

The idea is to use the callback of an eager option to modify all (other) Options of the Command.

POTENTIAL WEAK POINTS:

  • This is an all or nothing solution, i.e. either all of the prompts are active or none. For my use-case, that's exactly what I wanted, but for others that might not be the case.
  • This works only in one direction, i.e. by turning the prompts off. So each Option that may or may not display a prompt must be configured as if displaying a prompt.
  • If we turn off the prompts, we still need a value for that Option, so an alternate value-source is a must. I.e. a default value must be provided.

Upvotes: 7

Related Questions