1

Consider the following toy example:

cat extended_help.py
import argparse

ap = argparse.ArgumentParser()
ap.add_argument("-H", "--help-all", action = "version",
                help = """show extended help message (incl. advanced
                       parameters) and exit""",
                version = "This is just a dummy implementation.")
common_args = ap.add_argument_group("common parameters",
                                    """These parameters are typically
                                    enough to run the tool. `%(prog)s
                                    -h|--help` should list these
                                    parameters.""")
advanced_args = ap.add_argument_group("advanced parameters",
                                      """These parameters are for advanced
                                      users with special needs only. To make
                                      the help more accessible, `%(prog)s
                                      -h|--help` should not include these
                                      parameters, while `%(prog)s
                                      -H|--help-all` should include them (in
                                      addition to those included by `%(prog)s
                                      -h|--help`.""")
common_args.add_argument("-f", "--foo", metavar = "<foo>",
                         help = "the very common Foo parameter")
common_args.add_argument("--flag", action = "store_true",
                         help = "a flag enabling a totally normal option")
advanced_args.add_argument("-b", "--bar", metavar = "<bar>",
                           help = "the rarely needed Bar parameter")
advanced_args.add_argument("-B", "--baz", metavar = "<bar>",
                           help = "the even more obscure Baz parameter")
advanced_args.add_argument("--FLAG", action = "store_true",
                           help = "a flag for highly advanced users only")
ap.parse_args()
python extended_help.py -h

prints

usage: extended_help.py [-h] [-H] [-f <foo>] [--flag] [-b <bar>] [-B <bar>] [--FLAG]

options:
  -h, --help       show this help message and exit
  -H, --help-all   show extended help message (incl. advanced parameters) and exit

common parameters:
  These parameters are typically enough to run the tool. `extended_help.py -h|--help` should list these parameters.

  -f, --foo <foo>  the very common Foo parameter
  --flag           a flag enabling a totally normal option

advanced parameters:
  These parameters are for advanced users with special needs only. To make the help more accessible, `extended_help.py -h|--help` should not include these parameters, while
  `extended_help.py -H|--help-all` should include them (in addition to those included by `extended_help.py -h|--help`.

  -b, --bar <bar>  the rarely needed Bar parameter
  -B, --baz <bar>  the even more obscure Baz parameter
  --FLAG           a flag for highly advanced users only

while

python extended_help.py -H

only generates the placeholder message

This is just a dummy implementation.

How would I need to modify extended_help.py to have

python extended_help.py -h

print only

usage: extended_help.py [-h] [-H] [-f <foo>] [--flag] [-b <bar>] [-B <bar>] [--FLAG]

options:
  -h, --help       show this help message and exit
  -H, --help-all   show extended help message (incl. advanced parameters) and exit

common parameters:
  These parameters are typically enough to run the tool. `extended_help.py -h|--help` should list these parameters.

  -f, --foo <foo>  the very common Foo parameter
  --flag           a flag enabling a totally normal option

and have

python extended_help.py -H

reproduce the full help message currently printed by

python extended_help.py -h

?

I am looking for a solution that avoids manually duplicating the help message(s of certain arguments).


edit:

I know I can make -H replace -h as follows:

import argparse

ap = argparse.ArgumentParser(add_help = False)
ap.add_argument("-h", "--help", action = "version",
                help = "show help message (common parameters only) and exit",
                version = """I know I could add the entire (short) help here
                          but I'd like to avoid that.""")
ap.add_argument("-H", "--help-all", action = "help",
                help = """show extended help message (incl. advanced
                       parameters) and exit""")
common_args = ap.add_argument_group("common parameters",
                                    """These parameters are typically
                                    enough to run the tool. `%(prog)s
                                    -h|--help` should list these
                                    parameters.""")
# The rest would be the same as above.

This way,

python extended_help.py -H

already works as intended:

usage: extended_help.py [-h] [-H] [-f <foo>] [--flag] [-b <bar>] [-B <bar>] [--FLAG]

options:
  -h, --help       show help message (common parameters only) and exit
  -H, --help-all   show extended help message (incl. advanced parameters) and exit

common parameters:
  These parameters are typically enough to run the tool. `extended_help.py -h|--help` should list these parameters.

  -f, --foo <foo>  the very common Foo parameter
  --flag           a flag enabling a totally normal option

advanced parameters:
  These parameters are for advanced users with special needs only. To make the help more accessible, `extended_help.py -h|--help` should not include these parameters, while
  `extended_help.py -H|--help-all` should include them (in addition to those included by `extended_help.py -h|--help`.

  -b, --bar <bar>  the rarely needed Bar parameter
  -B, --baz <bar>  the even more obscure Baz parameter
  --FLAG           a flag for highly advanced users only

However, now

python extended_help.py -h

only prints a placeholder:

I know I could add the entire help here but I'd like to avoid that.

update:

I managed to get quite close:

import argparse

ap = argparse.ArgumentParser(add_help = False, conflict_handler = "resolve")
ap.add_argument("-h", "--help", action = "help",
                help = "show help message (common parameters only) and exit")
ap.add_argument("-H", "--help-all", action = "help",
                help = """show extended help message (incl. advanced
                       parameters) and exit""")
common_args = ap.add_argument_group("common parameters",
                                    """These parameters are typically
                                    enough to run the tool. `%(prog)s
                                    -h|--help` should list these
                                    parameters.""")
common_args.add_argument("-f", "--foo", metavar = "<foo>",
                         help = "the very common Foo parameter")
common_args.add_argument("--flag", action = "store_true",
                         help = "a flag enabling a totally normal option")
ap.add_argument("-h", "--help", action = "version", version = ap.format_help())
advanced_args = ap.add_argument_group("advanced parameters",
                                      """These parameters are for advanced
                                      users with special needs only. To make
                                      the help more accessible, `%(prog)s
                                      -h|--help` should not include these
                                      parameters, while `%(prog)s
                                      -H|--help-all` should include them (in
                                      addition to those included by `%(prog)s
                                      -h|--help`.""")
advanced_args.add_argument("-b", "--bar", metavar = "<bar>",
                           help = "the rarely needed Bar parameter")
advanced_args.add_argument("-B", "--baz", metavar = "<bar>",
                           help = "the even more obscure Baz parameter")
advanced_args.add_argument("--FLAG", action = "store_true",
                           help = "a flag for highly advanced users only")
ap.parse_args()

This captures the help message before adding the advanced arguments and overwrites the -h|--help flag's 'version' string (ab-)used to store/print the short help.

python extended_help.py -H

already works as intended, but

python extended_help.py -h

swallows all line breaks and spaces from the help message:

usage: extended_help.py [-h] [-H] [-f <foo>] [--flag] options: -h, --help show help message (common parameters only) and exit -H, --help-all show extended help message (incl.
advanced parameters) and exit common parameters: These parameters are typically enough to run the tool. `extended_help.py -h|--help` should list these parameters. -f, --foo
<foo> the very common Foo parameter --flag a flag enabling a totally normal option

update:

The problem remaining in the version above turned out to be related to me abusing the version action. I solved it by defining my own custom action for the short help ('inspired' by the help action implementation in the argparse module itself).

I'll leave the above steps here for documentation reasons. Feel free to clean up the question (or prompt me to do so), if preferred.

Any feedback to my solution or alternative suggestions would be welcome.

1
  • 1
    Why use version at all? You found the help action option. Have you explored the alternative 'help_formatters'? You may need to explore the argparse.py code, such as methods like format_help. And how the alternative formatters work. Commented Nov 13, 2024 at 15:51

2 Answers 2

0

parser.print_help calls parser.format_help. That in turn creates a formatter, and populates it, with the description, prolog and epilog, and with the _action_groups:

    # positionals, optionals and user-defined groups
    for action_group in self._action_groups:
        formatter.start_section(action_group.title)
        formatter.add_text(action_group.description)
        formatter.add_arguments(action_group._group_actions)
        formatter.end_section()

_action_groups includes the default positionals and optionals, plus your user defined ones - in this case the advanced and common.

I can imagine writing a variant on this method that just sends the common group to the formatter.

The HelpFormatter class is defined at the start of the argparse.py file. It defines methods such as those used in this excerpt. Subclasses like RawDescriptionHelpFormatter and RawTextHelpFormatter typically work by redefining one or more deeply nested methods, ones that change details like the handling of spaces and line feeds.

You can customize the help by specifying one of these subclasses, or even making your own subclass with its own formatting tweak.

argparse is designed to allow a lot of customization via subclasses, like your action, and the formatter.

Sign up to request clarification or add additional context in comments.

Comments

0

This is the solution I came up with myself:

import argparse

# See https://stackoverflow.com/a/8632404/2451238 + argparse._HelpAction source
def short_help(help_message):
  class shortHelpAction(argparse.Action):
    def __init__(self,
                 option_strings,
                 dest = argparse.SUPPRESS,
                 default = argparse.SUPPRESS,
                 help = None,
                 deprecated = False):
      super(shortHelpAction, self).__init__(
            option_strings = option_strings,
            dest = dest,
            default = default,
            nargs = 0,
            help = help,
            deprecated = deprecated)
    def __call__(self, parser, namespace, args, option_string = None):
        print(help_message)
        parser.exit()
  return shortHelpAction

ap = argparse.ArgumentParser(add_help = False, conflict_handler = "resolve")
ap.add_argument("-h", "--help", action = short_help(None),
                help = "show help message (common parameters only) and exit")
ap.add_argument("-H", "--help-all", action = "help",
                help = """show extended help message (incl. advanced
                       parameters) and exit""")
ap.add_argument("-v", "--version", action = "version", version = "1.0")
common_args = ap.add_argument_group("common parameters",
                                    """These parameters are typically
                                    enough to run the tool. `%(prog)s
                                    -h|--help` should list these
                                    parameters.""")
common_args.add_argument("-f", "--foo", metavar = "<foo>",
                         help = "the very common Foo parameter")
common_args.add_argument("--flag", action = "store_true",
                         help = "a flag enabling a totally normal option")
ap.add_argument("-h", "--help", action = short_help(ap.format_help()))
advanced_args = ap.add_argument_group("advanced parameters",
                                      """These parameters are for advanced
                                      users with special needs only. To make
                                      the help more accessible, `%(prog)s
                                      -h|--help` should not include these
                                      parameters, while `%(prog)s
                                      -H|--help-all` should include them (in
                                      addition to those included by `%(prog)s
                                      -h|--help`.""")
advanced_args.add_argument("-b", "--bar", metavar = "<bar>",
                           help = "the rarely needed Bar parameter")
advanced_args.add_argument("-B", "--baz", metavar = "<bar>",
                           help = "the even more obscure Baz parameter")
advanced_args.add_argument("--FLAG", action = "store_true",
                           help = "a flag for highly advanced users only")
ap.parse_args()
python extended_help.py -h

prints

usage: extended_help.py [-h] [-H] [-v] [-f <foo>] [--flag]

options:
  -h, --help       show help message (common parameters only) and exit
  -H, --help-all   show extended help message (incl. advanced parameters) and exit
  -v, --version    show program's version number and exit

common parameters:
  These parameters are typically enough to run the tool. `extended_help.py -h|--help` should list these parameters.

  -f, --foo <foo>  the very common Foo parameter
  --flag           a flag enabling a totally normal option

,

python extended_help.py -v
1.0

, and

python extended_help.py -H

usage: extended_help.py [-H] [-v] [-f <foo>] [--flag] [-h] [-b <bar>] [-B <bar>] [--FLAG]

options:
  -H, --help-all   show extended help message (incl. advanced parameters) and exit
  -v, --version    show program's version number and exit
  -h, --help

common parameters:
  These parameters are typically enough to run the tool. `extended_help.py -h|--help` should list these parameters.

  -f, --foo <foo>  the very common Foo parameter
  --flag           a flag enabling a totally normal option

advanced parameters:
  These parameters are for advanced users with special needs only. To make the help more accessible, `extended_help.py -h|--help` should not include these parameters, while
  `extended_help.py -H|--help-all` should include them (in addition to those included by `extended_help.py -h|--help`.

  -b, --bar <bar>  the rarely needed Bar parameter
  -B, --baz <bar>  the even more obscure Baz parameter
  --FLAG           a flag for highly advanced users only

.


edit:

The class definition can be shortened by relying on the internal definition on the help action for the initialization:

def short_help(help_message):
  class shortHelpAction(argparse._HelpAction):
    def __call__(self, parser, namespace, args, option_string = None):
        print(help_message)
        parser.exit()
  return shortHelpAction

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.