how to allow -non_number as arguments in python argparse - python

python test.py --arg -foo -bar
test.py: error: argument --arg: expected at least one argument
python test.py --arg -8
['-8']
How do I allow -non_number to work with argparse?
Is there a way to disable short arguments?

Call it like this:
python test.py --arg='-foo'
To allow specifying multiple:
parser.add_argument('--arg', action='append')
# call like python test.py --arg=-foo --arg=-bar

I think you're looking for the nargs parameter to argparser.
parser.add_argument('--arg', nargs='?')
At the moment, --arg is interpreting the value '-8' as the input, whereas it thinks '-f' (with parameters 'oo') is a new argument.
Alternatively, you could use action='store_true', which will represent the presence or absence of the argument with a boolean.
parser.add_argument('--arg', action='store_true')
https://docs.python.org/3/library/argparse.html#nargs

Related

How to make argparse treat all arguments as positional?

How can I make an argparse parser treat all arguments as positional, even the ones that look like options? For example, with this definition:
parser.add_argument('cmd', nargs='*', help='The command to run')
I want to be able to run
prog.py mycomand --foo arg
and have ['mycomand', '--foo', 'arg'] be captured as the cmd argument.
It turned out I just needed to replace nargs='*' with nargs=argparse.REMAINDER. From the docs:
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.
This also works, but is less convenient:
prog.py mycomand -- --foo arg

Configure argparse to accept quoted arguments

I am writing a program which, among other things, allows the user to specify through an argument a module to load (and then use to perform actions). I am trying to set up a way to easily pass arguments through to this inner module, and I was attempting to use ArgParse's action='append' to have it build a list of arguments that I would then pass through.
Here is a basic layout of the arguments that I am using
parser.add_argument('-M', '--module',
help="Module to run on changed files - should be in format MODULE:CLASS\n\
Specified class must have function with the signature run(src, dest)\
and return 0 upon success",
required=True)
parser.add_argument('-A', '--module_args',
help="Arg to be passed through to the specified module",
action='append',
default=[])
However - if I then try to run this program with python my_program -M module:class -A "-f filename" (where I would like to pass through the -f filename to my module) it seems to be parsing the -f as its own argument (and I get the error my_program: error: argument -A/--module_args: expected one argument
Any ideas?
With:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-M', '--module',
help="Module to run on changed files - should be in format MODULE:CLASS\n\
Specified class must have function with the signature run(src, dest)\
and return 0 upon success",
)
parser.add_argument('-A', '--module_args',
help="Arg to be passed through to the specified module",
action='append',
default=[])
import sys
print(sys.argv)
print(parser.parse_args())
I get:
1028:~/mypy$ python stack45146728.py -M module:class -A "-f filename"
['stack45146728.py', '-M', 'module:class', '-A', '-f filename']
Namespace(module='module:class', module_args=['-f filename'])
This is using a linux shell. The quoted string remains one string, as seen in the sys.argv, and is properly interpreted as an argument to -A.
Without the quotes the -f is separate and interpreted as a flag.
1028:~/mypy$ python stack45146728.py -M module:class -A -f filename
['stack45146728.py', '-M', 'module:class', '-A', '-f', 'filename']
usage: stack45146728.py [-h] [-M MODULE] [-A MODULE_ARGS]
stack45146728.py: error: argument -A/--module_args: expected one argument
Are you using windows or some other OS/shell that doesn't handle quotes the same way?
In Argparse `append` not working as expected
you asked about a slightly different command line:
1032:~/mypy$ python stack45146728.py -A "-k filepath" -A "-t"
['stack45146728.py', '-A', '-k filepath', '-A', '-t']
usage: stack45146728.py [-h] [-M MODULE] [-A MODULE_ARGS]
stack45146728.py: error: argument -A/--module_args: expected one argument
As I already noted -k filepath is passed through as one string. Because of the space, argparse does not interpret that as a flag. But it does interpret the bare '-t' as a flag.
There was a bug/issue about the possibility of interpreting undefined '-xxx' strings as arguments instead of flags. I'd have to look that up to see whether anything made it into to production.
Details of how strings are categorized as flag or argument can be found in argparse.ArgumentParser._parse_optional method. It contains a comment:
# if it contains a space, it was meant to be a positional
if ' ' in arg_string:
return None
http://bugs.python.org/issue9334 argparse does not accept options taking arguments beginning with dash (regression from optparse) is an old and long bug/issue on the topic.
The solution is to accept arbitrary arguments - there's an example in argparse's doc here:
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:
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('--foo')
>>> parser.add_argument('command')
>>> parser.add_argument('args', nargs=argparse.REMAINDER)
>>> print(parser.parse_args('--foo B cmd --arg1 XX ZZ'.split()))
Namespace(args=['--arg1', 'XX', 'ZZ'], command='cmd', foo='B')

How to pass and parse a list of strings from command line with argparse.ArgumentParser in Python?

I want to pass a list of names into my program written in Python from console. For instance, I would like to use a way similar to this (I know it shouldn't work because of bash):
$ python myprog.py -n name1 name2
So, I tried this code:
# myprog.py
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-n', '--names-list', default=[])
args = parser.parse_args()
print(args.names_list) # I need ['name1', 'name2'] here
That led to the error:
usage: myprog.py [-h] [-n NAMES_LIST]
myprog.py: error: unrecognized arguments: name2
I know I could pass the names with quotes "name1 name2" and split it in my code args.names_list.split(). But I'm curious, is there a better way to pass the list of strings via argparse module.
Any ideas would be appreciated.
Thanks!
You need to define --names-list to take an arbitrary number of arguments.
parser.add_argument('-n', '--names-list', nargs='+', default=[])
Note that options with arbitrary number of arguments don't typically play well with positional arguments, though:
# Is this 4 arguments to -n, or
# 3 arguments and a single positional argument, or ...
myprog.py -n a b c d
You need to use nargs:
parser.add_argument('-n', '--names-list', nargs="*")
https://docs.python.org/3/library/argparse.html#nargs
parser.add_argument('-n', '--names-list', default=[], nargs='+')

Intrepret parameter starting with a hyphen as a string [duplicate]

I would like to parse a required, positional argument containing a comma-separated list of integers. If the first integer contains a leading minus ('-') sign, argparse complains:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional')
parser.add_argument('-t', '--test', action='store_true')
opts = parser.parse_args()
print opts
$ python example.py --test 1,2,3,4
Namespace(positional='1,2,3,4', test=True)
$ python example.py --test -1,2,3,4
usage: example.py [-h] [-t] positional
example.py: error: too few arguments
$ python example.py --test "-1,2,3,4"
usage: example.py [-h] [-t] positional
example.py: error: too few arguments
I've seen people suggest using some other character besides - as the flag character, but I'd rather not do that. Is there another way to configure argparse to allow both --test and -1,2,3,4 as valid arguments?
You need to insert a -- into your command-line arguments:
$ python example.py --test -- -1,2,3,4
Namespace(positional='-1,2,3,4', test=True)
The double-dash stops argparse looking for any more optional switches; it's the defacto standard way of handling exactly this use case for command-line tools.
From the documentation:
The parse_args() method attempts to give errors whenever the user has
clearly made a mistake, but some situations are inherently ambiguous.
For example, the command-line argument -1 could either be an attempt
to specify an option or an attempt to provide a positional argument.
The parse_args() method is cautious here: positional arguments may
only begin with - if they look like negative numbers and there are no
options in the parser that look like negative numbers:
Since -1,2,3,4 does not look like a negative number you must "escape" it with the -- as in most *nix systems.
An other solution would be to use nargs for the positional and pass the numbers as space separated:
#test.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional', nargs='*') #'+' for one or more numbers
print parser.parse_args()
Output:
$ python test.py -1 2 3 -4 5 6
Namespace(positional=['-1', '2', '3', '-4', '5', '6'])
A third way to obtain what you want is to use parse_known_args instead of parse_args.
You do not add the positional argument to the parser and parse it manually instead:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--test', action='store_true')
parsed, args = parser.parse_known_args()
print parsed
print args
Result:
$ python test.py --test -1,2,3,4
Namespace(test=True)
['-1,2,3,4']
This has the disadvantage that the help text will be less informative.

An exception in Argparse

I have the following code for reading the arguments from a file and process them using argparse, but I am getting an error, why is this the case?
import argparse
from ConfigParser import ConfigParser
import shlex
parser = argparse.ArgumentParser(description='Short sample app',
fromfile_prefix_chars='#')
parser.add_argument('--abool', action="store_true", default=False)
parser.add_argument('--bunit', action="store", dest="bunit",type=int)
parser.add_argument('--cpath', action="store", dest="c", type=str)
print parser.parse_args(['#argparse_fromfile_prefix_chars.txt']) #name of the file is argparse_fromfile_prefix_chars.txt
Error:
usage: -c [-h] [--abool] [--bunit BUNIT] [--cpath C]
-c: error: unrecognized arguments: --bunit 289 --cpath /path/to/file.txt
To exit: use 'exit', 'quit', or Ctrl-D.
Contents of the file argparse_fromfile_prefix_chars.txt
--abool
--bunit 289
--cpath /path/to/file.txt
argparse expects arguments from files to be one per line. Meaning the whole line is one quoted argument. So your current args file is interpreted as
python a.py '--abool' '--bunit 289' '--cpath /path/to/file.txt'
which causes the error. Instead, your args file should look like this
--abool
--bunit
289
--cpath
/path/to/file.txt
The documentation for fromfile_prefix_chars states:
Arguments read from a file must by default be one per line (but see
also convert_arg_line_to_args()) and are treated as if they were in
the same place as the original file referencing argument on the
command line.
Note that one argument does not mean one option followed by all its arguments. It means a command line argument. Currently the whole lines are interpreted as if they were a single argument.
In other words your file should look like:
--abool
--bunit
289
--cpath
/path/to/file.txt
Alternatively you can override the convert_arg_line_to_args() method to parse the file in an other way. The documentation already provides an implementation that parses white-space separated arguments instead of line-separated arguments:
def convert_arg_line_to_args(self, arg_line):
# consider using shlex.split() instead of arg_line.split()
for arg in arg_line.split():
if not arg.strip():
continue
yield arg
I believe you can either subclass ArgumentParser and reimplement this method, or, probably, even setting the attribute on an ArgumentParser instance should work.
For some reason the default implementation of convert_arg_line_to_args doesn't work properly:
$echo '--abool
--bunit
289
--cpath
/here/is/a/path
' > file.txt
$cat test_argparse.py
import argparse
parser = argparse.ArgumentParser(fromfile_prefix_chars='#')
parser.add_argument('--abool', action='store_true')
parser.add_argument('--bunit', type=int)
parser.add_argument('--cpath')
print(parser.parse_args(['#file.txt']))
$python test_argparse.py
usage: test_argparse.py [-h] [--abool] [--bunit BUNIT] [--cpath CPATH]
test_argparse.py: error: unrecognized arguments:
However if you use the implementation above it works:
$cat test_argparse.py
import argparse
def convert_arg_line_to_args(arg_line):
for arg in arg_line.split():
if not arg.strip():
continue
yield arg.strip()
parser = argparse.ArgumentParser(fromfile_prefix_chars='#')
parser.add_argument('--abool', action='store_true')
parser.add_argument('--bunit', type=int)
parser.add_argument('--cpath')
parser.convert_arg_line_to_args = convert_arg_line_to_args
print(parser.parse_args(['#file.txt']))
$python test_argparse.py
Namespace(abool=True, bunit=289, cpath='/here/is/a/path')
An other workaround is to use the --option=argument syntax:
--abool
--bunit=289
--cpath=/the/path/to/file.txt
However this will not work when an option has more than one argument. In such a case you have to use a different implementation of convert_arg_line_to_args.
Trying to debug, it seems like the convert_line_arg_to_args gets called with an empty string which gets added to the arguments, and the empty string is considered an argument (which isn't defined).
The problem is that there are two newlines at the end of the file.
In fact if you create the file without this double newline at the end, it works:
$echo -n '--abool
--bunit
289
--cpath
/here/is/a/path
' > file.txt
$python test_argparse.py
Namespace(abool=True, bunit=289, cpath='/here/is/a/path')
(echo -n doesn't add a newline at the end of the output).

Categories

Resources