Argparse: Making required flags - python

I am attempting to create a required flag "-f" that accepts the input "filename.pdb" in Argparse.
This is simple enough. The standard solution is to add the option "required=True."
Unfortunately, after doing this, the "-f" flag still appears under optional arguments in the help list. Even more confusingly, the "-f" flag appears as required in the "usage" prompt in the help list.
Here is my code:
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--file_name", required=True, help="enter name of .pdb file")
parser.add_argument("-bw", "--bin_width", default=.25, help="enter desired bin width in nanometers. default = .25")
parser.add_argument("-bn","--base_name", default="IDP", help="custom prefix for output file naming. default = IDP")
args = parser.parse_args()
And here is the help window that is returned by --help
usage: rgcalc.py [-h] -f FILE_NAME [-bw BIN_WIDTH] [-bn BASE_NAME]
optional arguments:
-h, --help show this help message and exit
-f FILE_NAME, --file_name FILE_NAME
enter name of .pdb file
-bw BIN_WIDTH, --bin_width BIN_WIDTH
enter desired bin width in nanometers. default = .25
-bn BASE_NAME, --base_name BASE_NAME
custom prefix for output file naming. default = IDP
As you can see in the "usage" block, "-f" has been taken out of brackets, indicating that it is required. Despite this, "-f" still appears in the "optional arguments" section.
Is it possible to:
A) Custom format the help window to fix this
or
B) Add some code to have the flag "-f", "--file_name" appear as a positional (opposed to an optional) argument, yet still require a flag?
I read that Argparse intentionally did this to avoid positional flags, but I am supposed to do it in order to cater to traditional linux users.
Thank you kind interwebbers!

This issue has been discussed in http://bugs.python.org/issue9694, 'argparse required arguments displayed under "optional arguments"'
It's a terminology issue that isn't easily resolved, due to historical practice (in UNIX as well as Python), and a lack of good alternatives.
Arguments that take a 'flag' like '-f' have historically been called options or optionals. Generally you don't use them unless you want some value that is different from the default. But 'argparse' lets you specify required=True, so now you have a 'required optional'. And with nargs='?', it is possible to have 'positionals' which are not required.
Until Python developers come up with some alternative terminology, your best choice is to use an 'ArgumentGroup', with the title and description that you like. By default the parser has 2 ArgumentGroups, 'optional arguments' and 'positional arguments'. It has to put the argument in one or the other. You can create others, and populate them as you wish.
see http://bugs.python.org/issue9694#msg132327 (post by the original argparse developer).
The 'usage' line is the one that accurately describes how arguments are used and whether they are required or not. 'ArgumentGroups' don't affect usage or parsing. They just determine how the help lines are grouped.
For your code:
parser = argparse.ArgumentParser()
req_grp = parser.add_argument_group(title='Required Optional')
req_grp.add_argument("-f", "--file_name", required=True, help="enter name of .pdb file")
parser.add_argument("-bw", "--bin_width", default=.25, help="enter desired bin width in nanometers. default = .25")
parser.add_argument("-bn","--base_name", default="IDP", help="custom prefix for output file naming. default = IDP")
args = parser.parse_args()
"""
usage: stack26227536.py [-h] -f FILE_NAME [-bw BIN_WIDTH] [-bn BASE_NAME]
optional arguments:
-h, --help show this help message and exit
-bw BIN_WIDTH, --bin_width BIN_WIDTH
enter desired bin width in nanometers. default = .25
-bn BASE_NAME, --base_name BASE_NAME
custom prefix for output file naming. default = IDP
Required Optional:
-f FILE_NAME, --file_name FILE_NAME
enter name of .pdb file
"""
Compare this with the help produced by dropping the -f flag:
usage: stack26227536.py [-h] [-bw BIN_WIDTH] [-bn BASE_NAME] file_name
positional arguments:
file_name enter name of .pdb file
optional arguments:
-h, --help show this help message and exit
-bw BIN_WIDTH, --bin_width BIN_WIDTH
enter desired bin width in nanometers. default = .25
-bn BASE_NAME, --base_name BASE_NAME
custom prefix for output file naming. default = IDP

Related

argparse: Ignore positional arguments if a flag is set?

I want to provide the command with 3 arguments: <version>, <input file> and <output file> under normal usage. Except there's a specific flag --init, which will basically run the program without any input and output file specifications required.
I would therefore ideally have a command which usage is:
py program.py --init
OR
py program.py <version> <input file> <output file>
However, since positional arguments are always required (because all 3 are required in any other circumstance than --init), there seems to be no way to cleanly get this syntax and all I could think of would be to make the 3 positional arguments into an optional flag, raise an exception if the optional flag isn't there when --init is not called. And that all seems ugly.
My code so far:
def get_args():
parser = argparse.ArgumentParser(description="Tool for FOO-ing a BAR.")
parser.add_argument(dest="version", help="The version.")
parser.add_argument(dest="input", help="The input file.")
parser.add_argument(dest="output", help="The output file.")
parser.add_argument("-i", "--init", dest="init", action="store_true", help="Foo Init.")
return parser.parse_args()
To Clarify:
Either all 3 arguments (<version> <input> <output>) MUST be specified.
OR
The program is only ran with --init flag and 0 arguments should be specified.
The program should NOT accept with 0, 1 or 2 arguments specified (without the --init flag).
You can define your own action class:
class init_action(argparse.Action):
def __init__(self, option_strings, dest, **kwargs):
return super().__init__(option_strings, dest, nargs=0, default=argparse.SUPPRESS, **kwargs)
def __call__(self, parser, namespace, values, option_string, **kwargs):
# Do whatever should be done here
parser.exit()
def get_args():
parser = argparse.ArgumentParser(description="Tool for FOO-ing a BAR.")
parser.add_argument(dest="version", help="The version.")
parser.add_argument(dest="input", help="The input file.")
parser.add_argument(dest="output", help="The output file.")
parser.add_argument("-i", "--init", action=init_action, help="Foo Init.")
return parser.parse_args()
I had the exact same problem, and fixed it by adding nargs="?" to all my positional arguments
def get_args():
parser = argparse.ArgumentParser(description="Tool for FOO-ing a BAR.")
parser.add_argument(dest="version", nargs="?", help="The version.")
parser.add_argument(dest="input", nargs="?", help="The input file.")
parser.add_argument(dest="output", nargs="?", help="The output file.")
parser.add_argument("-i", "--init", dest="init", action="store_true", help="Foo Init.")
return parser.parse_args()
but I'm not completely satisfied with it since when you run
py program.py -h
the synopsys is printed as
usage: program.py [-h] [--init] [version] [input] [output]
while I would want it to be
usage: program.py [-h] [--init] version input output
since version, input and output are required, just that they are not required when you use the --init flag.
Another downside is that you do not get the help text printed if you do not provide the positional arguments correctly.
I think it is sad that argparse have no way to set ignore_positional_args=True or something on flags. Especially since this property already is true for the -h flag. At least I'm not able to find a way to do it.
EDIT:
I actually found a way to better way to do it, but put it in a separate answer because I think this can be a useful approach as well.
parser.add_argument has a default parameter (docs) which can be used here for version, input and output parameters. Now you won't need third init param
One possible solution is to use sys.argv to determine whether the flag is enabled.
Using your example,
import sys
if '--init' not in sys.argv:
parser.add_argument(dest="version", help="The version.")
parser.add_argument(dest="input", help="The input file.")
parser.add_argument(dest="output", help="The output file.")
Argparse will allow you to run the program with either --init flag without any arguments or force you to include other arguments if --init flag is not present.
One downside to this approach is that you will see the following output, when you run py program.py -h:
usage: program.py [--init] version input output
positional arguments:
version - The version.
input - The input file.
output - The output file.
optional arguments:
--init - ...
which makes it hard for the user to understand the logic unless you clarify it in the description in the help section.

python - how to accept arguments via command line using ArgumentParser

Background Information
I am writing a python script that will contain a set of methods that will be triggered via the command line. Most functions will accept one or two parameters.
Problem
I've been reading up about the ArgumentParser but it's not clear to me how to write my code so that a function can be triggered using the "-" or "--" notation, and also ensure that if / when a specific function is invoked, the user is passing the correct number of arguments and type.
Code
Sample function inside the script:
def restore_db_dump(dump, dest_db):
"""restore src (dumpfile) to dest (name of database)"""
popen = subprocess.Popen(['psql', '-U', 'postgres', '-c', 'CREATE DATABASE ' + dest_db], stdout=subprocess.PIPE, universal_newlines=True)
print popen.communicate()
popen.stdout.close()
popen.wait()
popen = subprocess.Popen(['pg_restore','-U', 'postgres', '-j', '2', '-d', 'provisioning', '/tmp/'+ dump + '.sql' ], stdout=subprocess.PIPE, u
niversal_newlines=True)
print popen.communicate()
popen.stdout.close()
popen.wait()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-r', '--restore', dest='restoredbname',action='store_const', const=restore_dump, help='Restore specified dump file as dbname. Must supply <pathtodumpfile> and <dbname>')
args = parser.parse_args()
if __name__ == '__main__':
main()
Code Execution
The help system seems to be working as you can see below, but I don't know how to write logic that forces / checks to see if "restore_dump" is triggered, the user is passing the correct parameters:
lab-1:/tmp# ./test-db.py -h
usage: test-db.py [-h] [-r]
optional arguments:
-h, --help show this help message and exit
-r, --restore Restore specified dump file as dbname. Must supply
<pathtodumpfile> and <dbname>
Question
Can someone point me in the right direction about how to add logic that will check when the restore_db_dump file is called, the right number of parameters are passed?
As far as how to "link" the -r argument so that it triggers the right function, I saw another post here on stackoverflow so I'm going to check that out.
THanks.
EDIT 1:
I forgot to mention that I'm currently reading: https://docs.python.org/2.7/library/argparse.html - 15.4.1. Example
But it's not clear to me how to apply this to my code. It seems that in the case of the sum function the order of the parameters is the integers first and then the function name later.
In my case, I would like the function name first (as an optional arg) and then the parameters required by the function to follow.)
EDIT 2:
Changed the code to look like this:
def main():
parser = argparse.ArgumentParser()
parse = argparse.ArgumentParser(prog='test-db.py')
parser.add_argument('-r', '--restore', nargs=2, help='Restore specified dump file as dbname. Must supply <pathtodumpfile> and <dbname>')
args = parser.parse_args()
if args.restore:
restore_db_dump(args.restore[0], args.restore[1])
if __name__ == '__main__':
main()
And when I run it with one missing arg, it now correctly returns an error! Which is great!!!
But I'm wondering how to fix the help so it's more meaningful. It seems that for each argument, the system is showing the word "RESTORE". How do I change this so that its actually a useful message?
lab-1:/tmp# ./test-db.py -h
usage: test-db.py [-h] [-r RESTORE RESTORE]
optional arguments:
-h, --help show this help message and exit
-r RESTORE RESTORE, --restore RESTORE RESTORE
Restore specified dump file as dbname. Must supply
<pathtodumpfile> and <dbname>
Try the following:
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-r', '--restore', nargs=2,
metavar=('path-to-dump-file', 'db-name'),
help='Restore specified dump file as dbname. Must supply <pathtodumpfile> and <dbname>')
args = parser.parse_args()
if args.restore:
restore_db_dump(args.restore[0], args.restore[1])
if __name__ == '__main__':
main()
Notes:
I have removed const=, because it's not clear whether you want a default.
There is now a nargs=2 parameter, so the -r option requires two values to be given.
the metavar parameter sets the names of the arguments to -r in the help text.

python script with 1 or 3 positional arguments

I am writing a python script, it takes either 3 positional arguments (name, date, location, let's say) or 1 argument, which is a setup file which contains that information.
I know that I can use argparse and I can make the positional arguments optional with:
parser.add_argument('name_OR_setupFile')
parser.add_argument('date', nargs='?')
parser.add_argument('location', nargs='?')
and then I can error-check, to make sure that the user didn't do anything stupid
The problem is that now the help message will be very confusing, because it's unclear what the 1st argument really is. I'd LIKE a way to do this as two different add_argument lines, somehow, but I'm not sure how.
I also know that I could use a --setupFile argument, and make the three optional... but I'd rather not do that as well, if I don't have to.
A third option is to use:
parser.add_argument('ARGS', nargs='+', help='ARGS is either of the form setupFile, or name date location')
and then error check later...
ETA for clarification:
I want to be able to call the script with either:
python foo.py setupFile
or
python foo.py name date location
I want the help text to be something like:
usage:
foo.py setupFile
foo.py name date location
I think the clearest design using argparse is:
parser = argparse.ArgumentParser()
g = parser.add_mutually_exclusive_group()
g.add_argument('--setup','-s',metavar='FILE',help='your help')
g.add_argument('--name',nargs=3,metavar=('NAME','DATE','LOCATION'),hel
...: p='your help')
parser.print_help() produces:
usage: ipython3 [-h] [--setup FILE | --name NAME DATE LOCATION]
optional arguments:
-h, --help show this help message and exit
--setup FILE, -s FILE
your help
--name NAME DATE LOCATION
your help
I've handled the 1 or 3 arguments requirement with mutually exclusive optionals. And used metavar to add clarity to the arguments. (As noted in another recent question, metavar does not work well with positionals.)
Another option is to use subparsers. That still requires a key word like setup and name, only they are entered without the --. And the help structure for subparsers is quite different.
Not totally sure this is what you meant, but if I understand you correctly:
if __name__ =='__main__':
def dem_args(*args):
if len(args) == 1:
if os.path.isfile(args[0]):
#go file
else:
#error regarding this being a bad filename or nonexistent file
elif len(args) == 3:
#try to process / raise errors regarding name, date, location
else:
#error reg. wrong number of arguments, possible arguments are either this or that
Ok, this is what I'm currently doing. I'm putting this here for people to comment on, and in case it ends up being useful, for posterity.
I'm actually solving an additional problem here. The problem is actually a little bit more complicated than even I specified. Because there's actually 3 ways to run the program, and I want to be able to have a --help option for only give me the details for one type. So I want -h, -h 1 and -h 2 to all do different things.
My current code is:
import argparse
baseParser = argparse.ArgumentParser(add_help=False)
baseParser.add_argument('-f', '--foo', help ='foo argument')
baseParser.add_argument('-h', '--help', nargs='?' , const = 'all')
parser1 = argparse.ArgumentParser(parents = [baseParser], add_help=False)
parser1.add_argument('name', help='name argument (type 1)')
parser1.add_argument('date', help='date argument')
parser1.add_argument('location', help='location argument')
setupParser=argparse.ArgumentParser(parents = [baseParser],add_help=False)
setupParser.add_argument('setup', help='setup file')
parser2 = argparse.ArgumentParser(parents = [baseParser],add_help=False)
parser2.add_argument('name', help='name argument (type 2)')
parser2.add_argument('baa', help='sheep?')
realParser = argparse.ArgumentParser(parents=[baseParser], add_help=False)
realParser.add_argument('ARGS', nargs = '*', help = 'positional arguments')
args = realParser.parse_args()
if args.help:
if args.help == 'all':
print 'This product can be used in multiple ways:'
print 'setup'
setupParser.print_usage()
print 'type1'
parser1.print_usage()
print'type2'
parser2.print_usage()
print 'use help [type] for more details'
elif args.help=='setup':
setupParser.print_help()
elif args.help=='1':
parser1.print_help()
else:
parser2.print_help()
exit(0)
#actually parse the args in args.ARGS, and work with that

Argparse using optional argument with value in conjunction with positional argument

In my script I have, for simplicity, three arguments:
parser.add_argument("-c", "--compile")
parser.add_argument("--verbose",
help = "stores compilation results in specified log file as they come (default name: %(const)s)",
nargs = '?',
const = DEFAULT_LOG_FILE_NAME,
metavar = "LOGFILE_NAME")
parser.add_argument("path", nargs = "*")
-c and --verbose are both optional, as well as path, which is a positional argument. In addition, the argument to --verbose is also optional. If none is provided,
Say I want to combine these three in a single command.
I would run it as follows:
myscript.py -c --verbose path1 path2 path3
The problem here is that in this case, the script will interpret path1 as an argument to --verbose, unless I use --verbose=<log_name>. As far as I have been able to find, there is no way of restricting argparse to only allowing the = syntax instead of a space. I cannot count on my users understanding that either = must be used, or put --verbose as one of the last arguments.
How would I fix this? Any help is appreciated.
In this case, you are overloading --verbose to do 2 things: as an on/off flag, and as a log file option. Consider separating it into two different options: --verbose and --log=LOGFILE_NAME

Python: Allow the positional argument to be specified last or write it first in the help output when using argparse

This code
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('target', help='Specifiy who to attack!')
magic = ['fireball', 'heal', 'microwave']
parser.add_argument('-m', '--magic', nargs='*',
choices=magic,
help=('Magic'))
parsed_arguments = parser.parse_args()
Produces this help output
usage: Example.py [-h]
[-m [{fireball,heal,microwave} [{fireball,heal,microwave} ...]]]
target
positional arguments:
target Specifiy who to attack!
optional arguments:
-h, --help show this help message and exit
-m [{fireball,heal,microwave} [{fireball,heal,microwave} ...]], --magic [{fireball,heal,microwave} [{fireball,heal,microwave} ...]]
Magic
I think the help output is confusing and makes it look like the target should be specified last, which however does not work: python example.py -m fireball troll gives argument -m/--magic: invalid choice: 'troll'.
I realize the grammar of the language becomes ambiguous, but it would still be possible to tell that as there should exist one word (target) last in the sentence troll is not an argument to the -m option.
Questions:
Is there a way to let the positional argument be specified last without beating argparse to much?
Is there a way to let the argparse help output indicate that target should indeed be specified first?
As previously mentioned in a comment, the standard methods on POSIX systems to denote the end of options is to separate the arguments and options by --.
As for the second question: You might have to create your own HelpFormatter to achieve this, see formatter-class. You might be able to inherit from the default formatter and override only the necessary functions to generate the usage line.

Categories