click-option-group icon indicating copy to clipboard operation
click-option-group copied to clipboard

Mutually exclusive options with the same variable name don't work

Open craigds opened this issue 4 years ago • 5 comments

If I have two options sharing the same variable name, like this:

#!/usr/bin/env python
import click
from click_option_group import optgroup, MutuallyExclusiveOptionGroup


@click.command()
@click.pass_context
@optgroup.group('Output format', cls=MutuallyExclusiveOptionGroup)
@optgroup.option(
    "--text", "output_format", flag_value="text", default=True,
)
@optgroup.option(
    "--json", "output_format", flag_value="json",
)
def mycommand(ctx, *, output_format, **kwargs):
    print(output_format)


if __name__ == '__main__':
    mycommand()

The mutual exclusive option group doesn't work - whichever option is specified last on the command line wins:

$ python ./mycommand.py --json --text
text

This may be difficult to solve, but here's my completely custom hack for making it work: https://github.com/koordinates/sno/pull/106

Maybe elements of that solution could be incorporated into this project?

craigds avatar Jun 08 '20 20:06 craigds

@craigds thanks for the report. I will try to investigate this behavior and your solution. I will see what I can do. :)

In any case your example looks like a very convenient way to work with mutually exclusive options. Would be great to fix/implement this.

espdev avatar Jun 09 '20 08:06 espdev

I was just about to report the same issue. Probably you are aware of it already but in case you are not: The behaviour that "the last option given on command line wins" is actually built into Click itself and does not have to do with click-option-group (I did not read code just tested results of Click-only-code). I am not sure if it is considered a feature in Click or a bug but at least the manual does not point out the intended behaviour exactely: https://click-docs-cn.readthedocs.io/zh_CN/latest/options.html#feature-switches

@user.command(context_settings=cont_set)
@click.pass_context
@click.argument('user_id', type=str)
...
@click.option('--deactivate', 'deactivation_state', flag_value='deactivate',
      help='''deactivate user. Use with caution! Deactivating a user
      removes their active access tokens, resets their password, kicks them out
      of all rooms and deletes third-party identifiers (to prevent the user
      requesting a password reset). See also "user deactivate" command.''')
@click.option('--activate', 'deactivation_state', flag_value='activate',
      help='''re-activate user.''')
def modify(ctx, user_id, password, password_prompt, display_name, threepid,
      avatar_url, admin, deactivation_state):
    '''create or modify a local user. Provide matrix user ID (@user:server)
    as argument.'''
...

Without posting further code details, this should best describe Click's behaviour:

$ synadm user modify userid --deactivate --activate 
Current user account settings:

User account settings after modification:
deactivation_state: activate

Are you sure you want to modify user? (y/N): 

other way round:

$ synadm user modify userid --activate --deactivate
Current user account settings:

User account settings after modification:
deactivation_state: deactivate

Are you sure you want to modify user? (y/N): 

HTH :-)

JOJ0 avatar Nov 26 '20 07:11 JOJ0

I guess it could work to key them by (option.name, option.flag_value) instead of just by option.name:

https://github.com/click-contrib/click-option-group/blob/fba6855bfc1fea971d237b8b44142d9c9c09cefb/click_option_group/_core.py#L243-L246

becomes

def _option_memo(self, func):
    ...
    self._options[func][option.name, option.flag_value] = option

flying-sheep avatar Jan 04 '21 12:01 flying-sheep