Optional CLI arguments with python Click library option

Viewed 3372

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
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.

Related