rich-python
Use for writing, reviewing, debugging, or testing Python terminal presentation built with Rich, including Console streams, Text and markup safety, tables, trees, panels, progress, status, Live display
Files1
View on GitHubSKILL.mdskills/rich-python/SKILL.md
GitHubRich Python
Design terminal output around a render target and data contract. Rich objects
describe presentation; a Console decides capabilities, dimensions, stream,
and rendering.
Boundary
Use this skill when a Python project already uses Rich or the user requests Rich terminal output. Do not introduce styling into stdout promised as JSON, CSV, a pipe protocol, or another machine-readable format. Do not use Rich to define command arguments (use the CLI framework), to replace structured logs, or to build a web interface.
Know the objects
| Object | Meaning | Use |
|---|---|---|
Console |
Rendering environment plus output stream | Own terminal detection, width, color, capture, and printing. |
Text |
Styled text with explicit spans | Dynamic/untrusted text or programmatic styling. |
| Renderable | Object implementing Rich's console protocol | Table, Tree, Panel, Syntax, user-defined views. |
Progress / Status |
Managed transient display | Bounded work with a lifecycle. |
Live |
Refresh controller for a changing renderable | Multi-field dashboards or custom updating layouts. |
Group / layout container |
Composition of renderables | One coherent frame rather than interleaved prints. |
Read the rendering model when output breaks under redirection, width changes, markup-like data, or tests.
Ordered workflow
- Classify the channel: human stdout, diagnostics stderr, machine stdout, captured test output, file export, or interactive TTY.
- Identify the semantic structure: prose, records, hierarchy, source, progress, or a changing dashboard. Choose one matching renderable.
- Create or reuse a
Consoleat the application boundary. Inject it into code that must be testable; do not scatter independently configured consoles. - Convert untrusted/dynamic strings to literal
Textor escape markup. Never interpolate user text inside markup tags. - Define width, overflow, wrapping, and empty-state behavior. Tables need stable column meaning, not one visual column per arbitrary key.
- Put transient output in a context manager so teardown occurs on errors.
- Test at a fixed width with color disabled unless ANSI output itself is the contract. Test non-TTY behavior for progress/live code.
Intent to renderable
| Intent | Use | Avoid |
|---|---|---|
| One styled message | Console.print with Text or trusted markup |
Building a one-cell table |
| Aligned records with headers | Table |
Manual spaces/tabs |
| Nested ownership or paths | Tree |
Flattened repeated prefixes |
| Framed summary | Panel around a composed renderable |
ANSI box-drawing strings |
| Source/config excerpt | Syntax |
Hand-injected color codes |
| Known iterable progress | track or Progress.track |
Printing every iteration |
| Multiple tasks/columns | Explicit Progress and task IDs |
Nested independent progress bars |
| Arbitrary changing view | Live |
Clear-screen loops |
| Machine-readable data | Plain serialization on stdout | Rich markup or tables |
Canonical static output
from rich.console import Console
from rich.table import Table
from rich.text import Text
def render_jobs(console: Console, jobs: list[dict[str, object]]) -> None:
table = Table("ID", "State", "Owner", show_header=True)
for job in jobs:
state = str(job["state"])
state_text = Text(state, style="green" if state == "ready" else "yellow")
table.add_row(str(job["id"]), state_text, Text(str(job["owner"])))
console.print(table if jobs else Text("No jobs.", style="dim"))
Text(str(value)) keeps brackets and markup-like input literal. repr-style
highlighting is not a stable serialization format.
Streams and terminal capability
- Reserve stdout for the documented primary result. Send diagnostics to a
console configured with
stderr=True. - Let Rich auto-detect terminal capability for ordinary execution. Do not force color or interactive mode to make redirected output look like a TTY.
- Respect
NO_COLOR, dumb terminals, narrow width, and noninteractive CI. - Use
force_terminal,force_interactive, or a fixed width only when the caller/test explicitly owns that rendering environment. - If the command supports
--json, bypass Rich on stdout entirely; Rich may still render errors to stderr.
Progress and live lifecycle
Choose Progress when task completion is the primary state. Choose Live when
the whole view changes. Keep one active live display per console; print through
its console so messages do not corrupt the frame. Update at meaningful work
boundaries, not every byte or tight-loop iteration. Always use with so final
refresh and cursor restoration occur after success or failure.
Read progress and live rules before combining tasks, nesting displays, redirecting output, or running in CI.
Deterministic verification
Inject Console(file=StringIO(), width=<fixed>, color_system=None, force_terminal=False) for semantic text tests. Use console.capture() for a
small local capture. Assert content, row order, empty state, and absence of ANSI
codes; avoid full snapshots unless exact layout is contractual. Read testing
terminal output.
Inspect the installed version of Rich and its signatures when a constructor keyword, environment override, or live behavior may drift. The authoring anchors were executed on Rich 15.0.0; official rendered docs may show an earlier stable version. Completion requires correct stream separation, safe dynamic text, readable narrow/non-TTY output, managed live teardown, deterministic tests, and no Rich formatting in machine-readable output.