โ† Home

132. Rich

Beautiful terminal output with Rich

132. Rich

๐ŸŽจ Aryan wants the RAM manager to show a color-coded table of processes in the terminal โ€” red for high RAM, yellow for medium, green for low. His Rich table is missing columns and uses the wrong color threshold logic.

๐Ÿ’ก Fun fact: Rich was created by Will McGugan and first released in 2019. Within two years it became one of the most-starred Python projects on GitHub. The key insight: terminals have supported ANSI color codes since the 1970s, but nobody had made a library that made them actually easy to use. Rich renders markdown, syntax-highlighted code, progress bars, and tables โ€” all in your terminal.

โš ๏ธ Watch out: Rich uses square-bracket markup [red]text[/red], NOT HTML-style <red>text</red>. If you use angle brackets, Rich will try to render them as markup and either silently fail or display the literal <red> tags. This is the single most common Rich mistake.

๐Ÿค” Think about it: The color logic is semantic: high RAM is a warning (red), low RAM means everything is fine (green). This matches traffic lights, status LEDs, and every monitoring dashboard on the planet. When you see a reversed color scheme in code, what does that tell you about whether the developer tested their work?

Learning objectives

  • Use add_column() with positional title argument
  • Apply Rich markup [color]text[/color] for colored output
  • Use correct threshold logic (high = red, low = green)
  • Add rows with positional string arguments to add_row()
  • Print tables with console.print()

Key concepts

  • rich.console.Console โ€” styled terminal output
  • rich.table.Table โ€” tabular data display
  • Rich markup โ€” [color]โ€ฆ[/color] syntax
  • add_column(title) โ€” positional title argument
  • add_row(*values) โ€” positional string arguments
  • box styles โ€” ROUNDED, SIMPLE, HEAVY

Try it

Concept detail

Beautiful Terminal Output with Rich

pip install rich โ€” colors, tables, progress bars, and more.

Console

from rich.console import Console
console = Console()

console.print('[bold red]Error![/bold red] Something went wrong.')
console.print('[green]โœ“[/green] Operation succeeded.')
console.print('Normal text with [cyan]colored[/cyan] section.')

Markup format

[red]text[/red]        โ† color
[bold]text[/bold]      โ† bold
[italic]text[/italic]  โ† italic
[bold red]text[/]      โ† combined, [/] closes all

Tables

from rich.table import Table
from rich import box

table = Table('Name', 'Value', box=box.ROUNDED)
# OR:
table = Table(box=box.SIMPLE)
table.add_column('Name', style='cyan', justify='left')
table.add_column('Value', justify='right')

table.add_row('RAM', '[red]87%[/red]')
table.add_row('Swap', '[green]12%[/green]')

console.print(table)

Useful box styles

box.ROUNDED    # โ•ญโ”€โ”€โ”€โ”€โ”€โ•ฎ
box.SIMPLE     # minimal lines
box.HEAVY      # bold lines
box.MARKDOWN   # compatible with markdown tables

Color logic for monitoring

def ram_color(percent: float) -> str:
    if percent > 80: return 'red'
    if percent > 60: return 'yellow'
    return 'green'

Solution

from rich.console import Console
from rich.table import Table
from rich import box

console = Console()

def make_process_table(processes: list[dict]) -> Table:
    """Create a Rich table showing top processes with color coding."""
    table = Table(title='Top Processes', box=box.ROUNDED)

    table.add_column('Process', style='cyan')
    table.add_column('PID', justify='right')
    table.add_column('RSS (MB)', justify='right')
    table.add_column('Status', justify='center')

    for proc in processes:
        rss = proc['rss_mb']
        # High RAM = red warning, low RAM = green (all good)
        if rss > 500:
            color = 'red'
        elif rss > 100:
            color = 'yellow'
        else:
            color = 'green'

        status = '๐Ÿ”ด High' if rss > 500 else ('๐ŸŸก Med' if rss > 100 else '๐ŸŸข Low')
        table.add_row(
            proc['name'],
            str(proc['pid']),
            f'[{color}]{rss:.1f}[/{color}]',
            status,
        )
    return table

def print_ram_summary(percent: float, used_gb: float, total_gb: float):
    """Print a color-coded RAM usage summary line."""
    color = 'red' if percent > 80 else ('yellow' if percent > 60 else 'green')
    console.print(f'[{color}]RAM: {percent:.1f}% ({used_gb:.1f}/{total_gb:.1f} GB)[/{color}]')

Tests

from io import StringIO
from rich.console import Console

PROCS = [
    {'name': 'chrome',  'pid': 100, 'rss_mb': 800.0},
    {'name': 'python',  'pid': 200, 'rss_mb': 150.0},
    {'name': 'vim',     'pid': 300, 'rss_mb': 30.0},
]

def test_table_has_four_columns():
    table = make_process_table(PROCS)
    assert len(table.columns) == 4

def test_table_has_correct_rows():
    table = make_process_table(PROCS)
    assert table.row_count == 3

def test_high_rss_shows_red():
    table = make_process_table(PROCS)
    buf = StringIO()
    c = Console(file=buf, highlight=False, markup=True)
    c.print(table)
    output = buf.getvalue()
    # High RAM (chrome, 800 MB) should show red, not green
    assert 'red' in output.lower() or '๐Ÿ”ด' in output, (
        'Process with >500 MB RSS should show red/๐Ÿ”ด, not green'
    )

def test_low_rss_shows_green():
    table = make_process_table(PROCS)
    buf = StringIO()
    c = Console(file=buf, highlight=False, markup=True)
    c.print(table)
    output = buf.getvalue()
    assert 'green' in output.lower() or '๐ŸŸข' in output, (
        'Process with <100 MB RSS should show green/๐ŸŸข'
    )

def test_print_ram_summary_uses_rich_markup():
    import inspect
    src = inspect.getsource(print_ram_summary)
    # Must use [color]...[/color] bracket syntax, not <color>...</color> HTML tags
    assert '<red>' not in src and '<green>' not in src and '<yellow>' not in src, (
        'Rich markup uses [color]text[/color] square brackets, not <color> HTML tags'
    )
    assert '[' in src, 'Must use Rich markup with square brackets: [color]text[/color]'

def test_print_ram_summary_no_html_tags():
    # Capture output by patching the global console object
    buf = StringIO()
    test_console = Console(file=buf, highlight=False, markup=True)
    import unittest.mock as mock
    # Replace the global 'console' used inside print_ram_summary
    import sys
    module = sys.modules[__name__]
    with mock.patch.object(module, 'console', test_console):
        print_ram_summary(85.0, 6.8, 8.0)
    output = buf.getvalue()
    assert '<red>' not in output, 'Output must not contain HTML-style <red> tags'

def test_print_ram_summary_high_ram_outputs_red():
    import inspect
    src = inspect.getsource(print_ram_summary)
    # The logic should map percent > 80 to 'red'
    assert 'red' in src, 'High RAM (>80%) must use red color in markup'

def test_print_ram_summary_low_ram_outputs_green():
    import inspect
    src = inspect.getsource(print_ram_summary)
    # The logic should map low percent to 'green'
    assert 'green' in src, 'Low RAM must use green color in markup'

Resources