classification
Title: support title and description in argparse add_mutually_exclusive_group
Type: enhancement Stage: test needed
Components: Library (Lib) Versions: Python 3.4
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: barry, bethard, chris.jerdonek, paul.j3, r.david.murray, tati_alchueyr, tshepang
Priority: normal Keywords:

Created on 2013-02-17 00:38 by chris.jerdonek, last changed 2016-09-20 23:44 by barry.

Files
File name Uploaded Description Edit
test_argparse_mutex_with_title.py tati_alchueyr, 2014-04-14 20:28 proposed behavior for add_mutually_exclusive_group() when title and description are provided
test_argparse_mutex_with_title1.py paul.j3, 2014-04-16 20:03
Messages (8)
msg182251 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2013-02-17 00:38
This issue is to add to argparse's add_mutually_exclusive_group() method support for passing a title and description.  From the argparse docs:

"Note that currently mutually exclusive argument groups do not support the title and description arguments of add_argument_group()."

(from http://docs.python.org/dev/library/argparse.html#argparse.add_mutually_exclusive_group )
msg216193 - (view) Author: Tatiana Al-Chueyr (tati_alchueyr) * Date: 2014-04-14 20:03
My proposal is that both:
- add_mutually_exclusive_group()
- add_argument_group()

print optional arguments in the same way when title and/or description are provided.

I've attached a test case of the proposed behavior.

Please, let me know if you have any objections or suggestions! :)

I'm working on the implementation right now.

Note: it is outside the existing Lib/test/test_argparse.py for clarifying the idea.

BTW: thanks to r.david.murray for mentoring and discussing use cases!
msg216203 - (view) Author: Tatiana Al-Chueyr (tati_alchueyr) * Date: 2014-04-14 20:28
uploading test file
msg216505 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-04-16 17:24
While mutually exclusive groups are a subclass of argument groups, they have very different uses.

Argument groups are used solely to organize the help section. Groups are not used at all during parsing.  'parse_args' doesn't even pay attention to those 2 default groups (positionals and optionals).  'parse_args' just uses the master list of actions ('parser._actions'). 

Mutually exclusive groups are not used at all while formatting the help lines.  They are used to format the usage line.  They also implement argument tests during parsing.

Groups are not set up for nesting.  However it is possible to define a mutually exclusive group within an argument group, and effectively give it a title and description.

    p=argparse.ArgumentParser()
    g=p.add_argument_group('title')
    g1=g.add_mutually_exclusive_group()
    g1.add_argument('--foo')
    p.print_help()

producing:

    usage: ipython [-h] [--foo FOO]
    optional arguments:
      -h, --help  show this help message and exit
    title:
      --foo FOO

Both kinds of groups are a superficial addition to argparse.  I argue in http://bugs.python.org/issue11588 that they aren't adequate for handling more complicated groupings (inclusive, nesting, etc).

While I'm not convinced the change proposed in this issue is necessary, if we are going implement it, I'd suggest a simple addition to 'add_mutually_exclusive_group()'.  If there's a title argument, add this group to '_action_groups' as well as '_mutually_exclusive_groups'.

    def add_mutually_exclusive_group(self, **kwargs):
        group = _MutuallyExclusiveGroup(self, **kwargs)
        self._mutually_exclusive_groups.append(group)
        try:
            kwargs.title
            self._action_groups.append(group)
        except AttributeError:
            pass
        return group

This should do the job without adding much complexity.
msg216559 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-04-16 19:15
Using a mutually_exclusive_group is a little more complicated than I implied in the previous post.

    p=argparse.ArgumentParser()
    g=p.add_mutually_exclusive_group()
    # currently the code objects to 'title' and 'description' keywords
    g.add_argument('--foo')
    g.add_argument('--bar')
    # but a title can be added after creation
    g.title='test'
    g.description='description'
    # now add the group to the list that is used for help formatting
    p._action_groups.append(g)
    p.print_help()

producing:

    usage: ipython [-h] [--foo FOO | --bar BAR]
    optional arguments:
      -h, --help  show this help message and exit
      --foo FOO
      --bar BAR
    test:
      description
      --foo FOO
      --bar BAR

Now the arguments appear in both the 'optional arguments' group (a default one), and the new group.  That's not what we want.

So the mutually_exclusive_group has to be changed so it accepts title and description.  And it also has to block the addition of arguments to one of the existing groups.

A key difference is in how _add_action is implemented for the 2 group classes:

For argument_group:

    def _add_action(self, action):
        action = super(_ArgumentGroup, self)._add_action(action)
        self._group_actions.append(action)
        return action

for mutually exclusive group

    def _add_action(self, action):
        ...
        action = self._container._add_action(action)
        self._group_actions.append(action)
        return action

The first uses 'super' to add the action to itself.  The second adds the action to its 'container'.

That difference allows you to add a mutually_exclusive_group to an argument_group (or to another mutually_exclusive_group), but you can't add an argument_group to another argument_group (no nesting).

I don't like the idea of using a different _add_action method depending on whether group has a 'title' or not.  That's too kludgy.

Another possibility is to have 'parser.add_mutually_exclusive_group()' do what I first demonstrated - first create an 'argument_group' with the title, and add the mutually_exclusive_group to that.  This is still kludgy, but the change is limited to one function.  For demonstration purposes it probably could be implemented in a new function (add_titled_mutually_exclusive_group).
msg216574 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-04-16 20:03
The attached file implements a solution using a subclassed ArgumentParser and a .add_titled_mutually_exclusive_group method (two versions).

I changed the test conditions a bit, removing a blank line, and making the usage 'exclusive'.
msg216761 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-04-17 22:27
The idea of nesting a mutually_exclusive_group in a titled argument_group is already present in `test_argparse.py`.

    class TestMutuallyExclusiveInGroup(MEMixin, TestCase):

    def get_parser(self, required=None):
        parser = ErrorRaisingArgumentParser(prog='PROG')
        titled_group = parser.add_argument_group(
            title='Titled group', description='Group description')
        mutex_group = \
            titled_group.add_mutually_exclusive_group(required=required)
        mutex_group.add_argument('--bar', help='bar help')
        mutex_group.add_argument('--baz', help='baz help')
        return parser

    failures = ['--bar X --baz Y', '--baz X --bar Y']
    successes = [
        ('--bar X', NS(bar='X', baz=None)),
        ('--baz Y', NS(bar=None, baz='Y')),
    ]
    successes_when_not_required = [
        ('', NS(bar=None, baz=None)),
    ]

    usage_when_not_required = '''\
        usage: PROG [-h] [--bar BAR | --baz BAZ]
        '''
    usage_when_required = '''\
        usage: PROG [-h] (--bar BAR | --baz BAZ)
        '''
    help = '''\

        optional arguments:
          -h, --help  show this help message and exit

        Titled group:
          Group description

          --bar BAR   bar help
          --baz BAZ   baz help
        '''
 
So now the question is - do we to modify `add_mutually_exclusive_group` to streamline this task?  An alternative is to add a note to the documentation, eg.

    Note that currently mutually exclusive argument groups do not     
    support the title and description arguments of add_argument_group().
    However a such a group can be added to a titled argument group.
    (and then add an example)
msg216763 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-04-17 23:22
oops - one more glitch (revealed by TestMutuallyExclusiveInGroup):

'add_mutually_exclusive_group' accepts a 'required' argument, but 'add_argument_group' does not.  So 'add_titled_mutually_exclusive_group' needs to be changed to temporarily remove that argument (if given).
History
Date User Action Args
2016-09-20 23:44:11barrysetnosy: + barry
2016-09-20 23:00:15berker.peksaglinkissue28220 superseder
2014-04-17 23:22:20paul.j3setmessages: + msg216763
2014-04-17 22:27:44paul.j3setmessages: + msg216761
2014-04-16 20:03:49paul.j3setfiles: + test_argparse_mutex_with_title1.py

messages: + msg216574
2014-04-16 19:15:20paul.j3setmessages: + msg216559
2014-04-16 17:24:54paul.j3setmessages: + msg216505
2014-04-14 20:28:56tati_alchueyrsetfiles: + test_argparse_mutex_with_title.py

messages: + msg216203
2014-04-14 20:27:23tati_alchueyrsetfiles: - test_argparse_mutex_with_title.py
2014-04-14 20:03:56tati_alchueyrsetfiles: + test_argparse_mutex_with_title.py
nosy: + r.david.murray, paul.j3, tati_alchueyr
messages: + msg216193

2013-02-22 20:14:58tshepangsetnosy: + tshepang
2013-02-17 00:38:22chris.jerdonekcreate