Usage
Basic Usage
The mono template comes with a simple greeting command to demonstrate CLI functionality.
Default Behavior
Running mono without arguments uses the default greeting:
Custom Greeting
Pass a name as an argument to customize the greeting:
Multi-Word Names
Use quotes for names with spaces:
Help and Documentation
Get Help
Shows all available commands, arguments, and options.
Building Your Own Commands
The template is designed to be extended with your own commands. Here are examples of common CLI patterns:
Simple Command
Add a basic command with required arguments:
# src/mono/cli.py
@app.command()
def greet(
name: str = typer.Argument(..., help="Person to greet"),
greeting: str = typer.Option("Hello", help="Greeting to use"),
) -> None:
"""Greet someone with a custom greeting."""
typer.echo(f"{greeting}, {name}!")
Usage:
File Processing Command
Process input files with optional output:
from pathlib import Path
@app.command()
def process(
input_file: Path = typer.Argument(..., help="Input file to process"),
output: Path | None = typer.Option(None, "--output", "-o", help="Output file"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
) -> None:
"""Process a file and optionally save results."""
if verbose:
typer.echo(f"Processing {input_file}...")
# Read and process
content = input_file.read_text()
result = content.upper() # Example: convert to uppercase
if output:
output.write_text(result)
typer.echo(f"Saved to {output}")
else:
typer.echo(result)
Usage:
mono process input.txt
mono process input.txt -o output.txt
mono process input.txt -o output.txt --verbose
Interactive Prompts
Get user input interactively:
@app.command()
def configure() -> None:
"""Configure the application interactively."""
name = typer.prompt("What's your name?")
age = typer.prompt("What's your age?", type=int)
confirm = typer.confirm("Are these details correct?")
if confirm:
typer.echo(f"Configured for {name}, age {age}")
else:
typer.echo("Configuration cancelled")
Usage:
mono configure
# What's your name?: Alice
# What's your age?: 30
# Are these details correct? [y/N]: y
# Configured for Alice, age 30
Multiple Commands (Subcommands)
Create command groups for organizing related functionality:
# src/mono/cli.py
# Main app
app = typer.Typer(help="Your CLI tool")
# Create a subcommand group
db_app = typer.Typer(help="Database commands")
app.add_typer(db_app, name="db")
@db_app.command()
def init() -> None:
"""Initialize the database."""
typer.echo("Database initialized")
@db_app.command()
def migrate() -> None:
"""Run database migrations."""
typer.echo("Running migrations...")
Usage:
mono db init
# Output: Database initialized
mono db migrate
# Output: Running migrations...
mono db --help
# Shows all database-related commands
Progress Bars
Show progress for long-running operations:
import time
@app.command()
def download(url: str) -> None:
"""Download a file with progress."""
items = range(100)
with typer.progressbar(items, label="Downloading") as progress:
for item in progress:
time.sleep(0.01) # Simulate work
typer.echo("Download complete!")
Rich Output
Use Typer's built-in styling for colorful output:
@app.command()
def status() -> None:
"""Show system status with colors."""
typer.secho("✓ Service running", fg=typer.colors.GREEN, bold=True)
typer.secho("⚠ Warning: High CPU usage", fg=typer.colors.YELLOW)
typer.secho("✗ Error: Disk full", fg=typer.colors.RED, bold=True)
Configuration Files
Read configuration from a file:
import json
from pathlib import Path
@app.command()
def run(
config: Path = typer.Option(
Path("config.json"),
"--config",
"-c",
help="Configuration file",
),
) -> None:
"""Run with configuration from file."""
if not config.exists():
typer.secho(f"Config file not found: {config}", fg=typer.colors.RED)
raise typer.Exit(1)
settings = json.loads(config.read_text())
typer.echo(f"Running with config: {settings}")
Error Handling
Exit gracefully with appropriate status codes:
@app.command()
def validate(file: Path) -> None:
"""Validate a file."""
if not file.exists():
typer.secho(f"Error: {file} not found", fg=typer.colors.RED, err=True)
raise typer.Exit(1)
if not file.suffix == ".json":
typer.secho("Error: File must be JSON", fg=typer.colors.RED, err=True)
raise typer.Exit(1)
typer.secho("✓ File is valid", fg=typer.colors.GREEN)
Testing Your Commands
The template includes pytest configuration for testing CLI commands:
# tests/test_mono.py
from typer.testing import CliRunner
from mono.cli import app
runner = CliRunner()
def test_custom_command() -> None:
"""Test a custom command."""
result = runner.invoke(app, ["greet", "Alice"])
assert result.exit_code == 0
assert "Hello, Alice!" in result.stdout
def test_with_options() -> None:
"""Test command with options."""
result = runner.invoke(app, ["greet", "Bob", "--greeting", "Hi"])
assert result.exit_code == 0
assert "Hi, Bob!" in result.stdout
def test_error_handling() -> None:
"""Test error conditions."""
result = runner.invoke(app, ["validate", "missing.json"])
assert result.exit_code == 1
assert "not found" in result.stdout
Run tests with:
Advanced Patterns
Environment Variables
Read configuration from environment variables:
import os
@app.command()
def connect() -> None:
"""Connect using environment variables."""
host = os.getenv("DB_HOST", "localhost")
port = int(os.getenv("DB_PORT", "5432"))
typer.echo(f"Connecting to {host}:{port}")
Custom Callbacks
Run code before/after commands:
@app.callback()
def callback(
verbose: bool = typer.Option(False, "--verbose", "-v"),
) -> None:
"""Global options that apply to all commands."""
if verbose:
typer.echo("Verbose mode enabled")
Shell Completion
Typer supports shell completion out of the box:
# Install completion for your shell
mono --install-completion
# Or manually:
# Bash
mono --show-completion bash >> ~/.bashrc
# Zsh
mono --show-completion zsh >> ~/.zshrc
# Fish
mono --show-completion fish >> ~/.config/fish/completions/mono.fish
Best Practices
- Use type hints - Typer relies on type hints for validation and help text
- Add help text - Document all commands, arguments, and options
- Validate inputs - Check file existence, validate ranges, etc.
- Use appropriate exit codes - 0 for success, non-zero for errors
- Provide feedback - Use progress bars, status messages, and colors
- Write tests - Test all commands and edge cases
- Keep commands focused - Each command should do one thing well
Next Steps
- Read the Typer documentation for more features
- Check out rich for even fancier terminal output
- Explore the Click documentation (Typer is built on Click)