I would like to use argparse to parse boolean command-line arguments written as "--foo True" or "--foo False". For example:
my_program --my_boolean_flag False
However, the following test code does not do what I would like:
import argparse
parser = argparse.ArgumentParser(description="My parser")
parser.add_argument("--my_bool", type=bool)
cmd_line = ["--my_bool", "False"]
parsed_args = parser.parse(cmd_line)
Sadly, parsed_args.my_bool evaluates to True. This is the case even when I change cmd_line to be ["--my_bool", ""], which is surprising, since bool("") evalutates to False.
How can I get argparse to parse "False", "F", and their lower-case variants to be False?
I think a more canonical way to do this is via:
command --feature
and
command --no-feature
argparse supports this version nicely:
Python 3.9+:
parser.add_argument('--feature', action=argparse.BooleanOptionalAction)
Python < 3.9:
parser.add_argument('--feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)
Of course, if you really want the --arg <True|False> version, you could pass ast.literal_eval as the "type", or a user defined function ...
def t_or_f(arg):
ua = str(arg).upper()
if 'TRUE'.startswith(ua):
return True
elif 'FALSE'.startswith(ua):
return False
else:
pass #error condition maybe?
Yet another solution using the previous suggestions, but with the "correct" parse error from argparse:
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
This is very useful to make switches with default values; for instance
parser.add_argument("--nice", type=str2bool, nargs='?',
const=True, default=False,
help="Activate nice mode.")
allows me to use:
script --nice
script --nice <bool>
and still use a default value (specific to the user settings). One (indirectly related) downside with that approach is that the 'nargs' might catch a positional argument -- see this related question and this argparse bug report.
If you want to allow --feature and --no-feature at the same time (last one wins)
This allows users to make a shell alias with --feature, and overriding it with --no-feature.
Python 3.9 and above
parser.add_argument('--feature', default=True, action=argparse.BooleanOptionalAction)
Python 3.8 and below
I recommend mgilson's answer:
parser.add_argument('--feature', dest='feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)
If you DON'T want to allow --feature and --no-feature at the same time
You can use a mutually exclusive group:
feature_parser = parser.add_mutually_exclusive_group(required=False)
feature_parser.add_argument('--feature', dest='feature', action='store_true')
feature_parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)
You can use this helper if you are going to set many of them:
def add_bool_arg(parser, name, default=False):
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('--' + name, dest=name, action='store_true')
group.add_argument('--no-' + name, dest=name, action='store_false')
parser.set_defaults(**{name:default})
add_bool_arg(parser, 'useful-feature')
add_bool_arg(parser, 'even-more-useful-feature')
Here is another variation without extra row/s to set default values. The boolean value is always assigned, so that it can be used in logical statements without checking beforehand:
import argparse
parser = argparse.ArgumentParser(description="Parse bool")
parser.add_argument("--do-something", default=False, action="store_true",
help="Flag to do something")
args = parser.parse_args()
if args.do_something:
print("Do something")
else:
print("Don't do something")
print(f"Check that args.do_something={args.do_something} is always a bool.")
oneliner:
parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
There seems to be some confusion as to what type=bool and type='bool' might mean. Should one (or both) mean 'run the function bool(), or 'return a boolean'? As it stands type='bool' means nothing. add_argument gives a 'bool' is not callable error, same as if you used type='foobar', or type='int'.
But argparse does have registry that lets you define keywords like this. It is mostly used for action, e.g. `action='store_true'. You can see the registered keywords with:
parser._registries
which displays a dictionary
{'action': {None: argparse._StoreAction,
'append': argparse._AppendAction,
'append_const': argparse._AppendConstAction,
...
'type': {None: <function argparse.identity>}}
There are lots of actions defined, but only one type, the default one, argparse.identity.
This code defines a 'bool' keyword:
def str2bool(v):
#susendberg's function
return v.lower() in ("yes", "true", "t", "1")
p = argparse.ArgumentParser()
p.register('type','bool',str2bool) # add type keyword to registries
p.add_argument('-b',type='bool') # do not use 'type=bool'
# p.add_argument('-b',type=str2bool) # works just as well
p.parse_args('-b false'.split())
Namespace(b=False)
parser.register() is not documented, but also not hidden. For the most part the programmer does not need to know about it because type and action take function and class values. There are lots of stackoverflow examples of defining custom values for both.
In case it isn't obvious from the previous discussion, bool() does not mean 'parse a string'. From the Python documentation:
bool(x): Convert a value to a Boolean, using the standard truth testing procedure.
Contrast this with
int(x): Convert a number or string x to an integer.
Simplest & most correct way is:
from distutils.util import strtobool
parser.add_argument('--feature', dest='feature',
type=lambda x: bool(strtobool(x)))
Do note that True values are y, yes, t, true, on and 1;
false values are n, no, f, false, off and 0. Raises ValueError if val is anything else.
A quite similar way is to use:
feature.add_argument('--feature',action='store_true')
and if you set the argument --feature in your command
command --feature
the argument will be True, if you do not set type --feature the arguments default is always False!
I was looking for the same issue, and imho the pretty solution is :
def str2bool(v):
return v.lower() in ("yes", "true", "t", "1")
and using that to parse the string to boolean as suggested above.
This works for everything I expect it to:
add_boolean_argument(parser, 'foo', default=True)
parser.parse_args([]) # Whatever the default was
parser.parse_args(['--foo']) # True
parser.parse_args(['--nofoo']) # False
parser.parse_args(['--foo=true']) # True
parser.parse_args(['--foo=false']) # False
parser.parse_args(['--foo', '--nofoo']) # Error
The code:
def _str_to_bool(s):
"""Convert string to bool (in argparse context)."""
if s.lower() not in ['true', 'false']:
raise ValueError('Need bool; got %r' % s)
return {'true': True, 'false': False}[s.lower()]
def add_boolean_argument(parser, name, default=False):
"""Add a boolean argument to an ArgumentParser instance."""
group = parser.add_mutually_exclusive_group()
group.add_argument(
'--' + name, nargs='?', default=default, const=True, type=_str_to_bool)
group.add_argument('--no' + name, dest=name, action='store_false')
Simplest. It's not flexible, but I prefer simplicity.
parser.add_argument('--boolean_flag',
help='This is a boolean flag.',
type=eval,
choices=[True, False],
default='True')
EDIT: If you don't trust the input, don't use eval.
In addition to what #mgilson said, it should be noted that there's also a ArgumentParser.add_mutually_exclusive_group(required=False) method that would make it trivial to enforce that --flag and --no-flag aren't used at the same time.
This is actually outdated. For Python 3.7+, Argparse now supports boolean args (search BooleanOptionalAction).
The implementation looks like this:
import argparse
ap = argparse.ArgumentParser()
# List of args
ap.add_argument('--foo', default=True, type=bool, help='Some helpful text that is not bar. Default = True')
# Importable object
args = ap.parse_args()
One other thing to mention: this will block all entries other than True and False for the argument via argparse.ArgumentTypeError. You can create a custom error class for this if you want to try to change this for any reason.
A simpler way would be to use as below.
parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
After previously following #akash-desarda 's excellence answer https://stackoverflow.com/a/59579733/315112 , to use strtobool via lambda, later, I decide to use strtobool directly instead.
import argparse
from distutils import util
parser.add_argument('--feature', type=util.strtobool)
Yes you're right, strtobool is returning an int, not a bool. But strtobool will not returning any other value except 0 and 1, and python will get them converted to a bool value seamlessy and consistently.
>>> 0 == False
True
>>> 0 == True
False
>>> 1 == False
False
>>> 1 == True
True
While on receiving a wrong input value like
python yours.py --feature wrong_value
An argparse.Action with strtobool compared to lambda will produce a slightly clearer/comprehensible error message:
yours.py: error: argument --feature: invalid strtobool value: 'wrong_value'
Compared to this code,
parser.add_argument('--feature', type=lambda x: bool(util.strtobool(x))
Which will produce a less clear error message:
yours.py: error: argument --feature: invalid <lambda> value: 'wrong_value'
Simplest way would be to use choices:
parser = argparse.ArgumentParser()
parser.add_argument('--my-flag',choices=('True','False'))
args = parser.parse_args()
flag = args.my_flag == 'True'
print(flag)
Not passing --my-flag evaluates to False. The required=True option could be added if you always want the user to explicitly specify a choice.
As an improvement to #Akash Desarda 's answer, you could do
import argparse
from distutils.util import strtobool
parser = argparse.ArgumentParser()
parser.add_argument("--foo",
type=lambda x:bool(strtobool(x)),
nargs='?', const=True, default=False)
args = parser.parse_args()
print(args.foo)
And it supports python test.py --foo
(base) [costa#costa-pc code]$ python test.py
False
(base) [costa#costa-pc code]$ python test.py --foo
True
(base) [costa#costa-pc code]$ python test.py --foo True
True
(base) [costa#costa-pc code]$ python test.py --foo False
False
I think the most canonical way will be:
parser.add_argument('--ensure', nargs='*', default=None)
ENSURE = config.ensure is None
Expanding on gerardw's answer
The reason parser.add_argument("--my_bool", type=bool) doesn't work is that bool("mystring") is True for any non-empty string so bool("False") is actually True.
What you want is
my_program.py
import argparse
parser = argparse.ArgumentParser(description="My parser")
parser.add_argument(
"--my_bool",
choices=["False", "True"],
)
parsed_args = parser.parse_args()
my_bool = parsed_args.my_bool == "True"
print(my_bool)
$ python my_program.py --my_bool False
False
$ python my_program.py --my_bool True
True
$ python my_program.py --my_bool true
usage: my_program.py [-h] [--my_bool {False,True}]
my_program.py: error: argument --my_bool: invalid choice: 'true' (choose from 'False', 'True')
Quick and easy, but only for arguments 0 or 1:
parser.add_argument("mybool", default=True,type=lambda x: bool(int(x)))
myargs=parser.parse_args()
print(myargs.mybool)
The output will be "False" after calling from terminal:
python myscript.py 0
I found good way to store default value of parameter as False and when it is present in commandline argument then its value should be true.
cmd command
when you want argument to be true:
python main.py --csv
when you want your argument should be false:
python main.py
import argparse
from ast import parse
import sys
parser = argparse.ArgumentParser(description='')
parser.add_argument('--csv', action='store_true', default = False
,help='read from csv')
args = parser.parse_args()
if args.csv:
print('reading from csv')
class FlagAction(argparse.Action):
# From http://bugs.python.org/issue8538
def __init__(self, option_strings, dest, default=None,
required=False, help=None, metavar=None,
positive_prefixes=['--'], negative_prefixes=['--no-']):
self.positive_strings = set()
self.negative_strings = set()
for string in option_strings:
assert re.match(r'--[A-z]+', string)
suffix = string[2:]
for positive_prefix in positive_prefixes:
self.positive_strings.add(positive_prefix + suffix)
for negative_prefix in negative_prefixes:
self.negative_strings.add(negative_prefix + suffix)
strings = list(self.positive_strings | self.negative_strings)
super(FlagAction, self).__init__(option_strings=strings, dest=dest,
nargs=0, const=None, default=default, type=bool, choices=None,
required=required, help=help, metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
if option_string in self.positive_strings:
setattr(namespace, self.dest, True)
else:
setattr(namespace, self.dest, False)
Similar to #Akash but here is another approach that I've used. It uses str than lambda because python lambda always gives me an alien-feelings.
import argparse
from distutils.util import strtobool
parser = argparse.ArgumentParser()
parser.add_argument("--my_bool", type=str, default="False")
args = parser.parse_args()
if bool(strtobool(args.my_bool)) is True:
print("OK")
just do the following , you can make --test = True by using
python filename --test
parser.add_argument("--test" , default=False ,help="test ?", dest='test', action='store_true')
Convert the value:
def __arg_to_bool__(arg):
"""__arg_to_bool__
Convert string / int arg to bool
:param arg: argument to be converted
:type arg: str or int
:return: converted arg
:rtype: bool
"""
str_true_values = (
'1',
'ENABLED',
'ON',
'TRUE',
'YES',
)
str_false_values = (
'0',
'DISABLED',
'OFF',
'FALSE',
'NO',
)
if isinstance(arg, str):
arg = arg.upper()
if arg in str_true_values:
return True
elif arg in str_false_values:
return False
if isinstance(arg, int):
if arg == 1:
return True
elif arg == 0:
return False
if isinstance(arg, bool):
return arg
# if any other value not covered above, consider argument as False
# or you could just raise and error
return False
[...]
args = ap.parse_args()
my_arg = options.my_arg
my_arg = __arg_to_bool__(my_arg)
You can create a BoolAction and then use it
class BoolAction(Action):
def __init__(
self,
option_strings,
dest,
nargs=None,
default: bool = False,
**kwargs,
):
if nargs is not None:
raise ValueError('nargs not allowed')
super().__init__(option_strings, dest, default=default, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
input_value = values.lower()
b = input_value in ['true', 'yes', '1']
if not b and input_value not in ['false', 'no', '0']:
raise ValueError('Invalid boolean value "%s".)
setattr(namespace, self.dest, b)
and then set action=BoolAction in parser.add_argument()
I'm experimenting with argparse, the program works, but default values don't work. Here's my code:
'''This simple program helps you in understanding how to feed the user input from command line and to show help on passing invalid argument.'''
import argparse
import sys
def calc(args):
#Enable variables within the function to take on values from 'args' object.
operation = args.operation
x = args.x
y = args.y
if (operation == "add"):
return x + y
elif (operation == "sub"):
return x - y
parser = argparse.ArgumentParser(description="This is a summing program") #parser is an object of the class Argument Parser.
parser.add_argument("x", type=float, default=1.0, help="What is the first number?") #add_argument is a method of the class ArgumentParser.
parser.add_argument("y", type=float, default=1.0, help='What is the second number?')
parser.add_argument("operation", type=str, default="add", help='What operation? Can choose add, sub, mul, or div')
args = parser.parse_args()
print(str(calc(args)))
This simple program work, however attempting to call it without values returns the following error:
usage: cmdline.py [-h] x y operation
cmdline.py: error: the following arguments are required: x, y, operation
Where am I going wrong?
You are missing nargs='?'. The following works:
import argparse
import sys
def calc(args):
#Enable variables within the function to take on values from 'args' object.
operation = args.operation
x = args.x
y = args.y
if (operation == "add"):
return x + y
elif (operation == "sub"):
return x - y
parser = argparse.ArgumentParser(description="This is a summing program") #parser is an object of the class Argument Parser.
parser.add_argument("x", nargs='?', type=float, default=1.0, help="What is the first number?") #add_argument is a method of the class ArgumentParser.
parser.add_argument("y", nargs='?', type=float, default=1.0, help='What is the second number?')
parser.add_argument("operation", nargs='?', type=str, default="add", help='What operation? Can choose add, sub, mul, or div')
args = parser.parse_args()
print(str(calc(args)))
Change these lines to indicate that you want named, optional command-line arguments (so "-x" not "x"):
parser.add_argument("-x", type=float, default=1.0, help="What is the first number?") #add_argument is a method of the class ArgumentParser.
parser.add_argument("-y", type=float, default=1.0, help='What is the second number?')
#jbcoe - I think you have a few typoes in your code, but thank you, it works! Here's the solution, cleaned up:
'''This simple program helps you in understanding how to feed the user input from command line and to show help on passing invalid argument. It uses the argparse module.'''
import argparse
def calc(args):
#Enable variables within the function to take on values from 'args' object.
operation = args.operation
x = args.x
y = args.y
if (operation == "add"):
return x + y
elif (operation == "sub"):
return x - y
parser = argparse.ArgumentParser(description="This is a summing program") #parser is an object of the class Argument Parser.
parser.add_argument("x", nargs='?', type=float, default=1.0, help="What is the first number?") #add_argument is a method of the class ArgumentParser.
parser.add_argument("y", nargs='?', type=float, default=2.0, help='What is the second number?')
parser.add_argument("operation", nargs='?', type=str, default="add", help='What operation? Can choose add, sub, mul, or div')
args = parser.parse_args()
print(str(calc(args)))
I have a functions like this
def add(x,y):
print x+y
def square(a):
print a**2
Now I am defining linux commands(options) for this functions using argparse.
I tried with this code
import argparse
# Create Parser and Subparser
parser = argparse.ArgumentParser(description="Example ArgumentParser")
subparser = parser.add_subparsers(help="commands")
# Make Subparsers
add_parser = subparser.add_parser('--add', help="add func")
add_parser.add_argument("x",type=float,help='first number')
add_parser.add_argument("y",type=float,help='second number')
add_parser.set_defaults(func='add')
square_parser = subparser.add_parser('--square', help="square func")
square_parser.add_argument("a",type=float,help='number to square')
square_parser.set_defaults(func='square')
args = parser.parse_args()
def add(x,y):
print x + y
def square(a):
print a**2
if args.func == '--add':
add(args.x,args.y)
if args.func == '--square':
square(args.a)
But I am getting error while passing command as python code.py --add 2 3
invalid choice: '2' (choose from '--add', '--square')
--add is the form of an optionals flag, add is the correct form for a subparser name
import argparse
# Create Parser and Subparser
parser = argparse.ArgumentParser(description="Example ArgumentParser")
subparser = parser.add_subparsers(dest='cmd', help="commands")
# Make Subparsers
add_parser = subparser.add_parser('add', help="add func")
add_parser.add_argument("x",type=float,help='first number')
add_parser.add_argument("y",type=float,help='second number')
add_parser.set_defaults(func='add')
square_parser = subparser.add_parser('square', help="square func")
square_parser.add_argument("a",type=float,help='number to square')
square_parser.set_defaults(func='square')
args = parser.parse_args()
print(args)
def add(x,y):
print x + y
def square(a):
print a**2
if args.func == 'add': # if args.cmd=='add': also works
add(args.x,args.y)
if args.func == 'square':
square(args.a)
producing
0950:~/mypy$ python stack43557510.py add 2 3
Namespace(cmd='add', func='add', x=2.0, y=3.0)
5.0
I added dest='cmd' to the add_subparsers command, and print(args) to give more information. Note that the subparser name is now available as args.cmd. So you don't need the added func.
However the argparse docs do suggest an alternative use of set_defaults
https://docs.python.org/3/library/argparse.html#sub-commands
add_parser.set_defaults(func=add)
With this args.func is actually a function object, not just a string name. So it can be used as
args.func(args)
Note that I had to change how the functions handle their parameters:
def add(args):
print(args.x + args.y)
def square(args):
print(args.a**2)
# Create Parser and Subparser
parser = argparse.ArgumentParser(description="Example ArgumentParser")
subparser = parser.add_subparsers(dest='cmd', help="commands")
# Make Subparsers
add_parser = subparser.add_parser('add', help="add func")
add_parser.add_argument("x",type=float,help='first number')
add_parser.add_argument("y",type=float,help='second number')
add_parser.set_defaults(func=add)
square_parser = subparser.add_parser('square', help="square func")
square_parser.add_argument("a",type=float,help='number to square')
square_parser.set_defaults(func=square)
args = parser.parse_args()
print(args)
args.func(args)
producing
1001:~/mypy$ python stack43557510.py add 2 3
Namespace(cmd='add', func=<function add at 0xb73fd224>, x=2.0, y=3.0)
5.0
How can I have a default sub-command, or handle the case where no sub-command is given using argparse?
import argparse
a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser('hi')
a.parse_args()
Here I'd like a command to be selected, or the arguments to be handled based only on the next highest level of parser (in this case the top-level parser).
joiner#X:~/src> python3 default_subcommand.py
usage: default_subcommand.py [-h] {hi} ...
default_subcommand.py: error: too few arguments
On Python 3.2 (and 2.7) you will get that error, but not on 3.3 and 3.4 (no response). Therefore on 3.3/3.4 you could test for parsed_args to be an empty Namespace.
A more general solution is to add a method set_default_subparser() (taken from the ruamel.std.argparse package) and call that method just before parse_args():
import argparse
import sys
def set_default_subparser(self, name, args=None, positional_args=0):
"""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 last position before global positional
# arguments, this implies no global options are specified after
# first positional argument
if args is None:
sys.argv.insert(len(sys.argv) - positional_args, name)
else:
args.insert(len(args) - positional_args, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def do_hi():
print('inside hi')
a = argparse.ArgumentParser()
b = a.add_subparsers()
sp = b.add_parser('hi')
sp.set_defaults(func=do_hi)
a.set_default_subparser('hi')
parsed_args = a.parse_args()
if hasattr(parsed_args, 'func'):
parsed_args.func()
This will work with 2.6 (if argparse is installed from PyPI), 2.7, 3.2, 3.3, 3.4. And allows you to do both
python3 default_subcommand.py
and
python3 default_subcommand.py hi
with the same effect.
Allowing to chose a new subparser for default, instead of one of the existing ones.
The first version of the code allows setting one of the previously-defined subparsers as a default one. The following modification allows adding a new default subparser, which could then be used to specifically process the case when no subparser was selected by user (different lines marked in the code)
def set_default_subparser(self, name, args=None, positional_args=0):
"""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
existing_default = False # check if default parser previously defined
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 sp_name == name: # check existance of default parser
existing_default = True
if not subparser_found:
# If the default subparser is not among the existing ones,
# create a new parser.
# As this is called just before 'parse_args', the default
# parser created here will not pollute the help output.
if not existing_default:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
x.add_parser(name)
break # this works OK, but should I check further?
# insert default in last position before global positional
# arguments, this implies no global options are specified after
# first positional argument
if args is None:
sys.argv.insert(len(sys.argv) - positional_args, name)
else:
args.insert(len(args) - positional_args, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
a = argparse.ArgumentParser()
b = a.add_subparsers(dest ='cmd')
sp = b.add_parser('hi')
sp2 = b.add_parser('hai')
a.set_default_subparser('hey')
parsed_args = a.parse_args()
print(parsed_args)
The "default" option will still not show up in the help:
python test_parser.py -h
usage: test_parser.py [-h] {hi,hai} ...
positional arguments:
{hi,hai}
optional arguments:
-h, --help show this help message and exit
However, it is now possible to differentiate between and separately handle calling one of the provided subparsers, and calling the default subparser when no argument was provided:
$ python test_parser.py hi
Namespace(cmd='hi')
$ python test_parser.py
Namespace(cmd='hey')
It seems I've stumbled on the solution eventually myself.
If the command is optional, then this makes the command an option. In my original parser configuration, I had a package command that could take a range of possible steps, or it would perform all steps if none was given. This makes the step a choice:
parser = argparse.ArgumentParser()
command_parser = subparsers.add_parser('command')
command_parser.add_argument('--step', choices=['prepare', 'configure', 'compile', 'stage', 'package'])
...other command parsers
parsed_args = parser.parse_args()
if parsed_args.step is None:
do all the steps...
Here's a nicer way of adding a set_default_subparser method:
class DefaultSubcommandArgParse(argparse.ArgumentParser):
__default_subparser = None
def set_default_subparser(self, name):
self.__default_subparser = name
def _parse_known_args(self, arg_strings, *args, **kwargs):
in_args = set(arg_strings)
d_sp = self.__default_subparser
if d_sp is not None and not {'-h', '--help'}.intersection(in_args):
for x in self._subparsers._actions:
subparser_found = (
isinstance(x, argparse._SubParsersAction) and
in_args.intersection(x._name_parser_map.keys())
)
if subparser_found:
break
else:
# insert default in first position, this implies no
# global options without a sub_parsers specified
arg_strings = [d_sp] + arg_strings
return super(DefaultSubcommandArgParse, self)._parse_known_args(
arg_strings, *args, **kwargs
)
Maybe what you're looking for is the dest argument of add_subparsers:
(Warning: works in Python 3.4, but not in 2.7)
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='cmd')
parser_hi = subparsers.add_parser('hi')
parser.parse_args([]) # Namespace(cmd=None)
Now you can just use the value of cmd:
if cmd in [None, 'hi']:
print('command "hi"')
You can duplicate the default action of a specific subparser on the main parser, effectively making it the default.
import argparse
p = argparse.ArgumentParser()
sp = p.add_subparsers()
a = sp.add_parser('a')
a.set_defaults(func=do_a)
b = sp.add_parser('b')
b.set_defaults(func=do_b)
p.set_defaults(func=do_b)
args = p.parse_args()
if args.func:
args.func()
else:
parser.print_help()
Does not work with add_subparsers(required=True), which is why the if args.func is down there.
In my case I found it easiest to explicitly provide the subcommand name to parse_args() when argv was empty.
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='commands')
runParser = subparsers.add_parser('run', help='[DEFAULT ACTION]')
altParser = subparsers.add_parser('alt', help='Alternate command')
altParser.add_argument('alt_val', type=str, help='Value required for alt command.')
# Here's my shortcut: If `argv` only contains the script name,
# manually inject our "default" command.
args = parser.parse_args(['run'] if len(sys.argv) == 1 else None)
print args
Example runs:
$ ./test.py
Namespace()
$ ./test.py alt blah
Namespace(alt_val='blah')
$ ./test.py blah
usage: test.py [-h] {run,alt} ...
test.py: error: invalid choice: 'blah' (choose from 'run', 'alt')
In python 2.7, you can override the error behaviour in a subclass (a shame there isn't a nicer way to differentiate the error):
import argparse
class ExceptionArgParser(argparse.ArgumentParser):
def error(self, message):
if "invalid choice" in message:
# throw exception (of your choice) to catch
raise RuntimeError(message)
else:
# restore normal behaviour
super(ExceptionArgParser, self).error(message)
parser = ExceptionArgParser()
subparsers = parser.add_subparsers(title='Modes', dest='mode')
default_parser = subparsers.add_parser('default')
default_parser.add_argument('a', nargs="+")
other_parser = subparsers.add_parser('other')
other_parser.add_argument('b', nargs="+")
try:
args = parser.parse_args()
except RuntimeError:
args = default_parser.parse_args()
# force the mode into namespace
setattr(args, 'mode', 'default')
print args
Here's another solution using a helper function to build a list of known subcommands:
import argparse
def parse_args(argv):
parser = argparse.ArgumentParser()
commands = []
subparsers = parser.add_subparsers(dest='command')
def add_command(name, *args, **kwargs):
commands.append(name)
return subparsers.add_parser(name, *args, **kwargs)
hi = add_command("hi")
hi.add_argument('--name')
add_command("hola")
# check for default command
if not argv or argv[0] not in commands:
argv.insert(0, "hi")
return parser.parse_args(argv)
assert parse_args([]).command == 'hi'
assert parse_args(['hi']).command == 'hi'
assert parse_args(['hi', '--name', 'John']).command == 'hi'
assert parse_args(['hi', '--name', 'John']).name == 'John'
assert parse_args(['--name', 'John']).command == 'hi'
assert parse_args(['hola']).command == 'hola'
You can add an argument with a default value that will be used when nothing is set I believe.
See this: http://docs.python.org/dev/library/argparse.html#default
Edit:
Sorry, I read your question a bit fast.
I do not think you would have a direct way of doing what you want via argparse. But you could check the length of sys.argv and if its length is 1 (only script name) then you could manually pass the default parameters for parsing, doing something like this:
import argparse
a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser('hi')
if len(sys.argv) == 1:
a.parse_args(['hi'])
else:
a.parse_args()
I think that should do what you want, but I agree it would be nice to have this out of the box.
For later reference:
...
b = a.add_subparsers(dest='cmd')
b.set_defaults(cmd='hey') # <-- this makes hey as default
b.add_parser('hi')
so, these two will be same:
python main.py hey
python main.py