pythonargparse

Argparse: How to accept any number of optional arguments (starting with `-` or `--`)


I'm trying to create a command line tool (let's call it 'X') that wraps another tool (let's call it 'Y').

I handle some cases specially, and add some options of myself, but I want to redirect whatever I don't want to handle to the tool Y.

So far I managed to redirect the arguments that don't come with dashes, for example X Y option1 option2 option3 will just call Y option1 option2 option3. I did this by adding a subparser Y and an argument any to it

Here's the code (x.py):

main_parser = argparse.ArgumentParser()
subparsers = main_parser.add_subparsers(dest="parser_name")

y_subparser = subparsers.add_parser('y')
y_options = y_subparser.add_argument('any', nargs='*')

Then in my handler code, i do this:

args = main_parser.parse_args()
if args.parser_name == 'y':
    command_string = ' '.join(['y'] + sys.argv[2:])
    os.system(command_string)

When I call python x.py y asdf zxcv qwer it works.

When I call python x.py y asdf zxcv qwer -option I get the error x.py: error: unrecognized arguments: -option

I realize that if stuff just gets too complicated with argparse i can always fall back to using sys.argv, but if you know this to be doable, please share.

I've also been looking through the argparse code, which is a little dense, and where it seems that ArgumentParser._parse_known_args does everything (300 lines). But before I go deeper, I thought maybe someone knows how to do this - if not, i'll post my discoveries here if someone else has the same problem.


Solution

  • From the doc of argparse, you can use argparse.REMAINDER :

    >>> parser = argparse.ArgumentParser(prog='PROG')
    >>> parser.add_argument('--foo')
    >>> parser.add_argument('command')
    >>> parser.add_argument('args', nargs=argparse.REMAINDER)
    >>> print(parser.parse_args('--foo B cmd --arg1 XX ZZ'.split()))
    Namespace(args=['--arg1', 'XX', 'ZZ'], command='cmd', foo='B')
    

    This work even with dashed in the subcommand arguments

    >>> parser = argparse.ArgumentParser(prog='PROG')
    >>> parser.add_argument('--foo')
    >>> parser.add_argument('command')
    >>> parser.add_argument('args', nargs=argparse.REMAINDER)
    >>> print(parser.parse_args('--foo B cmd --arg1 XX ZZ --foobar'.split()))
    Namespace(args=['--arg1', 'XX', 'ZZ', '--foobar'], command='cmd', foo='B')
    

    EDIT: this feature is not documented anymore since Py3.9, but still available (at least in Py3.11).