Typer CLI with rich Progress and Table for batch jobs: working versions (tested 2026-10-15)
A Typer command that squares a batch of numbers behind a rich Progress bar and prints a rich Table summary, tested offline through typer's CliRunner.
passed6 passed · 0 failed2026-10-15 18:12 UTC- Status
- passed
- Last passed
- 2026-10-15 18:12 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38783603208
The code
Exactly the files that passed. Your agent can pull them with get_recipe("rich-typer-progress-cli") (install), or see them on GitHub.
typer==0.27.3 rich==15.0.0 pytest==9.1.1
from __future__ import annotations
import io
import typer
from rich import box
from rich.console import Console
from rich.progress import (
BarColumn,
Progress,
SpinnerColumn,
TextColumn,
TimeElapsedColumn,
)
from rich.table import Table
app = typer.Typer(add_completion=False)
def square(n: int) -> int:
return n * n
def build_table(rows: list[tuple[int, int]], *, ascii_only: bool = False) -> Table:
table = Table(
title="Batch summary",
box=box.ASCII if ascii_only else box.HEAVY_HEAD,
header_style="bold",
)
table.add_column("input", justify="right")
table.add_column("square", justify="right")
for value, result in rows:
table.add_row(str(value), str(result))
return table
def render_table(rows: list[tuple[int, int]], *, ascii_only: bool = False) -> str:
console = Console(file=io.StringIO(), width=40, record=True)
console.print(build_table(rows, ascii_only=ascii_only))
return console.export_text()
@app.command()
def run(
values: list[int] = typer.Argument(..., help="Numbers to square."),
quiet: bool = typer.Option(False, "--quiet", "-q", help="Hide the progress bar."),
) -> None:
rows: list[tuple[int, int]] = []
console = Console()
progress = Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TimeElapsedColumn(),
console=console,
transient=True,
disable=quiet,
)
with progress:
task = progress.add_task("squaring", total=len(values))
for value in values:
rows.append((value, square(value)))
progress.advance(task)
console.print(build_table(rows))
if __name__ == "__main__":
app()
import re
from typer.testing import CliRunner
from app import app, render_table
runner = CliRunner()
# CI runners (GITHUB_ACTIONS, FORCE_COLOR) make rich emit ANSI styles; strip them before matching.
ANSI = re.compile(r"\x1b\[[0-9;]*m")
def test_run_renders_summary_table():
result = runner.invoke(app, ["1", "2", "3"])
assert result.exit_code == 0
assert "Batch summary" in result.stdout
assert "9" in result.stdout
def test_transient_progress_leaves_no_trace():
result = runner.invoke(app, ["1", "2"])
assert result.exit_code == 0
assert "squaring" not in result.stdout
def test_quiet_keeps_table_and_skips_progress():
result = runner.invoke(app, ["-q", "4", "5"])
assert result.exit_code == 0
assert "25" in result.stdout
assert "squaring" not in result.stdout
def test_invalid_value_exits_with_code_2():
result = runner.invoke(app, ["nope"])
assert result.exit_code == 2
assert "is not a valid int" in result.output
def test_help_is_rendered_by_rich():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
plain = ANSI.sub("", result.stdout)
assert "Arguments" in plain
assert "\u256d\u2500 Arguments" in plain
def test_ascii_box_is_deterministic():
text = render_table([(2, 4), (10, 100)], ascii_only=True)
assert "| 2 | 4 |" in text
assert "\u250f" not in text and "\u2500" not in text
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Typer CLI with Rich tables, progress and --json output
A Typer command that renders Rich tables and progress on stderr, with a --json mode for piping, tested offline with CliRunner and no-color Rich output.
Click CLI with rich Progress bars and Status for batch work
A Click command normalizes a batch while rich draws a Progress bar and a Status spinner, with tests that pin down terminal detection, transient output and ANSI stripping.
Typer CLI with subcommands, options and CliRunner tests
Build a Typer CLI with subcommands, typed options, env var fallbacks and exit codes, tested with typer.testing.CliRunner.