question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[FEATURE] Set a defaut command in multiple commands

See original GitHub issue

I wanna to be able to set a default command when I have multiples commands. For example, with:

import typer

app = typer.Typer()


@app.command()
def hello(name: str):
    typer.echo(f"Hello {name}")


@app.command()
def goodbye(name: str, formal: bool = False):
    if formal:
        typer.echo(f"Goodbye Ms. {name}. Have a good day.")
    else:
        typer.echo(f"Bye {name}!")


if __name__ == "__main__":
    app()

I would like to have some way of being able to call it without specifying the command, calling a default command (maybe with something like @app.command(default=True) like this:

$ python main.py Hiro
Hello Hiro

$ python main.py helo Hiro  # This also should work
Hello Hiro

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:8 (2 by maintainers)

github_iconTop GitHub Comments

8reactions
martsa1commented, Apr 21, 2020

UPDATE 2: I can accomplish what I need using the context from within the callback:

@APP.callback(invoke_without_command=True)
def default(
        ctx: typer.Context,
        sample_arg: int = typer.Option(
            None,
        ),
        secondary_arg: int = typer.Option(
            None,
        ),
) -> None:
    """Sub-command that I would like to be the default."""
    if ctx.invoked_subcommand is not None:
        print("Skipping default command to run sub-command.")
        return
    
    ...

This yields the CLI I wanted. 😃


UPDATE: I found the @APP.callback(invoke_without_command=True) stuff, which lets me run the callback like a command, I think that is probably enough for me to build the CLI I would like.

@tiangolo - I love the docs for this project, but when searching for specific options, I do miss an API reference page, would something like that be possible to add at some point? I’d be up for helping to build it, if that would help?


Apologies for opening an old thread, I bumped into this scenario just recently…

Is there a way to use a callback with a set of arguments, without specifying a subcommand?

What I would like to do, is provide a CLI along the lines of: tool release --option a, alongside tool release all. I would like these two actions to be exclusive, if I provide options to the default, but name no other command, I’d like to run that default command. If I provide a command to typer, I would like to run that command without (or simply skipping), the default command…

For example, here’s a minimal reproduction of what I currently see (in reality, this CLI is nested into a larger project, hence the APP):

import typer

APP = typer.Typer(
    no_args_is_help=True,
    help="Minimal repro example for default sub-command.",
)


@APP.callback()
def default(
        sample_arg: int = typer.Option(
            None,
        ),
        secondary_arg: int = typer.Option(
            None,
        ),
) -> None:
    """Sub-command that I would like to be the default."""
    print(f"Sample Arg: {sample_arg}")
    print(f"Secondary Arg: {secondary_arg}")


@APP.command(name="all")
def all_(
        optional: bool = typer.Option(
            False,
            "-a",
            "--all",
            help="Some option.",
        ),
) -> None:
    """Sub-command."""
    print(f"Optional: {optional}")


if __name__ == "__main__":
    APP()

Outputs:

$ python temp.py 
Usage: temp.py [OPTIONS] COMMAND [ARGS]...

  Minimal repro example for default sub-command.

Options:
  --sample-arg INTEGER
  --secondary-arg INTEGER
  --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:
  all  Sub-command.

$ python temp.py --sample-arg 1
Usage: temp.py [OPTIONS] COMMAND [ARGS]...
Try 'temp.py --help' for help.

Error: Missing command.
# ^^ This, I would like to run the content of the callback function without erroring.

$ python temp.py --sample-arg 1 all
Sample Arg: None
Secondary Arg: None
Optional: False
# ^^ This, I would like to call without the callback running, so that I wouldn't see `Sample Arg: None` in this case.

Given the above desired output, I feel that perhaps some kind of default subcommand, rather than a callback would be more appropriate? An example of a CLI like this would be git remote, where it has a set of functionality and options for just git remote, but also includes subcommands like add etc.

Is there something I’ve missed that would let me build something like this?

Thanks in advance, Typer is a wonderful library!

5reactions
JohnGiorgicommented, May 5, 2021

UPDATE 2: I can accomplish what I need using the context from within the callback:

@APP.callback(invoke_without_command=True)
def default(
        ctx: typer.Context,
        sample_arg: int = typer.Option(
            None,
        ),
        secondary_arg: int = typer.Option(
            None,
        ),
) -> None:
    """Sub-command that I would like to be the default."""
    if ctx.invoked_subcommand is not None:
        print("Skipping default command to run sub-command.")
        return
    
    ...

This yields the CLI I wanted. 😃

@ABitMoreDepth I am trying to do something similar to your example here, but where I have a str argument to my callback like this:

import typer


app = typer.Typer()


@app.command()
def subcommand() -> None:
    print("Running subcommand")


@app.callback(invoke_without_command=True)
def main(
    ctx: typer.Context,
    example_arg: str = typer.Argument(...),
) -> None:
    if ctx.invoked_subcommand is not None:
        return

    print("Running callback")


if __name__ == "__main__":
    app()

Unfortunately, if I try to call python test.py subcommand, typer interprets "subcommand" as the string argument for "example_arg" and therefore ctx.invoked_subcommand is None so it runs the callback main, not subcommand as I would expect.

python test.py subcommand 
Running callback

When "example_arg" is an int, like in your example, I simply get an error

python test.py subcommand
Usage: test.py [OPTIONS] EXAMPLE_ARG COMMAND [ARGS]...
Try 'test.py --help' for help.

Error: Invalid value for 'EXAMPLE_ARG': subcommand is not a valid integer

I am wondering if and how you got around this?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Lesson 1.2: Multiple, Default, and Hidden Commands
With Spectre.Console.Cli, we can define multiple commands, each with their own arguments and options, and each getting called when the user ...
Read more >
One or Multiple Commands - Typer - tiangolo
Typer, build great CLIs. Easy to code. Based on Python type hints.
Read more >
Modular Commands — cmd2 2.4 documentation
CommandSets represent a logical grouping of commands within an cmd2 application. By default, all CommandSets will be discovered and loaded automatically ...
Read more >
Commands and Groups — Click Documentation (7.x)
The default implementation for such a merging system is the CommandCollection class. It accepts a list of other multi commands and makes the...
Read more >
combine multiple commands into one r function - Stack Overflow
I need to create a function that will return several things, I started with a real data example and was able to get...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found