argparse option arg using nargs='?' before positional arg in subparser - python

import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
'--optional',
default=None,
const='some-const',
nargs='?',
help='optional')
subparsers = parser.add_subparsers()
subparser = subparsers.add_parser('subparser')
subparser.add_argument(
'positional',
help='positional')
args = parser.parse_args()
print args
./test.py --optional opt subparser positional
Namespace(optional='opt', positional='positional') <-- works as expected
./test.py --optional subparser positional
usage: test.py [-h] [--optional [OPTIONAL]] {subparser} ...
test.py: error: invalid choice: 'positional' (choose from 'subparser') <-- throws an error
Namespace(optional='some-const', positional='positional') <-- would expect to see this
Above is my simplest test code to demonstrate this problem. I would like to have an optional arg using nargs='?' and const before my positional arg in the subparser. I have read that I can pass the original parser as a parent to the child subparser, but this doesn't solve the problem. I have tried adding add_help=False and conflict_handler='resolve' to the initial parser declaration when I tried that. Can anyone point me in the right direction on this?
Thanks,
Scott

When parsing ./test.py --optional foo bar, argparse sees an optional string (starts with --) followed by two argument strings (no --)
So it starts by processing --optional. It's nargs is a 'greedy ?', so it consumes the foo argument, producing:
Namespace('optional'='foo')
That leaves bar to be consumed as a subcommand argument.
It does not check if foo is a valid subcommand argument.
The same reasoning applies to ./test.py --optional subparser positional.

This throws an error:
.test.py --optional
Because subparser is not optional:
usage: subparser.py [-h] [--optional [OPTIONAL]] {subparser} ...
test.py: error: too few arguments
subparser is getting eaten up and used as OPTIONAL in your second example. I don't understand why, other than argparse isn't figuring out in advance that subparser is a subparser.
This is the closest thing I could make to what you are describing:
import argparse
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument(
'--optional',
nargs='?',
default=None,
const='some-const',
help='optional')
sub_parser = argparse.ArgumentParser(parents=[parent_parser])
sub_parser.add_argument('--subparser', required=True)
args = sub_parser.parse_args()
print args
I think you've uncovered a bug.

Related

Python's argparse show default values for sub command arguments

I wrote the following command-line parses by using argparse that make use of sub-commands.
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
sub_parser = parser.add_subparsers(title='sub commands',
help='sub commands help')
foo = sub_parser.add_parser('foo')
foo.add_argument(
'--a',
action='store',
default='1234',
help='A'
)
parser.parse_args(['foo', '--help'])
When I print the usage help for sub-command foo, I would expect that the default value of the argument --a is shown. But that doesn't happen.
Here the current output:
usage: test_args.py foo [-h] [--a A]
optional arguments:
-h, --help show this help message and exit
--a A A
Process finished with exit code 0
By calling sub-commands foo without argument a, the default value is used. So, why isn't also the default value shown inside the usage output? Is that a bug?
Or do you know how to achieve that?
As suggested by #hpauly, I found a workaround by using (default: %(default)s).
foo = sub_parser.add_parser('foo')
foo.add_argument(
'--a',
action='store',
default='1234',
help='A. (default: %(default)s)'
)
add_parser takes the same arguments as ArgumentParser, so you can just do formatter_class=argparse.ArgumentDefaultsHelpFormatter to set the default argument formatter for the subparser.

Can I add a ArgumentParser to parse a subcommand? [duplicate]

I need to implement a command line interface in which the program accepts subcommands.
For example, if the program is called “foo”, the CLI would look like
foo cmd1 <cmd1-options>
foo cmd2
foo cmd3 <cmd3-options>
cmd1 and cmd3 must be used with at least one of their options and the three cmd* arguments are always exclusive.
I am trying to use subparsers in argparse, but with no success for the moment. The problem is with cmd2, that has no arguments:
if I try to add the subparser entry with no arguments, the namespace returned by parse_args will not contain any information telling me that this option was selected (see the example below).
if I try to add cmd2 as an argument to the parser (not the subparser), then argparse will expect that the cmd2 argument will be followed by any of the subparsers arguments.
Is there a simple way to achieve this with argparse? The use case should be quite common…
Here follows what I have attempted so far that is closer to what I need:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')
parser_2 = subparsers.add_parser(cmd2, help='...')
parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')
args = parser.parse_args()
First of all subparsers are never inserted in the namespace. In the example you posted if you try to run the script as:
$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1')
where test_args.py contain the code you provided (with the import argparse at the beginning and print(args) at the end).
Note that there is no mention to cmd1 only to its argument. This is by design.
As pointed out in the comments you can add that information passing the dest argument to the add_subparsers call.
The usual way to handle these circumstances is to use the set_defaults method of the subparsers:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')
parser_1.set_defaults(parser1=True)
parser_2 = subparsers.add_parser('cmd2', help='...')
parser_2.set_defaults(parser2=True)
parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')
parser_3.set_defaults(parser_3=True)
args = parser.parse_args()
print(args)
Which results in:
$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1', parser1=True)
$python3 test_args.py cmd2
Namespace(parser2=True)
In general different subparser will, most of the time, handle the arguments in completely different ways. The usual pattern is to have different functions to run the different commands and use set_defaults to set a func attribute. When you parse the arguments you simply call that callable:
subparsers = parser.add_subparsers()
parser_1 = subparsers.add_parser(...)
parser_1.set_defaults(func=do_command_one)
parser_k = subparsers.add_parser(...)
parser_k.set_defaults(func=do_command_k)
args = parser.parse_args()
if args.func:
args.func(args)
The subparser identity can be added to the main Namespace if the add_subparsers command is given a dest.
From the documentation:
However, if it is necessary to check the name of the subparser that was invoked, the dest keyword argument to the add_subparsers() call will work:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(dest='subparser_name')
>>> subparser1 = subparsers.add_parser('1')
>>> subparser1.add_argument('-x')
>>> subparser2 = subparsers.add_parser('2')
>>> subparser2.add_argument('y')
>>> parser.parse_args(['2', 'frobble'])
Namespace(subparser_name='2', y='frobble')
By default the dest is argparse.SUPPRESS, which keeps subparsers from adding the name to the namespace.

Python argpass: don't require positional arguments when certain options are specified

Positional parameters with nargs='+' or nargs=<integer> are required, meaning that argparse gives an error if the parameter is not specified at least once. However, no error is given if the user calls the program with the -h|--help option, regardless of whether the positional parameters were specified.
How can I implement custom options like -h that do not require positional parameters to be set, while still requiring positional parameters for all other options?
For example, if let's say my program has the following (or similar) usage text:
usage: PROG [-h] [-o] [-u] positional
How can I implement the following behaviour:
calling PROG with no options and no positionals is an error
calling PROG with -u and no positionals is an error
calling PROG with only positionals is allowed
calling PROG with -h or -o is allowed regardless of whatever else is specified
My code
This meets all requirements except the last one, namely -o still requires the positional parameter to be specified.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional', nargs=1)
parser.add_argument('-o', action='store_true')
parser.add_argument('-u', action='store_true')
args = parser.parse_args()
print(args)
Does argparse have a built-in way to say that positional is not required iff -o is specified, or must this behaviour be implemented manually?
Manual methods
Set nargs='?' or nargs='*' for the positional parameter (i.e. make it optional), and then add the following lines immediately after args = parser.parse_args():
if not args.o and args.positional is None:
parser.error("the following arguments are required: positional")
Or add these lines, courtesy of this answer:
if len(sys.argv) == 1:
# display help message when no args are passed.
parser.print_help()
sys.exit(1)
This simply prints the help message if no options or positional parameters were specified. This behaviour is consistent with many command line programs, e.g. git.
Of course, making positional optional will affect the usage message:
usage: PROG [-h] [-o] [-u] [positional]
If you really don't want this to happen you can override the usage method like this:
parser = argparse.ArgumentParser(usage="%(prog)s [-h] [-o] [-u] positional")

How to handle CLI subcommands with argparse

I need to implement a command line interface in which the program accepts subcommands.
For example, if the program is called “foo”, the CLI would look like
foo cmd1 <cmd1-options>
foo cmd2
foo cmd3 <cmd3-options>
cmd1 and cmd3 must be used with at least one of their options and the three cmd* arguments are always exclusive.
I am trying to use subparsers in argparse, but with no success for the moment. The problem is with cmd2, that has no arguments:
if I try to add the subparser entry with no arguments, the namespace returned by parse_args will not contain any information telling me that this option was selected (see the example below).
if I try to add cmd2 as an argument to the parser (not the subparser), then argparse will expect that the cmd2 argument will be followed by any of the subparsers arguments.
Is there a simple way to achieve this with argparse? The use case should be quite common…
Here follows what I have attempted so far that is closer to what I need:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')
parser_2 = subparsers.add_parser(cmd2, help='...')
parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')
args = parser.parse_args()
First of all subparsers are never inserted in the namespace. In the example you posted if you try to run the script as:
$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1')
where test_args.py contain the code you provided (with the import argparse at the beginning and print(args) at the end).
Note that there is no mention to cmd1 only to its argument. This is by design.
As pointed out in the comments you can add that information passing the dest argument to the add_subparsers call.
The usual way to handle these circumstances is to use the set_defaults method of the subparsers:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')
parser_1.set_defaults(parser1=True)
parser_2 = subparsers.add_parser('cmd2', help='...')
parser_2.set_defaults(parser2=True)
parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')
parser_3.set_defaults(parser_3=True)
args = parser.parse_args()
print(args)
Which results in:
$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1', parser1=True)
$python3 test_args.py cmd2
Namespace(parser2=True)
In general different subparser will, most of the time, handle the arguments in completely different ways. The usual pattern is to have different functions to run the different commands and use set_defaults to set a func attribute. When you parse the arguments you simply call that callable:
subparsers = parser.add_subparsers()
parser_1 = subparsers.add_parser(...)
parser_1.set_defaults(func=do_command_one)
parser_k = subparsers.add_parser(...)
parser_k.set_defaults(func=do_command_k)
args = parser.parse_args()
if args.func:
args.func(args)
The subparser identity can be added to the main Namespace if the add_subparsers command is given a dest.
From the documentation:
However, if it is necessary to check the name of the subparser that was invoked, the dest keyword argument to the add_subparsers() call will work:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(dest='subparser_name')
>>> subparser1 = subparsers.add_parser('1')
>>> subparser1.add_argument('-x')
>>> subparser2 = subparsers.add_parser('2')
>>> subparser2.add_argument('y')
>>> parser.parse_args(['2', 'frobble'])
Namespace(subparser_name='2', y='frobble')
By default the dest is argparse.SUPPRESS, which keeps subparsers from adding the name to the namespace.

Creating mutually inclusive positional arguments with argparse

I'm trying to build a command line interface with Python's argparse module. I want two positional arguments where one depends on the other (mutually inclusive). Here is what I want:
prog [arg1 [arg2]]
Here's what I have so far:
prog [arg1] [arg2]
Which is produced by:
parser = argparse.ArgumentParser()
parser.add_argument('arg1', nargs='?')
parser.add_argument('arg2', nargs='?')
How do I get from there to having a mutually inclusive arg2?
Module argparse doesn't have options for creating mutually inclusive arguments.
However it's simple to write it by yourself.
Start with adding both arguments as optional:
parser.add_argument('arg1', nargs='?')
parser.add_argument('arg2', nargs='?')
After parsing arguments check if arg1 is set and arg2 is not:
args = parser.parse_args()
if args.arg1 and not args.arg2:
(this may be more tricky if you change default value from None for not used arguments to something different)
Then use parser.error() function to display normal argparse error message:
parser.error('the following arguments are required: arg2')
Finally change usage: message to show that arg2 depends on arg1:
parser = argparse.ArgumentParser(usage='%(prog)s [arg1 [arg2]]')
A complete script:
import argparse
parser = argparse.ArgumentParser(usage='%(prog)s [arg1 [arg2]]')
parser.add_argument('arg1', nargs='?')
parser.add_argument('arg2', nargs='?')
args = parser.parse_args()
if args.arg1 and not args.arg2:
parser.error('the following arguments are required: arg2')
You can do something similar to this using sub_parsers.
Here are the docs and examples:
http://docs.python.org/2/library/argparse.html#sub-commands

Categories

Resources