This page describes how Rich transforms user-supplied objects into terminal output — the complete journey from a call to console.print() down to ANSI escape sequences written to a file handle. It covers protocol resolution, ConsoleOptions, the render() and render_lines() methods, the output buffer, and the RenderHook interception mechanism.
For documentation on the Console class itself (constructor, color detection, public API), see Console. For the Segment type that is the end product of rendering, see Segments. For the Style and Color types used within segments, see Styles and Colors.
The rendering pipeline has one job: convert any Python object that satisfies the renderable protocol into a flat stream of Segment objects, then serialize those segments to ANSI-escaped strings and write them to the console's output file.
Pipeline stages diagram:
Sources: rich/console.py1-70 rich/console.py252-277 rich/console.py542-547
Rich defines two complementary protocols and one union type that together describe anything renderable.
RichCast — the simple protocolAn object implementing __rich__ delegates rendering to whatever it returns. The return value may itself be a RichCast, so resolution is recursive. The helper function rich_cast (imported from rich/protocol.py) keeps calling __rich__() until it reaches a non-RichCast value.
ConsoleRenderable — the full protocol__rich_console__ is passed the live Console and a ConsoleOptions snapshot. It returns RenderResult, which is:
Each yielded item is either a nested renderable (which render() will recursively resolve) or a raw Segment (which passes through directly). This recursive design means a Table can yield Panel objects, which yield Text objects, which yield Segment objects, and it all resolves in one pass.
RenderableTypePlain str is handled directly — no method call needed. Console.render() converts strings to Text objects internally before continuing.
Sources: rich/console.py252-277 rich/console.py540-566
ConsoleOptions is a @dataclass passed to every __rich_console__ call. It carries rendering constraints and hints from the console at the time of the call.
| Field | Type | Description |
|---|---|---|
size | ConsoleDimensions | Full terminal size (width × height) |
min_width | int | Minimum width the renderable must fit within |
max_width | int | Maximum width available |
max_height | int | Maximum height of the enclosing container |
height | Optional[int] | Fixed height constraint, or None for unconstrained |
is_terminal | bool | True when writing to an interactive terminal |
encoding | str | File encoding, e.g. "utf-8" |
legacy_windows | bool | Restricts box characters on old Windows consoles |
justify | Optional[JustifyMethod] | Overrides text alignment: "left", "center", "right", "full" |
overflow | Optional[OverflowMethod] | Overrides overflow handling: "fold", "crop", "ellipsis", "ignore" |
no_wrap | Optional[bool] | Disables line-wrapping when True |
highlight | Optional[bool] | Overrides automatic syntax highlighting |
markup | Optional[bool] | Enables or disables console markup parsing |
ascii_only (computed from encoding) returns True when the encoding does not start with "utf", signalling that box-drawing characters and emoji should be avoided.
ConsoleOptions is treated as immutable by convention. All mutation methods return a copy:
| Method | What it changes |
|---|---|
copy() | Returns an identical copy |
update(**kwargs) | Sets any combination of fields; width sets both min_width and max_width |
update_width(width) | Sets both min_width and max_width to the same value |
update_height(height) | Sets both height and max_height |
reset_height() | Sets height to None (unconstrained) |
update_dimensions(width, height) | Sets all four width/height fields at once |
Renderables that need to constrain their children call these methods before recursing:
Sources: rich/console.py119-249
Console.render()Console.render(renderable, options) is the core recursive function. It resolves the renderable protocol and yields a flat stream of Segment objects.
The method:
rich_cast(renderable) to resolve any __rich__ chain.str, converts it to a Text object (applying markup and highlighting according to options).__rich_console__, calls it with (self, options) and recursively calls render() on every item it yields.Segment, yields it directly.errors.NotRenderableError.Sources: rich/console.py252-277 rich/console.py550-566
Console.render_lines()render_lines() is used when output must be a fixed-width grid of lines — for example by Layout, Live, Panel, and Table. It:
render() to produce a raw Segment stream.Segment.split_and_crop_lines(), which splits on newlines, crops lines that exceed max_width, and pads shorter lines to fill the full width.List[List[Segment]] — a list of lines, each line being a list of Segment objects.The pad parameter controls whether short lines are padded to max_width with spaces. The new_lines parameter controls whether a newline Segment is appended to each line.
render_lines() is the engine behind any rendering where exact cell-level alignment matters, because it guarantees that every returned line has exactly the requested width in terminal cells.
Sources: rich/console.py1-70 rich/segment.py309-354
The console maintains a per-thread output buffer so that nested rendering contexts and multi-step operations can accumulate output before flushing.
buffer and buffer_index are stored in threading.local, giving each thread its own independent buffer. The _buffer and _buffer_index properties on Console delegate to this thread-local object.
buffer_index)_buffer_index is a nesting counter. When it reaches 0, the buffer is eligible to flush.
| Operation | Effect |
|---|---|
_enter_buffer() | Increments _buffer_index |
_exit_buffer() | Decrements _buffer_index, then calls _check_buffer() |
_check_buffer() | Flushes and writes if _buffer_index == 0 |
The Console object itself is a context manager:
__enter__ calls _enter_buffer()__exit__ calls _exit_buffer()begin_capture() / end_capture() use the same buffer depth mechanism. begin_capture() calls _enter_buffer(). end_capture() calls _render_buffer() on the accumulated buffer contents, clears the buffer, then calls _exit_buffer().
The Capture context manager wraps these two calls and exposes the captured string via Capture.get().
rich/console.py316-346 rich/console.py872-885
Sources: rich/console.py542-547 rich/console.py819-885
RenderHook MechanismRenderHook is an abstract base class that allows code to intercept the list of renderables just before they are passed to render().
process_renderables receives the full list and may return a new or modified list. Any number of hooks may be active simultaneously; they are stored in a stack (_render_hooks) and are applied in order.
| Method | Effect |
|---|---|
push_render_hook(hook) | Appends a RenderHook to the stack |
pop_render_hook() | Removes the most recently added hook |
Both methods are guarded by Console._lock (a threading.RLock) for thread safety.
LiveThe Live display (see Live Display) registers itself as a RenderHook when it starts. Its process_renderables implementation intercepts output and routes it through the live display's own rendering path, preventing ordinary print() calls from scrolling past the live region.
Sources: rich/console.py550-566 rich/console.py828-861 rich/console.py750-756
To make the pipeline concrete, here is what happens when user code calls console.print(Panel("Hello")):
print() wraps Panel("Hello") in a Styled if a style was supplied, otherwise uses it directly.RenderHook instances receive [Panel("Hello")] via process_renderables().render(Panel("Hello"), options) is called.Panel.__rich_console__(console, options) is called. It yields border Segment objects and the inner Text("Hello") renderable.render(Text("Hello"), child_options) is called recursively and yields Segment objects.Segment objects accumulate in _buffer._check_buffer() fires when _buffer_index reaches 0._render_buffer() joins segment text and wraps styled segments in ANSI escape sequences.console.file.Sources: rich/console.py252-277 rich/console.py819-885 rich/segment.py63-107
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.