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.4---56# CLI Development (Typer + Rich)78Consult `python3-core` for standing defaults. Load `python3-testing` for test patterns.910## Standards1112- `Annotated[Type, typer.Option(...)]` syntax for all CLI params13- `rich_help_panel` to group options14- Rich emoji tokens (`:white_check_mark:`) not Unicode literals15- Architecture: CLI (Typer) → Business Logic → Services → Display (Rich)16- `uv run <script>` over `python3 <script>`17- Factory pattern for dependency injection1819## App Structure2021```python22import typer23from rich.console import Console2425app = typer.Typer()26console = Console()272829@app.command()30def process(31 input_file: Annotated[Path, typer.Argument(help="Input file")],32 verbose: Annotated[bool, typer.Option("--verbose", "-v")] = False,33) -> None:34 """Process input file."""35 ...36```3738## Rich Width Handling3940```python41from rich.console import Console42from rich.table import Table43from rich.measure import Measurement444546def get_table_width(table: Table) -> int:47 temp = Console(width=9999)48 m = Measurement.get(temp, temp.options, table)49 return int(m.maximum)50```5152## Testing5354```python55from typer.testing import CliRunner5657runner = CliRunner()585960def test_app_runs() -> None:61 result = runner.invoke(app, ["--help"])62 assert result.exit_code == 063```6465## Async Patterns6667Use semaphores for I/O-bound CLI tasks:6869```python70import asyncio71import typer72from typing import Annotated737475@app.command()76def fetch(urls: Annotated[list[str], typer.Argument()], max_concurrent: Annotated[int, typer.Option()] = 10) -> None:77 """Fetch multiple URLs concurrently."""78 results = asyncio.run(_fetch_all(urls, max_concurrent))79 for result in results:80 console.print(result)818283async def _fetch_all(urls: list[str], limit: int) -> list[str]:84 sem = asyncio.Semaphore(limit)85 async with httpx.AsyncClient() as client:86 tasks = [_fetch_one(client, u, sem) for u in urls]87 return await asyncio.gather(*tasks)88```8990## PEP 723 Shebang9192```python93#!/usr/bin/env -S uv --quiet run --active --script94# /// script95# requires-python = ">=3.11"96# dependencies = ["typer>=0.21", "rich>=13.0"]97# ///98```99100## References101102- `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, subcommands103- `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 displays104- `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, testing105106## Related Skills107108Load `python-engineering:textual` when the task involves Textual TUI widgets, screen stack, CSS styling, reactive attributes, Pilot testing, or background workers.109110Load `python-engineering:typer` when the task is focused on Typer commands, parameter configuration, subcommand composition, or Typer-specific documentation.111112Load `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.113114## Assets115116- `assets/python-cli-demo.py` — complete working example117- `assets/typer_examples/index.md` — working scripts demonstrating non-TTY display solutions (Panel/Table width, wrapping, cropping)118- `assets/nested-typer-exceptions/` — runnable demos of Typer nested exception anti-patterns and fixes