1

I'm trying to create a command like

prog [-h] [-i ID [ID ...]] | -x [SOMETHING] 
     {cmd1,cmd2,cmd3}...

So basically at the top level I have a parser that has a mutual exlusive group for the -i and -x options, and then following those (and possibly other) options, I have a command that I want to run. Each command has their own set of options that they use. I can get the commands working fine with the add_subparsers(), but the problem I'm running into is when I try to add an argument to the root parser that has nargs='+'. When I do that, it slurps up all of the arguments for -i thinking that the command is an argument and not an ID.

Is there a way around this? It seems like it would have to look through the arguments to -i looking for a command word and then tell argparse that it should resume parsing at that point.

FuriousGeorge
  • 4,561
  • 5
  • 29
  • 52
  • can you show us a [mcve] / your code if not too big? – Jean-François Fabre Jan 19 '17 at 12:53
  • 1
    Checkout [Click](http://click.pocoo.org/5/) for building CLIs with python and save yourself the headache. – garnertb Jan 19 '17 at 13:13
  • @garnertb +1. Can confirm, `click` is great and handles subcommands really well. – Tagc Jan 19 '17 at 20:48
  • I ran into the same issue and checked out `click` because of the comments above. But click is not a solution for this situation. It has exactly the same issue: https://github.com/pallets/click/issues/1153 – mahkitah Aug 01 '23 at 12:38

1 Answers1

3

I had to read your description several times, but I think this is the problem:

prog -i id1 id2 cmd1 -foo 3 ....

and it gives some sort of warning about not finding {cmd1,cmd2,cmd3}. The exact error may differ because in some versions subparsers aren't actually required.

In any case, the arguments to -i are ['id1','id2','cmd1'], everything up to the next - flag. To the main parser, the subparsers argument is just another positional one (with choices). When allocating strings to -i it does not check whether the string matches one of the cmds. It just looks at whether it starts with - or not.

The only way you can use an nargs='+' (or '*') in the context is to include some other flagged argument, e.g.

prog -i id1 id2 -x 3 cmd1 --foo ...

I realize that goes against your mutually_exclusive group.

The basic point is non flag strings are allocated based on position, not value. For a variable nargs you have to have some sort of explicit list terminator.

From the sidebar

Argparse nargs="+" is eating positional argument

It's similar except that your next positional is the subparsers cmd.

==============

A positional with '+' will work right before a subparsers cmd

usage: prog [-h] foo [foo ...] {cmd1,cmd2} ...

In [160]: p1.parse_args('1 22 3 cmd1'.split())
Out[160]: Namespace(cmd='cmd1', foo=['1', '22', '3'])

But that's because strings for foo and cmd are allocated with one regex pattern test.

In

usage: prog [-h] [--bar BAR [BAR ...]] {cmd1,cmd2} ...

strings are allocated to bar without reference to the needs of the following positional, cmd. As shown in the suggested patches for http://bugs.python.org/issue9338, changing this behavior is not a trivial change. It requires an added look-ahead trial-and-error loop.

Community
  • 1
  • 1
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • 1
    As a corollary to this: `nargs='+'` generally does not play well with others. Either use a single-argument multiple times (`-i id1 -i id2 cmd1`) or define the option to take a single parseable argument (`-i id1,id2 cmd1`). – chepner Jan 19 '17 at 17:41
  • Yah, that's the simple solution that I came up with, but it's not ideal. I'd like it to be smart enough to know that the commands are options and continue parsing without having to know the "secret sauce" of doing comma seperated list, --, etc. Ideally, the parser would inspect id2 and realize that it is a command and parse as such. Essentially I want to do a "peek next id. If not in commands, append to ids, else parse as command" – FuriousGeorge Jan 19 '17 at 20:30
  • That's contrary to the basic parsing method of `argparse`. `optparse` can do something like what you want. But `argparse` works entirely from position and flag strings. It does not 'look-ahead' to see whether the next string fits some value criteria. It allocates strings to an Action first, and then runs `type` and `choices` tests, not the other way around. – hpaulj Jan 19 '17 at 20:45
  • I added a link to a bug issue that tries to implement a kind of look-ahead. – hpaulj Jan 19 '17 at 22:51