Tutorial

This tutorial details how we transformed the canonical click example:

click CLI help screen

Into this:

click-extra CLI help screen

All bells and whistles

The canonical click example is implemented that way:

import click

@click.command
@click.option("--count", default=1, help="Number of greetings.")
@click.option("--name", prompt="Your name", help="The person to greet.")
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for _ in range(count):
        click.echo(f"Hello, {name}!")

Whose help screen renders as:

$ hello --help
Usage: hello [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER  Number of greetings.
  --name TEXT      The person to greet.
  --help           Show this message and exit.

To augment the example above with all the bells and whistles click-extra has in store, you just need to import the same decorators and functions from its namespace:

import click_extra

@click_extra.command
@click_extra.option("--count", default=1, help="Number of greetings.")
@click_extra.option("--name", prompt="Your name", help="The person to greet.")
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for _ in range(count):
        click_extra.echo(f"Hello, {name}!")

And now you get:

$ hello --help
Usage: hello [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER       Number of greetings.  [default: 1]
  --name TEXT           The person to greet.
  --time / --no-time    Measure and print elapsed execution time.  [default: no-
                        time]
  --color, --ansi / --no-color, --no-ansi
                        Strip out all colors and all ANSI codes from output.
                        [default: color]
  --config CONFIG_PATH  Location of the configuration file. Supports local path
                        with glob patterns or remote URL.  [default: ~/.config/h
                        ello/*.toml|*.yaml|*.yml|*.json|*.json5|*.jsonc|*.hjson|
                        *.ini|*.xml]
  --no-config           Ignore all configuration files and only use command line
                        parameters and environment variables.
  --show-params         Show all CLI parameters, their provenance, defaults and
                        value, then exit.
  --table-format [asciidoc|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|html|jira|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|tsv|unsafehtml|vertical|youtrack]
                        Rendering style of tables.  [default: rounded-outline]
  --verbosity LEVEL     Either CRITICAL, ERROR, WARNING, INFO, DEBUG.  [default:
                        WARNING]
  -v, --verbose         Increase the default WARNING verbosity by one level for
                        each additional repetition of the option.  [default: 0]
  --version             Show the version and exit.
  -h, --help            Show this message and exit.

That’s it!

Tip

click_extra is proxy-ing the whole click and cloud namespace, so you can use it as a drop-in replacement.

Mix and match

If you do not like the opiniated way the @click_extra.command decorator is setup, with all its defaults options, you are still free to pick them up independently.

If, for example, you’re only interested in using the --config option, you’re free to to use it with a standard Click CLI. Just take the @config_option decorator from click_extra and add it to your command:

import click
from click_extra import config_option

@click.command
@click.option("--count", default=1, help="Number of greetings.")
@click.option("--name", prompt="Your name", help="The person to greet.")
@config_option
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for _ in range(count):
        click.echo(f"Hello, {name}!")

Which now renders to:

$ hello --help
Usage: hello [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER       Number of greetings.
  --name TEXT           The person to greet.
  --config CONFIG_PATH  Location of the configuration file. Supports local path
                        with glob patterns or remote URL.  [default: ~/.config/h
                        ello/*.toml|*.yaml|*.yml|*.json|*.json5|*.jsonc|*.hjson|
                        *.ini|*.xml]
  --help                Show this message and exit.

This option behave like any Click option and can be customized easily:

import click
from click_extra import config_option

@click.command
@click.option("--count", default=1, help="Number of greetings.")
@click.option("--name", prompt="Your name", help="The person to greet.")
@config_option("--hello-conf", metavar="CONF_FILE", help="Loads CLI config.")
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for _ in range(count):
        echo(f"Hello, {name}!")
$ hello --help
Usage: hello [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER         Number of greetings.
  --name TEXT             The person to greet.
  --hello-conf CONF_FILE  Loads CLI config.  [default: ~/.config/hello/*.toml|*.
                          yaml|*.yml|*.json|*.json5|*.jsonc|*.hjson|*.ini|*.xml]
  --help                  Show this message and exit.

Cloup integration

All Click Extra primitives are sub-classes of Cloup’s and supports all its features.

Like option groups:

import click
import cloup
from click_extra import config_option

@cloup.command()
@click.option("--count", default=1, help="Number of greetings.")
@click.option("--name", prompt="Your name", help="The person to greet.")
@cloup.option_group(
    "Cool options",
    cloup.option("--foo", help="The option that starts it all."),
    cloup.option("--bar", help="Another important option."),
    config_option("--hello-conf", metavar="CONF_FILE", help="Loads CLI config."),
    constraint=cloup.constraints.RequireAtLeast(1),
)
def hello(count, name, foo, bar, hello_conf):
    """Simple program that greets NAME for a total of COUNT times."""
    for _ in range(count):
        click.echo(f"Hello, {name}!")

See how the configuration option is grouped with others:

$ hello --help
Usage: hello [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Cool options: [at least 1 required]
  --foo TEXT              The option that starts it all.
  --bar TEXT              Another important option.
  --hello-conf CONF_FILE  Loads CLI config.  [default: ~/.config/hello/*.toml|*.
                          yaml|*.yml|*.json|*.json5|*.jsonc|*.hjson|*.ini|*.xml]

Other options:
  --count INTEGER         Number of greetings.
  --name TEXT             The person to greet.
  --help                  Show this message and exit.

Caution

Notice in the example above how the @command() decorator from Cloup is used with parenthesis. Contrary to Click and Click Extra, Cloup requires parenthesis on its decorators.

Available options

Click Extra provides these additional, pre-configured options decorators you can use standalone. Some of them are included by default in the @extra_command and @extra_group decorators (see the last column):

Decorator

Specification

Default

@timer_option

--time / --no-time

@color_option

--color, --ansi / --no-color, --no-ansi

@config_option

--config CONFIG_PATH

@no_config_option

--no-config

@show_params_option

--show-params

@table_format_option

--table-format FORMAT

@verbosity_option

--verbosity LEVEL

@verbose_option

-v, --verbose

@version_option

--version

@help_option

-h, --help

@telemetry_option

--telemetry / --no-telemetry

Note

Because single-letter options are a scarce resource, Click Extra does not impose them on you. All the options above are specified with their long names only. You can always customize them to add a short name if you wish.

That’s a general rule, unless some short names follow a widely-accepted convention or an overwhelmingly-followed tradition. Which is the case for -v, --verbose and -h, --help.

Tip

If you find the click_extra namespace too long to type, you can always alias it to something shorter.

A popular choice is clickx:

import click
import click_extra as clickx


@click.command
@click.option("--foo")
@clickx.config_option
def first(foo): ...

Standalone script

You can create a full-featured CLI based on Click Extra, without any Python project boilerplate.

The trick is to rely on uv to run simple Python scripts.

Taking the canonical example again, you can create a file named hello.py with this content:

hello.py
 1#!/usr/bin/env -S uv run --script
 2# /// script
 3# dependencies = ["click-extra >= 7.0.0"]
 4# ///
 5
 6from click_extra import command, echo, option
 7
 8
 9@command
10@option("--count", default=1, help="Number of greetings.")
11@option("--name", prompt="Your name", help="The person to greet.")
12def hello(count, name):
13    """Simple program that greets NAME for a total of COUNT times."""
14    for _ in range(count):
15        echo(f"Hello, {name}!")
16
17
18if __name__ == "__main__":
19    hello()

See the first few commented lines? The first line is a shebang that tells the OS to run the script with uv. The other comments are script dependencies.

Now all you need to do is to make the script executable and run it:

$ chmod +x ./hello.py
$ ./hello.py --help

The magic happens because uv will read the script comments and install click-extra and its dependencies in an isolated environment before running the Python code.

And just like that, you have a self-contained, single-file CLI, with all the features of Click Extra, including multi-platform support.

Hint

You can target specific versions of Click Extra in your script dependencies:

#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["click-extra"]
# ///
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["click-extra == 7.2.0"]
# ///
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["click-extra @ git+https://github.com/kdeldycke/click-extra"]
# ///
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["click-extra @ file:///Users/me/code/click-extra"]
# ///