Set the default command in Python Typer CLI

TL;DR - jump to the solution.

As I mentioned previously, Typer is great, but it's documentation isn't.

While building a Typer app that has only one @app.command() decorated function that function is treated as the default command.

import typer

app = typer.Typer(add_completion=False)

@app.command()
def foo(lat: float = None, long: float = None, method: str = None):
    typer.echo(f"{lat}, {long}, {method}")

if __name__ == "__main__":
    app()

E.g. if you run the above program

/usr/bin/python3 main.py --lat 20.5 --long 88.3 --method cartesian

You will get an output like below

20.5 88.3 cartesian

This is the expected behaviour because Typer has set foo() as the default action if the program is run. But if you create two commands like the example below

import typer

app = typer.Typer(add_completion=False)


@app.command()
def foo(lat: float = None, long: float = None, method: str = None):
    typer.echo(f"{lat}, {long}, {method}")


@app.command()
def bar():
    typer.echo("I'm just here to mess things up...")


if __name__ == "__main__":
    app()

If you re-run this program

/usr/bin/python3 main.py --lat 20.5 --long 88.3 --method cartesian

You will rightly get an error as below stating there are no such options

Usage: main.py [OPTIONS] COMMAND [ARGS]...
Try 'main.py --help' for help.

Error: No such option: --lat

The reason is, since there are two commands, typer is expecting to see at least one of them.

/usr/bin/python3 main.py --help

Checking using help option

As you can see below, there are no default options and two commands, and Typer doesn't know which one to use as default.

Usage: main.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  bar
  foo

Setting a default command

The answer to out problems? Callbacks!

Typer has a callback functionality allows the developer to create CLI parameters for the main CLI application itself.

So in our example, we will make two changes 1. change foo() decorator from @app.command() to @app.callback() 2. Add invoke_without_command=True argument to the above

import typer

app = typer.Typer(add_completion=False)


@app.callback(invoke_without_command=True)
def foo(lat: float = None, long: float = None, method: str = None):
    typer.echo(f"{lat}, {long}, {method}")


@app.command()
def bar():
    typer.echo("I'm just here to mess things up...")


if __name__ == "__main__":
    app()

If you check the help again,

Usage: main.py [OPTIONS] COMMAND [ARGS]...

Options:
  --lat FLOAT
  --long FLOAT
  --method TEXT
  --help         Show this message and exit.

Commands:
  bar

You don't see foo() since that has become the default action and the --lat, --long, & --method are added as default options.

If you run the program again

/usr/bin/python3 main.py --lat 20.5 --long 88.3 --method cartesian

You will get an output like below

20.5 88.3 cartesian
Need Help? Open a discussion thread on GitHub.