Skip to content

Commands

We have seen how to create a CLI program with possibly several CLI options and CLI arguments.

But Typer allows you to create CLI programs with several commands (also known as subcommands).

For example, the program git has several commands.

One command of git is git push. And git push in turn takes its own CLI arguments and CLI options.

For example:

// The push command with no parameters
$ git push

---> 100%

// The push command with one CLI option --set-upstream and 2 CLI arguments
$ git push --set-upstream origin master

---> 100%

Another command of git is git pull, it also has some CLI parameters.

It's like if the same big program git had several small programs inside.

Tip

A command looks the same as a CLI argument, it's just some name without a preceding --. But commands have a predefined name, and are used to group different sets of functionalities into the same CLI application.

Command or subcommand

It's common to call a CLI program a "command".

But when one of these programs have subcommands, those subcommands are also frequently called just "commands".

Have that in mind so you don't get confused.

Here I'll use CLI application or program to refer to the program you are building in Python with Typer, and command to refer to one of these "subcommands" of your program.

A CLI application with multiple commands

Typer allows creating CLI applications with multiple commands/subcommands.

Now that you know how to create an explicit typer.Typer() application and add one command, let's see how to add multiple commands.

Let's say that we have a CLI application to manage users.

We'll have a command to create users and another command to delete them.

To begin, let's say it can only create and delete one single predefined user:

import typer

app = typer.Typer()


@app.command()
def create():
    print("Creating user: Hiro Hamada")


@app.command()
def delete():
    print("Deleting user: Hiro Hamada")


if __name__ == "__main__":
    app()

Now we have a CLI application with 2 commands, create and delete:

// Check the help
$ python main.py --help

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

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:
  create
  delete

// Test them
$ python main.py create

Creating user: Hiro Hamada

$ python main.py delete

Deleting user: Hiro Hamada

// Now we have 2 commands! 🎉

Notice that the help text now shows the 2 commands: create and delete.

Tip

By default, the names of the commands are generated from the function name.

Show the help message if no command is given

By default, we need to specify --help to get the command's help page.

However, by setting no_args_is_help=True when defining the typer.Typer() application, the help function will be shown whenever no argument is given:

import typer

app = typer.Typer(no_args_is_help=True)


@app.command()
def create():
    print("Creating user: Hiro Hamada")


@app.command()
def delete():
    print("Deleting user: Hiro Hamada")


if __name__ == "__main__":
    app()

Now we can run this:

// Check the help without having to type --help
$ python main.py

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

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:
  create
  delete

Sorting of the commands

Note that by design, Typer shows the commands in the order they've been declared.

So, if we take our original example, with create and delete commands, and reverse the order in the Python file:

import typer

app = typer.Typer()


@app.command()
def delete():
    print("Deleting user: Hiro Hamada")


@app.command()
def create():
    print("Creating user: Hiro Hamada")


if __name__ == "__main__":
    app()

Then we will see the delete command first in the help output:

// Check the help
$ python main.py --help

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

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:
  delete
  create

Click Group

If you come from Click, a typer.Typer app with subcommands is more or less the equivalent of a Click Group.

Technical Details

A typer.Typer app is not a Click Group, but it provides the equivalent functionality. And it creates a Click Group when calling it.

It is not directly a Group because Typer doesn't modify the functions in your code to convert them to another type of object, it only registers them.

Decorator Technical Details

When you use @app.command() the function under the decorator is registered in the Typer application and is then used later by the application.

But Typer doesn't modify that function itself, the function is left as is.

That means that if your function is simple enough that you could create it without using typer.Option() or typer.Argument(), you could use the same function for a Typer application and a FastAPI application putting both decorators on top, or similar tricks.

Click Technical Details

This behavior is a design difference with Click.

In Click, when you add a @click.command() decorator it actually modifies the function underneath and replaces it with an object.