I've been using argparse for a Python program that can -process, -upload or both:
parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload', action='store_true')
args = parser.parse_args()
The program is meaningless without at least one parameter. How can I configure argparse to force at least one parameter to be chosen?
UPDATE:
Following the comments: What's the Pythonic way to parametrize a program with at least one option?
if not (args.process or args.upload):
parser.error('No action requested, add -process or -upload')
args = vars(parser.parse_args())
if not any(args.values()):
parser.error('No arguments provided.')
I know this is old as dirt, but the way to require one option but forbid more than one (XOR) is like this:
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload', action='store_true')
args = parser.parse_args()
print args
Output:
>opt.py
usage: multiplot.py [-h] (-process | -upload)
multiplot.py: error: one of the arguments -process -upload is required
>opt.py -upload
Namespace(process=False, upload=True)
>opt.py -process
Namespace(process=True, upload=False)
>opt.py -upload -process
usage: multiplot.py [-h] (-process | -upload)
multiplot.py: error: argument -process: not allowed with argument -upload
If not the 'or both' part (I have initially missed this) you could use something like this:
parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload', action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
parser.error("One of --process or --upload must be given")
Though, probably it would be a better idea to use subcommands instead.
Requirements Review
use argparse (I will ignore this one)
allow one or two actions to be called (at least one required).
try to by Pythonic (I would rather call it "POSIX"-like)
There are also some implicit requirements when living on command line:
explain the usage to the user in a way which is easy to understand
options shall be optional
allow specifying flags and options
allow combining with other parameters (like file name or names).
Sample solution using docopt (file managelog.py):
"""Manage logfiles
Usage:
managelog.py [options] process -- <logfile>...
managelog.py [options] upload -- <logfile>...
managelog.py [options] process upload -- <logfile>...
managelog.py -h
Options:
-V, --verbose Be verbose
-U, --user <user> Username
-P, --pswd <pswd> Password
Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
from docopt import docopt
args = docopt(__doc__)
print args
Try to run it:
$ python managelog.py
Usage:
managelog.py [options] process -- <logfile>...
managelog.py [options] upload -- <logfile>...
managelog.py [options] process upload -- <logfile>...
managelog.py -h
Show the help:
$ python managelog.py -h
Manage logfiles
Usage:
managelog.py [options] process -- <logfile>...
managelog.py [options] upload -- <logfile>...
managelog.py [options] process upload -- <logfile>...
managelog.py -h
Options:
-V, --verbose Be verbose
-U, --user <user> Username
-P, --pswd <pswd> P managelog.py [options] upload -- <logfile>...
Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
And use it:
$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
'--pswd': 'secret',
'--user': 'user',
'--verbose': True,
'-h': False,
'<logfile>': ['alfa.log', 'beta.log'],
'process': False,
'upload': True}
Short alternative short.py
There can be even shorter variant:
"""Manage logfiles
Usage:
short.py [options] (process|upload)... -- <logfile>...
short.py -h
Options:
-V, --verbose Be verbose
-U, --user <user> Username
-P, --pswd <pswd> Password
Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
from docopt import docopt
args = docopt(__doc__)
print args
Usage looks like this:
$ python short.py -V process upload -- alfa.log beta.log
{'--': True,
'--pswd': None,
'--user': None,
'--verbose': True,
'-h': False,
'<logfile>': ['alfa.log', 'beta.log'],
'process': 1,
'upload': 1}
Note, that instead of boolean values for "process" and "upload" keys there are counters.
It turns out, we cannot prevent duplication of these words:
$ python short.py -V process process upload -- alfa.log beta.log
{'--': True,
'--pswd': None,
'--user': None,
'--verbose': True,
'-h': False,
'<logfile>': ['alfa.log', 'beta.log'],
'process': 2,
'upload': 1}
Conclusions
Designing good command line interface can be challenging sometime.
There are multiple aspects of command line based program:
good design of command line
selecting/using proper parser
argparse offers a lot, but restricts possible scenarios and can become very complex.
With docopt things go much shorter while preserving readability and offering high degree of flexibility. If you manage getting parsed arguments from dictionary and do some of conversions (to integer, opening files..) manually (or by other library called schema), you may find docopt good fit for command line parsing.
For http://bugs.python.org/issue11588 I am exploring ways of generalizing the mutually_exclusive_group concept to handle cases like this.
With this development argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py
I am able to write:
parser = argparse.ArgumentParser(prog='PROG',
description='Log archiver arguments.')
group = parser.add_usage_group(kind='any', required=True,
title='possible actions (at least one is required)')
group.add_argument('-p', '--process', action='store_true')
group.add_argument('-u', '--upload', action='store_true')
args = parser.parse_args()
print(args)
which produces the following help:
usage: PROG [-h] (-p | -u)
Log archiver arguments.
optional arguments:
-h, --help show this help message and exit
possible actions (at least one is required):
-p, --process
-u, --upload
This accepts inputs like '-u', '-up', '--proc --up' etc.
It ends up running a test similar to https://stackoverflow.com/a/6723066/901925, though the error message needs to be clearer:
usage: PROG [-h] (-p | -u)
PROG: error: some of the arguments process upload is required
I wonder:
are the parameters kind='any', required=True clear enough (accept any of the group; at least one is required)?
is usage (-p | -u) clear? A required mutually_exclusive_group produces the same thing. Is there some alternative notation?
is using a group like this more intuitive than phihag's simple test?
The best way to do this is by using python inbuilt module add_mutually_exclusive_group.
parser = argparse.ArgumentParser(description='Log archiver arguments.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-process', action='store_true')
group.add_argument('-upload', action='store_true')
args = parser.parse_args()
If you want only one argument to be selected by command line just use required=True as an argument for group
group = parser.add_mutually_exclusive_group(required=True)
If you require a python program to run with at least one parameter, add an argument that doesn't have the option prefix (- or -- by default) and set nargs=+ (Minimum of one argument required). The problem with this method I found is that if you do not specify the argument, argparse will generate a "too few arguments" error and not print out the help menu. If you don't need that functionality, here's how to do it in code:
import argparse
parser = argparse.ArgumentParser(description='Your program description')
parser.add_argument('command', nargs="+", help='describe what a command is')
args = parser.parse_args()
I think that when you add an argument with the option prefixes, nargs governs the entire argument parser and not just the option. (What I mean is, if you have an --option flag with nargs="+", then --option flag expects at least one argument. If you have option with nargs="+", it expects at least one argument overall.)
This achieves the purpose and this will also be relfected in the argparse autogenerated --help output, which is imho what most sane programmers want (also works with optional arguments):
parser.add_argument(
'commands',
nargs='+', # require at least 1
choices=['process', 'upload'], # restrict the choice
help='commands to execute'
)
Official docs on this:
https://docs.python.org/3/library/argparse.html#choices
Maybe use sub-parsers?
import argparse
parser = argparse.ArgumentParser(description='Log archiver arguments.')
subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
parser_process = subparsers.add_parser('process', help='Process logs')
parser_upload = subparsers.add_parser('upload', help='Upload logs')
args = parser.parse_args()
print("Subparser: ", args.subparser_name)
Now --help shows:
$ python /tmp/aaa.py --help
usage: aaa.py [-h] {process,upload} ...
Log archiver arguments.
positional arguments:
{process,upload} sub-command help
process Process logs
upload Upload logs
optional arguments:
-h, --help show this help message and exit
$ python /tmp/aaa.py
usage: aaa.py [-h] {process,upload} ...
aaa.py: error: too few arguments
$ python3 /tmp/aaa.py upload
Subparser: upload
You can add additional options to these sub-parsers as well. Also instead of using that dest='subparser_name' you can also bind functions to be directly called on given sub-command (see docs).
For cases like
parser.add_argument("--a")
parser.add_argument("--b")
We can use the following
if not args.a and not args.b:
parser.error("One of --a or --b must be present")
Use append_const to a list of actions and then check that the list is populated:
parser.add_argument('-process', dest=actions, const="process", action='append_const')
parser.add_argument('-upload', dest=actions, const="upload", action='append_const')
args = parser.parse_args()
if(args.actions == None):
parser.error('Error: No actions requested')
You can even specify the methods directly within the constants.
def upload:
...
parser.add_argument('-upload', dest=actions, const=upload, action='append_const')
args = parser.parse_args()
if(args.actions == None):
parser.error('Error: No actions requested')
else:
for action in args.actions:
action()
Using
parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload', action='store_true')
args = parser.parse_args()
Maybe try:
if len([False for arg in vars(args) if vars(args)[arg]]) == 0:
parsers.print_help()
exit(-1)
At least this is what I just used; hopefully this helps someone in the future!
I have code:
parser = ArgumentParser()
parser.add_argument('--verbose', action='count', default=0, help='debug output')
subparsers = parser.add_subparsers(help='subparser')
parser1 = subparsers.add_parser('action', help='Do something')
parser1.add_argument('--start', action='store_true', help='start')
parser1.add_argument('--stop', action='store_true', help='stop')
parser2 = subparsers.add_parser('control', help='Control something')
parser2.add_argument('--input', action='store_true', help='start')
parser2.add_argument('--output', action='store_true', help='stop')
args = parser.parse_args()
Then I can run script:
script.py --verbose action --start
script.py --verbose control --output
but not
script.py action --start --verbose
script.py control --output --verbose
Can I transfer option --verbose to the end, without adding it to each group?
To elaborate on my comment:
argparse parses the input list (sys.argv[1:]) in order, matching the strings with the Actions (add_argument object). So if the command is
python foo.py --arg1=3 cmd --arg2=4
it tries to handle '--arg1', then 'cmd'. If 'cmd' matches a subparser name, it then delegates the parsing to that parser, giving the remaining strings to it. If the cmd subparser can handle --arg2, it returns that as an unrecognized argument.
The main parser does not resume parsing. Rather it just handles the unrecognized arguments as it normally would - raising an error if using parse_args, and returning them in the extras list if using parse_known_args.
So if you want to put --verbose at the end, you have define it as a subparser argument. Or do some further parsing after parse_known_args.
You are allowed to define --verbose at both levels, though sometimes such a definition can create conflicts (especially if defaults differ).
The parents mechanism can be used to reduce the amount of typing, though you could just as easily write your own utility functions.
I'm using argsparse to parse the options passed to my python scripts.
I want to enable passing '-a', and to allow passing '-b' with the same affect.
No problem, I'll call parser.add_argument() twice, with the same description:
parser.add_argument('-a', help='do something')
parser.add_argument('-b', help='do something')
But now when displaying the script help, I will see both, as such:
-a do something
-b do something
This is ugly.
I would prefer to have {-a, -b} or {-a|b).
I could not find in argsparse documentation any way around this (admittedly, not critical) issue.
You could try to pass both arguments to the same add_argument call:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', '-b', help='do something')
parser.parse_args(['--help'])
output
usage: test.py [-h] [-a A]
optional arguments:
-h, --help show this help message and exit
-a A, -b A do something
I want to create a script like this
./myscript.py -g parser -a parserapp
and whenever the script is missing one option such as ./myscript.py -g parser, it will print out usage and exit the script
The script should be: ./myscript.py -g parser -a parserapp
So, my question is how can I check if option -a or-g is missing; so, it can print out the usage for the script and exit.
Thank you for your help
Use argparse
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-g", "--gggg", help="g parameter",required=True)
parser.add_argument("-a", "--aaaa", help="a parameter",required=True)
args = parser.parse_args()
print args.g, args.a
When you run it with -h it will show help:
~#:python myscript.py -h
usage: myscript.py [-h] -g GGGG -a AAAA
optional arguments:
-h, --help show this help message and exit
-g GGGG, --gggg GGGG g parameter
-a AAAA, --aaaa AAAA a parameter
If you miss a parameter it will print error:
myscript.py: error: argument -g/--gggg is required
For your need argparse can check wether an argument is missing or not like this :
import argparse
parser = argparse.ArgumentParser(description='My Super Script')
parser.add_argument(
'--parser', '-p',
required=True,
help='Parser Type'
)
parser.add_argument(
'--application', '-a',
required=True,
help='Application Name'
)
args = parser.parse_args()
By default argparse will consider a named argument optional unless you specify required=True
If the two args are not provided this will print a default help.
ref
Parsing of parameters can be done with optparse or argparse. You can make nifty help options and so on. It's also easy to make different kinds of parameters to be accepted.
Use argparse: https://docs.python.org/2/howto/argparse.html
You can use argparse module.
For Python3.x, you can look here.
For Python2.x, you can look here.
Apart from argparse, you can also use getopt module.
Use getopt:
https://docs.python.org/3.1/library/getopt.html(Python3.x)
https://docs.python.org/2/library/getopt.html(Python2.x)
Is there a Python module for doing gem/git-style command line arguments? What I mean by gem/git style is:
$ ./MyApp.py
The most commonly used MyApp commands are:
add Add file contents to the index
bisect Find by binary search the change that introduced a bug
branch List, create, or delete branches
checkout Checkout a branch or paths to the working tree
...
$ ./MyApp.py branch
* current-branch
master
With no arguments, the output tells you how you can proceed. And there is a special "help" command:
$ ./MyApp.py help branch
Which gets you deeper tips about the "branch" command.
Edit:
And by doing I mean it does the usage printing for you, exits with invalid input, runs your functions according to your CLI specification. Sort of a "URL mapper" for the command line.
Yes, argparse with add_subparsers().
It's all well explained in the Sub-commands section.
Copying one of the examples from there:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers()
>>> checkout = subparsers.add_parser('checkout', aliases=['co'])
>>> checkout.add_argument('foo')
>>> parser.parse_args(['checkout', 'bar'])
Namespace(foo='bar')
Edit: Unfortunately there's no self generated special help command, but you can get the verbose help message (that you seem to want) with -h or --help like one normally would after the command:
$ ./MyApp.py branch --help
By verbose I don't mean that is like a man page, it's like every other --help kind of help: listing all the arguments, etc...
Example:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(description='Sub description')
>>> checkout = subparsers.add_parser('checkout', description='Checkout description')
>>> checkout.add_argument('foo', help='This is the foo help')
>>> parser.parse_args(['checkout', '--help'])
usage: checkout [-h] foo
Checkout description
positional arguments:
foo This is the foo help
optional arguments:
-h, --help show this help message and exit
If you need to, it should be easy to implement an help command that redirects to --help.
A reasonable hack to get the gem/git style "help" behavior (I just wrote this for what I'm working on anyway):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='sub_commands')
parser_branch = subparsers.add_parser('branch', description='list of branches')
parser_help = subparsers.add_parser('help')
parser_help.add_argument('command', nargs="?", default=None)
# I can't find a legitimate way to set a default subparser in the docs
# If you know of one, please let me know!
if len(sys.argv) < 2:
sys.argv.append('--help')
parsed = parser.parse_args()
if parsed.sub_commands == "help":
if not parsed.command:
parser.parse_args(['--help'])
else:
parser.parse_args([parsed.command, '--help'])
argparse is definitely a step up from optparse and other python solutions I've come across. But IMO the gem/git style of handling args is just a more logical and safer way to do things so it's annoying that it's not supported.
I wanted to do something similar to git commands, where I would load a second script based off of one of the command line options, and have that script populate more command line options, and also have the help work.
I was able to do this by disabling the help option, parse known args, add more arguments, re-enable the help option, and then parse the rest of the arguments.
This is what I came up with.
import argparse
#Note add_help=False
arg_parser = argparse.ArgumentParser(description='Add more arguments after parsing.',add_help=False)
arg_parser.add_argument('MODE', default='default',type=str, help='What commands to use')
args = arg_parser.parse_known_args()[0]
if args.MODE == 'branch':
arg_parser.add_argument('-d', '--delete', default='Delete a branch')
arg_parser.add_argument('-m', '--move', default='move a branch')
elif args.MODE == 'clone' :
arg_parser.add_argument('--local', '-l')
arg_parser.add_argument('--shared')
#Finally re-enable the help option, and reparse the arguments
arg_parser.add_argument(
'-h', '--help',
action='help', default=argparse.SUPPRESS,
help=argparse._('show this help message and exit'))
args = arg_parser.parse_args()