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:
fast βπ¬ The push command with no parametersgit push π¬ The push command with one CLI option --set-upstream and 2 CLI argumentsgit push --set-upstream origin master restart β»
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.
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.
But that is actually a shortcut. Under the hood, Typer converts that to a CLI application with typer.Typer() and executes it. All that inside of typer.run().
There's also a more explicit way to achieve the same:
When you use typer.run(), Typer is doing more or less the same as above, it will:
Create a new typer.Typer() "application".
Create a new "command" with your function.
Call the same "application" as if it was a function with "app()".
@decorator Info
That @something syntax in Python is called a "decorator".
You put it on top of a function. Like a pretty decorative hat (I guess that's where the term came from).
A "decorator" takes the function below and does something with it.
In our case, this decorator tells Typer that the function below is a "command".
Both ways, with typer.run() and creating the explicit application, achieve almost the same.
Tip
If your use case is solved with just typer.run(), that's fine, you don't have to create the explicit app and use @app.command(), etc.
You might want to do that later when your app needs the extra features, but if it doesn't need them yet, that's fine.
If you run the second example, with the explicit app, it works exactly the same:
fast βπ¬ Without a CLI argumentpython main.py Usage: main.py [OPTIONS] NAME Try "main.py --help" for help.
Error: Missing argument 'NAME'.
π¬ With the NAME CLI argumentpython main.py Camila Hello Camila
π¬ Asking for helppython main.py --help Usage: main.py [OPTIONS] NAME
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.
Now we have a CLI application with 2 commands, create and delete:
fast βπ¬ Check the helppython 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 thempython main.py create Creating user: Hiro Hamada
fast βπ¬ Check the help without having to type --helppython 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.
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.