I have a class that gets initialized with a previously unknown number of arguments and I want it to be done on CLI using Python’s click
package. My issue is that I can’t manage to initialize it and run a click
command:
$ python mycode.py arg1 arg2 ... argN click_command
Setting a defined number of arguments, like nargs=5
, solves the issue of missing command but obligates me to input 5 arguments before my command. With variadic arguments like nargs=-1
, click
doesn’t recognize click_command
as a command.
How can I input n-many arguments, and then run the command using click
?
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.argument('myargs', nargs=-1) @click.pass_context def main(ctx, myargs): ctx.obj = Foo(myargs) print("arguments: ", myargs) @main.command() @pass_foo def log(foo): foo.log() main()
I expect to be able to run a click
command after passing n-many args to my Foo()
class, so I can initialize it and run its log()
method as a CLI command, but the output is:
Error: Missing command
Advertisement
Answer
I am not entirely sure what you are trying to do is the best way to approach this problem. I would think that placing the variadic arguments after the command would be a bit more logical, and would definitely more align with the way click works. But, you can do what you are after with this:
Custom Class:
class CommandAfterArgs(click.Group): def parse_args(self, ctx, args): parsed_args = super(CommandAfterArgs, self).parse_args(ctx, args) possible_command = ctx.params['myargs'][-1] if possible_command in self.commands: ctx.protected_args = [possible_command] ctx.params['myargs'] = ctx.params['myargs'][:-1] elif possible_command in ('-h', '--help'): if len(ctx.params['myargs']) > 1 and ctx.params['myargs'][-2] in self.commands: ctx.protected_args = [ctx.params['myargs'][-2]] parsed_args = ['--help'] ctx.params['myargs'] = ctx.params['myargs'][:-2] ctx.args = [possible_command] return parsed_args
Using Custom Class:
Then to use the custom class, pass it as the cls
argument to the group decorator like:
@click.group(cls=CommandAfterArgs) @click.argument('myargs', nargs=-1) def main(myargs): ...
Test Code:
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(cls=CommandAfterArgs) @click.argument('myargs', nargs=-1) @click.pass_context def main(ctx, myargs): ctx.obj = Foo(*myargs) print("arguments: ", myargs) @main.command() @pass_foo def log(foo): foo.log() if __name__ == "__main__": commands = ( 'arg1 arg2 log', 'log --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('-----------') 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
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)] ----------- > arg1 arg2 log arguments: ('arg1', 'arg2') self.args: ('arg1', 'arg2') ----------- > log --help arguments: () Usage: test.py log [OPTIONS] Options: --help Show this message and exit. ----------- > --help Usage: test.py [OPTIONS] [MYARGS]... COMMAND [ARGS]... Options: --help Show this message and exit. Commands: log