CLI Development (Typer + Rich)
Consult python3-core for standing defaults. Load python3-testing for test patterns.
Standards
Annotated[Type, typer.Option(...)] syntax for all CLI params
rich_help_panel to group options
- Rich emoji tokens (
:white_check_mark:) not Unicode literals
- Architecture: CLI (Typer) → Business Logic → Services → Display (Rich)
uv run <script> over python3 <script>
- Factory pattern for dependency injection
App Structure
import typer
from rich.console import Console
app = typer.Typer()
console = Console()
@app.command()
def process(
input_file: Annotated[Path, typer.Argument(help="Input file")],
verbose: Annotated[bool, typer.Option("--verbose", "-v")] = False,
) -> None:
"""Process input file."""
...
Rich Width Handling
from rich.console import Console
from rich.table import Table
from rich.measure import Measurement
def get_table_width(table: Table) -> int:
temp = Console(width=9999)
m = Measurement.get(temp, temp.options, table)
return int(m.maximum)
Testing
from typer.testing import CliRunner
runner = CliRunner()
def test_app_runs() -> None:
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
Async Patterns
Use semaphores for I/O-bound CLI tasks:
import asyncio
import typer
from typing import Annotated
@app.command()
def fetch(urls: Annotated[list[str], typer.Argument()], max_concurrent: Annotated[int, typer.Option()] = 10) -> None:
"""Fetch multiple URLs concurrently."""
results = asyncio.run(_fetch_all(urls, max_concurrent))
for result in results:
console.print(result)
async def _fetch_all(urls: list[str], limit: int) -> list[str]:
sem = asyncio.Semaphore(limit)
async with httpx.AsyncClient() as client:
tasks = [_fetch_one(client, u, sem) for u in urls]
return await asyncio.gather(*tasks)
PEP 723 Shebang
#!/usr/bin/env -S uv --quiet run --active --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["typer>=0.21", "rich>=13.0"]
# ///
References
references/typer-app-and-commands.md, references/typer-parameters.md, references/typer-parameter-types.md, references/typer-advanced-patterns.md, references/typer-subcommands.md, references/typer-testing.md — Typer commands, arguments, parameters, subcommands
references/rich-console-and-markup.md, references/rich-renderables.md, references/rich-text-and-syntax.md, references/rich-advanced-patterns.md, references/rich-progress-and-live.md, references/rich-logging-and-tracebacks.md — Rich tables, panels, progress, live displays
references/typer-rich-non-tty-patterns.md, references/typer-rich-tables.md, references/typer-rich-exception-handling.md, references/typer-rich-testing-patterns.md — Typer+Rich integration, non-TTY, width, testing
Related Skills
Load python-engineering:textual when the task involves Textual TUI widgets, screen stack, CSS styling, reactive attributes, Pilot testing, or background workers.
Load python-engineering:typer when the task is focused on Typer commands, parameter configuration, subcommand composition, or Typer-specific documentation.
Load python-engineering:typer-and-rich when the task involves Rich table rendering in non-TTY contexts, Typer/Rich integration pitfalls, or correctness review of CLI output handling.
Assets
assets/python-cli-demo.py — complete working example
assets/typer_examples/index.md — working scripts demonstrating non-TTY display solutions (Panel/Table width, wrapping, cropping)
assets/nested-typer-exceptions/ — runnable demos of Typer nested exception anti-patterns and fixes
1---2name: python3-cli3description: Use when building CLI applications with Typer and Rich — creating commands with Annotated parameter syntax, defining arguments and options, composing subcommands, async concurrent CLI tasks with semaphores, testing with CliRunner, PEP 723 shebang scripts, progress bars, Rich terminal output, or non-TTY display width handling.4user-invocable: false5---67# CLI Development (Typer + Rich)89Consult `python3-core` for standing defaults. Load `python3-testing` for test patterns.1011## Standards1213- `Annotated[Type, typer.Option(...)]` syntax for all CLI params14- `rich_help_panel` to group options15- Rich emoji tokens (`:white_check_mark:`) not Unicode literals16- Architecture: CLI (Typer) → Business Logic → Services → Display (Rich)17- `uv run <script>` over `python3 <script>`18- Factory pattern for dependency injection1920## App Structure2122```python23import typer24from rich.console import Console2526app = typer.Typer()27console = Console()282930@app.command()31def process(32 input_file: Annotated[Path, typer.Argument(help="Input file")],33 verbose: Annotated[bool, typer.Option("--verbose", "-v")] = False,34) -> None:35 """Process input file."""36 ...37```3839## Rich Width Handling4041```python42from rich.console import Console43from rich.table import Table44from rich.measure import Measurement454647def get_table_width(table: Table) -> int:48 temp = Console(width=9999)49 m = Measurement.get(temp, temp.options, table)50 return int(m.maximum)51```5253## Testing5455```python56from typer.testing import CliRunner5758runner = CliRunner()596061def test_app_runs() -> None:62 result = runner.invoke(app, ["--help"])63 assert result.exit_code == 064```6566## Async Patterns6768Use semaphores for I/O-bound CLI tasks:6970```python71import asyncio72import typer73from typing import Annotated747576@app.command()77def fetch(urls: Annotated[list[str], typer.Argument()], max_concurrent: Annotated[int, typer.Option()] = 10) -> None:78 """Fetch multiple URLs concurrently."""79 results = asyncio.run(_fetch_all(urls, max_concurrent))80 for result in results:81 console.print(result)828384async def _fetch_all(urls: list[str], limit: int) -> list[str]:85 sem = asyncio.Semaphore(limit)86 async with httpx.AsyncClient() as client:87 tasks = [_fetch_one(client, u, sem) for u in urls]88 return await asyncio.gather(*tasks)89```9091## PEP 723 Shebang9293```python94#!/usr/bin/env -S uv --quiet run --active --script95# /// script96# requires-python = ">=3.11"97# dependencies = ["typer>=0.21", "rich>=13.0"]98# ///99```100101## References102103- `references/typer-app-and-commands.md`, `references/typer-parameters.md`, `references/typer-parameter-types.md`, `references/typer-advanced-patterns.md`, `references/typer-subcommands.md`, `references/typer-testing.md` — Typer commands, arguments, parameters, subcommands104- `references/rich-console-and-markup.md`, `references/rich-renderables.md`, `references/rich-text-and-syntax.md`, `references/rich-advanced-patterns.md`, `references/rich-progress-and-live.md`, `references/rich-logging-and-tracebacks.md` — Rich tables, panels, progress, live displays105- `references/typer-rich-non-tty-patterns.md`, `references/typer-rich-tables.md`, `references/typer-rich-exception-handling.md`, `references/typer-rich-testing-patterns.md` — Typer+Rich integration, non-TTY, width, testing106107## Related Skills108109Load `python-engineering:textual` when the task involves Textual TUI widgets, screen stack, CSS styling, reactive attributes, Pilot testing, or background workers.110111Load `python-engineering:typer` when the task is focused on Typer commands, parameter configuration, subcommand composition, or Typer-specific documentation.112113Load `python-engineering:typer-and-rich` when the task involves Rich table rendering in non-TTY contexts, Typer/Rich integration pitfalls, or correctness review of CLI output handling.114115## Assets116117- `assets/python-cli-demo.py` — complete working example118- `assets/typer_examples/index.md` — working scripts demonstrating non-TTY display solutions (Panel/Table width, wrapping, cropping)119- `assets/nested-typer-exceptions/` — runnable demos of Typer nested exception anti-patterns and fixes