Reputation: 67
Currently when I try to run one of my options in argparse I have to write something before, example:
python3 main.py https://example.com ping-history
This is fine for my other options (ping and others) since I need to specify which website I want to access. However I need it to make an exception for one of my options. So it would look like this instead:
python3 main.py ping-history
I've tried adding:
nargs="?"
to:
parser.add_argument('filename', help="input file")
This solves the problem, however when I try to run --verbose or --silent:
python3 main.py https://example.com -v title
I get this:
main.py: error: argument commands: invalid choice: 'https://example.com'
I'm not sure what is happening, could someone please explain?
edit: Parser file:
import argparse
VERSION = "v1.0.0 (2017-06-16)"
def add_options(parser):
"""add options"""
group = parser.add_mutually_exclusive_group()
group.add_argument("-s", "--silent", dest="silent", help="Less output", action="store_true")
group.add_argument("-v", "--verbose", dest="verbose", help="More output", action="store_true")
parser.add_argument("-V", "--version", action="version", version=VERSION)
parser.add_argument('filename', help="input file", nargs="?")
def add_commands(parser):
"""Adds commands"""
subparser = parser.add_subparsers(title="commands (positional arguments)", help="Available commands",\
dest="commands")
subparser.add_parser("lines", help="count lines in textfile")
subparser.add_parser("words", help="count words in textfile")
subparser.add_parser("letters", help="count letters in textfile")
subparser.add_parser("all", help="count all")
subparser.add_parser("word_frequency", help="count word frequency")
subparser.add_parser("letter_frequency", help="count letter frequency")
#subparser.add_parser("filename", help= "filename to analyze")
subparser.add_parser("ping", help="ping a website")
subparser.add_parser("ping-history", help="show past status code")
subparser.add_parser("quote", help="retrieve todays quote")
subparser.add_parser("title", help="retrive titel from page")
def parse_options():
"""add options"""
parser = argparse.ArgumentParser()
add_options(parser)
add_commands(parser)
arg, unknown_args = parser.parse_known_args()
options = {}
options["known_args"] = vars(arg)
options["unknown_args"] = unknown_args
return options
Upvotes: 0
Views: 1502
Reputation: 231355
In my comment I guessed that you had an optional positional last. But on further thought your error is more consistent with an initial optional positional.
In [42]: parser=argparse.ArgumentParser()
In [43]: a1 = parser.add_argument('name');
In [44]: parser.add_argument('-v',action='store_true');
In [45]: parser.add_argument('cmds',choices=['one','two'])
In [46]: parser.parse_args('foo one'.split())
Out[46]: Namespace(cmds='one', name='foo', v=False)
In [47]: parser.parse_args('foo -v one'.split())
Out[47]: Namespace(cmds='one', name='foo', v=True)
Change the first argument to be optional:
In [48]: a1.nargs
In [49]: a1.nargs='?'
In [50]: parser.parse_args('foo one'.split())
Out[50]: Namespace(cmds='one', name='foo', v=False)
In [51]: parser.parse_args('foo -v one'.split())
usage: ipython3 [-h] [-v] [name] {one,two}
ipython3: error: argument cmds: invalid choice: 'foo' (choose from 'one', 'two')
...
I get the same error if I just give it one string:
In [54]: parser.parse_args('foo'.split())
usage: ipython3 [-h] [-v] [name] {one,two}
ipython3: error: argument cmds: invalid choice: 'foo'
parse_args
alternates between evaluating positionals and optionals. When doing positionals it tries to handle as many strings as possible (using regex
style pattern matching). A '?' positional can match an empty string. So when it sees just one string 'foo', it matches []
with 'name' and tries to match 'foo' with 'cmd'.
There are bug/issues about making the parsing look further ahead, and anticipate that the 2nd positional might satisfied with a string after the flag. But for now, don't mix flags and positionals when one or more of positionals is 'optional'.
In [55]: parser.parse_args('foo one -v'.split())
Out[55]: Namespace(cmds='one', name='foo', v=True)
In [56]: parser.parse_args('-v foo one'.split())
Out[56]: Namespace(cmds='one', name='foo', v=True)
In your full parser, the second positional
is the subparser. As I demonstrated above, it tries to apply ['https://example.com']
to both a '?' argument and the subparser.
So the working call is:
python3 main.py -v https://example.com title
If only some of the commands
need a filename
, consider assigning that argument to those subparsers rather than make it an optional one for the parent parser.
In general parent positionals are tricky when using subparsers. It's easier to make all the parent arguments flagged.
Upvotes: 1