Hiding selected subcommands using argparse

Question:

I am using argparse and have set-up subcommands to my program. I have created sub-parsers to define these sub-commands. I have some admin commands that shouldn’t be shown to users in the help screen. I know we could hide arguments of a sub-command, but I don’t know how we could hide few of the subcommands from showing up in the help list.

Here’s my code snippet,

parser = argparse.ArgumentParser(prog='myProg',
                                    description=desc,
                                   formatter_class=argparse.RawDescriptionHelpFormatter)

subparsers = parser.add_subparsers(dest='sub_parser_name')


myProg_query.add_subparser(subparsers)
myProg_update.add_subparser(subparsers)
myProg_configure.add_subparser(subparsers)
myProg_result.add_subparser(subparsers)

When I run the help command, I get this

%> myProg --help
usage: myProg [-h] 

positional arguments:
{query,update,configure,result}
query               query information
update              Update 
configure           Configure system
result              tabulate the result

From the help output, I would want to display only “query” and “result” to the user. I tried to use argparse.SUPPRESS in add_subparser method, but it would hide all the subcommands. Whatever I searched only talked about hiding individual arguments of each sub-command, but not about hiding sub-command. I might have to create a custom formatter method, but wanted to check if there were any other way to achieve this.

Asked By: abs

||

Answers:

metavar might do the trick:

import argparse
parser = argparse.ArgumentParser()
sp = parser.add_subparsers(metavar='{cmd1,cmd2}')
sp1 = sp.add_parser('cmd1')
sp2 = sp.add_parser('cmd2')
sp3 = sp.add_parser('cmd3')
parser.parse_args()

With this cmd3 does not appear in the usage or help. But it does appear in the error message

error: argument {cmd1,cmd2}: invalid choice: ‘cmd’ (choose from ‘cmd1’, ‘cmd2’, ‘cmd3’)


You may have already discovered this use of help=SUPPRESS. But it requires a custom usage (and possibly description) parameters:

import argparse
parser = argparse.ArgumentParser(usage='%(prog)s [-h] {cmd1,cmd2}')
sp = parser.add_subparsers(help=argparse.SUPPRESS)
sp1 = sp.add_parser('cmd1')
sp2 = sp.add_parser('cmd2')
sp3 = sp.add_parser('cmd3')
parser.parse_args()

To the main parser, subparsers look like choices of a positional argument. As best I can tell there isn’t a way of selectively suppressing choices.


With this level of question, examining the argparse.py code itself can be more help than the docs. In this case I looked at the code for class _SubParsersAction(Action). That’s doubly true if you want to customize the formatter. The existing alternative formatters modify just one or two methods buried deep in the class.


This issue has been raised as a bug issue, http://bugs.python.org/issue22848.

There is a patch that would modify the choices display based on help=SUPPRESS for individual subparsers. But I’m recommending the metavar solution, at least for now. There are other proposed patches for dealing with choices.

Answered By: hpaulj

looks like I found a solution for this issue without any patches for argparse.
It’s enough to modify ‘metavar’ and do not set ‘help’ for the certain subparser.

import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='commands', metavar='{command}')

command = subparsers.add_parser("command",
                                help='command help.',
                                description='command description.')
suppress_command = subparsers.add_parser("suppress_command",
                                         help=argparse.SUPPRESS,
                                         description='suppress command.')
hidden_command = subparsers.add_parser("hidden_command",
                                       description='Hidden command.')

This results in

[root@localhost ~]# parser -h
usage: parser [-h] {command} ...

optional arguments:
-h, --help        print help message and exit

commands:
{command}
    command           command help.
    suppress_command  ==SUPPRESS==

[root@localhost ~]# parser hidden_command -h
usage: parser hidden_command [-h]

Hidden command.

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

In addition to the previous answer. For the hidden command appearing in error message, I have a dirty solution, tested in Python 3.7 (might require adaptation for higher versions).

Once metavar is set the (choose from ...) becomes redundant. To suppress it, this does the trick:

import argparse

class ArgumentErrorPatch(argparse.ArgumentError):
  def __str__(self):
    return re.sub(r" (choose from .*)", "", super().__str__())

argparse.ArgumentError = ArgumentErrorPatch

and

error: argument {cmd1,cmd2}: invalid choice: ‘cmd’ (choose from ‘cmd1’, ‘cmd2’, ‘cmd3’)

becomes

error: argument {cmd1,cmd2}: invalid choice: ‘cmd’

that is, according to me, clear enough and more readable (especially if you have many commands).

Of course, this requires to set metavar for each subcommand (e.g., for subcmd in myapp cmd subcmd args). Still in Python 3.7, this does the trick to fill metavar for a complete subparser (i.e., whose other subparsers have been added) without having to detail commands manually:

def _metavar(parser, hidden_cmds=set()):
  parser.metavar = (
    '{' + ','.join(
      cmd for cmd in parser._name_parser_map if not cmd in hidden_cmds
    ) + '}'
  )

Full example

If you have cmd1, cmd2, cmd3 and you want to hide cmd3:

import argparse
import re

class ArgumentErrorPatch(argparse.ArgumentError):
  def __str__(self):
    return re.sub(r" (choose from .*)", "", super().__str__())

argparse.ArgumentError = ArgumentErrorPatch

def _metavar(parser, hidden_cmds=set()):
  parser.metavar = (
    '{' + ','.join(
      cmd for cmd in parser._name_parser_map if not cmd in hidden_cmds
    ) + '}'
  )

parser = argparse.ArgumentParser()
sp = parser.add_subparsers()
sp1 = sp.add_parser("cmd1")
sp2 = sp.add_parser("cmd2")
sp3 = sp.add_parser("cmd3")

_metavar(sp, hidden_cmds={"cmd3"})
parser.parse_args()

print("ok")

Giving cmd3 results in:

ok

while giving cmd4 results in:

usage: script.py [-h] {cmd1,cmd2} …

script.py: error: argument {cmd1,cmd2}: invalid choice: ‘cmd4’

Answered By: benlaug
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.