How to make argparse treat all arguments as positional? - python

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

Related

how to allow -non_number as arguments in python argparse

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

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='+')

Optional argument after subparsers in argparse

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.

In Python, is there a better way to deal with command line options when each option could have several arguments

I've a Python app like this:
$ app.py -a arg1 -a arg2 -b file1.b -b file2.b
Depending on the option, in app.py, I use two different functions to process the input files. As each option can have several file arguments, I would like something like this:
$ app.py -a arg1 arg2 -b *.b
After searching on the net, I find these three modules: getopt, argparse, optparse
And I wrote a simple one for my app:
optionDict= {'-a':aFunction, '-b':bFunction}
for arg in sys.argv[1:]:
if arg in optionDict: # arg is an option here.
funcName = optionDict[arg]
else: # arg is not an option, then it's a fileName. Deal with this file with the function found according to previous arg.
funcName(arg)
My quesion: Are there any other modules or better ways to do this?
Since 2012 Python has an easy, powerful and very cool module for argument parsing called docopt. It works with Python from 2.5 to 3.3 and needs no installation. Here is the code for your particular case:
'''app.py
Usage:
app.py -a ARG ... -b FILE ...
app.py (-h | -v)
'''
from docopt import docopt
args = docopt(__doc__, version='1.0')
# now do something based on the value of args dict
So this is it: one line of code plus your doc string which is essential. I told you it's cool -- didn't I ;-)
If you found argparse to be too hard to get started with because of the complexity, then you would probably like plac which is available on PyPi. It is a wrapper around argparse which hides the complexity, and it takes care of a lot of boilerplate argument checking for you.
It works for both Python 2 and 3, and has a decorator that allows Python 2 users to get access to a nice Python 3 feature for function parameters. And you can even nest commands in one another so that you could have -arguments (beginning with dash) and .arguments (beginning with dot) on one command line.
Here are the docs http://plac.googlecode.com/hg/doc/plac.html
I think that argparse is the way to go. It does a great job, it is easy to use and in the standard library since 2.7. Take a look a the nargs argument. From the docs
Fixed elements to consume
parser = argparse.ArgumentParser()
parser.add_argument('--foo', nargs=2)
parser.add_argument('bar', nargs=1)
parser.parse_args('c --foo a b'.split())
Namespace(bar=['c'], foo=['a', 'b'])
One arg will be consumed from the command line if possible, and produced as a single item. If no command-line arg is present, the value from default will be produced
parser = argparse.ArgumentParser()
parser.add_argument('--foo', nargs='?', const='c', default='d')
parser.add_argument('bar', nargs='?', default='d')
parser.parse_args('XX --foo YY'.split())
Namespace(bar='XX', foo='YY')
parser.parse_args('XX --foo'.split())
Namespace(bar='XX', foo='c')
parser.parse_args(''.split())
Namespace(bar='d', foo='d')
All command-line args present are gathered into a list.
parser = argparse.ArgumentParser()
parser.add_argument('--foo', nargs='*')
parser.add_argument('--bar', nargs='*')
parser.add_argument('baz', nargs='*')
parser.parse_args('a b --foo x y --bar 1 2'.split())
Namespace(bar=['1', '2'], baz=['a', 'b'], foo=['x', 'y'])

Categories

Resources