typer icon indicating copy to clipboard operation
typer copied to clipboard

Text blob from `--help`; descriptions from docstrings?

Open dmyersturnbull opened this issue 5 years ago • 4 comments

Related to: #188.
Running mycli --help outputs a wall of text separated from the documentation on arguments and options.

Could Argument/Option help fields be set by parsing Napoleon-style docstrings?
Here's an example of the issue:

class Commands:
    @staticmethod
    @cli.command()
    def new(
        name: str,
        newest=typer.Option(False, hidden=True),
        prompt: bool = False,
    ) -> None:
        """
        Generates something.
        
        Args:
            name: The name to use
            newest: Use the newest version
            prompt: Prompt for full info
        """
mycli new --help
Usage: mycli new [OPTIONS] NAME

  Generates something.

  Args:     name: The name of the project, including any dashes or capital 
  letters   newest: Use the newest nightly release   prompt: Prompt for full info

Arguments:
  NAME  [required]

Options:
  --prompt / --no-prompt          [default: False]

dmyersturnbull avatar Jan 24 '21 00:01 dmyersturnbull

I also use napolean / google-style docstrings and would also love to see this feature implemented

If that's not feasible, is there at least a way to get typer to stop mangling docstrings when it includes them in help text? When I say mangling, i mean that when typer sees a docstring with a single newline character in it, it strips out the newline character, as in @dmyersturnbull's example, but when it encounters two newline characters in a row, it doesn't strip either of them

alextremblay avatar Feb 01 '21 13:02 alextremblay

oh, i see now that it actually strips out ALL newline characters except the ones that separate the docstring summary from the rest of the docstring :(

alextremblay avatar Feb 01 '21 13:02 alextremblay

Yes! This ^^^ I don't use that format but I do use a format. It would be nice if different formats were supported (reStructuredText for instance), but even if only one format was supported that would be awesome.

marcstreeter avatar Jun 14 '21 14:06 marcstreeter

I had the same problem. Suppose this module is called cli.py

import typer

CLI = typer.Typer()


@CLI.command()
def greet() -> None:
    """
    Greet someone.

    Parameters
    ----------
    param1 : str
        description.
    param2 : str
        description.
    param3 : int
        description, by default typer.Option(...)
    param4 : int
        description, by default typer.Option(...)
    param5 : int
        description, by default typer.Option(...)
    """
    print("Hello there!")


@CLI.command()
def greet_back() -> None:
    """
    Greet Obi_wan back.

    Parameters
    ----------
    param1 : str
        description.
    param2 : str
        description.
    param3 : int
        description, by default typer.Option(...)
    param4 : int
        description, by default typer.Option(...)
    param5 : int
        description, by default typer.Option(...)
    """
    print("General Kenobi!")


if __name__ == "__main__":
    CLI()

by running python cli.py greet --help (with typer version 0.6.1), I receive whole_docstring

But I would like to receive only_sumary_docstring

What I did to avoid duplication, was to write this function

import inspect
from typing import Callable, Any

DOCSTRING_SECTIONS = {
    # Numpy style docstring
    "Parameters": True,
    "Returns": True,
    "Raises": True,
    # Google style docstring
    "Args:": True,
    "Returns:": True,
    "Raises:": True,
}
# reST (Sphinx) style docstring
SPHINX_PARAM = ":param"
SPHINX_RETURNS = ":returns"
SPHINX_RAISES = ":raises"


def get_help_from_docstring(command: Callable[..., Any]) -> str:
    """
    Get help message from callable object.

    Parameters
    ----------
    command : Callable[..., Any]
        Callable object (command, callback, ...).

    Returns
    -------
    str
        Docstring summary, if exists; empty string otherwise.
    """
    docstring = inspect.getdoc(command)
    if not docstring:
        return ""
    docstring_lines = docstring.strip().splitlines()
    help_message = ""
    for line in docstring_lines:
        if DOCSTRING_SECTIONS.get(line.strip()) or line.strip().startswith(
            (SPHINX_PARAM, SPHINX_RETURNS, SPHINX_RAISES)
        ):
            break
        help_message += line + "\n" if line else ""
    return help_message

I believe this resolves the problem for the 3 principal docstring formats today.

To it to be added to the Typer source code, I believe it should change all the inspect.getdoc(object) occurrences in typer/main.py to get_help_from_docstring(object).

Of course, this does not solve the problem from #336

mateusoliveira43 avatar Jul 23 '22 18:07 mateusoliveira43