问题
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:
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.
来源:https://stackoverflow.com/questions/60469503/python-click-nosuchoption-exception-when-manually-attaching-option-objects-to-c