This question already has answers here:
Python argparse mutual exclusive group
(4 answers)
Closed 6 years ago.
I have a single Python file which includes unit tests in the source code. It works like this:
parser = argparse.ArgumentParser()
parser.add_argument('--test', action='store_true', help="Run unit tests and return.")
args = parser.parse_args()
if args.test:
testsuite = unittest.TestLoader().loadTestsFromTestCase(SanitizeTestCase)
unittest.TextTestRunner(verbosity=1).run(testsuite)
If args.test is false, the program runs as expected. I don't want to have to make this into an entire setuptools project, it's a pretty simple script with some unit tests to evaluate that it does what it's supposed to.
I now find myself needing to parse other arguments, and that's where things start to fall apart. --test is a mutually exclusive parameter and all of the other parameters don't apply if --test is passed.
Is there a way to have mutually-exclusive argument groups in argparse?
There is a mutually exclusive group mechanism, but all the arguments in that group are mutually exclusive. You can't say, --test xor any of the other others.
But such a group doesn't do anything profound. It adds some markings to the usage line (try it), and it complains when your user (yourself?) violates the exclusivity.
You can do the same things yourself, and fine tune them. You can give the parser a custom usage line. And after parsing you can choose to ignore conflicting values, or you can choose to raise your own error message (parser.error('dumb user, cant you read ...?')). You could, for example, ignore all the other arguments, regardless of value, if this args.test is True.
Related
I'm trying to implement the following argument dependency using the argparse module:
./prog [-h | [-v schema] file]
meaning the user must pass either -h or a file, if a file is passed the user can optionally pass -v schema.
That's what I have now but that doesn't seem to be working:
import argparse
parser = argparse.ArgumentParser()
mtx = parser.add_mutually_exclusive_group()
mtx.add_argument('-h', ...)
grp = mtx.add_argument_group()
grp.add_argument('-v', ...)
grp.add_argument('file', ...)
args = parser.parse_args()
It looks like you can't add an arg group to a mutex group or am I missing something?
If -h means the default help, then this is all you need (this help is already exclusive)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file')
parser.add_argument('-s','--schema')
parser.parse_args('-h'.split()) # parser.print_help()
producing
usage: stack23951543.py [-h] [-s SCHEMA] file
...
If by -h you mean some other action, lets rename it -x. This would come close to what you describe
parser = argparse.ArgumentParser()
parser.add_argument('-s','--schema', default='meaningful default value')
mxg = parser.add_mutually_exclusive_group(required=True)
mxg.add_argument('-x','--xxx', action='store_true')
mxg.add_argument('file', nargs='?')
parser.parse_args('-h'.split())
usage is:
usage: stack23951543.py [-h] [-s SCHEMA] (-x | file)
Now -x or file is required (but not both). -s is optional in either case, but with a meaningful default, it doesn't matter if it is omitted. And if -x is given, you can just ignore the -s value.
If necessary you could test args after parsing, to confirm that if args.file is not None, then args.schema can't be either.
Earlier I wrote (maybe over thinking the question):
An argument_group cannot be added to a mutually_exclusive_group. The two kinds of groups have different purposes and functions. There are previous SO discussions of this (see 'related'), as well as a couple of relevant Python bug issues. If you want tests that go beyond a simple mutually exclusive group, you probably should do your own testing after parse_args. That may also require your own usage line.
An argument_group is just a means of grouping and labeling arguments in the help section.
A mutually_exclusive_group affects the usage formatting (if it can), and also runs tests during parse_args. The use of 'group' for both implies that they are more connected than they really are.
http://bugs.python.org/issue11588 asks for nested groups, and the ability to test for 'inclusivity' as well. I tried to make the case that 'groups' aren't general enough to express all the kinds of testing that users want. But it's one thing to generalize the testing mechanism, and quite another to come up with an intuitive API. Questions like this suggest that argparse does need some sort of 'nested group' syntax.
This question already has answers here:
Argparse with required subparser
(2 answers)
Closed 3 years ago.
I would like to make sure at least one of the sub commands is selected. But there is no required option for add_subparsers() how can I enforce at least one subparser is selected?
Currently I did this to mimic the effect:
subparsers = parser.add_subparsers(
title='sub commands',
help='valid sub commands',
)
subparser1 = subparsers.add_parser('subcmd1')
subparser1.set_defaults(which_subcmd='subcmd1')
subparser2 = subparsers.add_parser('subcmd2')
subparser2.set_defaults(which_subcmd='subcmd2')
parsedargs = parser.parse_args()
if 'which_subcmd' not in parsedargs:
parser.print_help()
But I want an official way to do this and make the help content display something like {subcmd1 | subcmd2}
Update:
according to #hpaulj, in 3.7 there is required option. But I want some work around can work in python 3.5 and 3.6
Instead of printing help, I would prefer to raise an error:
if which_subcmd not in parsedargs:
msg = "Subcommands needed: subcmd1, subcmd2"
raise argparse.ArgumentTypeError(msg)
This way is more consistent with other argparse errors. But just a matter of taste. I don't see anything wrong on your approach as long as you exit from your script after that print_help() statement.
Did some research, but couldn't find any working solution. I'm trying to parse the following command line, where 'test' and 'train' are two independent subcommands each having distinct arguments:
./foo.py train -a 1 -b 2
./foo.py test -a 3 -c 4
./foo.py train -a 1 -b 2 test -a 3 -c 4
I've been trying using two subparsers ('test','train') but it seems like only one can be parsed at the time. Also it would be great to have those subparsers parents of the main parser such that, e.g. command '-a' doesn't have to be added both to the subparsers 'train' and 'test'
Any solution?
This has been asked before, though I'm not sure the best way of finding those questions.
The whole subparser mechanism is designed for one such command. There are several things to note:
add_subparsers creates a positional argument; unlike optionals a `positional acts only once.
'add_subparsers' raises an error if you invoke it several times
the parsing is built around only one such call
One work around that we've proposed in the past is 'nested' or 'recursive' subparers. In other words train is setup so it too takes a subparser. But there's the complication as to whether subparsers are required or not.
Or you can detect and call multiple parsers, bypassing the subparser mechanism.
From the sidebar
Multiple invocation of the same subcommand in a single command line
and
Parse multiple subcommands in python simultaneously or other way to group parsed arguments
This question already has answers here:
Python argparse mutual exclusive group
(4 answers)
Closed 6 years ago.
My program has two functionalities. One is run without any arguments, and the other can have optional arguments. The groups can't interfere with each other.
import argparse
parser = argparse.ArgumentParser()
root_group = parser.add_mutually_exclusive_group()
group_export = root_group.add_argument_group()
group_export.add_argument('--export', action='store_true', help='Exports data from database')
group_export.add_argument('-l', action='append', help='Reduce output with league name')
group_export.add_argument('-d', action='append', help='Reduce output with date range')
group_run = root_group.add_argument_group()
group_run.add_argument('--run', action='store_true', help='Start gathering of data')
I want this to be allowed:
python file.py --export -l name1 -l name2 -d 1/1/2015
python file.py --export
python file.py --run
And this to be not allowed:
python file.py --run --export # Namespace(d=None, export=True, l=None, run=True)
python file.py --run -l name1 # Namespace(d=None, export=False, l=['name1'], run=True)
However, as on now neither of the disallowed operations rises an error, as indicated by the comments.
Argument groups don't nest inside a mutually exclusive group. Despite the names, the two kinds of groups have different purposes.
Argument groups group arguments in the help display. They do nothing during parsing.
Mutually exclusive groups test the occurrence of arguments, and try to display that in the usage line.
You could make --export and --run mutually exclusive. But it won't block the use of l or d with run. But you could just ignore those values. Or you could do your own tests after parsing, and complain that the point.
What would be a meaningful way of representing this constrain in the usage line? You may need to customize that.
Another possibility is to use subparsers. That might fit your case better. The 'export' parser would define the arguments that work with that. The 'run' would not accept any further arguments.
In one way or other this has been discussed in other argparse questions. The sidebar seems to have found some possible matches.
Using argparse, is there a simple way to specify arguments which are mutually exclusive so that the application asks for one of these arguments have to be provided but only one of them?
Example of fictive use-case:
> myapp.py foo --bar
"Foo(bar) - DONE"
> myapp.py read truc.txt
"Read: truc.txt - DONE"
>myapp.py foo read
Error: use "myapp.py foo [options]" or "myapp.py read [options]" (or something similar).
> myapp.py foo truc.txt
Error: "foo" action don't need additional info.
> myapp.py read --bar
Error: "read" action don't have a "--bar" option.
My goal is to have a "driver" application(1) that would internally apply one action depending on the first command line argument and have arguments depending on the action.
So far I see no obvious ways to do this with argparse without manually processing the arguments myself, but maybe I missed something Pythonic? (I'm not a Python3 expert...yet)
I call it "driver" because it might be implemented by calling another application, like gcc does with different compilers.
What you're trying to do is actually supported quite well in Python.
See Mutual Exclusion
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('foo', dest='foo', nargs=1)
group.add_argument('read', dest='read', nargs=1)
args = parser.parse_args()
return args