python: argparse with optional command-line arguments - python

I'd like to implement arguments parsing.
./app.py -E [optional arg] -T [optional arg]
The script requires at least one of parameters: -E or -T
What should I pass in parser.add_argument to have such functionality?
UPDATE
For some reason(s) the suggested solution with add_mutually_exclusive_group didn't work, when I added nargs='?' and const= attributes:
parser = argparse.ArgumentParser(prog='PROG')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-F', '--foo', nargs='?', const='/tmp')
group.add_argument('-B', '--bar', nargs='?', const='/tmp')
parser.parse_args([])
Running as script.py -F still throws error:
PROG: error: one of the arguments -F/--foo -B/--bar is required
However, the following workaround helped me:
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('-F', '--foo', nargs='?', const='/tmp')
parser.add_argument('-B', '--bar', nargs='?', const='/tmp')
args = parser.parse_args()
if (not args.foo and not args.bar) or (args.foo and args.bar):
print('Must specify one of -F/-B options.')
sys.exit(1)
if args.foo:
foo_func(arg.foo)
elif args.bar:
bar_func(args.bar)
...

You can make them both optional and check in your code if they are set.
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
parser.add_argument('--bar')
args = parser.parse_args()
if args.foo is None and args.bar is None:
parser.error("please specify at least one of --foo or --bar")
If you want only one of the two arguments to be present, see [add_mutually_exclusive_group] (https://docs.python.org/2/library/argparse.html#mutual-exclusion) with required=True
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> group = parser.add_mutually_exclusive_group(required=True)
>>> group.add_argument('--foo', action='store_true')
>>> group.add_argument('--bar', action='store_false')
>>> parser.parse_args([])
usage: PROG [-h] (--foo | --bar)
PROG: error: one of the arguments --foo --bar is required

Related

How can I remove three-dots at the end of usage line in argparse

Python argparse keep putting space and three-dots ( ...) at the end of usage: line, example: usage: program.sh [-h] command [<options>...] .... Would it be possible to remove them?
Example code:
def helper():
parser = argparse.ArgumentParser(
"program.py",
)
subparsers = parser.add_subparsers(dest="command", metavar="command [<options>...]")
driver = subparsers.add_parser(
"driver", help="Example script")
driver.add_argument("--bn", type=int, default=0, help="Block number to start fetch blocks from")
return parser
Output:
$ ./program.sh --help
usage: program.sh [-h] command [<options>...] ...
Direct answer: you could write your own usage summary:
parser = argparse.ArgumentParser(
"program.py",
usage="usage: %(prog)s [-h] command [<options>...]",
)
However, there is something that does not make sense to me.
The ... is caused by using subparsers. There is usually one subparser per command, for example: 3 commands, i.e. 3 subparsers:
Usage:
myprog cmd1 [options and args specific for cmd1]
myprog cmd2 [different options and args for cmd2]
myprog cmd3 [yet another set of options and args]
And that can be hardly summarized in one line except with:
Usage: myprog command ...
unless you are not using any options and args for the commands, which means there is nothing to parse.
So, if you want to get rid of the trailing ... and still have a valid usage synopsis, you probably do not need subparsers at all.
That usage is produced by the implied nargs of the subparsers argument, 'A...'.
I get the same thing if I create a positional argument with the same nargs:
In [393]: import argparse
In [394]: p = argparse.ArgumentParser()
In [395]: p.add_argument('foo',nargs=argparse.PARSER)
Out[395]: _StoreAction(option_strings=[], dest='foo', nargs='A...', const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [396]: p.print_help()
usage: ipython3 [-h] foo ...
positional arguments:
foo
optional arguments:
-h, --help show this help message and exit
Note the nargs string in Out[395]. add_subparsers creates a positional argument with 'A...' nargs value.
In [403]: p = argparse.ArgumentParser()
In [404]: p.add_subparsers(dest='foo', metavar='FOO')
Out[404]: _SubParsersAction(option_strings=[], dest='foo', nargs='A...', const=None, default=None, type=None, choices={}, help=None, metavar='FOO')
In [405]: p.print_help()
usage: ipython3 [-h] FOO ...
positional arguments:
FOO
optional arguments:
-h, --help show this help message and exit
That nargs takes all the remaining strings while requiring at least one. The add_parser lines add choices to that subparser Action.
In formatting its usage the main parser "knows nothing" about what the sub-parsers do. To it, the subparsers argument is just another positional argument with choices. The same applies when parsing. It just does the suparsers.__call__ with the remaining argv strings. That in turn passes those to the chosen parser.

argparse: unable to get subparser_name after declaring a global argument

I've got a parser with some sub-parsers. I setup a global argument to be used on all subparser. Here's the relevant snippet
parser = argparse.ArgumentParser(prog="my_prog", add_help=False)
parser.add_argument('-d', '--debug', action='store_true', help='debug flag')
subparsers = parser.add_subparsers(dest="subparser_name", help='some help notes')
parser_cmd1 = subparsers.add_parser('cmd1', parents=[parser])
parser_cmd1.add_argument('-f', '-foo', type=str, action=foo, required=False, help='foo command')
parser_cmd2 = subparsers.add_parser('cmd2', parents=[parser])
parser_cmd2.add_argument('-b', '-bar', type=str, action=bar, required=False, help='bar command')
args = parser.parse_args()
parser = args.subparser_name
print(args)
if args.debug:
logging.basicConfig(level=logging.INFO)
if parser == 'cmd1':
if args.foo:
//do foo stuff
if parser == 'cmd2':
if args.bar:
//do bar stuff
So you can a command like such my_prog.py cmd1 -d -f inp_str. Here's the problem: subparser_name is None. The output of print(args) looks kind of like this
Namespace(debug=True, foo="inp_str", subparser_name=None)
Before I added the global debug argument, subparser_name would be the name of the command I ran, i.e. 'cmd1' or 'cmd2'. Now, it's 'None'. Even with the parents=[parser] addition in the subparser creation. How can I fix this? How do I know which command was called?
Split out the common args to a separate ArgumentParser, which is then used as parent for the sub parsers. Also your foo and bar options were specified using -foo and -bar whi should be --foo and --bar. Also you didn't have default values for these so e.g. when -f/--foo wasn't specified args.foo correctly didn't exist.
This works better:
import argparse
common_args = argparse.ArgumentParser(prog="my_prog", add_help=False)
common_args.add_argument('-d', '--debug', action='store_true', help='debug flag')
parser = argparse.ArgumentParser(prog="my_prog", add_help=True)
subparsers = parser.add_subparsers(dest="subparser_name", help='some help notes')
parser_cmd1 = subparsers.add_parser('cmd1', parents=[common_args])
parser_cmd1.add_argument('-f', '--foo', type=str, default='', required=False, help='foo command')
parser_cmd2 = subparsers.add_parser('cmd2', parents=[common_args])
parser_cmd2.add_argument('-b', '--bar', type=str, default='', required=False, help='bar command')
args = parser.parse_args()
parser = args.subparser_name
print(args)
if args.debug:
logging.basicConfig(level=logging.INFO)
if parser == 'cmd1':
if args.foo:
#//do foo stuff
print( f"foo {args.foo}" )
if parser == 'cmd2':
if args.bar:
#//do bar stuff
print( f"bar {args.bar}" )
run with:
args.py cmd1 -f asd
output:
Namespace(subparser_name='cmd1', debug=False, foo='asd')
foo asd
Update:
If you want to be able to use e.g. args.py -d cmd1 then on the creation of parser, specify parents=[common_args]
parser = argparse.ArgumentParser(prog="my_prog", add_help=True, parents=[common_args])
Next time you ask a question ensure you only post code as a minimal reproducible example - i.e. that can be run without adding anything
The subparser's defaults have priority of any values set by the main parser - default or user input. The main does set the subparser_name to 'cmd1', but the subparser changes it back to the default None.
While not evident in your test case, defining debug at both levels has the same problem. The subparser's default overwrites anything set in the main.
In general, it is not a good idea to use the same dest in the main and subparsers. Flags can be the same, but the dest should be different - at least if you want to see anything set by the main.
And using the main parser as a parent to the sub, is just asking for confusion.

Can't figure out how to pass arguments to argparse

I'm going through the docs on the argparse module and I can't figure out how to get the same results as explained in the docs. You can either specify the arguments on the command-line, or it allows you to specify them within parse_args() which is helpful for testing. Here is an example:
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
parser.parse_args('--foo 1'.split())
That is directly from the docs here:
https://docs.python.org/3.6/library/argparse.html#action
It is supposed to output this:
Namespace(foo='1')
But this is what I get:
Namespace(foo=None)
I also tried the following:
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='store_true')
parser.add_argument('--bar', action='store_false')
parser.add_argument('--baz', action='store_false')
parser.parse_args(['--foo', '--bar'])
And that one outputs this:
Namespace(bar=True, baz=True, foo=False)
which is what it's supposed to do. Can anyone tell me what's going on here? Here is my full code that I used to generate the output for both snippets of code shown above:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
parser.parse_args('--foo 1'.split())
args = parser.parse_args()
print(args)
#supposed to be Namespace(foo='1')
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='store_true')
parser.add_argument('--bar', action='store_false')
parser.add_argument('--baz', action='store_false')
parser.parse_args(['--foo', '--bar'])
args = parser.parse_args()
print(args)
#supposed to be Namespace(foo=True, bar=False, baz=True)
I don't know if it makes a difference, but I'm doing this in Spyder 3.1.4 and I'm running Python 3.6.0
UPDATE
Due to some ambiguity in the docs I didn't know how they got from setting the command-line arguments to displaying the output. With the help of #hpaulj I realized all I was doing was displaying the output relative to arg.sysv instead of the specified custom command-line - oops! Here's the corrected code:
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
#added this assignment to args
args = parser.parse_args('--foo 1'.split())
#following line was wrong - removing
#args = parser.parse_args()
print(args)
#supposed to be Namespace(foo='1')
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='store_true')
parser.add_argument('--bar', action='store_false')
parser.add_argument('--baz', action='store_false')
#added this assignment to args
args = parser.parse_args(['--foo', '--bar'])
#following line was wrong - removing
#args = parser.parse_args()
print(args)
#supposed to be Namespace(foo=True, bar=False, baz=True)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
args = parser.parse_args('--foo 1'.split())
print(args)
This will give you your answer. You are trying to parse again thats why it was giving None

python's argument parser printing the argument name in upper case

I am trying to write usage/help for my python script using the argparse library.
This is my sample code:
import argparse
parser = argparse.ArgumentParser(
description='My description')
parser.add_argument(
"-r", "--remote",
help="help message")
parser.print_help()
Output:
usage: [-h] [-r REMOTE]
My description
optional arguments:
-h, --help show this help message and exit
-r REMOTE, --remote REMOTE
help message
I have no idea why it is printing REMOTE after the -r and --remote switches in the above output.
Can anyone tell me what am I doing wrong here or what should I do to get rid of it?
You are looking at the metavar; it is autogenerated from the option string to form a placeholder. It tells the user that it is there that they need to fill in a value.
You can set an explicit metavar value with the metavar keyword argument:
When ArgumentParser generates help messages, it needs some way to refer to each expected argument. By default, ArgumentParser objects use the dest value as the “name” of each object. By default, for positional argument actions, the dest value is used directly, and for optional argument actions, the dest value is uppercased.
You see it because your argument takes a value; if you expected it to be a toggle, use action='store_true'; in that case the option defaults to False unless the user specifies the switch.
Demo of the latter:
>>> import argparse
>>> parser = argparse.ArgumentParser(
... description='My description')
>>> parser.add_argument("-r", "--remote", action='store_true', help="help message")
_StoreTrueAction(option_strings=['-r', '--remote'], dest='remote', nargs=0, const=True, default=False, type=None, choices=None, help='help message', metavar=None)
>>> parser.print_help()
usage: [-h] [-r]
My description
optional arguments:
-h, --help show this help message and exit
-r, --remote help message
>>> opts = parser.parse_args([])
>>> opts.remote
False
>>> opts = parser.parse_args(['-r'])
>>> opts.remote
True
You are missing action.
import argparse
parser = argparse.ArgumentParser(
description='My description')
parser.add_argument(
"-r", "--remote", action="store_true", # add action
help="help message")
parser.print_help()
usage: -c [-h] [-r]
My description
optional arguments:
-h, --help show this help message and exit
-r, --remote help message

Remove long sub-command help output?

I'm using argparse to produce my CLI and using the add_subparsers function. This works exception the --help output is really ugly. It lists all the commands in the overall syntax. For example:
usage: redid [-h] [--config CONFIG] [--verbose] [--show-curl] [--save [SAVE]]
{setup,show-config,check-auth,version,get-profile,put-profile,delete-profile,get-profile-signature,list-profiles,list-resources,ls-resources,get-resource-record,delete-resource,get-resource,upload-resource,get-resource-url}
...
I'd much prefer to have a more traditional and clean output similar to:
usage: redid [OPTIONS...] Command ...
How can I do this?
Try adding the metavar argument to your subparser definition and give it no value:
parser.add_subparsers(title="the_commands", metavar="")
From the documentation:
Description of parameters:
....
metavar - string presenting available sub-commands in help; by default it is None and presents sub-commands in form {cmd1, cmd2, ..}
Here's an example, I'm not sure how you've set up your sub-parsers but:
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='foo help')
subparsers = parser.add_subparsers(help="sub-command help", metavar="sub command")
parser_a = subparsers.add_parser('a', help='a help')
parser_a.add_argument('bar', type=int, help='bar help')
parser_b = subparsers.add_parser('b', help='b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')
>>> parser.print_help()
usage: PROG [-h] [--foo] sub command ...
positional arguments:
sub command sub-command help
a a help
b b help
optional arguments:
-h, --help show this help message and exit
--foo foo help

Categories