How to define common python-click options for multiple commands?

Question:

In python 3.8 I want to define some click options that are common to multiple commands. I tried the following piece of code:

import click


@click.group()
@click.option(
    "-v",
    "--verbose",
    count=True,
    default=0,
    help="-v for DEBUG",
)
@click.option(
    "--path",
    help="Main Path.",
)
def cli():
  pass


@click.command("list")
@click.option(
    "--list-option",
    help="Special option for list command.",
)
def my_list_command(verbose, path, list_option):
    print(verbose, path, list_option)

@click.command("find")
@click.option(
    "--find-option",
    help="Special option for find command.",
)
def my_find_command(verbose, path, find_option):
    print(verbose, path, find_option)

cli.add_command(my_list_command)
cli.add_command(my_find_command)

if __name__ == '__main__':
    cli()

But when I try to run the command

python script.py list

I get an error

TypeError: cli() got an unexpected keyword argument 'verbose'

What I want, is that the command list has the following three options: verbose, path and list-option and that the command find has the following three options: verbose, path and find-option. I do not want to define the options for verbose and path twice.

Is there a way to do this?

I also tried to use @click.pass_context but that does not seem to solev the issue.

Asked By: Alex

||

Answers:

The way you currently defined it it will work, but the --verbose option belongs to the main command group, so you’d need to call it as python script.py --verbose list (and my_find_command and my_list_command won’t receive it as an argument, only cli).

To use the same option across multiple commands without repeating yourself too much, you can just assign it to a variable and then use it twice:

verbose_option = click.option(
    "-v",
    "--verbose",
    count=True,
    default=0,
    help="-v for DEBUG",
)

...

@click.command()
@verbose_option
def foo(verbose):
    ...

@click.command()
@verbose_option
def bar(verbose):
    ...

Unrelated, but while we’re at it: There’s a simpler way of grouping commands, without having to do cli.add_command(my_find_command): Just use @cli.command() instead of @click.command():

import click

option_verbose = click.option(
    "-v",
    "--verbose",
    count=True,
    default=0,
    help="-v for DEBUG",
)

@click.group()
def cli():
    pass


@cli.command("list")
@option_verbose
@click.option(
    "--list-option",
    help="Special option for list command.",
)
def my_list_command(verbose, list_option):
    print(verbose, list_option)

@cli.command("find")
@option_verbose
@click.option(
    "--find-option",
    help="Special option for find command.",
)
def my_find_command(verbose, list_option):
    print(verbose, list_option)

if __name__ == '__main__':
    cli()

If there’s several options you want to apply, you can define your own decorator that calls all those options on the argument:

def common_options(fn):
    return click.option(
        "-v",
        "--verbose",
    )(
        click.option(
            "-n",
            "--dry-run",
        )(fn)
    )
Answered By: L3viathan
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.