1

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)?

0

1 Answer 1

1

The custom class found here, can be adapted to your case.

Using the Custom Class:

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."""
    ...

Test Code:

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

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

1 Comment

Perfect. Thanks a lot!

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.