Mutually exclusive option groups in python Click
I ran into this same use case recently; this is what I came up with. For each option, you can give a list of conflicting options.
from click import command, option, Option, UsageErrorclass MutuallyExclusiveOption(Option): def __init__(self, *args, **kwargs): self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', [])) help = kwargs.get('help', '') if self.mutually_exclusive: ex_str = ', '.join(self.mutually_exclusive) kwargs['help'] = help + ( ' NOTE: This argument is mutually exclusive with ' ' arguments: [' + ex_str + '].' ) super(MutuallyExclusiveOption, self).__init__(*args, **kwargs) def handle_parse_result(self, ctx, opts, args): if self.mutually_exclusive.intersection(opts) and self.name in opts: raise UsageError( "Illegal usage: `{}` is mutually exclusive with " "arguments `{}`.".format( self.name, ', '.join(self.mutually_exclusive) ) ) return super(MutuallyExclusiveOption, self).handle_parse_result( ctx, opts, args )
Then use the regular option
decorator but pass the cls
argument:
@command(help="Run the command.")@option('--jar-file', cls=MutuallyExclusiveOption, help="The jar file the topology lives in.", mutually_exclusive=["other_arg"])@option('--other-arg', cls=MutuallyExclusiveOption, help="The jar file the topology lives in.", mutually_exclusive=["jar_file"])def cli(jar_file, other_arg): print "Running cli." print "jar-file: {}".format(jar_file) print "other-arg: {}".format(other_arg)if __name__ == '__main__': cli()
Here's a gist that includes the code above and shows the output from running it.
If that won't work for you, there's also a few (closed) issues mentioning this on the click github page with a couple of ideas that you may be able to use.
You could use the following package:https://github.com/espdev/click-option-group
import clickfrom click_option_group import optgroup, RequiredMutuallyExclusiveOptionGroup@click.command()@optgroup.group('Grouped options', cls=RequiredMutuallyExclusiveOptionGroup, help='Group description')@optgroup.option('--all', 'all_', is_flag=True, default=False)@optgroup.option('--color')def cli(all_, color): print(all_, color)if __name__ == '__main__': cli()
app help:
$ app.py --helpUsage: app.py [OPTIONS]Options: Grouped options: [mutually_exclusive, required] Group description --all --color TEXT --help Show this message and exit.
You could use Cloup, a package that adds option groups and constraints to Click. You have two options to solve this problem in Cloup.
Disclaimer: I'm the author of the package.
Option 1: @option_group
When you define an option group using @option_group
, the options in each group are shown in separate help sections (like in argparse). You can apply constraints (like mutually_exclusive
) to option groups as follows:
from cloup import command, option, option_groupfrom cloup.constraints import mutually_exclusive@command()@option_group( 'Color options', option('--all', 'all_colors', is_flag=True), option('--color'), constraint=mutually_exclusive)def cmd(**kwargs): print(kwargs)
The help will be:
Usage: cmd [OPTIONS]Color options [mutually exclusive]: --all --color TEXTOther options: --help Show this message and exit.
Option 2: @constraint
If you don't want option groups to show up in the command help, you can use @constraint
and specify the constrained options by their (destination) name:
from cloup import command, optionfrom cloup.constraints import constraint, mutually_exclusive@command()@option('--all', 'all_colors', is_flag=True)@option('--color')@constraint(mutually_exclusive, ['all_colors', 'color'])def cmd(**kwargs): print(kwargs)
Constraints defined this way can be documented in command help! This feature is disabled by default but can be easily enabled passing show_constraints=True
to @command
. The result:
Usage: cmd [OPTIONS]Options: --all --color TEXT --help Show this message and exit.Constraints: {--all, --color} mutually exclusive
The error message
In both case, if you run cmd --all --color red
, you get:
Usage: cmd [OPTIONS]Try 'cmd --help' for help.Error: the following parameters are mutually exclusive: --all --color
Other constraints
Cloup defines constraints that should cover 99.9% of your needs. It even supports conditional constraints!
For example, if the user must provide one of your mutually exclusive options, replace mutually_exclusive
with RequireExactly(1)
in the example above.
You can find all available constraints here.