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

1

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.

Fare answered 22/3, 2021 at 9:57 Comment(0)
D
15

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)
    )
Donnadonnamarie answered 22/3, 2021 at 10:2 Comment(5)
Thanks - I tried to use that in combination with command group, but it does not work for some reason: pastebin.com/V9qz3VfqFare
It seems that @cli.command() does not workFare
You misunderstand me: You can just remove those lines and instead use @cli.command() where you now use @click.command. See my last edit.Donnadonnamarie
@Donnadonnamarie 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?Cheshvan
@Cheshvan You can just define a decorator that applies those two decorators. I've added an example.Donnadonnamarie

© 2022 - 2024 — McMap. All rights reserved.