5.2 KiB
5.2 KiB
name, description, allowed-tools
| name | description | allowed-tools |
|---|---|---|
| typer-patterns | Modern type-safe Typer CLI patterns with type hints, Enums, and sub-apps. Use when building CLI applications, creating Typer commands, implementing type-safe CLIs, or when user mentions Typer, CLI patterns, type hints, Enums, sub-apps, or command-line interfaces. | Read, Write, Edit, Bash |
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:
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:
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:
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:
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:
./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 CLIexamples/enum-cli/: Enum-based optionsexamples/subapp-cli/: Multi-command with sub-appsexamples/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
@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
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
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/*/