How to make python argparse mutually exclusive group arguments without prefix? - python

Python2.7 argparse only accepts optional arguments (prefixed) in mutually exclusive groups:
parser = argparse.ArgumentParser(prog='mydaemon')
action = parser.add_mutually_exclusive_group(required=True)
action.add_argument('--start', action='store_true', help='Starts %(prog)s daemon')
action.add_argument('--stop', action='store_true', help='Stops %(prog)s daemon')
action.add_argument('--restart', action='store_true', help='Restarts %(prog)s daemon')
$ mydaemon -h
usage: mydaemon [-h] (--start | --stop | --restart)
optional arguments:
-h, --help show this help message and exit
--start Starts mydaemon daemon
--stop Stops mydaemon daemon
--restart Restarts mydaemon daemon
Is there a way to make argparse arguments behaves like traditional unix daemon control:
(start | stop | restart) and not (--start | --stop | --restart) ?

from pymotw
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-a', action='store_true')
group.add_argument('-b', action='store_true')
print parser.parse_args()
output:
$ python argparse_mutually_exclusive.py -h
usage: argparse_mutually_exclusive.py [-h] [-a | -b]
optional arguments:
-h, --help show this help message and exit
-a
-b
$ python argparse_mutually_exclusive.py -a
Namespace(a=True, b=False)
$ python argparse_mutually_exclusive.py -b
Namespace(a=False, b=True)
$ python argparse_mutually_exclusive.py -a -b
usage: argparse_mutually_exclusive.py [-h] [-a | -b]
argparse_mutually_exclusive.py: error: argument -b: not allowed with argument -a
version2
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='commands')
# A list command
list_parser = subparsers.add_parser('list', help='List contents')
list_parser.add_argument('dirname', action='store', help='Directory to list')
# A create command
create_parser = subparsers.add_parser('create', help='Create a directory')
create_parser.add_argument('dirname', action='store', help='New directory to create')
create_parser.add_argument('--read-only', default=False, action='store_true',
help='Set permissions to prevent writing to the directory',
)
# A delete command
delete_parser = subparsers.add_parser('delete', help='Remove a directory')
delete_parser.add_argument('dirname', action='store', help='The directory to remove')
delete_parser.add_argument('--recursive', '-r', default=False, action='store_true',
help='Remove the contents of the directory, too',
)
print parser.parse_args(['list', 'a s d', ])
>>> Namespace(dirname='a s d')
print parser.parse_args(['list', 'a s d', 'create' ])
>>> error: unrecognized arguments: create

For all the abilities and options in argparse I don't think you'll ever get a "canned" usage string that looks like what you want.
That said, have you looked at sub-parsers since your original post?
Here's a barebones implementation:
import argparse
parser = argparse.ArgumentParser(prog='mydaemon')
sp = parser.add_subparsers()
sp_start = sp.add_parser('start', help='Starts %(prog)s daemon')
sp_stop = sp.add_parser('stop', help='Stops %(prog)s daemon')
sp_restart = sp.add_parser('restart', help='Restarts %(prog)s daemon')
parser.parse_args()
Running this with the -h option yields:
usage: mydaemon [-h] {start,stop,restart} ...
positional arguments:
{start,stop,restart}
start Starts mydaemon daemon
stop Stops mydaemon daemon
restart Restarts mydaemon daemon
One of the benefits of this approach is being able to use set_defaults for each sub-parser to hook up a function directly to the argument. I've also added a "graceful" option for stop and restart:
import argparse
def my_stop(args):
if args.gracefully:
print "Let's try to stop..."
else:
print 'Stop, now!'
parser = argparse.ArgumentParser(prog='mydaemon')
graceful = argparse.ArgumentParser(add_help=False)
graceful.add_argument('-g', '--gracefully', action='store_true', help='tries to terminate the process gracefully')
sp = parser.add_subparsers()
sp_start = sp.add_parser('start', help='Starts %(prog)s daemon')
sp_stop = sp.add_parser('stop', parents=[graceful],
description='Stops the daemon if it is currently running.',
help='Stops %(prog)s daemon')
sp_restart = sp.add_parser('restart', parents=[graceful], help='Restarts %(prog)s daemon')
# Hook subparsers up to functions
sp_stop.set_defaults(func=my_stop)
# Uncomment when my_start() and
# my_restart() are implemented
#
# sp_start.set_defaults(func=my_start)
# sp_restart.set_defaults(func=my_restart)
args = parser.parse_args()
args.func(args)
Showing the "help" message for stop:
$ python mydaemon.py stop -h
usage: mydaemon stop [-h] [-g]
Stops the daemon if it is currently running.
optional arguments:
-h, --help show this help message and exit
-g, --gracefully tries to terminate the process gracefully
Stopping "gracefully":
$ python mydaemon.py stop -g
Let's try to stop...

It sounds like you want a positional argument instead of mutually exclusive options. You can use 'choices' to restrict the possible acceptable options.
parser = ArgumentParser()
parser.add_argument('action', choices=('start', 'stop', 'restart'))
This produces a usage line that looks like this:
usage: foo.py [-h] {start,stop,restart}

Building on Adam's answer... if you wanted to specify a default you could always do the following so they can effectively leave it blank.
import argparse
ActionHelp = """
Start = Starts the daemon (default)
Stop = Stops the daemon
Restart = Restarts the daemon
"""
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('action', nargs = '?', choices=('start', 'stop', 'restart'),
default = 'start', help = ActionHelp)
print parser.parse_args(''.split())
print
print parser.parse_args('-h'.split())
which will print:
Namespace(action='start')
usage: program.py [-h] [{start,stop,restart}]
postional arguments:
{start,stop,restart}
Start = Starts the daemon (default)
Stop = Stops the daemon
Restart = Restarts the daemon
optional arguments:
-h, --help show this help message and exit

Related

Put subparser command at the beginning of arguments

I wanted to create an interface with argparse for my script with subcommands; so, if my script is script.py, I want to call it like python script.py command --foo bar, where command is one of the N possible custom commands.
The problem is, I already tried looking for a solution here on StackOverflow, but it seems like everything I tried is useless.
What I have currently is this:
parser = argparse.ArgumentParser()
parser.add_argument("-x", required=True)
parser.add_argument("-y", required=True)
parser.add_argument("-f", "--files", nargs="+", required=True)
# subparsers for commands
subparsers = parser.add_subparsers(title="Commands", dest="command")
subparsers.required = True
summary_parser = subparsers.add_parser("summary", help="help of summary command")
If I try to run it with:
args = parser.parse_args("-x 1 -y 2 -f a/path another/path".split())
I got this error, as it should be: script.py: error: the following arguments are required: command.
If, however, I run this command:
args = parser.parse_args("summary -x 1 -y 2 -f a/path another/path".split())
I got this error, that I shouldn't have: script.py: error: the following arguments are required: -x, -y, -f/--files.
If I put the command at the end, changing also the order of arguments because of -f, it works.
args = parser.parse_args("-x 1 -f a/path another/path -y 2 summary".split())
If I add the parents keyword in subparser, so substitute the summary_parser line with summary_parser = subparsers.add_parser("summary", help=HELP_CMD_SUMMARY, parents=[parser], add_help=False), then I got:
script.py summary: error: the following arguments are required: command when summary is in front of every other argument;
script.py summary: error: the following arguments are required: -x, -y, -f/--files, command when summary is at the end of the args.
My question is, how I have to setup the parsers to have the behaviour script.py <command> <args>? Every command shares the same args, because they are needed to create certain objects, but at the same time every command can needs other arguments too.
Creating another parser helped me getting what I wanted.
The root parser should add all the optional arguments - and also have add_help=False, to avoid an help message conflict -, then another parser - parser2, with a lot of fantasy - will be created.
The second parser will have subparsers, and they all needs to specify as parents the root parser.
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument() ...
parser2 = argparse.ArgumentParser()
subparsers = parser2.add_subparsers(title="Commands", dest="command")
subparsers.required = True
summary_parser = subparsers.add_parser("summary", parents=[parser])
summary_parser.add_argument("v", "--verbose", action="store_true")
# parse args
args = parser2.parse_args()
Now the output will be this:
usage: script.py [-h] {summary} ...
optional arguments:
-h, --help show this help message and exit
Commands:
{summary}
summary For each report print its summary, then exit
usage: script.py summary [-h] -x X -y Y -f FILES [FILES ...] [-v]
optional arguments:
-h, --help show this help message and exit
-x X
-y Y
-f FILES [FILES ...], --files FILES [FILES ...]
-v, --verbose

Argparse using subcommands/subparsers -- AttributeError: 'Namespace' object has no attribute

I need to use a python script having the following usages:
script.py ( commands ) ( options )
My problem is how i add arguments for "commands" and "options"?
What i did now is this:
parser = argparse.ArgumentParser()
parser._optionals.title = "Options"
parser.add_argument('-help','--help', action="store_true", dest="help", help='help')
subparsers = parser.add_subparsers(help="All available commands", title="Commands")
parser_start = subparsers.add_parser('start', help='Starts the script', add_help=False)
parser_start._optionals.title = "Options"
parser_start.add_argument('--help', action="store_true", dest="help_start")
parser_start.add_argument('-f', type=str, dest="file", help='simulation file to start')
parser_ls = subparsers.add_parser('ls', help='Lists running simulations', add_help=False)
parser_ls._optionals.title = "Options"
parser_ls.add_argument('--help', action="store_true", dest="help_ls")
parser_ls.add_argument('--all', action="store_true", help='Display all simulations')
parser_stop = subparsers.add_parser('stop', help='Stops simulation', add_help=False)
parser_stop._optionals.title = "Options"
parser_stop.add_argument('--help', action="store_true", dest="help_down")
parser_stop.add_argument('--sim-name', type=str, dest="sim_name")
args = parser.parse_args()
If i try to access args.help_up i receive: AttributeError: 'Namespace' object has no attribute 'help_start'
How do i pass the parser_up, parser_stop and parser_ls to the parse_args?
And how do i access them afterwards?
Objective is to have custom help messages ( which i have atm that is why i disabled the help ) and to run the script like this:
script.py start -f (name of file)
script.py stop --sim-name (name of simulation)
EDIT:
If i add args2 = parser_start.parse_args() i am able to get a read on args2.help_start, but i am not able to find any of the start, ls or down arguments!
I've made a few changes to your code; hopefully it will clarify what's going on:
import argparse
parser = argparse.ArgumentParser()
parser._optionals.title = "Options"
#parser.add_argument('-help','--help', action="store_true", dest="help", help='help')
# conflicts with original help
subparsers = parser.add_subparsers(help="All available commands", title="Commands",
dest='cmd') # NEW
parser_start = subparsers.add_parser('start', help='Starts the script', add_help=False)
parser_start._optionals.title = "Options"
parser_start.add_argument('--help', action="store_true", dest="help_start")
parser_start.add_argument('-f', type=str, dest="file", help='simulation file to start')
parser_ls = subparsers.add_parser('ls', help='Lists running simulations', add_help=False)
parser_ls._optionals.title = "Options"
parser_ls.add_argument('--help', action="store_true", dest="help_ls")
parser_ls.add_argument('--all', action="store_true", help='Display all simulations')
parser_stop = subparsers.add_parser('stop', help='Stops simulation', add_help=False)
parser_stop._optionals.title = "Options"
parser_stop.add_argument('--help', action="store_true", dest="help_down")
parser_stop.add_argument('--sim-name', type=str, dest="sim_name")
args = parser.parse_args()
print(args) # NEW
and sample runs:
0939:~/mypy$ python3 stack62716530.py
Namespace(cmd=None)
0939:~/mypy$ python3 stack62716530.py --help
usage: stack62716530.py [-h] {start,ls,stop} ...
Options:
-h, --help show this help message and exit
Commands:
{start,ls,stop} All available commands
start Starts the script
ls Lists running simulations
stop Stops simulation
0939:~/mypy$ python3 stack62716530.py start
Namespace(cmd='start', file=None, help_start=False)
0939:~/mypy$ python3 stack62716530.py start --help
Namespace(cmd='start', file=None, help_start=True)
and if I add:
if getattr(args, 'help_start',False):
parser_start.print_help()
I get
0940:~/mypy$ python3 stack62716530.py start --help
Namespace(cmd='start', file=None, help_start=True)
usage: stack62716530.py start [--help] [-f FILE]
Options:
--help
-f FILE simulation file to start
The key is that help_start is an attribute only if the start subparser is invoked.
In first read(s) of your code I missed the dest='help_start' parameters. Thus I couldn't tell why you expected to see such an attribute in args.

Argparse Positional arguments inside an Optional argument

I want the following structure with argparse :
usage: ms1.py [-h] [-c] [-u] [password] [DB_name]
optional arguments:
-h, --help show this help message and exit
-c, --c show available items
-u password DB_name, Update database
I wish to place -h -c -u all as optional.
but if -u was declared by user - password & DB_name must be positional to it.
What is the right code ? (I'm so confused by the documentation, many thanks)
Something like this should work for you:
from argparse import ArgumentParser
p = ArgumentParser()
p.add_argument('--c', required=False)
p.add_argument('--u', required=False)
p.add_argument('password')
p.add_argument('db_name')
args = p.parse_args()
print(args)
You don't need to have -h. argparse does it for you at no extra cost.
c and u are optional and password, db_name are required and the position matter.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-c', action='store_true', help='show available items')
parser.add_argument('-u', nargs=2, metavar=('password', 'DB_name'), help='Update database')
args = parser.parse_args()
print(args)
samples:
1442:~/mypy$ python3 stack62967549.py -h
usage: stack62967549.py [-h] [-c] [-u password DB_name]
optional arguments:
-h, --help show this help message and exit
-c show available items
-u password DB_name Update database
1442:~/mypy$ python3 stack62967549.py -c
Namespace(c=True, u=None)
1443:~/mypy$ python3 stack62967549.py -u foobar FOO
Namespace(c=False, u=['foobar', 'FOO'])

Extra Characters in python Argparse?

I have an argparse function:
def argParse():
# Possible Arguments
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--search", dest="s", help="Seach parameters")
parser.add_argument("-d", "--directory", dest="d", help="Choose Directory to save data")
args = parser.parse_args()
params = str(args.s)
directory = str(args.d)
if params == "None":
print("Search parameters are null, exiting program.")
parser.print_help()
exit(1)
if directory == "None":
print("directory is null, storing to current working directory.")
But, it is adding a extra capitol S and D to the useage output? Any idea on how to clean this up?
usage: scan.py [-h] [-s S] [-d D]
optional arguments:
-h, --help show this help message and exit
-s S, --search S Seach parameters
-d D, --directory D Choose Directory to save data
I think you're looking for the metavar kwarg.
try this:
import argparse
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('-s', '--search', dest='s', metavar='\b', help='Search parameters')
args = parser.parse_args()
parse_args()
\b is the backspace character, so it also removes the space between the two,
as opposed to using "" which I beleive would still leave a space after the initial variable name.
output of python argparse_test.py -h:
usage: argparse_test.py [-h] [-s]
optional arguments:
-h, --help show this help message and exit
-s, --search Search parameters

Using mutually exclusive between groups

I'm trying to have a mutually exclusive group between different groups:
I have the arguments -a,-b,-c, and I want to have a conflict with -a and -b together, or -a and -c together. The help should show something like [-a | ([-b] [-c])].
The following code does not seem to do have mutually exclusive options:
import argparse
parser = argparse.ArgumentParser(description='My desc')
main_group = parser.add_mutually_exclusive_group()
mysub_group = main_group.add_argument_group()
main_group.add_argument("-a", dest='a', action='store_true', default=False, help='a help')
mysub_group.add_argument("-b", dest='b', action='store_true',default=False,help='b help')
mysub_group.add_argument("-c", dest='c', action='store_true',default=False,help='c help')
parser.parse_args()
Parsing different combinations - all pass:
> python myscript.py -h
usage: myscript.py [-h] [-a] [-b] [-c]
My desc
optional arguments:
-h, --help show this help message and exit
-a a help
> python myscript.py -a -c
> python myscript.py -a -b
> python myscript.py -b -c
I tried changing the mysub_group to be add_mutually_exclusive_group turns everything into mutually exclusive:
> python myscript.py -h
usage: myscript.py [-h] [-a | -b | -c]
My desc
optional arguments:
-h, --help show this help message and exit
-a a help
-b b help
-c c help
How can I add arguments for [-a | ([-b] [-c])]?
So, this has been asked a number of times. The simple answer is "with argparse, you can't". However, that's the simple answer. You could make use of subparsers, so:
import argparse
parser = argparse.ArgumentParser(description='My desc')
sub_parsers = parser.add_subparsers()
parser_a = sub_parsers.add_parser("a", help='a help')
parser_b = sub_parsers.add_parser("b", help='b help')
parser_b.add_argument("-c", dest='c', action='store_true',default=False,help='c help')
parser.parse_args()
You then get:
$ python parser -h
usage: parser [-h] {a,b} ...
My desc
positional arguments:
{a,b}
a a help
b b help
optional arguments:
-h, --help show this help message and exit
and:
$ python parser b -h
usage: parser b [-h] [-c]
optional arguments:
-h, --help show this help message and exit
-c c help
If you would prefer your arguments as stated in the question, have a look at docopt, it looks lovely, and should do exactly what you want.
argument_groups don't affect the parsing. They just contribute to the help formatting. So defining a group within an mutually_exclusive_group does not help with this problem.
There is a proposed patch, http://bugs.python.org/issue10984, 'argparse add_mutually_exclusive_group should accept existing arguments to register conflicts', that would allow you to define two mutually_exclusive_groups, one [-a | -b] and the other [-a | -c]. Creating a 2nd group that includes an argument (-a) that is already defined is the trivial part of this patch. Producing a meaningful usage line is more difficult, and required a rewrite of several HelpFormatter methods.
import argparse
parser = argparse.ArgumentParser(description='My desc')
group1 = parser.add_mutually_exclusive_group()
action_a = group1.add_argument("-a", dest='a', action='store_true', default=False, help='a help')
group1.add_argument("-b", dest='b', action='store_true',default=False,help='b help')
group2 = parser.add_mutually_exclusive_group()
group2.add_argument("-c", dest='c', action='store_true',default=False,help='c help')
group2._group_actions.append(action_a) # THE KLUDGE
print parser.format_usage()
# usage: stack16769409.py [-h] [-a | -b] [-c]
args = parser.parse_args()
Usage does not show the 2 groups correctly. But it does accept -b -c, while objecting to -a -b and -a -c. But you can write a custom usage line.

Categories