argparse doesn't check for positional arguments - python

I'm creating a script that takes both positional and optional arguments with argparse. I have gone through Doug's tutorial and the python Docs but can't find an answer.
parser = argparse.ArgumentParser(description='script to run')
parser.add_argument('inputFile', nargs='?', type=argparse.FileType('rt'),
parser.add_argument('inputString', action='store', nargs='?')
parser.add_argument('-option1', metavar='percent', type=float, action='store')
parser.add_argument('-option2', metavar='outFile1', type=argparse.FileType('w'),
parser.add_argument('-option3', action='store', default='<10',
args = parser.parse_args()
# rest of script.... blah blah
As you can see, I want 2 positional and 3 optional arguments. However, when I try to run it in the terminal, it doesn't check for the positionals!
If I try: python script.py inputfile
it will run normally and output error halfway through the script when it cannot find a value for inputString.
If I try: python script.py xxx ; the output is:
usage script.py [-h] [-option1] [-option2] [-option3]
Can anyone explain why it doesn't check for the positional arguments?

Your problem is that you're specifying nargs='?'. From 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.
If you leave out the nargs='?' then the argument will be required, and argparse will display an error if it is not provided. A single argument is consumed if action='store' (the default).
You can also specify nargs=1; the difference is that this produces a list containing one item, as opposed to the item itself. See the documentation for more ways you can use nargs.

Works for me.
Code:
#!/usr/bin/python
import argparse
parser=argparse.ArgumentParser(description='script to run')
parser.add_argument('inputFile', nargs='?', type=argparse.FileType('rt'))
parser.add_argument('inputString', action='store', nargs='?')
parser.add_argument('-option1', metavar='percent', type=float, action='store')
parser.add_argument('-option2', metavar='outFile1', type=argparse.FileType('w'))
parser.add_argument('-option3', action='store', default='<10')
args = parser.parse_args()
Execution:
# ./blah.py -h
usage: blah.py [-h] [-option1 percent] [-option2 outFile1] [-option3 OPTION3]
[inputFile] [inputString]
script to run
positional arguments:
inputFile
inputString
optional arguments:
-h, --help show this help message and exit
-option1 percent
-option2 outFile1
-option3 OPTION3
Did you overlook the second line in the argument list?

It works as expected. There is no inputString if you run it as script.py inputfile (only one argument is given, but inputString is the second argument).
narg='?' means that the argument is optional (they are surrounded by [] in the help message).

Related

argparse claims argument althoug default value specified [duplicate]

I have a script which is meant to be used like this:
usage: installer.py dir [-h] [-v]
dir is a positional argument which is defined like this:
parser.add_argument('dir', default=os.getcwd())
I want the dir to be optional: when it's not specified it should just be cwd.
Unfortunately when I don't specify the dir argument, I get Error: Too few arguments.
Use nargs='?' (or nargs='*' if you need more than one dir)
parser.add_argument('dir', nargs='?', default=os.getcwd())
extended example:
>>> import os, argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-v', action='store_true')
_StoreTrueAction(option_strings=['-v'], dest='v', nargs=0, const=True, default=False, type=None, choices=None, help=None, metavar=None)
>>> parser.add_argument('dir', nargs='?', default=os.getcwd())
_StoreAction(option_strings=[], dest='dir', nargs='?', const=None, default='/home/vinay', type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args('somedir -v'.split())
Namespace(dir='somedir', v=True)
>>> parser.parse_args('-v'.split())
Namespace(dir='/home/vinay', v=True)
>>> parser.parse_args(''.split())
Namespace(dir='/home/vinay', v=False)
>>> parser.parse_args(['somedir'])
Namespace(dir='somedir', v=False)
>>> parser.parse_args('somedir -h -v'.split())
usage: [-h] [-v] [dir]
positional arguments:
dir
optional arguments:
-h, --help show this help message and exit
-v
As an extension to #VinaySajip answer. There are additional nargs worth mentioning.
parser.add_argument('dir', nargs=1, default=os.getcwd())
N (an integer). N arguments from the command line will be gathered together into a list
parser.add_argument('dir', nargs='*', default=os.getcwd())
'*'. All command-line arguments present are gathered into a list. Note that it generally doesn't make much sense to have more than one positional argument with nargs='*', but multiple optional arguments with nargs='*' is possible.
parser.add_argument('dir', nargs='+', default=os.getcwd())
'+'. Just like '*', all command-line args present are gathered into a list. Additionally, an error message will be generated if there wasn’t at least one command-line argument present.
parser.add_argument('dir', nargs=argparse.REMAINDER, default=os.getcwd())
argparse.REMAINDER. All the remaining command-line arguments are gathered into a list. This is commonly useful for command line utilities that dispatch to other command line utilities
If the nargs keyword argument is not provided, the number of arguments consumed is determined by the action. Generally this means a single command-line argument will be consumed and a single item (not a list) will be produced.
Edit (copied from a comment by #Acumenus) nargs='?' The docs say: '?'. 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.
Short Answer
As already shown in the previous two answers, you can accept an optional positional argument with nargs='?'. You could also turn the argument directly into a Path type and/or shorten the cwd to . if you wanted to:
myfile.py
import argparse
import pathlib
parser = argparse.ArgumentParser()
parser.add_argument("dir", nargs="?", default=".", type=pathlib.Path)
parsed_args = parser.parse_args()
print("Installing to", parsed_args.dir.resolve())
$ python myfile.py
Installing to /users/myname/myfolder
$ python myfile.py /usr/bin/
Installing to /usr/bin
Longer answer
Since you also mention the flag-style True/False options -h and -v in your question, these examples may be of use:
Flags (e.g. -v)
We might refer to optional options that take no arguments as "flags". With flags, we only care about whether they are given or not. -h is a flag that argparse adds automatically (along with the longer version --help) so we shouldn't really override that. If we consider -v then,
myfile.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"-v",
"--version",
action="store_true")
parsed_args = parser.parse_args()
if parsed_args.version:
print("version flag given")
else:
print("version flag not given")
Note that the second argument to add_argument() is a longer name for the option. It is not mandatory but it does make your subsequent code more readable (parsed_args.version vs parsed_args.v) and makes calls to your installer more explicit.
$ python myfile.py -v
version flag given
$ python myfile.py --version
version flag given
$ python myfile.py
version flag not given
Optional arguments (e.g. --installdir /usr/bin/)
One could argue that, in your case, you would be better off with an optional argument rather than a positional one.
myfile.py
import argparse
import pathlib
parser = argparse.ArgumentParser()
parser.add_argument(
"-i",
"--installdir", # Optional (but recommended) long version
type=pathlib.Path,
default="/bin"
)
parsed_args = parser.parse_args()
print("Installing to", parsed_args.installdir)
$ python myfile.py -i /usr/bin/
Installing to /usr/bin
$ python myfile.py --installdir /usr/bin/
Installing to /usr/bin
$ python myfile.py
Installing to /bin
parser.add_argument also has a switch required. You can use required=False.
Here is a sample snippet with Python 2.7:
parser = argparse.ArgumentParser(description='get dir')
parser.add_argument('--dir', type=str, help='dir', default=os.getcwd(), required=False)
args = parser.parse_args()

argparse: default for positional argument not working?

I have:
from argparse import ArgumentParser
parser = ArgumentParser(description='Test')
parser.add_argument("command",
help="the command to be executed",
choices=["dump", "delete", "update", "set"],
default="set")
parser.parse_args()
But when I run: python test.py I get:
usage: test.py [-h] {dump,delete,update,set}
test.py: error: too few arguments
Maybe I am just blind sighted today; but I can't figure what should be wrong about my input. Or is this simply not possible with argparse?
For default keyword argument to work, you have to add nargs='*' like below:
parser.add_argument("command",
help="the command to be executed",
choices=["dump", "delete", "update", "set"],
nargs='?',
default="set"
)
See https://docs.python.org/2/library/argparse.html#default for more information :)
Edit by OP: nargs='*' allows for multiple commands to be entered. Thus changed to nargs='?' as I am looking for exactly one command to be entered.

python argparse choices with a default choice

I'm trying to use argparse in a Python 3 application where there's an explicit list of choices, but a default if none are specified.
The code I have is:
parser.add_argument('--list', default='all', choices=['servers', 'storage', 'all'], help='list servers, storage, or both (default: %(default)s)')
args = parser.parse_args()
print(vars(args))
However, when I run this I get the following with an option:
$ python3 ./myapp.py --list all
{'list': 'all'}
Or without an option:
$ python3 ./myapp.py --list
usage: myapp.py [-h] [--list {servers,storage,all}]
myapp.py: error: argument --list: expected one argument
Am I missing something here? Or can I not have a default with choices specified?
Pass the nargs and const arguments to add_argument:
parser.add_argument('--list',
default='all',
const='all',
nargs='?',
choices=['servers', 'storage', 'all'],
help='list servers, storage, or both (default: %(default)s)')
If you want to know if --list was passed without an argument, remove the const argument, and check if args.list is None.
Documention:
nargs with '?'
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.
const
When add_argument() is called with option strings (like -f or --foo) and nargs='?'. This creates an optional argument that can be followed by zero or one command-line arguments. When parsing the command line, if the option string is encountered with no command-line argument following it, the value of const will be assumed instead. See the nargs description for examples.
Thanks #ShadowRanger. Subcommands is exactly what I need, combined with nargs and const. The following works:
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers()
parser_list = subparser.add_parser('list')
parser_list.add_argument('list_type', default='all', const='all', nargs='?', choices=['all', 'servers', 'storage'])
parser_create = subparser.add_parser('create')
parser_create.add_argument('create_type', default='server', const='server', nargs='?', choices=['server', 'storage'])
args = parser.parse_args()
pprint(vars(args))
$ python3 ./myapp.py -h
usage: dotool.py [-h] {list,create} ...
Digital Ocean tool
positional arguments:
{list,create}
optional arguments:
-h, --help show this help message and exit
list option alone:
$ python3 ./myapp.py list
{'list_type': 'all'}
List option with a parameter:
$ python3 ./myapp.py list servers
{'list_type': 'servers'}

Can argparse handle more than one positional command line arguements?

In the examples I saw,argparse all handles just one positional commandline argument, and arbitrarily number of optional commandline arguments. So I wonder if it can handle more than one positional arguments? If yes, how does it do and how are the commandline arguments specified? Thanks.
So I wonder if it can handle more than one positional arguments? If yes, how does it do and how are the commandline arguments specified?
Well, the very first example in the argparse documentation handles multiple non-option arguments. So that's probably a good place to start.
Here's a trivial example; place the following in argtest.py:
import argparse
def parse_args():
p = argparse.ArgumentParser()
p.add_argument('--option1', '-1')
p.add_argument('--option2', '-2')
p.add_argument('commandline1')
p.add_argument('commandline2')
return p.parse_args()
if __name__ == '__main__':
p = parse_args()
print p
And then:
python argtest.py
usage: argtest.py [-h] [--option1 OPTION1] [--option2 OPTION2]
commandline1 commandline2
argtest.py: error: too few arguments
Or:
python argtest.py hello world
Namespace(commandline1='hello', commandline2='world', option1=None, option2=None)
yes,
parser = argparse.ArgumentParser(description="my_example")
# these are your positional arguments
parser.add_argument("first", type=int, nargs=1)
parser.add_argument("second", type=str, nargs=1)
# this is your optional one
parser.add_argument("-r", this is an optional argument)
args = parser.parse_args()
parser.print_help()
print args.first args.second
On the command line
$ python argtest1.py 5 'c' -r R
Output:
usage: argtest1.py [-h] [-r R] first second
my_example
positional arguments:
first
second
optional arguments:
-h, --help show this help message and exit
-r R this is an optional argument
[5] ['c']
Note that you can put the positional arguments after the optional one on the command line: python argtest1.py -r R 5 'c'. You could also do this: python argtest1.py 5 -r R 'c'. Obviously you can't do this python argtest1.py -r R 'c' 5.

How to define a mutually exclusive group of two positional arguments?

I would like to use argparse to make some code to be used in the following two ways:
./tester.py all
./tester.py name someprocess
i.e. either all is specified OR name with some additional string.
I have tried to implement as follows:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('all', action='store_true', \
help = "Stops all processes")
group.add_argument('name', \
help = "Stops the named process")
print parser.parse_args()
which gives me an error
ValueError: mutually exclusive arguments must be optional
Any idea how to do it right? I also would like to avoid sub parsers in this case.
The question is a year old, but since all the answers suggest a different syntax, I'll give something closer to the OP.
First, the problems with the OP code:
A positional store_true does not make sense (even if it is allowed). It requires no arguments, so it is always True. Giving an 'all' will produce error: unrecognized arguments: all.
The other argument takes one value and assigns it to the name attribute. It does not accept an additional process value.
Regarding the mutually_exclusive_group. That error message is raised even before parse_args. For such a group to make sense, all the alternatives have to be optional. That means either having a -- flag, or be a postional with nargs equal to ? or *. And doesn't make sense to have more than one such positional in the group.
The simplest alternative to using --all and --name, would be something like this:
p=argparse.ArgumentParser()
p.add_argument('mode', choices=['all','name'])
p.add_argument('process',nargs='?')
def foo(args):
if args.mode == 'all' and args.process:
pass # can ignore the process value or raise a error
if args.mode == 'name' and args.process is None:
p.error('name mode requires a process')
args = p.parse_args()
foo(args) # now test the namespace for correct `process` argument.
Accepted namespaces would look like:
Namespace(mode='name', process='process1')
Namespace(mode='all', process=None)
choices imitates the behavior of a subparsers argument. Doing your own tests after parse_args is often simpler than making argparse do something special.
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-a','--all', action='store_true', \
help = "Stops all processes")
group.add_argument('-n','--name', \
help = "Stops the named process")
print parser.parse_args()
./tester.py -h
usage: zx.py [-h] (-a | -n NAME)
optional arguments:
-h, --help show this help message and exit
-a, --all Stops all processes
-n NAME, --name NAME Stops the named process
"OR name with some additional string."
positional argument cannot take additional string
I think the best solution for you is (named test.py):
import argparse
p = argparse.ArgumentParser()
meg = p.add_mutually_exclusive_group()
meg.add_argument('-a', '--all', action='store_true', default=None)
meg.add_argument('-n', '--name', nargs='+')
print p.parse_args([])
print p.parse_args(['-a'])
print p.parse_args('--name process'.split())
print p.parse_args('--name process1 process2'.split())
print p.parse_args('--all --name process1'.split())
$ python test.py
Namespace(all=None, name=None)
Namespace(all=True, name=None)
Namespace(all=None, name=['process'])
Namespace(all=None, name=['process1', 'process2'])
usage: t2.py [-h] [-a | -n NAME [NAME ...]]
t2.py: error: argument -n/--name: not allowed with argument -a/--all
I would agree that this looks exactly like a sub-parser problem, and that if you don't want to make it an optional argument by using --all and --name, one suggestion from me would be just to ignore the all and name altogether, and use the following semantics:
If tester.py is called without any arguments, stop all process.
If tester.py is called with some arguments, stop only those processes.
Which can be done using:
import argparse, sys
parser = argparse.ArgumentParser()
parser.add_argument('processes', nargs='*')
parsed = parser.parse(sys.argv[1:])
print parsed
which will behave as follows:
$ python tester.py
Namespace(processes=[])
$ python tester.py proc1
Namespace(processes=['proc1'])
Or, if you insist on your own syntax, you can create a custom class. And actually you're not having a "mutually exclusive group" case, since I assume if all is specified, you will ignore the rest of the arguments (even when name is one of the other arguments), and when name is specified, anything else after that will be regarded as processes' name.
import argparse
import sys
class AllOrName(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if len(values)==0:
raise argparse.ArgumentError(self, 'too few arguments')
if values[0]=='all':
setattr(namespace, 'all', True)
elif values[0]=='name':
if len(values)==1:
raise argparse.ArgumentError(self, 'please specify at least one process name')
setattr(namespace, 'name', values[1:])
else:
raise argparse.ArgumentError(self, 'only "all" or "name" should be specified')
parser = argparse.ArgumentParser()
parser.add_argument('processes', nargs='*', action=AllOrName)
parsed = parser.parse_args(sys.argv[1:])
print parsed
with the following behaviour:
$ python argparse_test.py name
usage: argparse_test.py [-h] [processes [processes ...]]
argparse_test.py: error: argument processes: please specify at least one process name
$ python argparse_test.py name proc1
Namespace(name=['proc1'], processes=None)
$ python argparse_test.py all
Namespace(all=True, processes=None)
$ python argparse_test.py host
usage: argparse_test.py [-h] [processes [processes ...]]
argparse_test.py: error: argument processes: only "all" or "name" should be specified
$ python argparse_test.py
usage: argparse_test.py [-h] [processes [processes ...]]
argparse_test.py: error: argument processes: too few arguments
This is probably what you're looking for:
group.add_argument('--all', dest=is_all, action='store_true')
group.add_argument('--name', dest=names, nargs='+')
Passing --name will then require at list one value and store them as a list.

Categories