Reputation: 174
Is it possible to set a click.option
on a click.group
but pass the option in the end of the entire command?
@click.option(
'-d', '--debug',
help="change to debug mode")
@click.group()
def cli(ctx, **kwargs):
"""
help message
"""
@click.command()
def version():
"""
version
"""
pass
And the desired command would look like this:
./cli.py version -d
But this fails with:
Error: no such option: -d
Upvotes: 1
Views: 2125
Reputation: 49794
One way to to set a click.option
on a click.group
but pass the option in the end of the entire command is to inherit from click.Group
, and customize the code which invokes the commands.
This question was partially answered here. From that question we will use the custom class: GroupWithCommandOptions
.
import click
class GroupWithCommandOptions(click.Group):
""" Allow application of options to group with multi command """
def add_command(self, cmd, name=None):
click.Group.add_command(self, cmd, name=name)
# add the group parameters to the command
for param in self.params:
cmd.params.append(param)
# hook the commands invoke with our own
cmd.invoke = self.build_command_invoke(cmd.invoke)
self.invoke_without_command = True
def build_command_invoke(self, original_invoke):
def command_invoke(ctx):
""" insert invocation of group function """
# separate the group parameters
ctx.obj = dict(_params=dict())
for param in self.params:
name = param.name
ctx.obj['_params'][name] = ctx.params[name]
del ctx.params[name]
# call the group function with its parameters
params = ctx.params
ctx.params = ctx.obj['_params']
self.invoke(ctx)
ctx.params = params
# now call the original invoke (the command)
original_invoke(ctx)
return command_invoke
To use the custom class, pass the cls
parameter to @click.group()
decorator like:
@click.group(cls=GroupWithCommandOptions)
Then create a click.CommandCollection
using the group
cli = click.CommandCollection(sources=[group1])
The command collection is important because we need to control when the group is invoked, and
CommandCollection
's do not invoke their group, so we can do it ourselves at the appropriate time.
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 the desired methods.
In this case we over ride click.Group.add_command()
to add the group parameters to the command
and then monkey patch the command's invoke()
method so can call the group invoke before command invoke to allow
it to process its options which were specified on the command.
@click.group(cls=GroupWithCommandOptions)
@click.option('-d', '--debug', help="change to debug mode", is_flag=True)
def group1(**kwargs):
"""Our great program"""
click.echo('debug: %s' % kwargs['debug'])
@group1.command()
@click.pass_context
def version(ctx):
"""Show the Version"""
click.echo('show version here')
cli = click.CommandCollection(sources=[group1])
if __name__ == "__main__":
commands = (
'version -d',
'-d version',
'version',
'--help',
'version --help',
)
import sys, time
time.sleep(1)
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
for cmd in commands:
try:
time.sleep(0.1)
print('-----------')
print('> ' + cmd)
time.sleep(0.1)
cli(cmd.split())
except BaseException as exc:
if str(exc) != '0' and \
not isinstance(exc, (click.ClickException, SystemExit)):
raise
Click Version: 6.7
Python Version: 3.6.2 (default, Jul 17 2017, 23:14:31)
[GCC 5.4.0 20160609]
-----------
> version -d
debug: True
show version here
-----------
> -d version
Error: no such option: -d
-----------
> version
debug: False
show version here
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
version Show the Version
-----------
> version --help
Usage: test.py version [OPTIONS]
Show the Version
Options:
-d, --debug change to debug mode
--help Show this message and exit.
Upvotes: 2
Reputation: 174
Achieving this using click is impossible as an option belongs to it's command. An option that is available to all commands should belong to the group that encloses the commands.
A couple of implementation examples for achieving that can be found here: https://github.com/pallets/click/issues/108
Upvotes: -1