You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
argparse add_mutually_exclusive_group should accept existing arguments to register conflicts #55193
The most straightforward use case argparse can not accommodate is the following: the user has three flags, '-a', '-b', and '-c'. The flag '-b' is incompatible with both '-a' and with '-c', however, '-a' and '-c' are compatible with each other.
Current practice is to register a conflict by first defining a conflict group with parser.add_mutually_exclusive_group(), and then create new arguments within that group using group.add_argument(). Because the programmer is not allowed to create the argument prior to creating the group, an argument cannot be registered in two exclusive groups.
I feel it would be much more useful to be given the option to create exclusive groups after the programmer has defined and created the options, as is the design for ConflictsOptionParser http://pypi.python.org/pypi/ConflictsOptionParser/
I've picked up on this as part of the EP 2013 sprints.
I've attached a patch which implements the behaviour described in the comments. It doesn't break any existing functionality but the help generated by argparse is definitely not quite right.
paulj3 commented on Jul 9, 2013
paulj3mannequin
Mannequin
More actions
This approach of simply adding the existing actions to the group's _group_actions works fine, at least when it comes catching the error.
It may be difficult to get a useful usage line. In usage, arguments appear in the order in which they were created, optionals first, positionals after. Group notation is added if the subset of the arguments appear in its list in the same order.
In the patch .rst,
usage: PROG [-h] (--foo | --bar) [--baz]
the foo,bar group is marked correctly; the foo,baz group is not contiguous and is omited.
In bethard's example neither group will be marked
(class TestMutuallyExclusiveGroupWithExistingArguments)
But the problem isn't just with adding existing arguments.
class TestMutuallyExclusiveOptionalsMixed illustrates this with a case where group and parser arguments overlap.
In class TestMutuallyExclusiveOptionalsAndPositionalsMixed, the mix of optionals and positionals makes group marking impossible.
If the groups are in order, but overlap, usage can be a confusing mix
Groups ab, bc, cd, produce:
[-a A | [-b B | [-c C | -d D]
But if created in a different order, the usage can be:
[-a A | [-b B | -c C] -d D]
So there are 2 issues
if groups are not continuous or overlap, what is a meaningful usage?
any choice is likely to require a major reworking of the formatting logic.
Since confusing group markings are worse than none, a first step might be to flag a group added via this patch as 'do not mark'. Also add a note to the documentation that user may need to write their own grouping instructions (in usage, description or epilog).
paulj3 commented on Jul 11, 2013
paulj3mannequin
Mannequin
More actions
One usage option is to make a subclass of HelpFormatter (the accepted way of customizing a formatter), and write a function that formats each group independently. For the example case, the resulting format might be:
usage: PROG [-h] [-b] [-a | -c] [-a | -d]
-h and -b are not part of any group. These are followed by the two groups. -a is repeated because it appears in both groups.
paulj3 commented on Jul 12, 2013
paulj3mannequin
Mannequin
More actions
While playing with some examples, I found that exclusive group formatting has another failure case. If the usage line is long enough to wrap, optionals and positionals are formatted separately, with positionals appearing on a separate line(s). That means that if a group includes a positional, it will not be marked.
So (shortening lines for convenience sake), instead of:
usage: [-h] ... (-a | -b | x)
we get
usage: [-h] ... [-a] [-b]
x
This is true even if arguments are added to the group in the normal way.
paulj3 commented on Jul 15, 2013
paulj3mannequin
Mannequin
More actions
This patch adds a MultiGroupHelpFormatter that formats groups even if they share actions or the actions are not in the same order as in the parse._actions list. It sorts the groups so positional actions, if any appear in the correct order.
A long test case generates this help:
usage: PROG [-h] [-a A | -c C] [-a A | -d D] [-a A | -b B] [-b B | -d D]
[-d D | x] foo [-b B | y]
positional arguments:
x x help
foo foo help
y y help
optional arguments:
-h, --help show this help message and exit
-a A a help
-b B b help
-c C c help
-d D d help
In the 2nd usage line, the 2 groups, and action foo, are shown in the order in which x, foo, y were defined (and hence will be parsed), even though the groups were not defined in that order.
The default formatter could not format these groups, generating '[-h] [-a A] [-b B] ... x foo y' instead.
I have included the latest patch from http://bugs.python.org/issue11874. This splits the usage line generated by _format_actions_usage into parts that are groups or independent actions. The goal there is to correctly split long usage lines into multiple lines. Here it makes it easier to format groups and actions in new order.
If existing actions are added to new group as in the original patch for this issue, that group gets a no_usage = True attribute. The default formatter then will not attempt to format this group. The MultiGroupHelpFormatter ignores this attribute.
This patch needs better documentation. Test cases also need refinement, improving the names, and eliminating redundancies. Some of the new tests are copies of existing ones, but using the new formatter.
paulj3 commented on Jul 16, 2013
paulj3mannequin
Mannequin
More actions
This patch produces the same usage as before, but I have rewritten _format_actions_usage() for both HelpFormatter and MultiGroupFormater.
The original HelpFormatter._format_actions_usage() formats the actions, splices in group markings, cleans up the text, if needed, tries to break it down into parts. But this is fragile, as shown here and in issues 11874, 18349).
Now _format_group_usage() and _format_just_actions_usage() format groups and actions directly, without the splice and divide steps. _format_actions_usage() for both classes call these to build a list of usage parts.
This does not cause problems with any existing tests, but makes it possible to add an action twice to a group. Why do that? To prevent an action from occurring more than once. For some actions like 'count' and 'append' repeated use is expected, but for others it isn't expected, and may sometimes be a nuisance (the last occurrence is the one that sticks).
An example use would be:
parser=argparse.ArgumentParser(prog="PROG",
formatter_class=argparse.MultiGroupHelpFormatter)
action=parser.add_argument('--arg', help='use this argument only once')
group1=parser.add_mutually_exclusive_group(action, action)
args=parser.parse_args()
calling this with:
python3 test_once.py --arg test --arg next
would produce this error message:
usage: PROG [-h] [--arg ARG | --arg ARG]
PROG: error: argument --arg: not allowed with argument --arg
The usage and error message aren't as clear as they might be if this feature was added 'from scratch'. But for a minor change like this, that may be an acceptable price.
paulj3 commented on Sep 17, 2013
paulj3mannequin
Mannequin
More actions
Another situation in which this MultiGroupHelpFormatter helps is when one or more of the groups includes an optional positional.
The regular formatter moves all the positionals to the end, after the optionals. This move could easily break up a mutually exclusive group, and make formatting it impossible. But the MultiGroupHelpFormatter gives the group order priority.
usage: PROG [-h] [-f F | foo] [-b B | -c C | bar [bar ...]]
In this last case, the positionals are listed with their respective groups, and the groups are ordered by the relative ordering of the positionals.
jamadagni commented on Feb 28, 2014
jamadagnimannequin
Mannequin
More actions
I also wish to see argparse allowing me to define a group of arguments that conflict with another argument or another group of arguments and FWIW I feel the help output should be like:
prog [ --conflicter | [ --opt1 ] [ --opt2 ] ]
where --conflicter conflicts with --opt1 and --opt2 but those two don't conflict with each other and all are optional.
paulj3 commented on Feb 28, 2014
paulj3mannequin
Mannequin
More actions
In http://bugs.python.org/issue11588 (Add "necessarily inclusive" groups to argparse) I propose a generalization to these testing groups that would solve your 'conflicter' case as follows:
usage='prog [ --conflicter | [ --opt1 ] [ --opt2 ] ]'parser=argparse.ArgumentParser(usage=usage)
conflicter=parser.add_argument("--conflicter", action='store_true')
opt1=parser.add_argument("--opt1", action='store_true')
opt2=parser.add_argument("--opt2", action='store_true')
@parser.crosstestdeftest(parser, seen_actions, *args):
ifconflicterinseen_actions:
if0<len(seen_actions.intersection([opt1, opt2])):
parser.error('--conflicter cannot be used with --opt1 or --opt2')
Groups, as currently defined, cannot handle nesting, and as a consequence cannot handle complex logic. My proposal is to replace groups with user defined conflict tests that would be run near the end of 'parse_args'.
This example shows, I think, that the proposal is powerful enough. I'm not sure about ease of use and logical transparency.
Formatting the usage line is a different issue, though the MultiGroupHelpFormatter that I propose here is a step in the right direction. For now a user written 'usage' is the simplest solution.
paulj3 commented on Jun 3, 2014
paulj3mannequin
Mannequin
More actions
Another way to add an existing Action to a group is to modify the 'add_argument' method for the Group subclass. For example we could add this to the _MutuallyExclusiveGroup class:
I also wish to see argparse allowing me to define a group of arguments that conflict with another argument or another group of arguments and FWIW I feel the help output should be like:
prog [ --conflicter | [ --opt1 ] [ --opt2 ] ]
where --conflicter conflicts with --opt1 and --opt2 but those two don't conflict with each other and all are optional.
I know this is a very old issue, but this, to me, is an important feature, and I think it would be very nice if this issue could be re-visited.
The simplest use case could be wanting to break down your arguments into distinct groups visually, which is very useful when one has a large set of options as it aids readability, and still have mutually exclusive options. This doesn't seem to be possible without modifying argparse classes, which adds some overheard to users.
I haven't read the code, but it appears that "group" and "mutually exclusive group" are themselves... mutually exclusive. When logically, there isn't a good reason for them to be? It's natural to want to group options together. That doesn't mean those options can't have mutually exclusive relationships within themselves.
Currently, the way to have both groups and mutually exclusive groups without modifying the base class is to individually check and test each arg individually. If you have a dozen mutually exclusive args, that can easily add another 50 lines of code. It's not very practical :(
@rhettinger tagging you as you seem to be the maintainer for this project. It appears @micktwomey wrote a patch which fixes this back in 2013. I imagine the code has changed a lot since this was first requested, but is there any chance of this being implemented?
I have just encountered this problem -- while I can write a workaround to functionally accomplish the desired behavior, I would prefer to do this via exclusive groups.
Activity
gotgenes commentedon Jan 22, 2011
argparse supports registering conflicting arguments, however, it does so in a way that an argument may belong to at most one group of conflicting arguments. The inspiration for this bug is Stack Overflow question #4770576.
http://stackoverflow.com/questions/4770576/does-argparse-python-support-mutually-exclusive-groups-of-arguments
The most straightforward use case argparse can not accommodate is the following: the user has three flags, '-a', '-b', and '-c'. The flag '-b' is incompatible with both '-a' and with '-c', however, '-a' and '-c' are compatible with each other.
Current practice is to register a conflict by first defining a conflict group with parser.add_mutually_exclusive_group(), and then create new arguments within that group using group.add_argument(). Because the programmer is not allowed to create the argument prior to creating the group, an argument cannot be registered in two exclusive groups.
I feel it would be much more useful to be given the option to create exclusive groups after the programmer has defined and created the options, as is the design for ConflictsOptionParser
http://pypi.python.org/pypi/ConflictsOptionParser/
bethard commentedon Jan 23, 2011
I'm definitely open to providing such functionality. I assume you're imagining something like:
If you can supply a patch, I'll take a look at it.
micktwomey commentedon Jul 6, 2013
I've picked up on this as part of the EP 2013 sprints.
I've attached a patch which implements the behaviour described in the comments. It doesn't break any existing functionality but the help generated by argparse is definitely not quite right.
paulj3 commentedon Jul 9, 2013
This approach of simply adding the existing actions to the group's _group_actions works fine, at least when it comes catching the error.
It may be difficult to get a useful usage line. In usage, arguments appear in the order in which they were created, optionals first, positionals after. Group notation is added if the subset of the arguments appear in its list in the same order.
In the patch .rst,
the foo,bar group is marked correctly; the foo,baz group is not contiguous and is omited.
In bethard's example neither group will be marked
(class TestMutuallyExclusiveGroupWithExistingArguments)
But the problem isn't just with adding existing arguments.
class TestMutuallyExclusiveOptionalsMixed illustrates this with a case where group and parser arguments overlap.
In class TestMutuallyExclusiveOptionalsAndPositionalsMixed, the mix of optionals and positionals makes group marking impossible.
If the groups are in order, but overlap, usage can be a confusing mix
Groups ab, bc, cd, produce:
[-a A | [-b B | [-c C | -d D]
But if created in a different order, the usage can be:
[-a A | [-b B | -c C] -d D]
So there are 2 issues
Since confusing group markings are worse than none, a first step might be to flag a group added via this patch as 'do not mark'. Also add a note to the documentation that user may need to write their own grouping instructions (in usage, description or epilog).
paulj3 commentedon Jul 11, 2013
One usage option is to make a subclass of HelpFormatter (the accepted way of customizing a formatter), and write a function that formats each group independently. For the example case, the resulting format might be:
usage: PROG [-h] [-b] [-a | -c] [-a | -d]
-h and -b are not part of any group. These are followed by the two groups. -a is repeated because it appears in both groups.
paulj3 commentedon Jul 12, 2013
While playing with some examples, I found that exclusive group formatting has another failure case. If the usage line is long enough to wrap, optionals and positionals are formatted separately, with positionals appearing on a separate line(s). That means that if a group includes a positional, it will not be marked.
So (shortening lines for convenience sake), instead of:
we get
This is true even if arguments are added to the group in the normal way.
paulj3 commentedon Jul 15, 2013
This patch adds a MultiGroupHelpFormatter that formats groups even if they share actions or the actions are not in the same order as in the parse._actions list. It sorts the groups so positional actions, if any appear in the correct order.
A long test case generates this help:
In the 2nd usage line, the 2 groups, and action foo, are shown in the order in which x, foo, y were defined (and hence will be parsed), even though the groups were not defined in that order.
The default formatter could not format these groups, generating '[-h] [-a A] [-b B] ... x foo y' instead.
I have included the latest patch from http://bugs.python.org/issue11874. This splits the usage line generated by _format_actions_usage into parts that are groups or independent actions. The goal there is to correctly split long usage lines into multiple lines. Here it makes it easier to format groups and actions in new order.
If existing actions are added to new group as in the original patch for this issue, that group gets a no_usage = True attribute. The default formatter then will not attempt to format this group. The MultiGroupHelpFormatter ignores this attribute.
This patch needs better documentation. Test cases also need refinement, improving the names, and eliminating redundancies. Some of the new tests are copies of existing ones, but using the new formatter.
paulj3 commentedon Jul 16, 2013
This patch produces the same usage as before, but I have rewritten _format_actions_usage() for both HelpFormatter and MultiGroupFormater.
The original HelpFormatter._format_actions_usage() formats the actions, splices in group markings, cleans up the text, if needed, tries to break it down into parts. But this is fragile, as shown here and in issues 11874, 18349).
Now _format_group_usage() and _format_just_actions_usage() format groups and actions directly, without the splice and divide steps. _format_actions_usage() for both classes call these to build a list of usage parts.
This change also solves http://bugs.python.org/issue11874 and http://bugs.python.org/issue18349, since it does not have to break up a formatted text line (and in the process get confused by [] and ()).
paulj3 commentedon Sep 2, 2013
A possible further tweak is, in take_action(), test for conflicts before adding the action to 'seen_non_default_actions'
This does not cause problems with any existing tests, but makes it possible to add an action twice to a group. Why do that? To prevent an action from occurring more than once. For some actions like 'count' and 'append' repeated use is expected, but for others it isn't expected, and may sometimes be a nuisance (the last occurrence is the one that sticks).
An example use would be:
calling this with:
would produce this error message:
The usage and error message aren't as clear as they might be if this feature was added 'from scratch'. But for a minor change like this, that may be an acceptable price.
paulj3 commentedon Sep 17, 2013
Another situation in which this MultiGroupHelpFormatter helps is when one or more of the groups includes an optional positional.
The regular formatter moves all the positionals to the end, after the optionals. This move could easily break up a mutually exclusive group, and make formatting it impossible. But the MultiGroupHelpFormatter gives the group order priority.
Thus for example:
produces (positionals at end, no group markings)
But the MultiGroupHelpFormatter produces:
In this last case, the positionals are listed with their respective groups, and the groups are ordered by the relative ordering of the positionals.
jamadagni commentedon Feb 28, 2014
I also wish to see argparse allowing me to define a group of arguments that conflict with another argument or another group of arguments and FWIW I feel the help output should be like:
prog [ --conflicter | [ --opt1 ] [ --opt2 ] ]
where --conflicter conflicts with --opt1 and --opt2 but those two don't conflict with each other and all are optional.
paulj3 commentedon Feb 28, 2014
In http://bugs.python.org/issue11588 (Add "necessarily inclusive" groups to argparse) I propose a generalization to these testing groups that would solve your 'conflicter' case as follows:
Groups, as currently defined, cannot handle nesting, and as a consequence cannot handle complex logic. My proposal is to replace groups with user defined conflict tests that would be run near the end of 'parse_args'.
This example shows, I think, that the proposal is powerful enough. I'm not sure about ease of use and logical transparency.
Formatting the usage line is a different issue, though the MultiGroupHelpFormatter that I propose here is a step in the right direction. For now a user written 'usage' is the simplest solution.
paulj3 commentedon Jun 3, 2014
Another way to add an existing Action to a group is to modify the 'add_argument' method for the Group subclass. For example we could add this to the _MutuallyExclusiveGroup class:
With this the 1st example might be written as:
This might be more intuitive to users.
avalonv commentedon Dec 5, 2022
I know this is a very old issue, but this, to me, is an important feature, and I think it would be very nice if this issue could be re-visited.
The simplest use case could be wanting to break down your arguments into distinct groups visually, which is very useful when one has a large set of options as it aids readability, and still have mutually exclusive options. This doesn't seem to be possible without modifying argparse classes, which adds some overheard to users.
I haven't read the code, but it appears that "group" and "mutually exclusive group" are themselves... mutually exclusive. When logically, there isn't a good reason for them to be? It's natural to want to group options together. That doesn't mean those options can't have mutually exclusive relationships within themselves.
avalonv commentedon Dec 5, 2022
Currently, the way to have both groups and mutually exclusive groups without modifying the base class is to individually check and test each arg individually. If you have a dozen mutually exclusive args, that can easily add another 50 lines of code. It's not very practical :(
avalonv commentedon Dec 5, 2022
@rhettinger tagging you as you seem to be the maintainer for this project. It appears @micktwomey wrote a patch which fixes this back in 2013. I imagine the code has changed a lot since this was first requested, but is there any chance of this being implemented?
feat(cli): break --help options into categories
LeggoMahEggo commentedon Mar 16, 2023
I have just encountered this problem -- while I can write a workaround to functionally accomplish the desired behavior, I would prefer to do this via exclusive groups.