Search code examples
pythoncommand-line-interfaceargsvariadicpython-click

Instantiate Foo() class on main click group command by running subcomand with Foo() arguments


I want to run a click subcommand with variadic arguments that are going to be used to instantiate a class Foo(*args) on main() group command in order to create an instance of Foo() to be used by its subcommands so that it aligns with the way click works:

$ python foo.py subcommand arg1 arg2 ... argN

This question is based on my initial question and @StephenRauch answer.

import click

class Foo(object):
    def __init__(self, *args):
        self.args = args

    def log(self):
        print('self.args:', self.args)

pass_foo = click.make_pass_decorator(Foo)

@click.group()
@click.pass_context
def main(ctx):
    magic_to_get_myargs()

    ctx.obj = Foo(myargs)
    print("main:\n", "ctx.obj.args:", ctx.obj.args)

@main.command()
@click.argument('myargs', nargs=-1)
@pass_foo
def run(foo, myargs):
    magic_to_send_myargs()

    print("run:\n", 'foo.args:', foo.args)
    foo.log()

main()

I expect to initialize Foo class on main group command by running a subcommand and get back its object to use it within subcommand.


Solution

  • Based on @StephenRauch in a similar answer I have managed to find a solution by myself.

    Code

    import click
    
    class MyGroup(click.Group):
        def invoke(self, ctx):
            ctx.obj = tuple(ctx.args)
            super(MyGroup, self).invoke(ctx)
    
    class Foo(object):
        def __init__(self, *args):
            self.args = args
    
        def log(self):
            print('self.args:', self.args)
    
    pass_foo = click.make_pass_decorator(Foo)
    
    @click.group(cls=MyGroup)
    @click.pass_context
    def main(ctx):
        ctx.obj = Foo(*ctx.obj)
        print("main:\n", "ctx.obj.args:", ctx.obj.args)
    
    @main.command()
    @pass_foo
    @click.argument('myargs', nargs=-1)
    def run(foo, myargs):
        print("run:\n", 'foo.args:', foo.args)
        foo.log()
    
    if __name__ == "__main__":
        commands = (
            'run arg1 arg2 arg3',
            'run --help',
            '--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("\n", '-' * 50)
                print('> ' + cmd)
                time.sleep(0.1)
                main(cmd.split())
    
            except BaseException as exc:
                if str(exc) != '0' and \
                        not isinstance(exc, (click.ClickException, SystemExit)):
                    raise
    

    Result

    Click Version: 7.0
    Python Version: 3.7.2 (default, Dec 29 2018, 06:19:36) 
    [GCC 7.3.0]
    
     --------------------------------------------------
    > run arg1 arg2 arg3
    main:
     ctx.obj.args: ('arg1', 'arg2', 'arg3')
    run:
     foo.args: ('arg1', 'arg2', 'arg3')
    self.args: ('arg1', 'arg2', 'arg3')
    
     --------------------------------------------------
    > run --help
    main:
     ctx.obj.args: ('--help',)
    Usage: test3.py run [OPTIONS] [MYARGS]...
    
    Options:
      --help  Show this message and exit.
    
     --------------------------------------------------
    > --help
    Usage: test3.py [OPTIONS] COMMAND [ARGS]...
    
    Options:
      --help  Show this message and exit.
    
    Commands:
      run