Python’s argh library: preserve docstring formatting in help message

馋奶兔 提交于 2019-12-03 13:32:56

I'm not familiar with argh, but apparently it is a wrapper to argparse. My guess is that it is taking your function __doc__, and making it the description of a parser, e.g.

parser = argparse.ArgumentParser(description=func.__doc__)

https://docs.python.org/2.7/library/argparse.html#argparse.RawDescriptionHelpFormatter

argparse has a RawDescriptionHelpFormatter that displays the description as is.

parser = argparse.ArgumentParser(description=func.__doc__,
    formatter_class=argparse.RawDescriptionHelpFormatter)

So the question is, is there a way of getting argh to use this formatter?

This argparse script produces the help that you want:

import argparse

def func(foo=1, bar=True):
    """Sample function.

        Parameters:
            foo: float
                An example argument.
            bar: bool
                Another argument.
    """
    print foo, bar

parser = argparse.ArgumentParser(prog='script.py',
    description=func.__doc__,
    formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('-f', '--foo', type=float)
parser.add_argument('-b', '--bar', action='store_false')
parser.print_help()

In argh/dispatching.py

def dispatch_command(function, *args, **kwargs):
    ...
    parser = argparse.ArgumentParser(formatter_class=PARSER_FORMATTER)
    set_default_command(parser, function)
    dispatch(parser, *args, **kwargs)

So you could either set:

PARSER_FORMATTER = argparse.RawDescriptionHelpFormatter

or write your own function:

def raw_dispatch_command(function, *args, **kwargs):
    ...
    parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
    set_default_command(parser, function)
    dispatch(parser, *args, **kwargs)

With the help of @hpaulj I finally managed to obtain the desired behaviour. To facilitate this I defined a custom decorator similar to argh.arg, with the goal to not have to write @argh.arg(‘—param’, help=“%(default)s”) for each parameter separately, but instead to only use one @arg_custom() decorator on my function:

def arg_custom():
    from argh.constants import ATTR_ARGS
    from argh.assembling import  _get_args_from_signature, _fix_compat_issue29

    def wrapper(func):
        declared_args = getattr(func, ATTR_ARGS, [])
        for a in list(_get_args_from_signature(func)):
             declared_args.insert(0, dict(option_strings=a['option_strings'], help="(default: %(default)s)"))
        setattr(func, ATTR_ARGS, declared_args)
        _fix_compat_issue29(func)
        return func
    return wrapper

The crucial point here is that a for loop takes care that all arguments get a corresponding help=“%(default)s” option. Together with changing the corresponding lines in argh/constants.py

class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
        pass
PARSER_FORMATTER = CustomFormatter

we can now conveniently use

@arg_custom()
def func(foo=1, bar=True):
    """Sample function.

        Parameters:
            foo: float
                An example argument.
            bar: bool
                Another argument.
    """
    print foo, bar

argh.dispatch_command(func)

yielding finally

usage: script.py [-h] [-f FOO] [-b]

Sample function.

        Parameters:
            foo: float
                An example argument.
            bar: bool
                Another argument.


optional arguments:
  -h, --help         show this help message and exit
  -f FOO, --foo FOO  (default: 1)
  -b, --bar          (default: True) 

when executing the script with the -h option.

Thanks for your interest in the Argh library. The solutions discussed here will be incorporated in the next release (argh ≥ 0.25). See also the issue #64 (already fixed).

Regarding the issue of getting defaults in the help lines, this argparse script combines the 2 formatter classes

import argparse

def func(foo=1, bar=True):
    ...
    """
    print foo, bar

class MyFormatter(argparse.RawDescriptionHelpFormatter,
    argparse.ArgumentDefaultsHelpFormatter):
    pass

parser = argparse.ArgumentParser(prog='script.py',
    description=func.__doc__,
    formatter_class=MyFormatter)
parser.add_argument('-f', '--foo', type=float, default=1, help='test')
parser.add_argument('-b', '--bar', action='store_false', help='test')
parser.print_help()

producing

usage: script.py [-h] [-f FOO] [-b]

Sample function.
   ...
optional arguments:
  -h, --help         show this help message and exit
  -f FOO, --foo FOO  test (default: 1)
  -b, --bar          test (default: True)

To get the defaults in the help lines I have to include some text (here 'test') in the original help line.

In argh, you might have to use annotations to give it help text.

If you are using annotations, you give it help lines with $(default)s:

parser = argparse.ArgumentParser(prog='script.py',
    description=func.__doc__,
    formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('-f', '--foo', type=float, default=1, help='default: %(default)s')
parser.add_argument('-b', '--bar', action='store_false', help='default: %(default)s')
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!