Compatibility with Rich¶
rs_rich implements Rich 15.0.0's API. Its output is Rich's, byte for byte,
except for the differences listed below. What the port cannot do raises
instead of rendering something different. The port's own crates (ext,
art, mermaid, plugins, the command line) have no Rich counterpart; their
output is compared with the Rust crates'.
What is covered¶
| Rich | rs_rich |
|---|---|
rich.print, rich.get_console, rich.print_json, rich.reconfigure, rich.inspect |
yes (reconfigure replaces the global console object) |
Console |
everything but Jupyter output: the constructor (with highlighter, tab_size, emoji_variant), print, log (any renderable, log_locals), out, rule, line, input, print_json (every option), capture, with console:, measure, render, render_lines, render_str, themes, the exports (with code_format and font_aspect_ratio), render hooks and the live stack, status, pager, screen, update_screen, update_screen_lines, print_exception, set_window_title, terminal control (Console) |
| The render protocol | __rich__, __rich_console__, __rich_measure__; ConsoleOptions, Measurement, Segment (The render protocol) |
Text |
the whole class, Span, Lines, meta data (Text) |
Style, rich.theme |
the whole class, StyleStack; Theme, from_file, read, config, ThemeStack (Style) |
rich.color |
Color, ColorTriplet, ColorSystem, ColorType, ColorParseError, parse_rgb_hex, blend_rgb (Color) |
rich.markup, rich.emoji |
escape, render, Tag; Emoji, NoEmoji |
rich.box, rich.errors, rich.terminal_theme |
every box, exception and palette |
Table |
every constructor, add_column and add_row option (footers, width, min_width, leading, row_styles, sections, annotation styles and justification), add_section, Table.grid; headers, footers and cells of any renderable. Headers are strings or renderables, not Column objects (Table) |
Panel |
every option, around any renderable (Panel) |
Rule, Padding, Align, VerticalCenter, Constrain, Styled, Bar, Spinner, SPINNERS |
yes (Rules, padding, alignment and bars) |
Columns, Group, group, Layout (splitters, Region, LayoutRender, refresh_screen), containers.Renderables, measure_renderables |
yes (Layout, columns and groups) |
Tree |
yes (Tree) |
Markdown |
yes, with highlighter= and fences= for the port's code highlighters (Markdown) |
Syntax |
every option (Syntax); colours as below |
Pretty, pprint, pretty_repr, install, JSON, inspect, rich.highlighter |
yes; containers, dataclasses and __rich_repr__ objects print as in Rich (Pretty, JSON, inspect and highlighters) |
Traceback, print_exception, install |
yes (Traceback) |
Live, LiveRender, Status, Screen, Pager |
yes (Live, status, screen and pager) |
Progress (every column, track, wrap_file, open), ProgressBar |
yes (Progress) |
rich.prompt |
Prompt, Confirm, IntPrompt, FloatPrompt, InvalidResponse (Prompts) |
RichHandler |
yes (Logging) |
rich-cli's rich command |
python -m rs_rich and the rich-rs script: the rs-rich rich binary, byte for byte (The command line) |
The port's own crates:
| Crate | rs_rich |
|---|---|
rs_rich.ext.* (39 modules) |
No Rich counterpart; output compared byte for byte with rs-rich-ext (Extensions) |
rs_rich.art (images, FIGlet, GIFs, image diff) |
No Rich counterpart; matches rs-rich-art byte for byte. Printing ImageArt is strict: it raises ImageArtError rather than falling back to ASCII (Art) |
rs_rich.chart |
No Rich counterpart; matches rich_ext::chart, compared with its doc examples (Charts) |
rs_rich.diagram |
No Rich counterpart; matches rs-rich-diagram, compared with its doc examples (Diagrams) |
rs_rich.mermaid |
No Rich counterpart; matches rs-rich-mermaid. The mmdc backend only in wheels built with mmdc (Mermaid) |
rs_rich.interact |
No Rich counterpart; the rs-rich-interact components paint the same frames for the same keys, checked headless (Interactive components) |
rs_rich.plugins |
No Rich counterpart: the rs-rich-plugin-api contract and rich-ext's ExtensionRegistry. Python plugins go through the Rust host and match the Rust plugins' output (Plugins) |
Known differences¶
| Difference | Why |
|---|---|
Code colours (Syntax, and code in Markdown and Traceback) come from syntect, not Pygments: monokai is not a theme, and some token classes differ |
The port highlights with syntect (Divergences #18); layout is Rich's byte for byte (Syntax: colours). |
Console(force_jupyter=True) raises NotImplementedError |
There is no Jupyter output. |
A Syntax.stylize_range position so far before the first line that Rich raises IndexError is ignored |
Core applies ranges when it renders, where it cannot raise. |
export_svg(unique_id=None) gives a different (stable) id |
Rich derives the default id from Python reprs. With an explicit unique_id the SVG is Rich's. |
Hyperlinks carry no id= |
Rich tags each link with a random id. The Rust port leaves it out so output is reproducible (Divergences #20). |
text.spans returns a copy |
Spans live in the core Text; assign text.spans to change them. |
Meta data on Text spans holds None, bool, int, float, str and lists or tuples of them (a tuple reads back as a list); other values raise TypeError |
Core's style meta keeps that subset of what marshal can store. A Style alone keeps any meta. |
Spinner.render() with renderable text returns a grid, not a Table; Status.renderable is not a Spinner |
The live area's spinner is core's; both print the same. |
Progress.make_tasks_table() returns a renderable grid; get_table_column() returns None for a column made without table_column=; SpinnerColumn has no spinner attribute (use set_spinner()) |
There is no rs_rich.table.Column. |
A Text with a style of its own, rendered justified inside a container (a table cell), has its padding in a separate ANSI run |
Core renders the padding as a second segment in the same style; the terminal shows the same. |
The theme stack, and capture(), belong to the console |
Rich keeps the theme stack per thread. Captures are per thread, as in Rich. |
repr(box.ROUNDED) is box.ROUNDED |
Rich prints Box(...) with the box's characters. Boxes compare and render the same. |
Console(width=...) and a table column's width, min_width and max_width are at most 65536, and a column's ratio at most 4294967295; larger values raise ValueError |
Rich accepts them, then runs out of memory or takes minutes to print. The Rust port would abort, overflow or take as long, so the binding refuses them up front. |
A Panel's padding is at most 65536 on each side; more raises ValueError |
Rich renders any padding, slowly; up to the limit output is Rich's. |
A console's height, ConsoleOptions heights (and widths) and tab_size are at most 65536, and Console.line writes at most 16777216 lines; larger values raise MemoryError |
Rich tries to allocate them and usually raises MemoryError too. |
A Text grows to at most 268435456 characters by padding (pad, pad_left, pad_right, extend_style, set_length, align, fit, truncate(pad=True), with_indent_guides); a Text tab_size, a Syntax tab_size (with tabs to expand), a Syntax code_width, an Align or Panel width, and a Pretty indent_size (with something to indent) are at most 65536, as are an Align or Panel height and a Table's leading (with rows to separate). Larger values raise MemoryError, or OverflowError past sys.maxsize, where Rich raises it |
Rich tries to allocate them and raises MemoryError (or OverflowError) too; the Rust port would abort the process. |
Pretty printing goes at most 3000 levels deep; deeper values print as <repr-error 'maximum recursion depth exceeded ...'> |
Rich's walk is recursive Python: it prints that node where it runs out of frames, a little under Python's recursion limit (about 990 levels by default), which the port matches. Only a raised recursion limit reaches the cap. |
Pretty-printing data nested deeper than Python's recursion limit shows Rich's <repr-error 'maximum recursion depth exceeded ...'> where the limit runs out, on every Python |
Rich does the same, within a few levels, on most Pythons; on 3.13 its walk lets the RecursionError escape instead. |
Renderables nest as deep as in Rich, then raise RecursionError. The depth follows Rich's frames per level from the caller's recursion limit, and matches it exactly for panels around a str, Text or Pretty, on every supported Python (a Pretty whose data holds itself is estimated as Rich's walk shows it, with ..., so it may differ by a level). |
Rich raises RecursionError where it runs out of Python frames. The port also stops, with RecursionError, where the calling thread's native stack would run out: a thread made with a small threading.stack_size stops sooner, where Rich, whose frames are on the heap, goes on. Rich also stops going deeper once a narrow console leaves the inner renderables no width, so it prints any depth there; the port converts the whole chain first and raises. |
A render hook's item from log, or from print with justify, prints only on the thread that collected it; on another thread it raises RuntimeError |
Every other item prints on any thread, as with Rich. |
A print from inside the same console's file.write raises RuntimeError |
Rich recurses until it hits Python's recursion limit. |
The command is rich-rs, not rich |
rich is installed by rich-cli. |
How compatibility is tested¶
crates/rich-py/tests holds the tests, run by the python workflow on Python
3.9 and 3.13 against the compiled wheel:
| File | What it checks |
|---|---|
test_compat.py, test_protocol.py, test_integration.py |
Programs written once against Rich's API run under Rich 15.0.0 and under rs_rich, in colour and without, and the output must be identical: printing, the console's options, exports, the render protocol, highlighters, log, print_json, render hooks and live displays, tables and panels. |
test_text_style.py, test_renderables.py, test_code.py, test_live.py |
The same comparison for each area: Text, Style, colours and themes; rules, layout, columns and trees; Markdown, Syntax, Pretty and tracebacks; live displays, progress, prompts and logging. |
test_ext*.py, test_art.py, test_mermaid.py, test_chart.py, test_diagram.py, test_plugins.py |
The port's crates, compared with what the Rust crates render for the same input (expected outputs generated by Rust; see crates/rich-py/oracles). |
test_cli.py |
python -m rs_rich against the rich binary built from the same source: stdout, stderr and exit status. |
test_api.py, test_console.py, test_text.py, test_style.py, test_table.py, test_panel.py, test_modules.py |
Rich's README example runs with only its imports changed; each class's arguments, validation and errors; the type stubs describe exactly the compiled module, and every module path exists. |
test_docs.py |
Runs every example in these pages (and ext/) and compares its output with the page. |
The tests assert that the installed reference really is Rich 15.0.0.