Command and Application Metadata
Typer uses internal metadata models to decouple the declaration of commands and applications from their execution. Instead of creating Click objects immediately when a decorator is used, Typer stores configuration in "blueprint" classes: CommandInfo and TyperInfo.
These models act as an intermediate representation, allowing Typer to resolve configuration priorities (like help text and default values) before finally generating the underlying Click objects.
Command Metadata with CommandInfo
The CommandInfo class in typer/models.py stores all the metadata required to build a single click.Command.
Lifecycle of a Command
When you use the @app.command() decorator, Typer does not create a command object yet. Instead, it instantiates a CommandInfo object and appends it to the self.registered_commands list of the Typer instance.
# From typer/main.py
def decorator(f: CommandFunctionType) -> CommandFunctionType:
self.registered_commands.append(
CommandInfo(
name=name,
cls=cls,
context_settings=context_settings,
callback=f,
help=help,
# ... other attributes
rich_help_panel=rich_help_panel,
)
)
return f
Conversion to Click
The actual click.Command is created only when the application is invoked. The function get_command_from_info in typer/main.py performs this conversion. It extracts parameters from the callback function and uses the metadata in CommandInfo to instantiate the command class (defaulting to TyperCommand).
# From typer/main.py
def get_command_from_info(
command_info: CommandInfo,
*,
pretty_exceptions_short: bool,
rich_markup_mode: MarkupMode,
) -> click.Command:
assert command_info.callback, "A command must have a callback function"
name = command_info.name or get_command_name(command_info.callback.__name__)
# ... logic to extract params ...
cls = command_info.cls or TyperCommand
command = cls(
name=name,
callback=get_callback(...),
params=params,
help=use_help,
# ...
)
return command
Application Metadata with TyperInfo
TyperInfo is used to store metadata for a Typer application instance or a group of commands. It is primarily used when nesting applications via app.add_typer().
Configuration Merging and Defaults
TyperInfo uses a Default placeholder system (an instance of DefaultPlaceholder) to distinguish between a value that is None because it wasn't set, and a value that was explicitly set to None by the developer.
This allows Typer to implement a priority system for settings like help text. The solve_typer_info_help function in typer/main.py demonstrates this hierarchy:
- Explicit value set in
app.add_typer(). - Explicit value set in the sub-app's
@app.callback(). - Explicit value set in the sub-app's constructor
typer.Typer(help=...). - Inferred docstring from the callback in
app.add_typer(). - Inferred docstring from the sub-app's callback function.
Group Registration
When app.add_typer(sub_app) is called, a TyperInfo object is created and stored in self.registered_groups.
# From typer/main.py
self.registered_groups.append(
TyperInfo(
typer_instance,
name=name,
cls=cls,
invoke_without_command=invoke_without_command,
no_args_is_help=no_args_is_help,
# ...
)
)
The Resolution Process
When a Typer app is executed (via __call__), it triggers the resolution process in get_command. This function determines whether the app should behave as a single command or a group:
- Group Resolution: If there are multiple commands, registered groups, or a specific callback,
get_groupis called. This usesget_group_from_infoto recursively build aTyperGroup(a subclass ofclick.Group), iterating through allregistered_commandsandregistered_groups. - Single Command Resolution: If there is exactly one command and no groups/callbacks, Typer optimizes by creating a single
click.Commanddirectly from the first item inregistered_commands.
Custom Command and Group Classes
Both CommandInfo and TyperInfo accept a cls argument. This allows developers to pass custom subclasses of TyperCommand or TyperGroup to hook into Click's low-level API while still using Typer's high-level declaration syntax.
# Example of providing a custom class via CommandInfo
command_info = CommandInfo(name="custom", cls=MyCustomClickCommandClass, callback=my_func)
This metadata-driven architecture ensures that Typer remains flexible, allowing for complex features like automatic help generation and Rich-formatted output while maintaining a clean separation from the underlying Click execution engine.