Command Management
Command management in Typer is centered around the Typer class, which acts as a registry for commands and subcommand groups. While Typer is built on top of Click, it uses Python type hints and decorators to define CLI structures, automatically handling the conversion of function signatures into CLI parameters.
Single Command Scripts
For simple scripts that only require a single command, Typer provides the typer.run() function. This is the quickest way to create a CLI without explicitly instantiating a Typer app.
import typer
def main(name: str):
print(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
Internally, typer.run() creates an implicit Typer instance, registers the function as a command, and executes it.
The Typer Class and Command Registration
For applications with multiple commands, you use the Typer class found in typer/main.py. Commands are registered using the @app.command() decorator.
Command Naming
By default, Typer converts function names to kebab-case for the CLI. For example, a function named create_user becomes the command create-user. This logic is implemented in typer.main.get_command_name:
def get_command_name(name: str) -> str:
return name.lower().replace("_", "-")
You can override this by providing a name argument to the decorator: @app.command("create").
Command Metadata
The @app.command() decorator supports various metadata options that influence the generated help text and CLI behavior:
help: The description of the command. If omitted, Typer uses the function's docstring.hidden: If set toTrue, the command will not appear in the help output.deprecated: Marks the command as deprecated in the help text.rich_help_panel: Organizes commands into specific panels when using Rich-formatted help.
Organizing Subcommands and Groups
Typer allows nesting applications to create complex CLI hierarchies using app.add_typer(). This method effectively turns another Typer instance into a subcommand group.
Nesting Applications
In larger projects, it is common to define commands in separate modules and then aggregate them in a main entry point.
users.py
import typer
app = typer.Typer()
@app.command()
def create(user_name: str):
print(f"Creating user: {user_name}")
main.py
import typer
import users
app = typer.Typer()
app.add_typer(users.app, name="users")
The "No Name" Gotcha
If you call add_typer() without a name argument, the commands from the sub-app are added directly to the parent app instead of being nested under a group. However, as noted in typer/main.py, if the sub-app has a callback defined, Typer will issue a warning because callbacks are not supported for "nameless" sub-apps:
if not sub_group.name:
if sub_group.callback:
import warnings
warnings.warn(
"The 'callback' parameter is not supported by Typer when using `add_typer` without a name",
stacklevel=5,
)
Shared Logic with Callbacks
The @app.callback() decorator is used to define logic that runs before any subcommand. This is the primary way to implement global options (like --verbose or --config) that apply to all commands in a group.
import typer
app = typer.Typer()
state = {"verbose": False}
@app.callback()
def main(verbose: bool = False):
"""
Manage users in the awesome CLI app.
"""
if verbose:
print("Will write verbose output")
state["verbose"] = True
If a Typer instance has only one command and no callback, it behaves as a single command. If it has multiple commands or a callback, it is treated as a TyperGroup (a subclass of click.Group).
Custom Command and Group Classes
Typer uses custom subclasses of Click's core classes to integrate its features, particularly Rich formatting and enhanced exception handling. These are defined in typer/core.py:
TyperCommand: Inherits fromclick.Command. It overridesformat_optionsandmainto support Rich-based help and error reporting.TyperGroup: Inherits fromclick.Group. It manages the collection of subcommands and ensures they are also instantiated asTyperCommandorTyperGroupobjects.
You can provide your own subclasses to the Typer constructor or the @app.command() decorator via the cls parameter if you need to extend Click's behavior directly.
Rich Integration and Formatting
Typer's command management is tightly integrated with the Rich library for "pretty" output. This is controlled by the TYPER_USE_RICH environment variable and the rich_markup_mode parameter.
When HAS_RICH is true (the default if the library is installed), Typer uses typer.rich_utils to format help screens, including:
- Markdown support: If
rich_markup_mode="markdown", docstrings are rendered as Markdown. - Colorized Panels: Commands and options can be grouped into visually distinct panels using
rich_help_panel. - Pretty Exceptions: If a command fails, Typer uses Rich to print a formatted traceback unless
pretty_exceptions_enableis set toFalse.