Search code examples
pythonargparsepython-click

How can I use click to parse a string for arguments?


Say I have a list of strings containing arguments and options, with argparse, I’m able to parse this list using the parse_args function into an object, as follows:

import argparse

extra_params = [‘—sum’, ‘7’, ‘-1’, ‘42’]

parser=argparse.ArgumentParser(description=“argparse docs example”)
parser.add_argument(‘integers’, metavar=‘N’, type=int, nargs=‘+’,
                    help=‘an integer for the accumulator’)
parser.add_argument(‘—sum’, dest=‘accumulate’, action=‘store_const’,
                    const=sum, default=max,
                    help=‘sum the integers (default: find the max)’)
parsed_object=parser.parse_args(extra_params)

Here, argparse has parsed a provided iterable of strings. Can one use click to also parse a provided iterable of strings?

I’ve searched through the API documentation for click and it appears that there’s a parse_args function within the *Command set of classes but don’t see anything in the docs around how I can do this. I’ve tried instantiating BaseCommand as well as Command but not sure how to get parse_args working without a correct context.

For broader context, this question is a result of having built a launcher application that end users use as a scaffold to launch their own applications. Here, the launcher consumes a number of arguments for which click decorators work perfectly. Unknown arguments can be handled as shown in the documentation here. This launcher then calls an end-user provided callable with these unparsed parameters. Click leaves unparsed parameters as a tuple of strings. How would the end-user, in this situation, be able to use Click to parse the argument's they're interested in? Here's a snippet to illustrate the issue:

import click
from typing import Tuple

@click.command(name="TestLauncher", context_settings={
  "ignore_unknown_options": True
})
@click.option('--uri', '-u',
  help="URI for the server")
@click.argument('unprocessed_args', nargs=-1,
  type=click.UNPROCESSED)
def main(uri: str, unprocessed_args: Tuple[str, ...]) -> None:
    print(f"Was passed a URI of {uri}")
    print(f"Additional args are {unprocessed_args}")

    child_function(unprocessed_args)

def child_function(unprocessed_args: Tuple[str, ...]) -> None:
    # How do I get Click to parse the provided args for me?
    pass

if __name__ == "__main__":
    # pylint: disable=no-value-for-parameter, unexpected-keyword-arg
    main()

Running this from the command line:

python3 so_test.py --uri test.com --prog-arg 10
Was passed a URI of test.com
Additional args are ('--prog-arg', '10')

Solution

  • Reviewing the comments and my ensuing edit, made me think that simply applying the click decorators to the child function may work. Indeed it seems to but I don't entirely know why.

    import click
    from typing import Tuple
    
    @click.command(name="TestLauncher", context_settings={
      "ignore_unknown_options": True
    })
    @click.option('--uri', '-u',
      help="URI for the server")
    @click.argument('unprocessed_args', nargs=-1,
      type=click.UNPROCESSED)
    def main(uri: str, unprocessed_args: Tuple[str, ...]) -> None:
        print(f"Was passed a URI of {uri}")
        print(f"Additional args are {unprocessed_args}")
    
        child_function(unprocessed_args)
    
    @click.command()
    @click.option('--prog-arg')
    def child_function(prog_arg: str) -> None:
        # How do I get Click to parse the provided args for me?
        print(f"Child function passed: {prog_arg}")
    
    if __name__ == "__main__":
        # pylint: disable=no-value-for-parameter, unexpected-keyword-arg
        main()
    
    python3 so_test.py --uri test.com --prog-arg 10
    Was passed a URI of test.com
    Additional args are ('--prog-arg', '10')
    Child function passed: 10