Skip to main content

Configuring Execution State

To manage configuration and shared data across multiple commands in a Typer application, you can use a state object initialized within a callback. For advanced use cases involving the typer command-line tool itself, you can programmatically modify the internal execution state to control how applications are loaded.

Sharing State Across Commands

The most common way to manage execution state is to define a global object (like a dictionary or a custom class) and update it within an @app.callback(). This allows you to capture global options (like --verbose) and make them available to all subcommands.

import typer

app = typer.Typer()
# Initialize a simple state container
state = {"verbose": False}

@app.callback()
def main(verbose: bool = False):
"""
Main entry point that captures global configuration.
"""
if verbose:
print("Will write verbose output")
state["verbose"] = True

@app.command()
def delete(username: str):
if state["verbose"]:
print(f"Deleting user: {username}")
print(f"User {username} deleted")

if __name__ == "__main__":
app()

In this pattern, the main callback runs before any subcommand, ensuring the state dictionary is populated with the correct values from the command-line arguments.

Using a Custom State Class

For more complex applications, using a custom class or dataclass provides better structure and type safety than a dictionary.

import typer
from typing import Optional

class AppState:
def __init__(self):
self.verbose: bool = False
self.config_file: Optional[str] = None

state = AppState()
app = typer.Typer()

@app.callback()
def main(
verbose: bool = typer.Option(False, "--verbose", "-v"),
config: Optional[str] = typer.Option(None, "--config", "-c")
):
state.verbose = verbose
state.config_file = config

@app.command()
def sync():
if state.verbose:
print(f"Syncing using config: {state.config_file}")
# Sync logic here

Configuring CLI Tool State

The typer command-line tool (used for running scripts or generating documentation) uses a specific internal class typer.cli.State to track the target application. You can programmatically modify the global state instance in typer.cli to control which file, module, or app object the CLI tool interacts with.

This is particularly useful when building tools that wrap or extend the Typer CLI's functionality, such as custom documentation generators.

from pathlib import Path
from typer.cli import state, get_typer_from_state

# Programmatically configure the CLI execution state
# This tells the Typer CLI logic where to find the app
state.file = Path("main.py")
state.app = "app" # The name of the Typer() instance in main.py

# Use the internal helper to load the Typer object based on the state
typer_app = get_typer_from_state()

if typer_app:
print(f"Successfully loaded app: {state.app} from {state.file}")

State Properties

The typer.cli.State class (defined in typer/cli.py) supports the following attributes:

  • file: A pathlib.Path object pointing to the Python script.
  • module: A string representing the Python module name (used if file is not set).
  • app: A string representing the name of the typer.Typer instance variable.
  • func: A string representing the name of a function to be treated as the entry point.

Automatic State Updates

In the standard Typer CLI flow, the state is automatically populated from Click context parameters using the maybe_update_state function. This function resolves whether a provided argument is a file path or a module name:

# Internal logic from typer/cli.py
def maybe_update_state(ctx: click.Context) -> None:
path_or_module = ctx.params.get("path_or_module")
if path_or_module:
file_path = Path(path_or_module)
if file_path.exists() and file_path.is_file():
state.file = file_path
else:
# Logic to treat as module...
state.module = path_or_module

Troubleshooting State Conflicts

  • Internal vs. External State: Do not confuse typer.cli.State with your own application state. typer.cli.State is strictly for the typer command-line tool's internal discovery mechanism. For your own app's logic, always define a separate state container.
  • Initialization Order: Ensure your state is modified within the @app.callback(). If you try to access state values in a subcommand that were supposed to be set in a callback, but the callback hasn't run (e.g., during unit testing without proper context), you may encounter default or uninitialized values.
  • CLI Discovery Priority: When using typer.cli.State, if both file and module are set, the discovery logic in get_typer_from_state prioritizes the file path if it exists on disk.