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: Subparser help does not respect SUPPRESS argument
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.5
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Brett.Hannigan, Drake Bridgewater, barry, bethard, derks, floreal, gaul, mattlong, mitar, paul.j3, r.david.murray
Priority: normal Keywords: patch

Created on 2014-11-11 17:51 by Brett.Hannigan, last changed 2022-04-11 14:58 by admin.

Files
File name Uploaded Description Edit
argparse_test.py Brett.Hannigan, 2014-11-11 17:51 Quick python script illustrating the issue.
argparse.py Brett.Hannigan, 2014-11-11 18:08 Proposed patch to argparse.py (python2.7)
argparse.patch Brett.Hannigan, 2014-11-12 18:19
issue22484.patch mattlong, 2015-04-15 16:17 help=SUPPRESS hides a subparser review
issue22848.py paul.j3, 2018-05-07 03:03
Messages (14)
msg231035 - (view) Author: Brett Hannigan (Brett.Hannigan) * Date: 2014-11-11 17:51
When adding an argument to a subparser and passing help=argparse.SUPPRESS, I would expect this argument to not show up when running help.  Instead, I find that the argument is listed and the help given is ==SUPPRESS==.  For example (also in attached python script):
import argparse

parser = argparse.ArgumentParser('test')
subparsers = parser.add_subparsers()
parser_foo = subparsers.add_parser('foo', help='This is help for foo')
parser_bar = subparsers.add_parser('bar', help=argparse.SUPPRESS)

parser.parse_args(['-h'])

usage: test [-h] {foo,bar} ...

positional arguments:
  {foo,bar}
    foo       This is help for foo
    bar       ==SUPPRESS==

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

I believe I have found the proper fix in argparse.py
In the class _SubParsersAction look at the method add_parser().
There is the following block of code:

if 'help' in kwargs:
            help = kwargs.pop('help')
            choice_action = self._ChoicesPseudoAction(name, help)
            self._choices_actions.append(choice_action)

This should instead check to see if help is SUPPRESS or not like so:

if 'help' in kwargs:
            help = kwargs.pop('help')
            if help != SUPPRESS:
                choice_action = self._ChoicesPseudoAction(name, help)
                self._choices_actions.append(choice_action)

If I make this change locally, then the above code does in fact suppress printing the bar option.
msg231098 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-11-13 06:32
A notational point - you are adding a subparser, not an argument, to the subparsers action.  

Why would a user want to use `help=argparse.SUPPRESS`, as  opposed to simply omitting the `help` parameter?  The effect would be the same as your patch.

Another possibility is to interpret this SUPPRESS as meaning, omit the subparser (and it's aliases?) from the choices list(s) as well.  In other words, make this an entirely stealth choice.

    usage: test [-h] {foo} ...
    positional arguments:
      {foo}
        foo       This is help for foo
        ...

'test bar -h' would still display a help for that subparser (unless given a `add_help=False` parameter).

I don't know how much work this stronger SUPPRESS would required, or whether such an interpretation would be intuitive to most users.  There isn't a mechanism to omit a regular argument from the usage, so why should there be a mechanism for omitting a subparsers choice from usage?

With the weaker interpretation, this patch fills in a hole in the logic, but doesn't add any functionality (that I can think of).
msg232400 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2014-12-09 20:50
Targetting to 3.5 and nosying myself.  It would be nice if it were possible to suppress the help of an entire subparser, but I took a quick look at the code and it seems tricky.
msg236253 - (view) Author: BJ Dierkes (derks) Date: 2015-02-20 06:59
I would like to add to this regarding the following:

[QUOTE]
Why would a user want to use `help=argparse.SUPPRESS`, as  opposed to simply omitting the `help` parameter?  The effect would be the same as your patch.
[/QUOTE]


This actually isn't the case, if you omit the 'help' option entirely, the sub-parser is still listed in the {short_list}.  For example:

    import argparse

    root_parser = argparse.ArgumentParser(prog='myapp')
    root_parser.add_argument('--foo', action=CustomAction)
    sub_parsers = root_parser.add_subparsers(dest='commands', title='subcommands')
    sub_parser = sub_parsers.add_parser('sub-command-1', help='useless help txt')
    sub_parser = sub_parsers.add_parser('sub-command-2', help=argparse.SUPPRESS)
    sub_parser = sub_parsers.add_parser('sub-command-3')

    args = root_parser.parse_args()


You end up with:

$ python argtest.py --help
usage: myapp [-h] [--foo FOO] {sub-command-1,sub-command-2,sub-command-3} ...

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

subcommands:
  {sub-command-1,sub-command-2,sub-command-3}
    sub-command-1       useless help txt
    sub-command-2       ==SUPPRESS==


The 'sub-command-3' is still listed in the {sub-parsers} .... where, if I want to hide a sub-parser... I don't want to see it anywhere.  Would be ideal to have a 'hide=True' option for sub-parsers and arguments alike.  All the same functionality, you just wouldn't see it in '--help'.
msg240883 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-04-14 13:36
I believe BJ is saying the same thing that Paul is, that SUPPRESS could omit the subparser from the list of subparsers.  I haven't checked, but I believe this means that the proposed patch doesn't do anything other than what omitting the help would do, so a different patch is required to remove the subparser from the list of subparsers in the help.
msg241111 - (view) Author: Matt Long (mattlong) * Date: 2015-04-15 15:08
I prefer the idea of help=SUPPRESSED resulting in a "hidden" subcommand. That is, one that does not show up at all in the usage/help output:

    import argparse

    parser = argparse.ArgumentParser(prog='myapp')
    parser.add_argument('--foo', action=CustomAction)
    sub_parsers = parser.add_subparsers(dest='commands', title='subcommands')
    sub_parser = sub_parsers.add_parser('sub-command-1', help='sub-command-1 help')
    sub_parser = sub_parsers.add_parser('sub-command-2', help=argparse.SUPPRESS)
    sub_parser = sub_parsers.add_parser('sub-command-3')

    parser.parse_args(['-h'])

Would result in:

usage: myapp [-h] [--foo FOO] {sub-command-1,sub-command-3} ...

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

subcommands:
  {sub-command-1,sub-command-3}
    sub-command-1       normal subcommand help


Assuming this behavior, what should happen if you request help for a subparser with help=SUPPRESSED? That is, you know the subcommand exists even though it's not mentioned in the usage. I would assume it would show usage as normal (note the description kwarg for sub-command-2):

    import argparse

    parser = argparse.ArgumentParser(prog='myapp')
    parser.add_argument('--foo', action='store_true')
    sub_parsers = parser.add_subparsers(dest='commands', title='subcommands')
    sub_parser = sub_parsers.add_parser('sub-command-1', help='sub-command-1 help')
    sub_parser = sub_parsers.add_parser('sub-command-2',
                                        help=argparse.SUPPRESS,
                                        description='description of suppressed sub-command-2')
    sub_parser = sub_parsers.add_parser('sub-command-3')

    parser.parse_args(['sub-command-2', '-h'])

Would result in:

usage: myapp sub-command-2 [-h]

description of suppressed sub-command-2

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


An edge case to consider: what should top-level help look like if ALL subparsers are suppressed? It seems like a degenerate scenario, but complete is important, right? The one that feels most correct to me is to follow the current behavior when you call add_subparsers but never call add_parser:

    import argparse

    parser = argparse.ArgumentParser(prog='myapp')
    parser.add_argument('--foo', action='store_true')
    sub_parsers = parser.add_subparsers(dest='commands', title='subcommands')
    parser.parse_args(['-h'])

Currently results in:
usage: myapp [-h] [--foo] {} ...

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

subcommands:
  {}

Another possibility would be to follow the current behavior when you never even call add_subparsers which would of course remove all notion that subcommands are accepted, but that doesn't feel right to me.
msg241124 - (view) Author: Matt Long (mattlong) * Date: 2015-04-15 16:17
Here's a patch for the proposal in my previous comment.

As Barry mentioned, it wasn't as straightforward as I had hoped due to parts of the usage text being generated by the state of both self._name_parser_map and self._choices_actions.
msg241133 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-04-15 17:03
This patch looks good to me, but I did not go through the argparse logic fully.  Paul, can you review this and make sure itliteral_eval makes sense?  There is a small concern about user code that provides a Mapping for choices having an attribute collision on _is_help_suppressed, but I think it is probably small enough we can ignore it.  Or consider it an "undocumented feature".
msg241203 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2015-04-16 06:00
It'll take me a while to work through this proposed patch.  My first reaction is that I am uncomfortable with adding an attribute to all parsers just to implement this help feature.  For one thing it seems to cross functionality boundaries.

I need to review how subparser help is implemented.  A lot of the work is done in the subparsers action itself, not the HelpFormatter.  I have a vague memory of another bug issue involving this subparser help.  I'll have to look it up.

There are several bug issues related to formatting `choices`.  In some cases they are too long to display properly.  And this formatting adds an iterablity requirement.  I worked on consolidating choices formatting into a separate method.  In one version it was module level function, in another refactoring I made it an Action method.  If it was an Action method, then the subparsers_action class could implement its own version.

Another thought - the formatting of choices is closely linked to the metavar.

Anyways, at this point I have a bunch of random thoughts that need research and organizing.
msg241310 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2015-04-17 04:23
Giving the `subparsers` Action a `metavar` might achieve everything the proposed patch does - without any code changes.

In the 1st test case:

    subparsers = parser.add_subparsers(title='subcommands',
                                           description='subcommands description',
                                           help='subcommands help',
                                           metavar='{1,2}')

and for the 2nd

    parser = argparse.ArgumentParser(prog='PROG_ALL',
                                            description='main description')
    subparsers = parser.add_subparsers(title='subcommands',
                                           description='subcommands description',
                                           help='subcommands help',
                                           metavar='{}')
    parser1 = subparsers.add_parser('1')  # no help
    parser2 = subparsers.add_parser('2', aliases=('2alias',))


Note that neither the patch nor the metavar affects how the choices are displayed in an error message, e.g.

    usage: PROG [-h] {} ...
    PROG: error: argument {}: invalid choice: 'foo' (choose from '1', '2', '2alias')

For more discussion on formatting of choices see http://bugs.python.org/issue16468


--------------------

While I'll continue to think about this issue, my tentative suggestion is to make this a documentation fix, rather than a code change.  Just let users know that `add_subparsers` takes a `metavar` parameter, just like `add_argument`.  It can hide or otherwise customize the listing of commands.
msg241841 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2015-04-23 04:26
A similar Stackoverflow question

http://stackoverflow.com/questions/21692395/hiding-selected-subcommands-using-argparse/21712058#21712058
msg316070 - (view) Author: Floreal (floreal) Date: 2018-05-02 13:54
Will this patch be integrated in future days / weeks / years? I still want to add a hidden subparser, but doing this by editing manually the metavar of the subparsers is not a good solution...
msg316137 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2018-05-03 20:26
I've reviewed the comments and proposed patch, and still think that the custom metavar is still the best fix.

subparses.metavar  can be changed after subparsers has been created.  The programmer could, for example, write a simple helper function that calls add_parser, and also appends names to a list.  Then at the end, turn that list into a properly formatted metavar string.

    subparsers.metavar = '(%s}'%','.join(['cmd1','foo','cmd3'])

In fact, if I were to write a patch, I'd take this approach, trying to confine all changes to the _SubParsersAction.add_parser method, and out of the HelpFormatter.
msg316249 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2018-05-07 03:03
I've attached a file that tries out the idea of building a custom `metavar` in the `add_parser` method.

It subclasses argparse._SubParsersAction, and registers it with the parser.  No other modification to production code is required.

Subparsers with a blank `help`, appear in the choices, but not the helps.

Subparsers with a SUPPRESS don't appear in either, but can still appear in error messages

If everything is SUPPRESS, the metavar is '{}'.  I have not tried to handle the case where the user provides his own 'metavar'.  The regular class can handle that just fine.

There are too many untested edge cases to add this to production, but if anyone wants to try it, feedback will be welcomed.

This makes SUPPRESS in the subparser help behave more like SUPPRESS in the regular Action help.  Otherwise a custom 'metavar' works just as well.
History
Date User Action Args
2022-04-11 14:58:10adminsetgithub: 67037
2020-05-03 05:31:01mitarsetnosy: + mitar
2018-05-18 20:48:24gaulsetnosy: + gaul
2018-05-07 03:03:40paul.j3setfiles: + issue22848.py

messages: + msg316249
2018-05-03 20:26:36paul.j3setmessages: + msg316137
2018-05-02 13:54:46florealsetnosy: + floreal
messages: + msg316070
2016-09-22 17:43:18Drake Bridgewatersetnosy: + Drake Bridgewater
2015-04-23 04:26:06paul.j3setmessages: + msg241841
2015-04-17 04:23:13paul.j3setmessages: + msg241310
2015-04-16 06:00:32paul.j3setmessages: + msg241203
2015-04-15 17:03:00r.david.murraysetmessages: + msg241133
2015-04-15 16:17:48mattlongsetfiles: + issue22484.patch

messages: + msg241124
2015-04-15 15:08:32mattlongsetnosy: + mattlong
messages: + msg241111
2015-04-14 13:36:53r.david.murraysetnosy: + r.david.murray
messages: + msg240883
2015-02-20 06:59:04derkssetnosy: + derks
messages: + msg236253
2014-12-09 20:50:23barrysetnosy: + barry

messages: + msg232400
versions: + Python 3.5, - Python 2.7
2014-11-14 21:10:02terry.reedysetnosy: + bethard
2014-11-13 06:32:07paul.j3setmessages: + msg231098
2014-11-12 23:19:42berker.peksagsetnosy: + paul.j3
2014-11-12 18:19:15Brett.Hannigansetfiles: + argparse.patch
keywords: + patch
2014-11-11 18:08:54Brett.Hannigansetfiles: + argparse.py
2014-11-11 17:51:37Brett.Hannigancreate