I have a python script that takes in an optional positional argument and has a few subcommands. Some of these subcommands require the positional argument, some don't. The problem I have appears when I try to use a subcommand that does not require the positional argument. Consider the following test file:
import argparse
argp = argparse.ArgumentParser()
argp.add_argument('inputfile', type=str, nargs='?',
help='input file to process')
argp.add_argument('--main_opt1', type=str,
help='global option')
subp = argp.add_subparsers(title='subcommands',
dest='parser_name',
help='additional help',
metavar="<command>")
tmpp = subp.add_parser('command1', help='command1 help')
tmpp.add_argument('pos_arg1', type=str,
help='positional argument')
print repr(argp.parse_args())
When I try to use the subcommand command1
with the first argument everything goes well.
macbook-pro:~ jmlopez$ python pytest.py filename command1 otherarg
Namespace(inputfile='filename', main_opt1=None, parser_name='command1', pos_arg1='otherarg')
But now let us assume that command1
doesn't need the first positional argument.
macbook-pro:~ jmlopez$ python pytest.py command1 otherarg
usage: pytest.py [-h] [--main_opt1 MAIN_OPT1] [inputfile] <command> ...
pytest.py: error: argument <command>: invalid choice: 'otherarg' (choose from 'command1')
I was somehow expecting inputfile
to be set to None
. Is there any way that argparse
can predict that command1
is actually a subcommand and thus inputfile
should be set to None?
you need to tell the parser that the first argument is different type. try adding flags option and default
None
value like this:now, you need to add
-i
before the inputfile argument, but it will work fine.and
To
argp
the subparser argument looks just like another positional, one that takes choices (the names of the subparsers). Alsoargp
knows nothing aboutpos_arg1
. That's intmpp
's list of arguments.When
argp
seesfilename command1 otherarg
,filename
andcommand1
satisfy its 2 positionals.otherarg
is then passed on thetmpp
.With
command1 otherarg
, again 2 strings, 2argp
positionals.command
is assigned toinputfile
. There's no logic to backtrack and saycommand1
fitssubcommands
better, or that `tmpp' needs one of those strings.You could change the 1st positional to an optional,
--inputfile
.Or you could
inputfile
another positional oftmpp
. If a number of the subparsers need it it, consider usingparents
.argparse
isn't a smart as you, and can't 'think ahead' or 'backtrack'. If it appears to do something smart it's because it usesre
pattern matching to handlenargs
values (e.g. ?, *, +).EDIT
One way to 'trick' argparse into recognizing the first positional as the subparser is to insert an optional after it. With
command1 -b xxx otherarg
,-b xxx
breaks up the list of positional strings, so onlycommand1
is matched againstinputfile
andsubcommands
.The issue here is how
argparse
handles postionals with variablenargs
. The fact that the 2nd positional is a subparser is not important. Whileargparse
allows variable length positionals in any order, how it handles them can be confusing. It's easier to predict whatargparse
will do if there is only one such positional, and it occurs at the end.