8

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.

1 Answer 1

17

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)
    )
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks - I tried to use that in combination with command group, but it does not work for some reason: pastebin.com/V9qz3Vfq
It seems that @cli.command() does not work
You misunderstand me: You can just remove those lines and instead use @cli.command() where you now use @click.command. See my last edit.
@L3viathan your solution works great! However, if I have a bunch of "common" options to share amongst sub-commands; is there a cleaner way instead of assigning each option to a separate variable and then adding bunch of @<that variable> to all my sub commands?
@shikhanshu You can just define a decorator that applies those two decorators. I've added an example.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.