2

I'm trying to write a program that is able to execute multiple sub-commands. The argparse module is very helpful, but I think it is lacking the ability to specify more than one sub-command. For example, if I have the following code:

parser = argparse.ArgumentParser(prog='My Prog')
sub_parsers = parser.add_subparsers()

subcommand_a = sub_parsers.add_parser('subcommand_a', help='a help')
subcommand_a.add_argument('req1', help='required argument 1 help')
subcommand_a.add_argument('--opt1', help='option 1 help')
subcommand_a.add_argument('--opt2', nargs='+', help='option 2 help')

subcommand_b = sub_parsers.add_parser('subcommand_b', help='b help')
subcommand_b.add_argument('req1', help='required argument 1 help')
subcommand_b.add_argument('--opt1', help='option 1 help')
subcommand_b.add_argument('--opt2', help='option 2 help')
subcommand_b.add_argument('--opt3', nargs='+', help='option 3 help')

parser.parse_args()

I cannot specify both subcommand_a and subcommand_b on the command line. I can only do one of them at a time. I'd imagine that this would require a custom action or possibly even subclassing argparse, but I'm not sure where to start. I'd like to be able to call this program like the following:

./prog.py subcommand_a FOO --opt1=bar --opt2 1 2 3 -- subcommand_b BAR --opt1='foo' --opt3 a b c --

Any ideas?

hpaulj
  • 221,503
  • 14
  • 230
  • 353
buggystick
  • 141
  • 1
  • 7
  • With `nargs='+'`, you need `--opt2 1 2 3`, not `--opt2=1 2 3`. The latter asigns `1` to `opt2`, but does nothing with `2 3`. – hpaulj Aug 15 '14 at 02:15
  • You should investigate [docopt](http://docopt.org/). It's a framework that uses your man page to parse arguments and options. It makes it much easier to do more complex argument specifications, makes them easier to support, is implemented in most languages, and you get the man page for free! – whereswalden Aug 15 '14 at 16:42

2 Answers2

2

Your problem is essentially the same as the one asked a few months back

Multiple invocation of the same subcommand in a single command line

That one wanted to call the same subcommand several times, but the issue the same - how to handle more than one subparsers argument.

The solutions there are either to split the command line before passing it in pieces that are parsed separately, or collect the 'unused' pieces from one parsing for using in a second or third.

Here's tweak to your code that returns 2 commands:

import argparse
parser = argparse.ArgumentParser(prog='My Prog')
sub_parsers = parser.add_subparsers(dest='cmd')

subcommand_a = sub_parsers.add_parser('subcommand_a', help='a help')
subcommand_a.add_argument('req1', help='required argument 1 help')
subcommand_a.add_argument('--opt1', help='option 1 help')
subcommand_a.add_argument('--opt2', nargs=3, help='option 2 help')

subcommand_b = sub_parsers.add_parser('subcommand_b', help='b help')
subcommand_b.add_argument('req1', help='required argument 1 help')
subcommand_b.add_argument('--opt1', help='option 1 help')
subcommand_b.add_argument('--opt2', help='option 2 help')
subcommand_b.add_argument('--opt3', nargs=3, help='option 3 help')

argv = "subcommand_a FOO --opt1=bar --opt2 1 2 3 subcommand_b BAR --opt1=foo --opt3 a b c"
rest = argv.split()
while rest:
    [args, rest] = parser.parse_known_args(rest)
    print args
    print rest

which prints:

Namespace(cmd='subcommand_a', opt1='foo', opt2=['1', '2', '3'], req1='FOO')
['subcommand_b', 'BAR', '--opt3', 'a', 'b', 'c']
Namespace(cmd='subcommand_b', opt1=None, opt2=None, opt3=['a', 'b', 'c'], req1='BAR')
[]

I removed the '--' (see the end of my other answer)

I changed opt2 and opt3 to take 3 arguments, not the variable +. With + it can't tell where the list for opt2 ends and the next command begins.

It is also important that the different commands take different optionals (flags). Note that the first opt1 ends up with the second value, 'foo', leaving none for the 2nd command. See the other thread for a discussion of the issues and ways around that.

Community
  • 1
  • 1
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • Thanks hpaulj, I'd like to be able to keep the variable '+' to nargs, so I think that I will need to parse it out and pass the resulting list to parse_known_args. This gets me most of the way though. – buggystick Aug 15 '14 at 23:01
0

I've done some testing and changing the subcommand to other string worked. if you use:

parser = argparse.ArgumentParser(prog='My Prog')
sub_parsers = parser.add_subparsers()

subcommand_a = sub_parsers.add_parser('subcommand_a', help='a help')
subcommand_a.add_argument('req1', help='required argument 1 help')
subcommand_a.add_argument('--opt1', help='option 1 help')
subcommand_a.add_argument('--opt2', nargs='+' help='option 2 help')

subcommand_b = sub_parsers.add_parser('subcommand_b', help='b help')
subcommand_b.add_argument('req1', help='required argument 1 help')
subcommand_b.add_argument('--opt3', help='option 1 help')
subcommand_b.add_argument('--opt4', help='option 2 help')
subcommand_b.add_argument('--opt5', nargs='+', help='option 3 help')

parser.parse_args()

it works.

I've done some research and not found a subcommand with a equal string. A overview of the documentation don't say nothing either. I presume it's a limitation ( by choice ) or a bug. Maybe you can check it in the Source Code Hosting

Cristiano Araujo
  • 1,632
  • 2
  • 21
  • 32
  • 'subcommand with an equal string' - are you talking about the `--opt2=1 2 3` arguments? – hpaulj Aug 15 '14 at 02:32
  • No, with its name. opt1, opt2, opt3. And in sub comand opt1 and opt2. Try chaning those to diferent names. – Cristiano Araujo Aug 15 '14 at 10:35
  • Normally subcommands can have duplicate argument flags. But that's when only one subcommand is given and parsed. Here, they can create confusion. – hpaulj Aug 15 '14 at 15:55
  • I understand the reasoning for changing the names of the subcommand options, but unfortunately that isn't possible for the application I'm working on – buggystick Aug 15 '14 at 23:04