If there is a specific nargs = -1 argument that you would like to decorate only onto the group, but be applicable to
all commands as needed, you can do that with some of extra plumbing like:
This answer is inspired by this answer.
Custom Class
class GroupNArgsForCommands(click.Group):
"""Add special arguments on group"""
def __init__(self, *args, **kwargs):
super(GroupNArgsForCommands, self).__init__(*args, **kwargs)
cls = GroupNArgsForCommands.CommandArgument
# gather the special arguments for later
self._cmd_args = {
a.name: a for a in self.params if isinstance(a, cls)}
# strip out the special arguments from self
self.params = [a for a in self.params if not isinstance(a, cls)]
class CommandArgument(click.Argument):
"""class to allow us to find our special arguments"""
@staticmethod
def command_argument(*param_decls, **attrs):
"""turn argument type into type we can find later"""
assert 'cls' not in attrs, "Not designed for custom arguments"
attrs['cls'] = GroupNArgsForCommands.CommandArgument
def decorator(f):
click.argument(*param_decls, **attrs)(f)
return f
return decorator
def group(self, *args, **kwargs):
# any derived groups need to be the same type
kwargs['cls'] = GroupNArgsForCommands
def decorator(f):
grp = super(GroupNArgsForCommands, self).group(
*args, **kwargs)(f)
self.add_command(grp)
# any sub commands need to hook the same special args
grp._cmd_args = self._cmd_args
return grp
return decorator
def add_command(self, cmd, name=None):
# call original add_command
super(GroupNArgsForCommands, self).add_command(cmd, name)
# if this command's callback has desired parameters add them
import inspect
args = inspect.signature(cmd.callback)
if len(args.parameters):
for arg_name in reversed(list(args.parameters)):
if arg_name in self._cmd_args:
cmd.params[:] = [self._cmd_args[arg_name]] + cmd.params
Using the Custom Class:
To use the custom class, pass the cls parameter to the click.group() decorator, use the
@GroupNArgsForCommands.command_argument decorator for the special argument, and then add a
parameter of the same name as the special argument to any commands as needed.
@click.group(cls=GroupNArgsForCommands)
@GroupNArgsForCommands.command_argument('special', nargs=-1)
def a_group():
"""My project description"""
@a_group.command()
def a_command(special):
"""a command under the group"""
How does this work?
This works because click is a well designed OO framework. The @click.group() decorator usually
instantiates a click.Group object but allows this behavior to be over ridden with the cls parameter.
So it is a relatively easy matter to inherit from click.Group in our own class and over ride desired methods.
In this case we over ride click.Group.add_command() so that when a command is added we can examine
the command callback parameters to see if they have the same name as any of our special arguments.
If they match, the argument is added to the command's arguments just as if it had been decorated directly.
In addition GroupNArgsForCommands implements a command_argument() method. This method is used as
a decorator when adding the special argument instead of using click.argument()
Test Class
import click
@click.group(cls=GroupNArgsForCommands)
@GroupNArgsForCommands.command_argument('sites', nargs=-1)
def cli():
click.echo("cli group")
@cli.command()
def command_one(sites):
click.echo("command_one: {}".format(sites))
@cli.group()
def subcommand():
click.echo("subcommand group")
@subcommand.command()
def one():
click.echo("subcommand_one")
@subcommand.command()
def two(sites):
click.echo("subcommand_two: {}".format(sites))
if __name__ == "__main__":
commands = (
'command_one site1 site2',
'command_one site1',
'command_one',
'subcommand',
'subcommand one site1 site2',
'subcommand one site1',
'subcommand one',
'subcommand two site1 site2',
'subcommand two site1',
'subcommand two',
'--help',
'command_one --help',
'subcommand --help',
'subcommand one --help',
'subcommand two --help',
'',
)
import sys, time
time.sleep(1)
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
for command in commands:
try:
time.sleep(0.1)
print('-----------')
print('> ' + command)
time.sleep(0.1)
cli(command.split())
except BaseException as exc:
if str(exc) != '0' and \
not isinstance(exc, (click.ClickException, SystemExit)):
raise
Results:
Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> command_one site1 site2
cli group
command_one: ('site1', 'site2')
-----------
> command_one site1
cli group
command_one: ('site1',)
-----------
> command_one
cli group
command_one: ()
-----------
> subcommand
cli group
Usage: test.py subcommand [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
one
two
-----------
> subcommand one site1 site2
Usage: test.py subcommand one [OPTIONS]
Error: Got unexpected extra arguments (site1 site2)
cli group
subcommand group
-----------
> subcommand one site1
cli group
subcommand group
Usage: test.py subcommand one [OPTIONS]
Error: Got unexpected extra argument (site1)
-----------
> subcommand one
cli group
subcommand group
subcommand_one
-----------
> subcommand two site1 site2
cli group
subcommand group
subcommand_two: ('site1', 'site2')
-----------
> subcommand two site1
cli group
subcommand group
subcommand_two: ('site1',)
-----------
> subcommand two
cli group
subcommand group
subcommand_two: ()
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
command_one
subcommand
-----------
> command_one --help
cli group
Usage: test.py command_one [OPTIONS] [SITES]...
Options:
--help Show this message and exit.
-----------
> subcommand --help
cli group
Usage: test.py subcommand [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
one
two
-----------
> subcommand one --help
cli group
subcommand group
Usage: test.py subcommand one [OPTIONS]
Options:
--help Show this message and exit.
-----------
> subcommand two --help
cli group
subcommand group
Usage: test.py subcommand two [OPTIONS] [SITES]...
Options:
--help Show this message and exit.
-----------
>
Usage: test.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
command_one
subcommand