1

I have to make a parser with command line arguments like this

python function.py -v 1 name1 -d abc xyz foo bar name2 -i 3 -p abc xyz

I did this:

import argparse
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('-v', type=int, help='number')

subparsers = parser.add_subparsers(help='sub-command help', dest="command")
parser_a = subparsers.add_parser('name1', help="name1 help")
parser_a.add_argument("-d", help="list", nargs="+")

parser_b = subparsers.add_parser('name2', help="name2 help")
parser_b.add_argument("-p", help="list2", nargs='+')
parser_b.add_argument("-i", help="number inside name2", type=int)

When I run this:

parser.parse_args('-v 1 name1 -d abc xyz foo bar name2 -i 3 -p abc xyz'.split())

It results in:

usage: PROG [-h] [-v V] {name1,name2} ...
PROG: error: unrecognized arguments: -i 3 -p abc xyz

And when running this:

parser.parse_args('-v 1 name1 -d abc xyz foo bar name2'.split())

It results in this:

Namespace(command='name1', d=['abc', 'xyz', 'foo', 'bar', 'name2'], v=1)

I want it to be something like this:

Namespace(command='name1', d=['abc', 'xyz', 'foo', 'bar'], command='name2', p=['abc', 'xyz'], i=3, v=1)

How do I do it?

Sachin Kelkar
  • 1,282
  • 2
  • 11
  • 16
  • Two problems: only one subparser can be invoked at a time (as the first positional argument for the main script), and even if you could invoke multiple ones, theres no way to tell where the arguments to the `-d` option end. Is `name2` a positional argument or an argument for `-d`? – chepner Mar 09 '16 at 18:07
  • name2 is supposed to be a positional argument. If it is not possible this way, is there any other way this can be done? – Sachin Kelkar Mar 09 '16 at 18:09
  • You could use this workaround: https://blog.elsdoerfer.name/2010/08/08/python-argparse-combine-nargs-with-subparsers/ – iroln Feb 15 '19 at 08:57

2 Answers2

1

As you note

In [24]: parser.parse_args('-v 1 name1 -d abc xyz foo bar name2'.split())
Out[24]: Namespace(command='name1', d=['abc', 'xyz', 'foo', 'bar', 'name2'], v=1)

name2 is seen as one of the arguments to parser_a -d argument

The rest ' -i 3 -p abc xyz' is not accepted because -i is understood to be an optional flag.

Effectively you are running:

In [28]: parser_a.parse_args('-d abc xyz foo bar name3 -i 3 -p abc xyz'.split())
usage: PROG name1 [-h] [-d D [D ...]]
PROG name1: error: unrecognized arguments: -i 3 -p abc xyz

The main parser has a positional argument that accepts 2 choices, name1, and name2. When it encounters name1, it passes the rest of the arguments to parser_a.

The net effect is that argparse accepts only one subcommand.

I have discussed some ways around this in previous SO questions. The sidebar found one:

Python argparser repeat subparse

(that may be a good enough fit to mark this question as a duplicate).


If I add a positional to parser_a; and use -- to separate bar and name2 (meaning, all that follows is positional)

In [32]: parser_a.add_argument('extra',nargs='*')
Out[32]: _StoreAction(option_strings=[], dest='extra', nargs='*', const=None, default=None, type=None, choices=None, help=None, metavar=None)

In [33]: parser.parse_args('-v 1 name1 -d abc xyz foo bar -- name2 -i 3 -p abc xyz'.split())
Out[33]: Namespace(command='name1', d=['abc', 'xyz', 'foo', 'bar'], extra=['name2', '-i', '3', '-p', 'abc', 'xyz'], v=1)

I can then pass the extra string (minus name2) to parser_b:

In [34]: args=_
In [36]: parser_b.parse_args(args.extra[1:])
Out[36]: Namespace(i=3, p=['abc', 'xyz'])

Admittedly this a convoluted solution, but the ones I suggested a couple of years ago aren't much better. And hopefully it helps clarify what is going on.


Do you really need to invoke 2 subcommands at a time. argparse would be a lot simpler if you could do:

python function.py -v 1 name1 -d abc xyz foo bar
python function.py -v 1 name2 -i 3 -p abc xyz

In other words, call the script twice. That is easy if each call to function.py performs an independent self contained action - with any connection between the two calls embodied in shared files or database. That is how subcommands are often used. Trying to put multiple subcommands in one call does not save typing.

Community
  • 1
  • 1
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • Actually the original command was: ```python function.py -v 1 name1 name2``` where name1, name2 are the sub-modules to run. Now I want those to accept command line arguments as well. – Sachin Kelkar Mar 10 '16 at 03:50
0

I think you are doing something wrong. First you should pass positional arguments, and then keyword arguments, e.g.

python myapp.py arg1 arg2 --kwarg1=1 --kwarg2 val1 --kwarg2 val2 
vitalii
  • 595
  • 4
  • 9
  • argparse is more flexible; it can handle mixed positionals and flagged ones. But in this case he's also using `subparsers` when introduces its own parsing constraints. – hpaulj Mar 09 '16 at 20:21
  • @hpaulj i mean that subparsers is overkill in this case – vitalii Mar 09 '16 at 20:25