Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional CLI arguments with python Click library option

I'm having a conundrum with the Python Click library when parsing some CLI options.

I would like an option to act as a flag by itself, but optionally accept string values. E.g.:

  1. $ myscript ⇒ option = False

  2. $ myscript -o ⇒ option = True

  3. $ myscript -o foobar ⇒ option = Foobar

Additionally, I would like the option to be "eager" (e.g. in "Click" terms abort execution after a callback), but this can be ignored for now.


When I define my arguments like this:

@click.command()
@click...
@click.option("-o", "option", is_flag=True, default=False)
def myscript(..., option):

I achieve example 1 and 2, but 3 is naturally impossible because the flag detects present/not present only.


When I define my arguments like this:

@click.command()
@click...
@click.option("-o", "--option", default="") # Let's assume I will cast empty string to False
def myscript(..., option):

I achieve 1 and 3, but 2 will fail with an Error: -c option requires an argument.


This does not seems like an out-of-this world scenario, but I can't seem to be able to achieve this or find examples that behave like this.

How can I define an @click.option that gets parsed like:

  • False when not set
  • True when set but without value
  • str when set with value
like image 916
kontur Avatar asked Mar 18 '20 14:03

kontur


1 Answers

One way that I have managed to achieve this behaviour was by actually using arguments as below. I'll post this as a workaround, while I try to see if it could be done with an option, and I'll update my post accordingly

@click.command(context_settings={"ignore_unknown_options": True})
@click.argument("options", nargs=-1)
def myscript(options):
    option = False
    if options is ():
        option = False
    if '-o' in options or '--option' in options:
        option = True
    if len(options) > 1:
        option = options[1]
    print(option)

Later Edit Using an option, I have managed to achieve this by adding an argument to the command definition.

@click.command()
@click.option('-o', '--option', is_flag=True, default=False, is_eager=True)
@click.argument('value', nargs=-1)
def myscript(option, value):
    if option and value != ():
        option = value[0]
    print(option)

The nargs can be removed if you only expect at most one argument to follow, and can be treated as not required.

@click.command()
@click.option('-o', '--option', is_flag=True, default=False, is_eager=True)
@click.argument('value', required=False)
def myscript(option, value=None):
    if option and value is not None:
        option = value
    print(option)

It might also be possible by putting together a context generator and storing some state, but that seems the least desirable solution, since you would be relying on the context storing your state.

like image 111
afterburner Avatar answered Sep 21 '22 23:09

afterburner