Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call a click command from code

Tags:

I have a function which is wrapped as a command using click. So it looks like this:

@click.command() @click.option('-w', '--width', type=int, help="Some helping message", default=0) [... some other options ...] def app(width, [... some other option arguments...]):     [... function code...] 

I have different use cases for this function. Sometimes, calling it through the command line is fine, but sometime I would also like to call directly the function

from file_name import app width = 45 app(45, [... other arguments ...])  

How can we do that? How can we call a function that has been wrapped as a command using click? I found this related post, but it is not clear to me how to adapt it to my case (i.e., build a Context class from scratch and use it outside of a click command function).

EDIT: I should have mentioned: I cannot (easily) modify the package that contains the function to call. So the solution I am looking for is how to deal with it from the caller side.

like image 469
Christian O'Reilly Avatar asked Feb 05 '18 09:02

Christian O'Reilly


People also ask

How do you call a click function in Python?

You can also call a Click function using the callback member function under some conditions. As per this GitHub issue: Assuming you know the given command is a direct wrapper for a function you wrote (and not a group or other type of command), you can get at the function with command. callback.

What is click command?

Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It's the “Command Line Interface Creation Kit”. It's highly configurable but comes with sensible defaults out of the box.

What is click echo in Python?

Click's echo() function will automatically strip ANSI color codes if the stream is not connected to a terminal. the echo() function will transparently connect to the terminal on Windows and translate ANSI codes to terminal API calls.


1 Answers

You can call a click command function from regular code by reconstructing the command line from parameters. Using your example it could look somthing like this:

call_click_command(app, width, [... other arguments ...]) 

Code:

def call_click_command(cmd, *args, **kwargs):     """ Wrapper to call a click command      :param cmd: click cli command function to call      :param args: arguments to pass to the function      :param kwargs: keywrod arguments to pass to the function      :return: None      """      # Get positional arguments from args     arg_values = {c.name: a for a, c in zip(args, cmd.params)}     args_needed = {c.name: c for c in cmd.params                    if c.name not in arg_values}      # build and check opts list from kwargs     opts = {a.name: a for a in cmd.params if isinstance(a, click.Option)}     for name in kwargs:         if name in opts:             arg_values[name] = kwargs[name]         else:             if name in args_needed:                 arg_values[name] = kwargs[name]                 del args_needed[name]             else:                 raise click.BadParameter(                     "Unknown keyword argument '{}'".format(name))       # check positional arguments list     for arg in (a for a in cmd.params if isinstance(a, click.Argument)):         if arg.name not in arg_values:             raise click.BadParameter("Missing required positional"                                      "parameter '{}'".format(arg.name))      # build parameter lists     opts_list = sum(         [[o.opts[0], str(arg_values[n])] for n, o in opts.items()], [])     args_list = [str(v) for n, v in arg_values.items() if n not in opts]      # call the command     cmd(opts_list + args_list) 

How does this work?

This works because click is a well designed OO framework. The @click.Command object can be introspected to determine what parameters it is expecting. Then a command line can be constructed that will look like the command line that click is expecting.

Test Code:

import click  @click.command() @click.option('-w', '--width', type=int, default=0) @click.option('--option2') @click.argument('argument') def app(width, option2, argument):     click.echo("params: {} {} {}".format(width, option2, argument))     assert width == 3     assert option2 == '4'     assert argument == 'arg'   width = 3 option2 = 4 argument = 'arg'  if __name__ == "__main__":     commands = (         (width, option2, argument, {}),         (width, option2, dict(argument=argument)),         (width, dict(option2=option2, argument=argument)),         (dict(width=width, option2=option2, argument=argument),),     )      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('> {}'.format(cmd))             time.sleep(0.1)             call_click_command(app, *cmd[:-1], **cmd[-1])          except BaseException as exc:             if str(exc) != '0' and \                     not isinstance(exc, (click.ClickException, SystemExit)):                 raise 

Test 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)] ----------- > (3, 4, 'arg', {}) params: 3 4 arg ----------- > (3, 4, {'argument': 'arg'}) params: 3 4 arg ----------- > (3, {'option2': 4, 'argument': 'arg'}) params: 3 4 arg ----------- > ({'width': 3, 'option2': 4, 'argument': 'arg'},) params: 3 4 arg 
like image 195
Stephen Rauch Avatar answered Sep 21 '22 02:09

Stephen Rauch