I was wondering if it is possible to have a positional argument follow an argument with an optional parameter. Ideally the last argument entered into the command line would always apply toward 'testname'.
import argparse
parser = argparse.ArgumentParser(description='TAF')
parser.add_argument('-r','--release',nargs='?',dest='release',default='trunk')
parser.add_argument('testname',nargs='+')
args = parser.parse_args()
I would like both of these calls to have smoketest apply to testname, but the second one results in an error.
>> python TAF.py -r 1.0 smoketest
>> python TAF.py -r smoketest
TAF.py: error: too few arguments
I realize that moving the positional argument to the front would result in the correct behavior of the optional parameter, however this is not quite the format I am looking for. The choices flag looks like an attractive alternative, however it throws an error instead of ignoring the unmatched item.
EDIT:
I've found a hacky way around this. If anyone has a nicer solution I would appreciate it.
import argparse
parser = argparse.ArgumentParser(description='TAF')
parser.add_argument('-r','--release',nargs='?',dest='release',default='trunk')
parser.add_argument('testname',nargs=argparse.REMAINDER)
args = parser.parse_args()
if not args.testname:
args.testname = args.release
args.release = ''
As stated in the documentation:
'?'. One argument will be consumed from the command line if possible,
and produced as a single item. If no command-line argument is present,
the value from default will be produced. Note that for optional
arguments, there is an additional case - the option string is present
but not followed by a command-line argument. In this case the value
from const will be produced.
So, the behaviour you want is not obtainable using '?'. Probably you could write some hack using argparse.Action and meddling with the previous results.(1)
I think the better solution is to split the functionality of that option. Make it an option that requires an argument(but the option itself is optional) and add an option without argument that sets the release to 'trunk'. In this way you can obtain the same results without any hack. Also I think the interface is simpler.
In your example:
python TAF.py -r smoketest
It's quite clear that smoketest will be interpreted as an argument to -r. At least following unix conventions. If you want to keep nargs='?' then the user must use --:
$ python TAF.py -r -- sometest
Namespace(release=None, testname=['sometest']) #parsed result
(1) An idea on how to do this: check if the option has an argument. If it has one check if it is a valid test name. If so put into by hand into testname and set release to the default value. You'll also have to set a "flag" that tells you that this thing happened.
Now, before parsing sys.argv you must redirect sys.stderr. When doing the parsing you must catch SystemExit, check the stderr and see if the error was "too few arguments", check if the flag was set, if so ignore the error and continue running, otherwise you should reprint to the original stderr the error message and exit.
This approach does not look robust, and it's probably buggy.
Related
This is a program that already exists and I'm trying to extend it - so my hands are tied somewhat :-(.
I have a program where I want to add an option that takes an unknown number of values so I'm trying to use nargs='+'. I can spot when a value is not actually for my option and is a positional argument and I can then use setattr to set the positional argument - but argparse doesn't get a chance to find the positional argument itself so complains.
The syntax for the command, as show in arparse generated help text, is
command [--option value [value...]] positional
In theory this is possible if I did this instead
command positional [--option value [value...]]
This is precisely how examples, even in the argparse documentation, work but that is NOT how the command is currently used, NOT how users typically provide programs with options and NOT how argparse generated help text shows the expected syntax.
So it there a way to somehow both handle the positional but also tell argparse 'oh, I found this positional so no need to complain that it is missing'?
in command line if I run my program
python parse.py config=abc.txt factor_date=20151001 like this
I want the position of argument will be fixed. That means if I pass argument like below
python parse.py factor_date=20151001 config=abc.txt
it has to show error.
import sys
config_file=sys.argv[1]
factor_date = sys.argv[2]
argstring=""+config_file+" "+factor_date+""
arg_list = argstring.split(' ')
input={}
for arg in arg_list:
#x=arg.split("--")
key,val=arg.split("=")[0],arg.split("=")[1]
if key == "config":
input[key]=val
if key =="factor_date":
input[key]=val
print input
You can have a look at click. It let's you create command line interfaces pretty much effortlessly. It's bases on using decorators.
You should have a look at argparse. Your use case is for positional arguments. If you specify the name of the argument (optional arguments with argparse) then it does not make sense to force a specific order.
Still, when using positional arguments one could call the program with worng arguments, you will have to check by yourself the values provided by the user. However, you can force a type and it will automagically convert the strings, which in the case you describe would solve the problem.
I have a Python script that accepts one or more input files and produces one or more output files (sort of a compiler, translating one syntax into another)
In my argparse section, I have configured so that the list of input files option is "nargs='+'", so that it will show a "too few arguments" error if user provides zero input files.
At the same time, I want to have a "--version" option that will just print the current script version and exit. When this option is provided, everything else (if provided) is irrelevant and should be ignored.
Just like ArgumentParser automatically adds the "--help" option which works like this, how can I add a "--version" option without changing the nargs='+' mechanism?
Try the version action class. From the docs:
'version' - This expects a version= keyword argument in the add_argument() call, and prints version information and exits when invoked:
>>>
>>> import argparse
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('--version', action='version', version='%(prog)s 2.0')
>>> parser.parse_args(['--version'])
PROG 2.0
It behaves like the help (-h) except it displays the version parameter that you define with it (or lacking that a version value that you give the parser itself).
My script is accepting --full, --last and --check using ArgParse. If no option is provided, it just show the help message. But in that message, the parameters appear as optional.
usage: script.py [-h] [--full] [--last] [--check log_file]
If I use the keyword required, then the script will always expect the parameter, which is not correct.
usage: script.py [-h] --full --last --check log_file
So, how can I show something like:
usage: script.py [-h] (--full |--last |--check log_file)
Indicating that the help is optional but that at least one of those parameters is required.
On the question of customizing the usage:
The parser constructor takes a usage parameter. The immediate effect is to set an attribute:
parser = argparse.ArgumentParser( ... usage=custom_usage...)
print(parser.usage)
# should show None or the custom_usage string
Being a normal Python object attribute, you can change it after the parser was created.
usage_str = parser.format_usage()
The format_usage method ask the parser for create the usage that will be shown in the help (and error messages). If the parser.usage value is None, it formats it from the arguments. If a string, it is used as is (I think it fills in values like %(prog)s).
So you could write a usage string from scratch. Or you could set up the parser, get the current usage string, and edit that to suit your needs. Editing is most likely something you'd do during development, while testing the parser in an IDE. But it could be done on the fly.
A crude example:
In [441]: parser=argparse.ArgumentParser()
In [442]: g=parser.add_mutually_exclusive_group()
In [443]: g.add_argument('--foo')
In [444]: g.add_argument('--bar')
In [445]: ustr = parser.format_usage()
# 'usage: ipython3 [-h] [--foo FOO | --bar BAR]\n'
In [450]: parser.usage = ustr.replace('[','(').replace(']',')')
In [451]: parser.format_usage()
# 'usage: usage: ipython3 (-h) (--foo FOO | --bar BAR)\n'
I've replaced the [] with () (even on the -h :( ).
For now testing logical combinations of the args attributes is the best choice. Inside the parse_args functions the parser maintains a list (set actually) of arguments that it has seen. That is used to test for required arguments, and for mutually_exclusive_arguments, but it is not available outside that code.
For store_true (or false) arguments, just check their truth value. For others I like to test for the default None. If you use other default values, test accordingly. A nice thing about None is that the user cannot give you that value.
Perhaps the most general way to test for arguments is to count the number of attributes which are not None:
In [461]: args=argparse.Namespace(one=None, tow=2, three=None)
In [462]: ll = ['one','tow','three']
In [463]: sum([getattr(args,l,None) is not None for l in ll])
Out[463]: 1
0 means none are found; >0 at least one present; ==len(ll) all found; >1 violates mutually exclusivity; '==1' for required mutually exclusive.
As #doublep explained in his answer, if you want to use more than one option at a time:
Change the usage message manually to the one you want.
Add the following code from Python argparse: Make at least one argument required:
if not (args.full or args.last or args.check):
parse.error('[-] Error: DISPLAY_ERROR_MESSAGE')
You can use add_mutually_exclusive_group():
parser = argparse.ArgumentParser ()
group = parser.add_mutually_exclusive_group (required = True)
group.add_argument ('--foo')
group.add_argument ('--bar')
However, the main effect is that you won't be able to use more than one option at a time.
I have read a similar question asked on SO, which doesn't address my own problem. For illustration purpose, say I have a Python program using argpase that provides two subcommands: copy and resume:
prog copy src dest # src, dest positional, required
prog resume id # id positional, required
However, the most natural way to invoke the "copy" command is NOT explicitly give the copy subcommand, that is, I was hoping for:
prog src dest
will do the default copy action, while still keep the benefits of have two subparsers, each handles a different set of arguments. Is it possible with argparse package?
Formally there isn't. The subcommand argument is a required positional argument, where the 'choices' are the subparser names (and their aliases).
That's evident in the help message, were {cmd1,cmd} are shown as choices.
usage: ipython3 [-h] {cmd1,cmd2} ...
positional arguments:
{cmd1,cmd2}
optional arguments:
-h, --help show this help message and exit
The latest Python has a 'bug' that actually lets it be optional. In other words, it does not raise an error if you don't give any positionals. It's a bug because it changes previous behavior, and most people what it to be required. But even when it is optional, and cmd1 defined as the default, it won't run the cmd1 parser on the remaining arguments. And any 'positional' argument will be treated as a wrong command string.
I think the best you can do is define one or more positionals. One might have choices and default, and be optional (nargs='?'). The rest (or other) might has nargs='+'. By playing around with those options you could approximate the desired behavior, but it won't make use of the subparsers mechanism.
Another way to think about the issue, is to consider an input like
prog one two
Should it interpret that as prog src dest or prog cmd id. The only basis for saying it should be the former is the fact that one is not one of copy or resume. But it isn't that smart when it comes to handling 'choices'. It assigns a value based on position, and then tests whether it meets criteria like 'choices' or 'type' (ie. integer, float v string).