typer icon indicating copy to clipboard operation
typer copied to clipboard

Main app with no commands no longer shows callback docstring in v0.4.0

Open jayqi opened this issue 2 years ago • 12 comments

First Check

  • [X] I added a very descriptive title to this issue.
  • [X] I used the GitHub search to find a similar issue and didn't find it.
  • [X] I searched the Typer documentation, with the integrated search.
  • [X] I already searched in Google "How to X in Typer" and didn't find any information.
  • [X] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [X] I already checked if it is not related to Typer but to Click.

Commit to Help

  • [X] I commit to help with one of those options 👆

Example Code

import typer

app = typer.Typer()

@app.callback()
def main():
    """Main program help text."""
    pass

@app.command()
def foo():
    """foo command help text."""
    typer.echo("Executed foo.")

if __name__ == "__main__":
    app()

Description

Not sure if this should be considered a bug or enhancement, since this was an undocumented change that could potentially be considered a regression.

  • In typer<=0.3.2, when the main application is executed without any commands, it would previously print the help text from the callback and exit with status code 0.
  • In typer 0.4.0, this same behavior prints only Try 'app.py --help' for help. and Error: Missing command. and exits with status code 2.

I'm fine with the status code change—it does seem to me like an improvement that it has an error code. Thank you for this change. (Though it would be nice for this to be documented in the change log.)

However, I think it would be useful to also directly print the help text without requiring users to make another invocation with --help, as was the previous behavior. The git program, for example, will exit with status 1 but also print the help text when used without any commands.

This appears to be only a typer change, and not a click change, as typer 0.4.0 behaves the same with way both click 8.0.1 and click 7.1.2.

typer 0.4.0; click 8.0.1

❯ python -c "import typer; print('typer', typer.__version__); import click; print('click', click.__version__)"
typer 0.4.0
click 8.0.1

❯ python app.py
Usage: app.py [OPTIONS] COMMAND [ARGS]...
Try 'app.py --help' for help.

Error: Missing command.

❯ echo $?
2

typer 0.4.0; click 7.1.2

❯ python -c "import typer; print('typer', typer.__version__); import click; print('click', click.__version__)"
typer 0.4.0
click 7.1.2

 ❯ python app.py
Usage: app.py [OPTIONS] COMMAND [ARGS]...
Try 'app.py --help' for help.

Error: Missing command.

❯ echo $?
2

typer 0.3.2; click 7.1.2

❯ python -c "import typer; print('typer', typer.__version__); import click; print('click', click.__version__)"
typer 0.3.2
click 7.1.2

❯ python app.py
Usage: app.py [OPTIONS] COMMAND [ARGS]...

  Main program help text.

Options:
  --install-completion  Install completion for the current shell.
  --show-completion     Show completion for the current shell, to copy it or
                        customize the installation.

  --help                Show this message and exit.

Commands:
  foo  foo command help text.

❯ echo $?
0

Operating System

macOS

Operating System Details

No response

Typer Version

0.4.0

Python Version

3.8.10

Additional Context

No response

jayqi avatar Sep 04 '21 16:09 jayqi

I am facing the same bug. It is very annoying. Have to stick to 0.3.2 due to this. Took 5 hours of lazy exploration to find out what's going on.

@tiangolo - could you kindly comment if this is an expected change in functionality, or a bug og 0.4.0? If it's expected, is there any chance to get the old functionality back? :)

Thanks!

toinbis avatar Dec 26 '21 22:12 toinbis

You can use the no_args_is_help for this behaviour:

cli = typer.Typer(no_args_is_help=True)

michaeloliverx avatar Jan 04 '22 11:01 michaeloliverx

Aha, the no_args_is_help argument does recover the old behavior. Thanks! However, it looks like then that the command's status code is 0 (the way it was before). Is there no way to get both the help printed and also have a non-zero status code? I think the status code change is an improvement that I would also like to keep.

jayqi avatar Jan 04 '22 15:01 jayqi

I do think that help strings, especially if not directly invoked with --help but maybe either way, should exit with status 2.

ghost avatar Jan 04 '22 18:01 ghost

Is there a way to set no_args_is_help=True globally? Currently, the only workarounds I can think of are:

  1. Set this parameter for every instance of Typer and command. This isn't ideal since it adds quite a lot of clutter.
  2. Create a wrapper for the Typer class. It would look something like this and be used everywhere instead:
# typer_wrapper.py

import typer

def Typer():
    return typer.Typer(no_args_is_help=True)
  1. CMD+click into typer.Typer and change the default value for no_args_is_help parameters in site-packages installed locally. Of course this only makes sense if you're using the scripts locally and not planning on packaging your CLI.
no_args_is_help: bool = True,

It would be good if there is a way to set it one time globally. I like being able to see the help text and command list without having to type --help every time.

But there is an issue where zero argument commands don't get executed so maybe defaulting no_args_is_help to False is by design...

travisviome avatar Feb 06 '22 04:02 travisviome

I am facing the same issue described by @jayqi, I have the following app:

app = typer.Typer()

@app.command()
def main(
    config_file: Optional[Path] = typer.Option(None),
    metrics: Optional[MetricsOptions] = typer.Option(None, help=""),
    input_file: Optional[Path] = typer.Option(
        None,
        exists=True,
        file_okay=True
    ),
    source: Optional[str] = typer.Option(None),
    fetch: Optional[str] = typer.Option(None)
):

if __name__ == "__main__":
    app()

If I run the app without arguments (pipenv run python ./omnim/src/cli/app.py), I get the following:

TypeError: stat: path should be string, bytes, os.PathLike or integer, not NoneType

Such behavior is kinda expected, as I have a argument that is a file and it must exists(based on the configuration of the app), therefore, if I update the type app to the following:

app = typer.Typer(no_args_is_help=True, invoke_without_command=True)

I would expect that to show up the help message, but this is not what is happening, I still get the error about the file.

I tried different approaches but it seems none is working, any considerations why?

The code is available at https://github.com/shadowman/omni-metric

marabesi avatar Feb 06 '22 11:02 marabesi

@marabesi I suspect that your error may be a different problem. I think it's coming from input_file having a default of None but requiring exists=True. It seems like maybe typer is running os.stat to check for that path's existence but it's getting run on None. This probably should be a new, separate issue—it seems like this is an uncaught error, and either typer should convert None to Path() and still work, or it should give a typer error that says the value for input_file is not a valid path.

jayqi avatar Feb 06 '22 17:02 jayqi

thanks for the heads up @jayqi

marabesi avatar Mar 06 '22 20:03 marabesi

Could we change the default of no_args_is_help to True? actually is pretty handy to get the help when no args are passed.

FrancescElies avatar May 31 '22 13:05 FrancescElies

Maybe we should add no_args_is_help in the docs? Maybe on this page https://typer.tiangolo.com/tutorial/options/help/ had to search quite a wile to find this thread :)

Benoss avatar Nov 04 '22 08:11 Benoss

These should be the default args in my opinion:

context_settings={"help_option_names": ["-h", "--help"]},
no_args_is_help=True,

#201

atgctg avatar Jul 03 '23 11:07 atgctg

+1 for the regression behavior, and vote on the default True behavior for no_args_is_help.

huxuan avatar Aug 07 '23 06:08 huxuan