I am implementing a command line program which has interface like this:
cmd [GLOBAL_OPTIONS] {command [COMMAND_OPTS]} [{command [COMMAND_OPTS]} ...]
I have gone through the argparse documentation. I can implement GLOBAL_OPTIONS
as optional argument using add_argument
in argparse
. And the {command [COMMAND_OPTS]}
using Sub-commands.
From the documentation it seems I can have only one sub-command. But as you can see I have to implement one or more sub-commands. What is the best way to parse such command line arguments useing argparse
?
You could try arghandler. This is an extension to argparse with explicit support for subcommands.
The solution provide by @Vikas fails for subcommand-specific optional arguments, but the approach is valid. Here is an improved version:
This uses
parse_known_args
instead ofparse_args
.parse_args
aborts as soon as a argument unknown to the current subparser is encountered,parse_known_args
returns them as a second value in the returned tuple. In this approach, the remaining arguments are fed again to the parser. So for each command, a new Namespace is created.Note that in this basic example, all global options are added to the first options Namespace only, not to the subsequent Namespaces.
This approach works fine for most situations, but has three important limitations:
myprog.py command_a --foo=bar command_b --foo=bar
.nargs='?'
ornargs='+'
ornargs='*'
).PROG --foo command_b command_a --baz Z 12
with the above code,--baz Z
will be consumed bycommand_b
, not bycommand_a
.These limitations are a direct limitation of argparse. Here is a simple example that shows the limitations of argparse -even when using a single subcommand-:
This will raise the
error: argument subparser_name: invalid choice: '42' (choose from 'command_a', 'command_b')
.The cause is that the internal method
argparse.ArgParser._parse_known_args()
it is too greedy and assumes thatcommand_a
is the value of the optionalspam
argument. In particular, when 'splitting' up optional and positional arguments,_parse_known_args()
does not look at the names of the arugments (likecommand_a
orcommand_b
), but merely where they occur in the argument list. It also assumes that any subcommand will consume all remaining arguments. This limitation ofargparse
also prevents a proper implementation of multi-command subparsers. This unfortunately means that a proper implementation requires a full rewrite of theargparse.ArgParser._parse_known_args()
method, which is 200+ lines of code.Given these limitation, it may be an options to simply revert to a single multiple-choice argument instead of subcommands:
It is even possible to list the different commands in the usage information, see my answer https://stackoverflow.com/a/49999185/428542
You can always split up the command-line yourself (split
sys.argv
on your command names), and then only pass the portion corresponding to the particular command toparse_args
-- You can even use the sameNamespace
using the namespace keyword if you want.Grouping the commandline is easy with
itertools.groupby
:untested