Python click allows specifying some command line options as "feature switches". Example from the official documentation:
@click.command()
@click.option('--upper', 'transformation', flag_value='upper',
default=True)
@click.option('--lower', 'transformation', flag_value='lower')
def info(transformation):
click.echo(getattr(sys.platform, transformation)())
As the name suggests, it is used for the case when there is several alternatives for some feature, and only one can be selected. The code above allows running the script as
$ test.py --upper
LINUX2
$ test.py --lower
linux2
$ test.py
LINUX2
However, the same script allows the user to specify both options on the command line. Click will silently use the last option specified:
$ test.py --upper --lower
linux2
Is there any way to force click to check that no more than one such option has been passed on the command line?
One way to approach what you are after is to inherit from click.Option
, and customize the parser.
import click
class OnceSameNameOption(click.Option):
def add_to_parser(self, parser, ctx):
def parser_process(value, state):
# method to hook to the parser.process
if self.name in state.opts:
param_same_name = [
opt.opts[0] for opt in ctx.command.params
if isinstance(opt, OnceSameNameOption) and opt.name == self.name
]
raise click.UsageError(
"Illegal usage: `{}` are mutually exclusive arguments.".format(
', '.join(param_same_name))
)
# call the actual process
self._previous_parser_process(value, state)
retval = super(OnceSameNameOption, self).add_to_parser(parser, ctx)
for name in self.opts:
our_parser = parser._long_opt.get(name) or parser._short_opt.get(name)
if our_parser:
self._previous_parser_process = our_parser.process
our_parser.process = parser_process
break
return retval
To use the custom class, pass the cls
parameter to @click.option()
decorator like:
@click.option("--an_option", 'option-name', cls=OnceSameNameOption)
The string option-name
is used to check for other invocations of the same option.
This works because click is a well designed OO framework. The @click.option()
decorator usually instantiates a
click.Option
object but allows this behavior to be over ridden with the cls
parameter. So it is a relatively
easy matter to inherit from click.Option
in our own class and over ride the desired methods.
In this case we over ride click.Option.add_to_parser()
and the monkey patch the parser so that we can
validate that the same name parameter has not been seen before.
@click.command()
@click.option('--upper', 'transformation', flag_value='upper',
cls=OnceSameNameOption, default=True)
@click.option('--lower', 'transformation', flag_value='lower',
cls=OnceSameNameOption)
def info(transformation):
"""Show the transformed platform"""
click.echo(getattr(sys.platform, transformation)())
if __name__ == "__main__":
commands = (
'--upper --lower',
'--upper',
'--lower',
'',
'--help',
)
import sys, time
time.sleep(1)
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
for cmd in commands:
try:
time.sleep(0.1)
print('-----------')
print('> ' + cmd)
time.sleep(0.1)
info(cmd.split())
except BaseException as exc:
if str(exc) != '0' and \
not isinstance(exc, (click.ClickException, SystemExit)):
raise
Click Version: 6.7
Python Version: 3.6.2 (default, Jul 17 2017, 23:14:31)
[GCC 5.4.0 20160609]
-----------
> --upper --lower
Error: Illegal usage: `--upper, --lower` are mutually exclusive arguments.
-----------
> --upper
LINUX
-----------
> --lower
linux
-----------
>
LINUX
-----------
> --help
Usage: test.py [OPTIONS]
Show the transformed platform
Options:
--upper
--lower
--help Show this message and exit.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With