This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: argparse help error: arguments created by add_mutually_exclusive_group() are shown outside their parent group created by add_argument_group()
Type: behavior Stage:
Components: Versions: Python 2.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: balage, paul.j3, srikanth
Priority: normal Keywords:

Created on 2015-12-16 13:14 by balage, last changed 2022-04-11 14:58 by admin.

Files
File name Uploaded Description Edit
issue25882.py paul.j3, 2015-12-17 03:32
Messages (7)
msg256518 - (view) Author: Balázs Regényi (balage) Date: 2015-12-16 13:14
So, a parent parser is created. It has a "global arguments" group (by add_argument_group()) and this group has a mutually exclusive group. Then a child parser is created used previous parser as parent. 
The error: in the child parser help: the arguments of the mutually exclusive group are printed into the part of child parser instead of their "global arguments" group.

The next code shows the problem:

import argparse
# create parent parser
parent_parser = argparse.ArgumentParser(add_help = False)
# create group for its arguments
global_group = parent_parser.add_argument_group("global arguments")
global_group.add_argument("--global-arg1")
global_group.add_argument("--global-arg2")
mutex_group = global_group.add_mutually_exclusive_group()
mutex_group.add_argument("--mutex-arg1")
mutex_group.add_argument("--mutex-arg2")
# create child parser
child_parser = argparse.ArgumentParser(parents = [parent_parser])
child_parser.add_argument("--child-arg1")
child_parser.add_argument("--child-arg2")
print("="*100)
parent_parser.print_help()
print("="*100)
child_parser.print_help()


The output:

====================================================================================================
usage: test.py [--global-arg1 GLOBAL_ARG1] [--global-arg2 GLOBAL_ARG2]
               [--mutex-arg1 MUTEX_ARG1 | --mutex-arg2 MUTEX_ARG2]

global arguments:
  --global-arg1 GLOBAL_ARG1
  --global-arg2 GLOBAL_ARG2
  --mutex-arg1 MUTEX_ARG1
  --mutex-arg2 MUTEX_ARG2
====================================================================================================
usage: test.py [-h] [--global-arg1 GLOBAL_ARG1] [--global-arg2 GLOBAL_ARG2]
               [--mutex-arg1 MUTEX_ARG1 | --mutex-arg2 MUTEX_ARG2]
               [--child-arg1 CHILD_ARG1] [--child-arg2 CHILD_ARG2]

optional arguments:
  -h, --help            show this help message and exit
  --mutex-arg1 MUTEX_ARG1
  --mutex-arg2 MUTEX_ARG2
  --child-arg1 CHILD_ARG1
  --child-arg2 CHILD_ARG2

global arguments:
  --global-arg1 GLOBAL_ARG1
  --global-arg2 GLOBAL_ARG2


The error is that the --mutex-arg-s can be seen in "optional arguments" part instead of "global arguments" part in the second (child help) case.
In the first (parent help) case the --mutex-arg-s are printed into the good section.
msg256569 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2015-12-17 00:06
In

_ActionsContainer._add_container_actions there is this note:

    # add container's mutually exclusive groups
    # NOTE: if add_mutually_exclusive_group ever gains title= and
    # description= then this code will need to be expanded as above

In other bug/issues we've noted that the way to give an exclusive group a title and description is to nest it in an argument_group.

So it looks like no one has worked through the details - in this parent situation - of adding a nested mutually exclusive group.

In my preliminary tests, it looks like the 'group_map' is being messed up by the block of code that adds the mutually_exclusive_groups.  I don't know how easy it is to fix that.

The immediate user solution is not use parents and nested mutually exclusive groups together.
msg256573 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2015-12-17 03:32
I've attached a file that monkey patches the _ActionsContainer._add_container_actions method.

It corrects the `_container` attribute of the copied mutually exclusive groups.  I've only tested it for the case presented in this issue (and the relate Stackoverflow question).

I may construct of formal patch later.
msg256597 - (view) Author: Balázs Regényi (balage) Date: 2015-12-17 11:33
paul.j3, thx the the patch, it is perfect!!!
msg256598 - (view) Author: Balázs Regényi (balage) Date: 2015-12-17 11:46
Unfortunately the problem consists in case of normal sub-group too :(.

If you complete my previous example code with the next code, then the problem can be seen:

sub_group = global_group.add_argument_group()
sub_group.add_argument("--sub-arg1")
sub_group.add_argument("--sub-arg2")

Part of unexpected output:

optional arguments:
  -h, --help            show this help message and exit
  --sub-arg1 SUB_ARG1
  --sub-arg2 SUB_ARG2
  --child-arg1 CHILD_ARG1
  --child-arg2 CHILD_ARG2



Is there a patch for it too? :)
msg256612 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2015-12-17 18:17
Argument groups are not designed to be nested.

If you print_help the parent parser, you'll see that the sub_args are missing entirely, not just displaced.  They appear in the usage, but not the help lines.  sub_group has no record that it was added to global_group (it doesn't have the ._container attribute).  

All containers, both parsers and groups, have a ._action_groups list (inherited from their common super), but I don't think any of the group code uses that list.  And the part of the help_formatter than handles groups is not recursive.  It handles just one level of groups.

I suspect the sub_group actions appear in the 'optional arguments' group for similar reasons as with the mutually_exclusive_group, but the details probably differ.

There have been a number of questions on SO about using argument_groups to add actions to a mutually_exclusive_group.  They either want a convenient way of adding a group of actions, or they want some sort of 'any'  logic applied to the subgroup.  We've had to say - no you can't nest groups like that.  I have explored in another bug/issue the idea of nesting groups and applying all sorts of logic (not just xor), but that's a big issue.
msg288606 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2017-02-26 18:04
Earlier issue on the same topic - passing a mutually exclusive group via parents

http://bugs.python.org/issue16807

Can they be consolidated?
History
Date User Action Args
2022-04-11 14:58:25adminsetgithub: 70070
2017-09-06 11:26:42martin.panterlinkissue31360 superseder
2017-02-26 18:04:42paul.j3setmessages: + msg288606
2016-02-10 23:25:46srikanthsetnosy: + srikanth
2015-12-17 18:17:24paul.j3setmessages: + msg256612
2015-12-17 11:46:25balagesetmessages: + msg256598
2015-12-17 11:33:10balagesetmessages: + msg256597
2015-12-17 03:32:01paul.j3setfiles: + issue25882.py

messages: + msg256573
2015-12-17 00:06:43paul.j3setnosy: + paul.j3
messages: + msg256569
2015-12-16 13:14:43balagecreate