argparse optional subparser (for --version) - python

I have the following code (using Python 2.7):
# shared command line options, like --version or --verbose
parser_shared = argparse.ArgumentParser(add_help=False)
parser_shared.add_argument('--version', action='store_true')
# the main parser, inherits from `parser_shared`
parser = argparse.ArgumentParser(description='main', parents=[parser_shared])
# several subcommands, which can't inherit from the main parser, since
# it would expect subcommands ad infinitum
subparsers = parser.add_subparsers('db', parents=[parser_shared])
...
args = parser.parse_args()
Now I would like to be able to call this program e.g. with the --version appended to the normal program or some subcommand:
$ prog --version
0.1
$ prog db --version
0.1
Basically, I need to declare optional subparsers. I'm aware that this isn't really supported, but are there any workarounds or alternatives?
Edit: The error message I am getting:
$ prog db --version
# works fine
$ prog --version
usage: ....
prog: error: too few arguments

According to documentation, --version with action='version' (and not with action='store_true') prints automatically the version number:
parser.add_argument('--version', action='version', version='%(prog)s 2.0')

FWIW, I ran into this also, and ended up "solving" it by not using subparsers (I already had my own system for printing help, so didn't lose anything there).
Instead, I do this:
parser.add_argument("command", nargs="?",
help="name of command to execute")
args, subcommand_args = parser.parse_known_args()
...and then the subcommand creates its own parser (similar to a subparser) which operates only on subcommand_args.

This seems to implement the basic idea of an optional subparser. We parse the standard arguments that apply to all subcommands. Then, if anything is left, we invoke the parser on the rest. The primary arguments are a parent of the subcommand so the -h appears correctly. I plan to enter an interactive prompt if no subcommands are present.
import argparse
p1 = argparse.ArgumentParser( add_help = False )
p1.add_argument( ‘–flag1′ )
p2 = argparse.ArgumentParser( parents = [ p1 ] )
s = p2.add_subparsers()
p = s.add_parser( ‘group’ )
p.set_defaults( group=True )
( init_ns, remaining ) = p1.parse_known_args( )
if remaining:
p2.parse_args( args = remaining, namespace=init_ns )
else:
print( ‘Enter interactive loop’ )
print( init_ns )

As discussed in http://bugs.python.org/issue9253 (argparse: optional subparsers), as of Python 3.3, subparsers are now optional. This was an unintended result of a change in how parse_args checked for required arguments.
I found a fudge that restores the previous (required subparsers) behavior, explicitly setting the required attribute of the subparsers action.
parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True # the fudge
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()
See that issue for more details. I expect that if and when this issue gets properly patched, subparsers will be required by default, with some sort of option to set its required attribute to False. But there is a big backlog of argparse patches.

Yeah, I just checked svn, which is used as an object example in the add_subparsers() documentation, and it only supports '--version' on the main command:
python zacharyyoung$ svn log --version
Subcommand 'log' doesn't accept option '--version'
Type 'svn help log' for usage.
Still:
# create common parser
parent_parser = argparse.ArgumentParser('parent', add_help=False)
parent_parser.add_argument('--version', action='version', version='%(prog)s 2.0')
# create the top-level parser
parser = argparse.ArgumentParser(parents=[parent_parser])
subparsers = parser.add_subparsers()
# create the parser for the "foo" command
parser_foo = subparsers.add_parser('foo', parents=[parent_parser])
Which yields:
python zacharyyoung$ ./arg-test.py --version
arg-test.py 2.0
python zacharyyoung$ ./arg-test.py foo --version
arg-test.py foo 2.0

While we wait for this feature to be delivered, we can use code like this:
# Make sure that main is the default sub-parser
if '-h' not in sys.argv and '--help' not in sys.argv:
if len(sys.argv) < 2:
sys.argv.append('main')
if sys.argv[1] not in ('main', 'test'):
sys.argv = [sys.argv[0], 'main'] + sys.argv[1:]

Although #eumiro's answer address the --version option, it can only do so because that is a special case for optparse. To allow general invocations of:
prog
prog --verbose
prog --verbose main
prog --verbose db
and have prog --version work the same as prog --verbose main (and prog main --verbose) you can add a method to Argumentparser and call that with the name of the default subparser, just before invoking parse_args():
import argparse
import sys
def set_default_subparser(self, name, args=None):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
for arg in sys.argv[1:]:
if arg in ['-h', '--help']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if not subparser_found:
# insert default in first position, this implies no
# global options without a sub_parsers specified
if args is None:
sys.argv.insert(1, name)
else:
args.insert(0, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def do_main(args):
print 'main verbose', args.verbose
def do_db(args):
print 'db verbose:', args.verbose
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--version', action='version', version='%(prog)s 2.0')
subparsers = parser.add_subparsers()
sp = subparsers.add_parser('main')
sp.set_defaults(func=do_main)
sp.add_argument('--verbose', action='store_true')
sp = subparsers.add_parser('db')
sp.set_defaults(func=do_db)
parser.set_default_subparser('main')
args = parser.parse_args()
if hasattr(args, 'func'):
args.func(args)
The set_default_subparser() method is part of the ruamel.std.argparse package.

Related

Python ArgParse add help message when subparsers are omitted [duplicate]

With python's argparse, how do I make a subcommand a required argument? I want to do this because I want argparse to error out if a subcommand is not specified. I override the error method to print help instead. I have 3-deep nested subcommands, so it's not a matter of simply handling zero arguments at the top level.
In the following example, if this is called like so, I get:
$./simple.py
$
What I want it to do instead is for argparse to complain that the required subcommand was not specified:
import argparse
class MyArgumentParser(argparse.ArgumentParser):
def error(self, message):
self.print_help(sys.stderr)
self.exit(0, '%s: error: %s\n' % (self.prog, message))
def main():
parser = MyArgumentParser(description='Simple example')
subs = parser.add_subparsers()
sub_one = subs.add_parser('one', help='does something')
sub_two = subs.add_parser('two', help='does something else')
parser.parse_args()
if __name__ == '__main__':
main()
There was a change in 3.3 in the error message for required arguments, and subcommands got lost in the dust.
http://bugs.python.org/issue9253#msg186387
There I suggest this work around, setting the required attribute after the subparsers is defined.
parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()
update
A related pull-request: https://github.com/python/cpython/pull/3027
In addition to hpaulj's answer: you can also use the required keyword argument with ArgumentParser.add_subparsers() since Python 3.7. You also need to pass dest as argument. Otherwise you will get an error: TypeError: sequence item 0: expected str instance, NoneType found.
Example file example.py:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', required=True)
foo_parser = subparsers.add_parser("foo", help="command foo")
args = parser.parse_args()
Output of the call without an argument:
$ python example.py
usage: example.py [-h] {foo} ...
example.py: error: the following arguments are required: command
How about using required=True? More info here.
You can use the dest argument, which is documented in the last example in the documentation for add_subparsers():
# required_subparser.py
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser_name')
one = subparsers.add_parser('one')
two = subparsers.add_parser('two')
args = parser.parse_args()
Running with Python 2.7:
$python required_subparser.py
usage: required_subparser.py [-h] {one,two} ...
required_subparser.py: error: too few arguments
$python required_subparser.py one
$# no error

Call function based on argparse

I'm new to python and currently playing with it.
I have a script which does some API Calls to an appliance. I would like to extend the functionality and call different functions based on the arguments given when calling the script.
Currently I have the following:
parser = argparse.ArgumentParser()
parser.add_argument("--showtop20", help="list top 20 by app",
action="store_true")
parser.add_argument("--listapps", help="list all available apps",
action="store_true")
args = parser.parse_args()
I also have a
def showtop20():
.....
and
def listapps():
....
How can I call the function (and only this) based on the argument given?
I don't want to run
if args.showtop20:
#code here
if args.listapps:
#code here
as I want to move the different functions to a module later on keeping the main executable file clean and tidy.
Since it seems like you want to run one, and only one, function depending on the arguments given, I would suggest you use a mandatory positional argument ./prog command, instead of optional arguments (./prog --command1 or ./prog --command2).
so, something like this should do it:
FUNCTION_MAP = {'top20' : my_top20_func,
'listapps' : my_listapps_func }
parser.add_argument('command', choices=FUNCTION_MAP.keys())
args = parser.parse_args()
func = FUNCTION_MAP[args.command]
func()
At least from what you have described, --showtop20 and --listapps sound more like sub-commands than options. Assuming this is the case, we can use subparsers to achieve your desired result. Here is a proof of concept:
import argparse
import sys
def showtop20():
print('running showtop20')
def listapps():
print('running listapps')
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
# Create a showtop20 subcommand
parser_showtop20 = subparsers.add_parser('showtop20', help='list top 20 by app')
parser_showtop20.set_defaults(func=showtop20)
# Create a listapps subcommand
parser_listapps = subparsers.add_parser('listapps', help='list all available apps')
parser_listapps.set_defaults(func=listapps)
# Print usage message if no args are supplied.
# NOTE: Python 2 will error 'too few arguments' if no subcommand is supplied.
# No such error occurs in Python 3, which makes it feasible to check
# whether a subcommand was provided (displaying a help message if not).
# argparse internals vary significantly over the major versions, so it's
# much easier to just override the args passed to it.
if len(sys.argv) <= 1:
sys.argv.append('--help')
options = parser.parse_args()
# Run the appropriate function (in this case showtop20 or listapps)
options.func()
# If you add command-line options, consider passing them to the function,
# e.g. `options.func(options)`
There are lots of ways of skinning this cat. Here's one using action='store_const' (inspired by the documented subparser example):
p=argparse.ArgumentParser()
p.add_argument('--cmd1', action='store_const', const=lambda:'cmd1', dest='cmd')
p.add_argument('--cmd2', action='store_const', const=lambda:'cmd2', dest='cmd')
args = p.parse_args(['--cmd1'])
# Out[21]: Namespace(cmd=<function <lambda> at 0x9abf994>)
p.parse_args(['--cmd2']).cmd()
# Out[19]: 'cmd2'
p.parse_args(['--cmd1']).cmd()
# Out[20]: 'cmd1'
With a shared dest, each action puts its function (const) in the same Namespace attribute. The function is invoked by args.cmd().
And as in the documented subparsers example, those functions could be written so as to use other values from Namespace.
args = parse_args()
args.cmd(args)
For sake of comparison, here's the equivalent subparsers case:
p = argparse.ArgumentParser()
sp = p.add_subparsers(dest='cmdstr')
sp1 = sp.add_parser('cmd1')
sp1.set_defaults(cmd=lambda:'cmd1')
sp2 = sp.add_parser('cmd2')
sp2.set_defaults(cmd=lambda:'cmd2')
p.parse_args(['cmd1']).cmd()
# Out[25]: 'cmd1'
As illustrated in the documentation, subparsers lets you define different parameter arguments for each of the commands.
And of course all of these add argument or parser statements could be created in a loop over some list or dictionary that pairs a key with a function.
Another important consideration - what kind of usage and help do you want? The different approaches generate very different help messages.
If your functions are "simple enough" take adventage of type parameter https://docs.python.org/2.7/library/argparse.html#type
type= can take any callable that takes a single string argument and
returns the converted value:
In your example (even if you don't need a converted value):
parser.add_argument("--listapps", help="list all available apps",
type=showtop20,
action="store")
This simple script:
import argparse
def showtop20(dummy):
print "{0}\n".format(dummy) * 5
parser = argparse.ArgumentParser()
parser.add_argument("--listapps", help="list all available apps",
type=showtop20,
action="store")
args = parser.parse_args()
Will give:
# ./test.py --listapps test
test
test
test
test
test
test
Instead of using your code as your_script --showtop20, make it into a sub-command your_script showtop20 and use the click library instead of argparse. You define functions that are the name of your subcommand and use decorators to specify the arguments:
import click
#click.group()
#click.option('--debug/--no-debug', default=False)
def cli(debug):
print(f'Debug mode is {"on" if debug else "off"}')
#cli.command() # #cli, not #click!
def showtop20():
# ...
#cli.command()
def listapps():
# ...
See https://click.palletsprojects.com/en/master/commands/
# based on parser input to invoke either regression/classification plus other params
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--path", type=str)
parser.add_argument("--target", type=str)
parser.add_argument("--type", type=str)
parser.add_argument("--deviceType", type=str)
args = parser.parse_args()
df = pd.read_csv(args.path)
df = df.loc[:, ~df.columns.str.contains('^Unnamed')]
if args.type == "classification":
classify = AutoML(df, args.target, args.type, args.deviceType)
classify.class_dist()
classify.classification()
elif args.type == "regression":
reg = AutoML(df, args.target, args.type, args.deviceType)
reg.regression()
else:
ValueError("Invalid argument passed")
# Values passed as : python app.py --path C:\Users\Abhishek\Downloads\adult.csv --target income --type classification --deviceType GPU
You can evaluate using evalwhether your argument value is callable:
import argparse
def list_showtop20():
print("Calling from showtop20")
def list_apps():
print("Calling from listapps")
my_funcs = [x for x in dir() if x.startswith('list_')]
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--function", required=True,
choices=my_funcs,
help="function to call", metavar="")
args = parser.parse_args()
eval(args.function)()

Simple argparse example wanted: 1 argument, 3 results

The documentation for the argparse python module, while excellent I'm sure, is too much for my tiny beginner brain to grasp right now. I don't need to do math on the command line or meddle with formatting lines on the screen or change option characters. All I want to do is "If arg is A, do this, if B do that, if none of the above show help and quit".
Here's the way I do it with argparse (with multiple args):
parser = argparse.ArgumentParser(description='Description of your program')
parser.add_argument('-f','--foo', help='Description for foo argument', required=True)
parser.add_argument('-b','--bar', help='Description for bar argument', required=True)
args = vars(parser.parse_args())
args will be a dictionary containing the arguments:
if args['foo'] == 'Hello':
# code here
if args['bar'] == 'World':
# code here
In your case simply add only one argument.
My understanding of the original question is two-fold. First, in terms of the simplest possible argparse example, I'm surprised that I haven't seen it here. Of course, to be dead-simple, it's also all overhead with little power, but it might get you started.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("a")
args = parser.parse_args()
if args.a == 'magic.name':
print 'You nailed it!'
But this positional argument is now required. If you leave it out when invoking this program, you'll get an error about missing arguments. This leads me to the second part of the original question. Matt Wilkie seems to want a single optional argument without a named label (the --option labels). My suggestion would be to modify the code above as follows:
...
parser.add_argument("a", nargs='?', default="check_string_for_empty")
...
if args.a == 'check_string_for_empty':
print 'I can tell that no argument was given and I can deal with that here.'
elif args.a == 'magic.name':
print 'You nailed it!'
else:
print args.a
There may well be a more elegant solution, but this works and is minimalist.
The argparse documentation is reasonably good but leaves out a few useful details which might not be obvious. (#Diego Navarro already mentioned some of this but I'll try to expand on his answer slightly.) Basic usage is as follows:
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--my-foo', default='foobar')
parser.add_argument('-b', '--bar-value', default=3.14)
args = parser.parse_args()
The object you get back from parse_args() is a 'Namespace' object: An object whose member variables are named after your command-line arguments. The Namespace object is how you access your arguments and the values associated with them:
args = parser.parse_args()
print (args.my_foo)
print (args.bar_value)
(Note that argparse replaces '-' in your argument names with underscores when naming the variables.)
In many situations you may wish to use arguments simply as flags which take no value. You can add those in argparse like this:
parser.add_argument('--foo', action='store_true')
parser.add_argument('--no-foo', action='store_false')
The above will create variables named 'foo' with value True, and 'no_foo' with value False, respectively:
if (args.foo):
print ("foo is true")
if (args.no_foo is False):
print ("nofoo is false")
Note also that you can use the "required" option when adding an argument:
parser.add_argument('-o', '--output', required=True)
That way if you omit this argument at the command line argparse will tell you it's missing and stop execution of your script.
Finally, note that it's possible to create a dict structure of your arguments using the vars function, if that makes life easier for you.
args = parser.parse_args()
argsdict = vars(args)
print (argsdict['my_foo'])
print (argsdict['bar_value'])
As you can see, vars returns a dict with your argument names as keys and their values as, er, values.
There are lots of other options and things you can do, but this should cover the most essential, common usage scenarios.
Matt is asking about positional parameters in argparse, and I agree that the Python documentation is lacking on this aspect. There's not a single, complete example in the ~20 odd pages that shows both parsing and using positional parameters.
None of the other answers here show a complete example of positional parameters, either, so here's a complete example:
# tested with python 2.7.1
import argparse
parser = argparse.ArgumentParser(description="An argparse example")
parser.add_argument('action', help='The action to take (e.g. install, remove, etc.)')
parser.add_argument('foo-bar', help='Hyphens are cumbersome in positional arguments')
args = parser.parse_args()
if args.action == "install":
print("You asked for installation")
else:
print("You asked for something other than installation")
# The following do not work:
# print(args.foo-bar)
# print(args.foo_bar)
# But this works:
print(getattr(args, 'foo-bar'))
The thing that threw me off is that argparse will convert the named argument "--foo-bar" into "foo_bar", but a positional parameter named "foo-bar" stays as "foo-bar", making it less obvious how to use it in your program.
Notice the two lines near the end of my example -- neither of those will work to get the value of the foo-bar positional param. The first one is obviously wrong (it's an arithmetic expression args.foo minus bar), but the second one doesn't work either:
AttributeError: 'Namespace' object has no attribute 'foo_bar'
If you want to use the foo-bar attribute, you must use getattr, as seen in the last line of my example. What's crazy is that if you tried to use dest=foo_bar to change the property name to something that's easier to access, you'd get a really bizarre error message:
ValueError: dest supplied twice for positional argument
Here's how the example above runs:
$ python test.py
usage: test.py [-h] action foo-bar
test.py: error: too few arguments
$ python test.py -h
usage: test.py [-h] action foo-bar
An argparse example
positional arguments:
action The action to take (e.g. install, remove, etc.)
foo-bar Hyphens are cumbersome in positional arguments
optional arguments:
-h, --help show this help message and exit
$ python test.py install foo
You asked for installation
foo
Yet another summary introduction, inspired by this post.
import argparse
# define functions, classes, etc.
# executes when your script is called from the command-line
if __name__ == "__main__":
parser = argparse.ArgumentParser()
#
# define each option with: parser.add_argument
#
args = parser.parse_args() # automatically looks at sys.argv
#
# access results with: args.argumentName
#
Arguments are defined with combinations of the following:
parser.add_argument( 'name', options... ) # positional argument
parser.add_argument( '-x', options... ) # single-char flag
parser.add_argument( '-x', '--long-name', options... ) # flag with long name
Common options are:
help: description for this arg when --help is used.
default: default value if the arg is omitted.
type: if you expect a float or int (otherwise is str).
dest: give a different name to a flag (e.g. '-x', '--long-name', dest='longName'). Note: by default --long-name is accessed with args.long_name
action: for special handling of certain arguments
store_true, store_false: for boolean args '--foo', action='store_true' => args.foo == True
store_const: to be used with option const '--foo', action='store_const', const=42 => args.foo == 42
count: for repeated options, as in ./myscript.py -vv '-v', action='count' => args.v == 2
append: for repeated options, as in ./myscript.py --foo 1 --foo 2 '--foo', action='append' => args.foo == ['1', '2']
required: if a flag is required, or a positional argument is not.
nargs: for a flag to capture N args ./myscript.py --foo a b => args.foo = ['a', 'b']
choices: to restrict possible inputs (specify as list of strings, or ints if type=int).
Note the Argparse Tutorial in Python HOWTOs. It starts from most basic examples, like this one:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
args = parser.parse_args()
print(args.square**2)
and progresses to less basic ones.
There is an example with predefined choice for an option, like what is asked:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
print("the square of {} equals {}".format(args.square, answer))
elif args.verbosity == 1:
print("{}^2 == {}".format(args.square, answer))
else:
print(answer)
Here's what I came up with in my learning project thanks mainly to #DMH...
Demo code:
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--flag', action='store_true', default=False) # can 'store_false' for no-xxx flags
parser.add_argument('-r', '--reqd', required=True)
parser.add_argument('-o', '--opt', default='fallback')
parser.add_argument('arg', nargs='*') # use '+' for 1 or more args (instead of 0 or more)
parsed = parser.parse_args()
# NOTE: args with '-' have it replaced with '_'
print('Result:', vars(parsed))
print('parsed.reqd:', parsed.reqd)
if __name__ == "__main__":
main()
This may have evolved and is available online: command-line.py
Script to give this code a workout: command-line-demo.sh
code file: argparseDemo.py
Simple: common case
name(abbr, full), with help
import argparse
argParser = argparse.ArgumentParser()
argParser.add_argument("-n", "--name", help="your name")
args = argParser.parse_args()
print("args=%s" % args)
print("args.name=%s" % args.name)
call
python argparseDemo.py -n Crifan
python argparseDemo.py --name Crifan
output: args=Namespace(name='Crifan') and args.name=Crifan
type
argParser.add_argument("-a", "--age", type=int, help="your current age")
print("type(args.age)=%s" % type(args.age))
call: python argparseDemo.py --age 30
output: type(args.age)=<class 'int'> and args.age=30
required
argParser.add_argument("-a", "--age", required=True, type=int, help="your current age")
call: python argparseDemo.py
output: error argparseDemo.py: error: the following arguments are required: -a/--age
default
argParser.add_argument("-a", "--age", type=int, default=20, help="your current age. Default is 20")
call: python argparseDemo.py
output: args.age=20
choices
argParser.add_argument("-f", "--love-fruit", choices=['apple', 'orange', 'banana'], help="your love fruits")
call: python argparseDemo.py -f apple
output: args=Namespace(love_fruit='apple') and args.love_fruit=apple
multi args
argParser.add_argument("-f", "--love-fruit", nargs=2, help="your love fruits")
call: python argparseDemo.py -f apple orange
output: args.love_fruit=['apple', 'orange']
Detail
most simple: -x
code:
import argparse
argParser = argparse.ArgumentParser()
argParser.add_argument("-a") # most simple -> got args.a, type is `str`
args = argParser.parse_args()
print("args.a=%s" % args.a)
usage = run in command line
python argparseDemo.py -a 30
or: ./argparseDemo.py -a 30
makesure argparseDemo.py is executable
if not, add it: chmod +x argparseDemo.py
output
args.a=30
Note
default type is str
argParser.add_argument("-a") == argParser.add_argument("-a", type=str)
print("type(args.a)=%s" % type(args.a)) -> type(args.a)=<class 'str'>
args type is Namespace
print("type(args)=%s" % type(args)) -> type(args)=<class 'argparse.Namespace'>
args value is Namespace(a='30')
print("args=%s" % args) -> args=Namespace(a='30')
so we can call/use args.a
parameter name
full parameter name: --xxx
code
argParser.add_argument("-a", "--age")
usage
python argparseDemo.py -a 30
or: python argparseDemo.py --age 30
get parsed value: args.age
Note: NOT args.a, and NOT exist args.a
full parameter name with multiple words: --xxx-yyy
code
argParser.add_argument("-a", "--current-age")
get parsed value: args.current_age
add help description: help
code
argParser.add_argument("-a", help="your age") # with help
output
use --help can see description
 python argparseDemo.py --help
usage: argparseDemo.py [-h] [-a A]
optional arguments:
-h, --help show this help message and exit
-a A your age
designate parameter type: type
code
argParser.add_argument("-a", type=int) # parsed arg is `int`, not default `str`
output
print("type(args.a)=%s" % type(args.a)) -> type(args.a)=<class 'int'>
print("args=%s" % args) -> args=Namespace(a=30)
add default value: default
code
argParser.add_argument("-a", type=int, default=20) # if not pass a, a use default value: 20
effect
usage: python argparseDemo.py
output: print("args.age=%s" % args.age) -> args=Namespace(a=20)
You could also use plac (a wrapper around argparse).
As a bonus it generates neat help instructions - see below.
Example script:
#!/usr/bin/env python3
def main(
arg: ('Argument with two possible values', 'positional', None, None, ['A', 'B'])
):
"""General help for application"""
if arg == 'A':
print("Argument has value A")
elif arg == 'B':
print("Argument has value B")
if __name__ == '__main__':
import plac
plac.call(main)
Example output:
No arguments supplied - example.py:
usage: example.py [-h] {A,B}
example.py: error: the following arguments are required: arg
Unexpected argument supplied - example.py C:
usage: example.py [-h] {A,B}
example.py: error: argument arg: invalid choice: 'C' (choose from 'A', 'B')
Correct argument supplied - example.py A :
Argument has value A
Full help menu (generated automatically) - example.py -h:
usage: example.py [-h] {A,B}
General help for application
positional arguments:
{A,B} Argument with two possible values
optional arguments:
-h, --help show this help message and exit
Short explanation:
The name of the argument usually equals the parameter name (arg).
The tuple annotation after arg parameter has the following meaning:
Description (Argument with two possible values)
Type of argument - one of 'flag', 'option' or 'positional' (positional)
Abbreviation (None)
Type of argument value - eg. float, string (None)
Restricted set of choices (['A', 'B'])
Documentation:
To learn more about using plac check out its great documentation:
Plac: Parsing the Command Line the Easy Way
To add to what others have stated:
I usually like to use the 'dest' parameter to specify a variable name and then use 'globals().update()' to put those variables in the global namespace.
Usage:
$ python script.py -i "Hello, World!"
Code:
...
parser.add_argument('-i', '--input', ..., dest='inputted_variable',...)
globals().update(vars(parser.parse_args()))
...
print(inputted_variable) # Prints "Hello, World!"
New to this, but combining Python with Powershell and using this template, being inspired by an in-depth and great Python Command Line Arguments – Real Python
There is a lot you can do within the init_argparse() and I am covering just the most simple scenario here.
import argparse
use if __name__ == "__main__": main() pattern to execute from terminal
parse arguments within the main() function that has no parameters as all
define a init_argparse() function
create a parser object by calling argparse.ArgumentParser()
declare one or more argumnent with parser.add_argument("--<long_param_name>")
return parser
parse args by creating an args object by calling parser.parse_args()
define a function proper with param1, param2, ...
call function_proper with params being assigned as attributes of an args object
e.g. function_proper(param1=args.param1, param2=args.param2)
within a shell call the module with named arguments:
e.g. python foobar.py --param1="foo" --param2=="bar"
#file: foobar.py
import argparse
def function_proper(param1, param2):
#CODE...
def init_argparse() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--param1")
parser.add_argument("--param2")
return parser
def main() -> None:
parser = init_argparse()
args = parser.parse_args()
function_proper(param1=args.param1, param2=args.param2)
if __name__ == "__main__":
main()
>>> python .\foobar.py --param1="foo" --param2=="bar"
I went through all the examples and answers and in a way or another they didn't address my need. So I will list her a scenario that I need more help and I hope this can explain the idea more.
Initial Problem
I need to develop a tool which is getting a file to process it and it needs some optional configuration file to be used to configure the tool.
so what I need is something like the following
mytool.py file.text -config config-file.json
The solution
Here is the solution code
import argparse
def main():
parser = argparse.ArgumentParser(description='This example for a tool to process a file and configure the tool using a config file.')
parser.add_argument('filename', help="Input file either text, image or video")
# parser.add_argument('config_file', help="a JSON file to load the initial configuration ")
# parser.add_argument('-c', '--config_file', help="a JSON file to load the initial configuration ", default='configFile.json', required=False)
parser.add_argument('-c', '--config', default='configFile.json', dest='config_file', help="a JSON file to load the initial configuration " )
parser.add_argument('-d', '--debug', action="store_true", help="Enable the debug mode for logging debug statements." )
args = parser.parse_args()
filename = args.filename
configfile = args.config_file
print("The file to be processed is", filename)
print("The config file is", configfile)
if args.debug:
print("Debug mode enabled")
else:
print("Debug mode disabled")
print("and all arguments are: ", args)
if __name__ == '__main__':
main()
I will show the solution in multiple enhancements to show the idea
First Round: List the arguments
List all input as mandatory inputs so second argument will be
parser.add_argument('config_file', help="a JSON file to load the initial configuration ")
When we get the help command for this tool we find the following outcome
(base) > python .\argparser_example.py -h
usage: argparser_example.py [-h] filename config_file
This example for a tool to process a file and configure the tool using a config file.
positional arguments:
filename Input file either text, image or video
config_file a JSON file to load the initial configuration
optional arguments:
-h, --help show this help message and exit
and when I execute it as the following
(base) > python .\argparser_example.py filename.txt configfile.json
the outcome will be
The file to be processed is filename.txt
The config file is configfile.json
and all arguments are: Namespace(config_file='configfile.json', filename='filename.txt')
But the config file should be optional, I removed it from the arguments
(base) > python .\argparser_example.py filename.txt
The outcome will be is:
usage: argparser_example.py [-h] filename config_file
argparser_example.py: error: the following arguments are required: c
Which means we have a problem in the tool
Second Round : Make it optimal
So to make it optional I modified the program as follows
parser.add_argument('-c', '--config', help="a JSON file to load the initial configuration ", default='configFile.json', required=False)
The help outcome should be
usage: argparser_example.py [-h] [-c CONFIG] filename
This example for a tool to process a file and configure the tool using a config file.
positional arguments:
filename Input file either text, image or video
optional arguments:
-h, --help show this help message and exit
-c CONFIG, --config CONFIG
a JSON file to load the initial configuration
so when I execute the program
(base) > python .\argparser_example.py filename.txt
the outcome will be
The file to be processed is filename.txt
The config file is configFile.json
and all arguments are: Namespace(config_file='configFile.json', filename='filename.txt')
with arguments like
(base) > python .\argparser_example.py filename.txt --config_file anotherConfig.json
The outcome will be
The file to be processed is filename.txt
The config file is anotherConfig.json
and all arguments are: Namespace(config_file='anotherConfig.json', filename='filename.txt')
Round 3: Enhancements
to change the flag name from --config_file to --config while we keep the variable name as is we modify the code to include dest='config_file' as the following:
parser.add_argument('-c', '--config', help="a JSON file to load the initial configuration ", default='configFile.json', dest='config_file')
and the command will be
(base) > python .\argparser_example.py filename.txt --config anotherConfig.json
To add the support for having a debug mode flag, we need to add a flag in the arguments to support a boolean debug flag. To implement it i added the following:
parser.add_argument('-d', '--debug', action="store_true", help="Enable the debug mode for logging debug statements." )
the tool command will be:
(carnd-term1-38) > python .\argparser_example.py image.jpg -c imageConfig,json --debug
the outcome will be
The file to be processed is image.jpg
The config file is imageConfig,json
Debug mode enabled
and all arguments are: Namespace(config_file='imageConfig,json', debug=True, filename='image.jpg')
A really simple way to use argparse and amend the '-h'/ '--help' switches to display your own personal code help instructions is to set the default help to False, you can also add as many additional .add_arguments as you like:
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-h', '--help', action='help',
help='To run this script please provide two arguments')
parser.parse_args()
Run: python test.py -h
Output:
usage: test.py [-h]
optional arguments:
-h, --help To run this script please provide two arguments
As an addition to existing answers, if you are lazy enough, it is possible to use code generation tool called protoargs. It generates arguments parser from the configuration. For python it uses argparse.
Configuration with optional A and B:
syntax = "proto2";
message protoargs
{
optional string A = 1; // A param description
optional string B = 2; // B param description
}//protoargs
Configuration with required A and B:
syntax = "proto2";
message protoargs
{
required string A = 1; // A param description
required string B = 2; // B param description
}//protoargs
Configuration with positional A and B:
syntax = "proto2";
message protoargs
{
required string A = 1; // A param description
required string B = 2; // B param description
}//protoargs
message protoargs_links
{
}//protoargs_links
Now all you should run is:
python ./protoargs.py -i test.proto -o . --py
And use it (it is possible to take other examples here):
import sys
import test_pa
class ArgsParser:
program = "test"
description = "Simple A and B parser test."
def parse(self, argv):
self.config = test_pa.parse(self.program, self.description, argv)
def usage(self):
return test_pa.usage(self.program, self.description)
if __name__ == "__main__":
parser = ArgsParser()
if len(sys.argv) == 1:
print(parser.usage())
else:
parser.parse(sys.argv[1:])
if parser.config.A:
print(parser.config.A)
if parser.config.B:
print(parser.config.B)
If you want more - change configuration, regenerate parser, use an updated parser.config.
UPD: As mentioned in rules, I must specify that this is my own project
code:
import argparse
parser=argparse.ArgumentParser()
parser.add_argument('-A', default=False, action='store_true')
parser.add_argument('-B', default=False, action='store_true')
args=parser.parse_args()
if args.A:
print('do this')
elif args.B:
print('do that')
else:
print('help')
running result:
$ python3 test.py
help
$ python3 test.py -A
do this
$ python3 test.py -B
do that
$ python3 test.py -C
usage: test.py [-h] [-A] [-B]
test.py: error: unrecognized arguments: -C
As for the original request (if A ....), I would use argv to solve it, not using argparse at all:
import sys
if len(sys.argv)==2:
if sys.argv[1] == 'A':
print('do this')
elif sys.argv[1] == 'B':
print('do that')
else:
print('help')
else:
print('help')
Since you have not clarified wheather the arguments 'A' and 'B' are positional or optional, I'll make a mix of both.
Positional arguments are required by default. If not giving one will throw 'Few arguments given' which is not the case for the optional arguments going by their name. This program will take a number and return its square by default, if the cube option is used it shall return its cube.
import argparse
parser = argparse.ArgumentParser('number-game')
parser.add_argument(
"number",
type=int,
help="enter a number"
)
parser.add_argument(
"-c", "--choice",
choices=['square','cube'],
help="choose what you need to do with the number"
)
# all the results will be parsed by the parser and stored in args
args = parser.parse_args()
# if square is selected return the square, same for cube
if args.c == 'square':
print("{} is the result".format(args.number**2))
elif args.c == 'cube':
print("{} is the result".format(args.number**3))
else:
print("{} is not changed".format(args.number))
usage
$python3 script.py 4 -c square
16
Here the optional arguments are taking value, if you just wanted to use it like a flag you can too. So by using -s for square and -c for cube we change the behaviour, by adding action = "store_true". It is changed to true only when used.
parser.add_argument(
"-s", "--square",
help="returns the square of number",
action="store_true"
)
parser.add_argument(
"-c", "--cube",
help="returns the cube of number",
action="store_true"
)
so the conditional block can be changed to,
if args.s:
print("{} is the result".format(args.number**2))
elif args.c:
print("{} is the result".format(args.number**3))
else:
print("{} is not changed".format(args.number))
usage
$python3 script.py 4 -c
64
The simplest answer!
P.S. the one who wrote the document of argparse is foolish
python code:
import argparse
parser = argparse.ArgumentParser(description='')
parser.add_argument('--o_dct_fname',type=str)
parser.add_argument('--tp',type=str)
parser.add_argument('--new_res_set',type=int)
args = parser.parse_args()
o_dct_fname = args.o_dct_fname
tp = args.tp
new_res_set = args.new_res_set
running code
python produce_result.py --o_dct_fname o_dct --tp father_child --new_res_set 1

Don't parse options after the last positional argument

I'm writing a wrapper around the ssh command line client. After the first positional argument that's part of command, all further options should also be treated as positional arguments.
Under optparse, I believe this would be done with disable_interspersed_args.
Presently I have something like this:
parser = argparse.ArgumentParser()
parser.add_argument('--parallel', default=False, action='store_true')
# maybe allow no command? this would ssh interactively into each machine...
parser.add_argument('command', nargs='+')
args = parser.parse_args()
But if options are passed as part of the command (such as my_wrapper ls -l), they're instead interpreted by ArgumentParser as unknown options. error: unrecognized arguments: -l
If I use parse_known_args(), the options may be taken out of order.
p = argparse.ArgumentParser()
p.add_argument('-a', action='store_true')
p.add_argument('command', nargs='+')
print(p.parse_known_args())
$ python3 bah.py -b ls -l -a
(Namespace(a=True, command=['ls']), ['-b', '-l'])
Here you can see that -b's position before ls has been lost, and -a has been parsed out from the command, which is not desired.
How can I:
Prevent arguments from being parsed after a certain point?
Disable parsing of interspersed arguments?
Allow arguments with a prefix to be consumed as positional arguments?
I had the same problem. I found the solution on the argparse bug tracker: http://code.google.com/p/argparse/issues/detail?id=52
The solution is simple: replace nargs='+' (or '*') with nargs=argparse.REMAINDER. This special value is not documented, but it does what you want.
I think your best bet to start solving these issues is to try out -- after all your optional args. -- is a pseudo-arg that tells ArgumentParser that everything after is a positional argument. Docs are here
As for prevent arguments from being parsed after a certain point, you can pass part of argv to parse_args. That combined with some introspection can be used to limit what is parsed.
What #dcolish suggested is the universal approach. Here is a sample implementation which also supports the standard -- separator, but its usage is not required for correct parsing.
Result:
# ./parse-pos.py -h
usage: parse-pos.py [-h] [-qa] [-qb] COMMAND [ARGS...]
# ./parse-pos.py -qa ls -q -h aa /bb
try_argv = ['-qa', 'ls']
cmd_rest_argv = ['-q', '-h', 'aa', '/bb']
parsed_args = Namespace(command='ls', qa=True, qb=False)
The code:
#!/usr/bin/python3
import argparse
import sys
from pprint import pprint
class CustomParserError(Exception):
pass
class CustomArgumentParser(argparse.ArgumentParser):
def error(self, message):
raise CustomParserError(message)
def original_error(self, message):
super().error(message)
def parse_argv():
parser = CustomArgumentParser(description='Example')
parser.add_argument('command', metavar='COMMAND [ARGS...]', help='the command to be executed')
parser.add_argument('-qa', action='store_true') # "ambiguous option" if you specify just "-q"
parser.add_argument('-qb', action='store_true') # "ambiguous option" if you specify just "-q"
def parse_until_positional(parser, _sys_argv = None):
if _sys_argv is None:
_sys_argv = sys.argv[1:] # skip the program name
for i in range(0, len(_sys_argv) + 1):
try_argv = _sys_argv[0:i]
try:
parsed_args = parser.parse_args(try_argv)
except CustomParserError as ex:
if len(try_argv) == len(_sys_argv):
# this is our last try and we still couldn't parse anything
parser.original_error(str(ex)) # sys.exit()
continue
# if we are here, we parsed our known optional & dash-prefixed parameters and the COMMAND
cmd_rest_argv = _sys_argv[i:]
break
return (parsed_args, cmd_rest_argv, try_argv)
(parsed_args, cmd_rest_argv, try_argv) = parse_until_positional(parser)
# debug
pprint(try_argv)
pprint(cmd_rest_argv)
pprint(parsed_args)
return (parsed_args, cmd_rest_argv)
def main():
parse_argv()
main()
Another option is to use parse_known_args, which stops parsing when an unknown argument is encountered.

Argparse - How to Specify a Default Subcommand

I am using the argparse package of Python 2.7 to write some option-parsing logic for a command-line tool. The tool should accept one of the following arguments:
"ON": Turn a function on.
"OFF": Turn a function off.
[No arguments provided]: Echo the current state of the function.
Looking at the argparse documentation led me to believe that I wanted two--possibly three--subcommands to be defined, since these three states are mutually exclusive and represent different conceptual activities. This is my current attempt at the code:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser.set_defaults(func=print_state) # I think this line is wrong.
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=set_state, newstate='ON')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=set_state, newstate='OFF')
args = parser.parse_args()
if(args.func == set_state):
set_state(args.newstate)
elif(args.func == print_state):
print_state()
else:
args.func() # Catchall in case I add more functions later
I was under the impression that if I provided 0 arguments, the main parser would set func=print_state, and if I provided 1 argument, the main parser would use the appropriate subcommand's defaults and call func=set_state. Instead, I get the following error with 0 arguments:
usage: cvsSecure.py [-h] {ON,OFF} ...
cvsSecure.py: error: too few arguments
And if I provide "OFF" or "ON", print_state gets called instead of set_state. If I comment out the parser.set_defaults line, set_state is called correctly.
I'm a journeyman-level programmer, but a rank beginner to Python. Any suggestions about how I can get this working?
Edit: Another reason I was looking at subcommands was a potential fourth function that I am considering for the future:
"FORCE txtval": Set the function's state to txtval.
The defaults of the top-level parser override the defaults on the sub-parsers, so setting the default value of func on the sub-parsers is ignored, but the value of newstate from the sub-parser defaults is correct.
I don't think you want to use subcommands. Subcommands are used when the available options and positional arguments change depending on which subcommand is chosen. However, you have no other options or positional arguments.
The following code seems to do what you require:
import argparse
def print_state():
print "Print state"
def set_state(s):
print "Setting state to " + s
parser = argparse.ArgumentParser()
parser.add_argument('state', choices = ['ON', 'OFF'], nargs='?')
args = parser.parse_args()
if args.state is None:
print_state()
elif args.state in ('ON', 'OFF'):
set_state(args.state)
Note the optional parameters to parser.add_argument. The "choices" parameter specifies the allowable options, while setting "nargs" to "?" specifies that 1 argument should be consumed if available, otherwise none should be consumed.
Edit: If you want to add a FORCE command with an argument and have separate help text for the ON and OFF command then you do need to use subcommands. Unfortunately there doesn't seem to be a way of specifying a default subcommand. However, you can work around the problem by checking for an empty argument list and supplying your own. Here's some sample code illustrating what I mean:
import argparse
import sys
def print_state(ignored):
print "Print state"
def set_state(s):
print "Setting state to " + s
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
on = subparsers.add_parser('ON', help = 'On help here.')
on.set_defaults(func = set_state, newstate = 'ON')
off = subparsers.add_parser('OFF', help = 'Off help here.')
off.set_defaults(func = set_state, newstate = 'OFF')
prt = subparsers.add_parser('PRINT')
prt.set_defaults(func = print_state, newstate = 'N/A')
force = subparsers.add_parser('FORCE' , help = 'Force help here.')
force.add_argument('newstate', choices = [ 'ON', 'OFF' ])
force.set_defaults(func = set_state)
if (len(sys.argv) < 2):
args = parser.parse_args(['PRINT'])
else:
args = parser.parse_args(sys.argv[1:])
args.func(args.newstate)
There are two problems with your approach.
First you probably already noticed that newstate is not some sub_value of the sub parser and needs to be addressed at the top level of args as args.newstate. That should explain that assigning a default to newstate twice will result in the first value being overwritten. Whether you call your programm with 'ON' or 'OFF' as a parameter, each time set_state() will be called with OFF. If you just want to be able to do python cvsSecure ON and
python cvsSecure OFF the following would work:
from __future__ import print_function
import sys
import argparse
def set_state(state):
print("set_state", state)
def do_on(args):
set_state('ON')
def do_off(args):
set_state('OFF')
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=do_on)
parser_on.add_argument('--fast', action='store_true')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=do_off)
args = parser.parse_args()
args.func(args)
The second problem is that argparse does handle subparsers as single value arguments, so you have to specify one before invoking parser.parse_args(). You can automate insertion of a lacking argument by adding a extra subparser 'PRINT' and automatically inserting
that using set_default_subparser added to argparse.ArgumentParser() (that code is part
of the package ruamel.std.argparse
from __future__ import print_function
import sys
import argparse
def set_default_subparser(self, name, args=None):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
for arg in sys.argv[1:]:
if arg in ['-h', '--help']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if not subparser_found:
# insert default in first position, this implies no
# global options without a sub_parsers specified
if args is None:
sys.argv.insert(1, name)
else:
args.insert(0, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def print_state(args):
print("print_state")
def set_state(state):
print("set_state", state)
def do_on(args):
set_state('ON')
def do_off(args):
set_state('OFF')
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_print = subparsers.add_parser('PRINT', help='default action')
parser_print.set_defaults(func=print_state)
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=do_on)
parser_on.add_argument('--fast', action='store_true')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=do_off)
parser.set_default_subparser('PRINT')
args = parser.parse_args()
args.func(args)
You don't need to handle in args to do_on(), etc., but it comes in handy if you start specifying options to the different subparsers.

Categories

Resources