ProvenPin Install

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
pinned versions3
typer@0.27.3rich@15.0.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
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.

requirements.txt
typer==0.27.3
rich==15.0.0
pytest==9.1.1
app.py
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()
tests/test_app.py
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.ini
[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.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.