3

My code sample:

import click


def std_cb(ctx, param, standardize):
    if standardize:
        opt = click.Option(param_decls=['-a'],
                           help='this option only exists when -S is set')
    else:
        opt = click.Option(param_decls=['-b'],
                           help='this option only exists when -S is not set')
    ctx.command.params.append(opt)
    return standardize


@click.command()
@click.option('-S', '--standardize/--no-standardize', is_eager=True,
              is_flag=True, default=False, callback=std_cb)
def get_options(standardize, **extra_opts):
    print(locals())


if __name__ == '__main__':
    uis = get_options.main(standalone_mode=False)

What I'm trying to achieve is to be able to dynamically create different options for a given command depending on the value of an eager flag option to the same command using the click library.

When I execute the above command on the CLI as $ python cli_test.py, this is printed to stdout {'standardize': False, 'extra_opts': {}}, as expected. Similarly $ python cli_test.py -S prints {'standardize': True, 'extra_opts': {}}, also expected.

And when I invoke the built-in --help option with $ python cli_test.py --help, I get:

Usage: cli_test.py [OPTIONS]

Options:
  -S, --standardize / --no-standardize
  -b TEXT                         this option only exists when -S is not set
  --help                          Show this message and exit.

Which seems to suggest that the attachment of the --no-standardize specific option via the std_cb callback for the -S flag is working as well.

Similarly, $ python cli_test.py --help -S, produces:

Usage: cli_test.py [OPTIONS]

Options:
  -S, --standardize / --no-standardize
  -a TEXT                         this option only exists when -S is set
  --help                          Show this message and exit.

Now with the -a option appearing due to the presence of the -S flag.

However, if I were to try and do $ python cli_test.py -b hello, I'd get the error: click.exceptions.NoSuchOption: no such option: -b.

And similarly, $ python cli_test.py -S -a world produces click.exceptions.NoSuchOption: no such option: -a, despite them showing up in the help page under their applicable -S flag value.

What I had expected to see from the given code example is of course, $ python cli_test.py -b hello printing {'standardize': True, 'extra_opts': {'b': 'hello'}}.

And $ python cli_test.py -S -a world printing {'standardize': True, 'extra_opts': {'a': 'world'}}.

In the Click docs, the authors do state that using @click.option "is equivalent to creating an Option instance manually and attaching it to the Command.params list.", so I'm not really sure what I'm doing wrong.

1 Answer 1

3

I'm not sure if your code should work, but I wonder if you could live with something like I've sketched here:

import click


def require_standardize_set(ctx, param, value):                                                                                                                   
    if value and not ctx.params['standardize']:
        raise click.UsageError('-{} requires that -S is set'.format(param.name))
    return value

def require_standardize_not_set(ctx, param, value):
    if value and ctx.params['standardize']:
        raise click.UsageError('-{} requires that -S is not set'.format(param.name))
    return value


@click.command()
@click.option('-S', '--standardize/--no-standardize',
              is_flag=True, default=False, is_eager=True)
@click.option('-a', help='this option requires that -S is set',
              callback=require_standardize_set)
@click.option('-b', help='this option requires that -S is not set',
              callback=require_standardize_not_set)
def get_options(standardize, **extra_opts):
    print(locals())


if __name__ == '__main__':
    uis = get_options.main(standalone_mode=False)

This seems to me to produce the same results (except extra_opts always includes both a and b, but with the values of None if not set). A benefit, from my point of view is that the documentation always documents both a and b. As a user, I assume I would want that.

Sign up to request clarification or add additional context in comments.

1 Comment

This is something that I'd considered as well. However it doesn't quite do what I want, as another application I'd like to have is to be able to dynamically set different default values for a set of shared options based on the value of the eager flag. Thanks for the try anyway, gave you an upvote.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.