typer-patterns
Provides modern type-safe Typer CLI patterns including type hints, Enum usage, sub-app composition, and Typer() instance patterns for building maintainable command-line applications.
Core Patterns
1. Type-Safe Commands with Type Hints
Use Python type hints for automatic validation and better IDE support:
python
import typer
from typing import Optional
from pathlib import Path
app = typer.Typer()
@app.command()
def process(
input_file: Path = typer.Argument(..., help="Input file path"),
output: Optional[Path] = typer.Option(None, help="Output file path"),
verbose: bool = typer.Option(False, "--verbose", "-v"),
count: int = typer.Option(10, help="Number of items to process")
) -> None:
"""Process files with type-safe parameters."""
if verbose:
typer.echo(f"Processing {input_file}")
2. Enum-Based Options
Use Enums for constrained choices with autocomplete:
python
from enum import Enum
class OutputFormat(str, Enum):
json = "json"
yaml = "yaml"
text = "text"
@app.command()
def export(
format: OutputFormat = typer.Option(OutputFormat.json, help="Output format")
) -> None:
"""Export with enum-based format selection."""
typer.echo(f"Exporting as {format.value}")
3. Sub-Application Composition
Organize complex CLIs with sub-apps:
python
app = typer.Typer()
db_app = typer.Typer()
app.add_typer(db_app, name="db", help="Database commands")
@db_app.command("migrate")
def db_migrate() -> None:
"""Run database migrations."""
pass
@db_app.command("seed")
def db_seed() -> None:
"""Seed database with test data."""
pass
4. Typer() Instance Pattern
Use Typer() instances for better organization and testing:
python
def create_app() -> typer.Typer:
"""Factory function for creating Typer app."""
app = typer.Typer(
name="myapp",
help="My CLI application",
add_completion=True,
no_args_is_help=True
)
@app.command()
def hello(name: str) -> None:
typer.echo(f"Hello {name}")
return app
app = create_app()
if __name__ == "__main__":
app()
Usage Workflow
- •Identify pattern need: Determine which Typer pattern fits your use case
- •Select template: Choose from templates/ based on complexity
- •Customize: Adapt type hints, Enums, and sub-apps to your domain
- •Validate: Run validation script to check type safety
- •Test: Use example tests as reference
Template Selection Guide
- •basic-typed-command.py: Single command with type hints
- •enum-options.py: Commands with Enum-based options
- •sub-app-structure.py: Multi-command CLI with sub-apps
- •typer-instance.py: Factory pattern for testable CLIs
- •advanced-validation.py: Custom validators and callbacks
Validation
Run the type safety validation:
bash
./scripts/validate-types.sh path/to/cli.py
Checks:
- •All parameters have type hints
- •Return types specified
- •Enums used for constrained choices
- •Proper Typer decorators
Examples
See examples/ for complete working CLIs:
- •
examples/basic-cli/: Simple typed CLI - •
examples/enum-cli/: Enum-based options - •
examples/subapp-cli/: Multi-command with sub-apps - •
examples/factory-cli/: Testable Typer factory pattern
Best Practices
- •Always use type hints: Enables auto-validation and IDE support
- •Prefer Enums over strings: For constrained choices
- •Use Path for file paths: Better validation than str
- •Document with docstrings: Typer uses them for help text
- •Keep commands focused: One command = one responsibility
- •Use sub-apps for grouping: Organize related commands together
- •Test with factory pattern: Makes CLIs unit-testable
Common Patterns
Callback for Global Options
python
@app.callback()
def main(
verbose: bool = typer.Option(False, "--verbose", "-v"),
ctx: typer.Context = typer.Context
) -> None:
"""Global options applied to all commands."""
ctx.obj = {"verbose": verbose}
Custom Validators
python
def validate_port(value: int) -> int:
if not 1024 <= value <= 65535:
raise typer.BadParameter("Port must be between 1024-65535")
return value
@app.command()
def serve(port: int = typer.Option(8000, callback=validate_port)) -> None:
"""Serve with validated port."""
pass
Rich Output Integration
python
from rich.console import Console
console = Console()
@app.command()
def status() -> None:
"""Show status with rich formatting."""
console.print("[bold green]System online[/bold green]")
Integration Points
- •Use with
cli-structureskill for overall CLI architecture - •Combine with
testing-patternsfor CLI test coverage - •Integrate with
packagingskill for distribution
References
- •Templates:
templates/ - •Scripts:
scripts/validate-types.sh,scripts/generate-cli.sh - •Examples:
examples/*/