1

I am trying to use https://click.palletsprojects.com/en/8.0.x/options/

I have a use case where one out of these 3 parameters has to be a required field.

This is how I am doing it.

10:45 $ python test.py Usage: test.py [OPTIONS]

Error: Must specify --foo or bar or car

import click

@click.command()
@click.option('--foo', help='foo is bar.')
@click.option('--count', help='Number of greetings.')
@click.option('--name',
              help='The person to greet.')
def hello(foo, count, name):
    if not (count or name or foo):
        raise click.UsageError( 'Must specify --foo or bar or car')
    
    
    click.echo(f"Hello {name}!")

if __name__ == '__main__':
    hello()

Is there a way to show one of these params as required field. something like this:

python test.py --help
Usage: test.py [OPTIONS]

Options:
  --foo TEXT    foo is bar
 or --count TEXT  Number of greetings
 or  --name TEXT   The person to greet [required]

  --help        Show this message and exit.

Try 1:

https://click.palletsprojects.com/en/8.0.x/options/#feature-switches

import click

@click.command()
@click.option('--foo', flag_value='foo', required=True, help='foo is bar.')
@click.option('--count', flag_value='count', required=True, help='Number of greetings.')
@click.option('--name', flag_value='name', required=True, help='The person to greet.')
def hello(foo, count, name):
    if not (count or name or foo):
        raise click.UsageError( 'Must specify --foo or bar or car')
    if foo:
        click.echo(f"Hello {foo}!")
    if count:
        click.echo(f"Hello {count}!")
    if name:
        click.echo(f"Hello {name}!")

if __name__ == '__main__':
    hello()
urawesome
  • 198
  • 1
  • 10
  • One and only one of those 3? – aaossa Mar 04 '22 at 18:54
  • Maybe checkout the example of [feature switches](https://click.palletsprojects.com/en/8.0.x/options/#feature-switches) and use `required=True`? – aaossa Mar 04 '22 at 18:56
  • yes, only one of the params are required out of those 3. Any one will work. – urawesome Mar 04 '22 at 20:32
  • tried feature switches but it needs all 3 params. Usage: test.py [OPTIONS] Options: --foo foo is bar. [required] --count Number of greetings. [required] --name The person to greet. [required] --help Show this message and exit. 12:41 $ python test.py --foo Usage: test.py [OPTIONS] Try 'test.py --help' for help. Error: Missing option '--count'. – urawesome Mar 04 '22 at 20:43
  • @urawesome, In Try1 you must enter `--foo` as switch. – EsmaeelE Apr 11 '22 at 12:41

1 Answers1

0

Your try1 code works but user must enter the --foo switch.

Try1

import click

@click.command()
@click.option('--foo', flag_value='foo', required=True, help='foo is bar.')
@click.option('--count', flag_value='count', required=True, help='Number of greetings.')
@click.option('--name', flag_value='name', required=True, help='The person to greet.')
def hello(foo, count, name):
    if not (count or name or foo):
        raise click.UsageError( 'Must specify --foo or bar or car')
    if foo:
        click.echo(f"Hello {foo}!")
    if count:
        click.echo(f"Hello {count}!")
    if name:
        click.echo(f"Hello {name}!")

if __name__ == '__main__':
    hello()

But you want

I have a use case where one out of these 3 parameters has to be a required field.

Using this answer by @Stephen Rauch that provide mutex to implement dependence option I think its your answer.

Here is the Code

import click


class Mutex(click.Option):
    def __init__(self, *args, **kwargs):
        self.not_required_if: list = kwargs.pop("not_required_if")

        assert self.not_required_if, "'not_required_if' parameter required"
        kwargs["help"] = (kwargs.get("help", "") + "Option is mutually exclusive with " +
                          ", ".join(self.not_required_if) + ".").strip()
        super(Mutex, self).__init__(*args, **kwargs)

    def handle_parse_result(self, ctx, opts, args):
        current_opt: bool = self.name in opts
        for mutex_opt in self.not_required_if:
            if mutex_opt in opts:
                if current_opt:
                    raise click.UsageError(
                        "Illegal usage: '" + str(self.name) + "' is mutually exclusive with " + str(mutex_opt) + ".")
                else:
                    self.prompt = None
        return super(Mutex, self).handle_parse_result(ctx, opts, args)


@click.command()
@click.option('--foo', flag_value='foo', cls=Mutex, not_required_if=["count", "name"], help='foo is bar.')
@click.option('--count', flag_value='count',  hide_input=True, cls=Mutex, not_required_if=["foo", "name"], help='Number of greetings.')
@click.option('--name', flag_value='name',  hide_input=True, cls=Mutex, not_required_if=["count", "foo"], help='The person to greet.')
def login(ctx=None, foo: bool = False, count: bool = False, name: bool = False) -> None:
    print("...do what you like with the params you got...")

    if foo:
        click.echo(f"Hello {foo}!")
    if count:
        click.echo(f"Hello {count}!")
    if name:
        click.echo(f"Hello {name}!")
    
if __name__ == '__main__':
    login()

Run

$ python3 cc.py --name
Hello name!

$ python3 cc.py --foo
Hello name!

$ python3 cc.py --name --foo
Error: Illegal usage: 'name' is mutually exclusive with foo.

Related link

Mutually exclusive option groups in python Click

EsmaeelE
  • 2,331
  • 6
  • 22
  • 31
  • Thank you so much. this almost solved my problem. I only have one more issue. Current these params are not required. if i use required=True, it doesnt work. Is there a way to notify users that One of these are required param python test.py --help Usage: test.py [OPTIONS] Options: --foo foo is bar.Option is mutually exclusive with count, name. --count Number of greetings.Option is mutually exclusive with foo, name. --name The person to greet.Option is mutually exclusive with count, foo. --help Show this message and exit. – urawesome Apr 28 '22 at 19:17
  • This is how I am looking for: python test.py --help Usage: test.py [OPTIONS] Options: --foo foo is bar.Option is mutually exclusive with count, name. [required] --count Number of greetings.Option is mutually exclusive with foo, name. [required] --name The person to greet.Option is mutually exclusive with count, foo. [required] --help Show this message and exit. – urawesome Apr 28 '22 at 19:17
  • how do we disallow supplying more than one option? either 'foo' or 'bar' but not both. – Vamsi Nerella Jun 23 '23 at 09:24
  • 1
    @VamsiNerella, This is exactly what you want. – EsmaeelE Jun 23 '23 at 14:19
  • @EsmaeelE it's not really working. I don't get any warning message even if I omit both the options. and I can't add required=True since it complains about missing option. – Vamsi Nerella Jun 29 '23 at 06:24
  • 1
    ok, i found the issue, i was using required, so, it should have self.required = None instead of self.prompt = None, ugh!! – Vamsi Nerella Jun 29 '23 at 07:39