Code of the Day
IntermediateBetter Interfaces

Tables and trees

Choose the right Rich component — tables for columnar data, trees for hierarchies — and know when to skip formatting entirely.

UtilitiesIntermediate5 min read
By the end of this lesson you will be able to:
  • Identify when tabular output is appropriate for structured, multi-column data
  • Identify when tree output fits hierarchical or nested data
  • Choose the right Rich component for each situation
  • Explain when plain text or JSON is better than formatted output

Rich gives you two high-level layout tools beyond coloured text: Table for columnar data and Tree for hierarchical data. Choosing the right one is a design decision, not a technical one.

Tables for columnar data

A table is the right choice when your output has a fixed set of named columns and one row per item. The human eye moves down a column far faster than it parses a line of key=value pairs.

Compare these two outputs from a file-scanner tool:

# Plain text (hard to scan)
report.py: 212 lines, 7 errors, last modified 2024-01-15
utils.py: 84 lines, 0 errors, last modified 2024-01-10
main.py: 301 lines, 12 errors, last modified 2024-01-18
# Table (easy to scan)
┌──────────┬───────┬────────┬──────────────┐
│ File     │ Lines │ Errors │ Modified     │
├──────────┼───────┼────────┼──────────────┤
│ report.py│   212 │      7 │ 2024-01-15   │
│ utils.py │    84 │      0 │ 2024-01-10   │
│ main.py  │   301 │     12 │ 2024-01-18   │
└──────────┴───────┴────────┴──────────────┘

A user looking for files with errors runs their eye down the Errors column in under a second. With the flat format, they read every line.

Trees for hierarchical data

A tree represents containment or parent-child relationships. File system structures, dependency graphs, and module hierarchies are natural trees. Flat lists cannot represent these without losing the structure.

Rich's Tree component makes it easy:

from rich.tree import Tree

tree = Tree("project/")
src = tree.add("src/")
src.add("main.py")
src.add("utils.py")
tree.add("tests/").add("test_main.py")
tree.add("pyproject.toml")

This renders with proper branch characters, indentation, and optional styling. The visual hierarchy mirrors the actual hierarchy.

When to skip formatting

Rich output is for humans. When your tool's output is consumed by another program — via a pipe, a shell script, or a CI log parser — Rich's ANSI codes and box-drawing characters are noise.

Two rules of thumb:

  • If the output goes to a file or a pipe, emit plain text or JSON.
  • Rich handles this automatically: it detects whether stdout is a terminal and strips ANSI codes when it is not.

For tools where machine consumption is a primary use case, add a --json flag that bypasses Rich entirely and outputs structured JSON. Let the human-friendly display be the default; let --json be the escape hatch.

Rich's Console(force_terminal=True) overrides the auto-detection. Use it in tests when you want to assert on styled output, and Console(no_color=True) when you want clean output for snapshot testing.

Where to go next

Next: colour conventions — establishing consistent colour semantics across your tool so users build reliable intuitions.

Finished reading? Mark it complete to track your progress.

On this page