I have a command line interface build with click which implements multiple commands.
Now I want to pass unspecified named options into one command which is here named command1
e.g. the number of options and their names should be able to vary flexibly.
import click
@click.group(chain=True)
@click.pass_context
def cli(ctx, **kwargs):
return True
@cli.command()
@click.option('--command1-option1', type=str)
@click.option('--command1-option2', type=str)
@click.pass_context
def command1(ctx, **kwargs):
"""Add command1."""
ctx.obj['command1_args'] = {}
for k, v in kwargs.items():
ctx.obj['command1_args'][k] = v
return True
@cli.command()
@click.argument('command2-argument1', type=str)
@click.pass_context
def command2(ctx, **kwargs):
"""Add command2."""
print(ctx.obj)
print(kwargs)
return True
if __name__ == '__main__':
cli(obj={})
I have already looked into forwarding unknown options like in this question but the problem is that I have to call (chanin) other commands after the first one which have to be asserted e.g. this call has to work but with arbitrary options for command1
:
$python cli.py command1 --command1-option1 foo --command1-option2 bar command2 'hello'
So how can I add unspecified named options to a single command and call (chain) another one at the same time (after it)?
The custom class found here, can be adapted to your case.
To use the custom class, just use the cls parameter to the click.command() decorator like:
@cli.command(cls=AcceptAllCommand)
@click.pass_context
def command1(ctx, **kwargs):
"""Add command1."""
...
import click
class AcceptAllCommand(click.Command):
def make_parser(self, ctx):
"""Hook 'make_parser' and allow the opt dict to find any option"""
parser = super(AcceptAllCommand, self).make_parser(ctx)
command = self
class AcceptAllDict(dict):
def __contains__(self, item):
"""If the parser does no know this option, add it"""
if not super(AcceptAllDict, self).__contains__(item):
# create an option name
name = item.lstrip('-')
# add the option to our command
click.option(item)(command)
# get the option instance from the command
option = command.params[-1]
# add the option instance to the parser
parser.add_option(
[item], name.replace('-', '_'), obj=option)
return True
# set the parser options to our dict
parser._short_opt = AcceptAllDict(parser._short_opt)
parser._long_opt = AcceptAllDict(parser._long_opt)
return parser
@click.group(chain=True)
@click.pass_context
def cli(ctx, **kwargs):
""""""
@cli.command(cls=AcceptAllCommand)
@click.pass_context
def command1(ctx, **kwargs):
"""Add command1."""
ctx.obj['command1_args'] = {}
for k, v in kwargs.items():
ctx.obj['command1_args'][k] = v
@cli.command()
@click.argument('command2-argument1', type=str)
@click.pass_context
def command2(ctx, **kwargs):
"""Add command2."""
print(ctx.obj)
print(kwargs)
if __name__ == "__main__":
commands = (
"command1 --cmd1-opt1 foo --cmd1-opt2 bar command2 hello",
'--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(), obj={})
except BaseException as exc:
if str(exc) != '0' and \
not isinstance(exc, (click.ClickException, SystemExit)):
raise
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)]
-----------
> command1 --cmd1-opt1 foo --cmd1-opt2 bar command2 hello
{'command1_args': {'cmd1_opt1': 'foo', 'cmd1_opt2': 'bar'}}
{'command2_argument1': 'hello'}
-----------
> --help
Usage: test.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
Options:
--help Show this message and exit.
Commands:
command1 Add command1.
command2 Add command2.