CodeSampleX

Ejemplo

rich 14.3.1: Implement custom Rich renderables via __rich_console__ and __rich_measure__ with ConsoleOptions, Segment, Measurement, and RegexHighlighter while avoiding str character iteration, line-break injection, and unmeasured container expansion

Muestra verificada para pypi rich 14.3.1: Implement custom Rich renderables via __rich_console__ and __rich_measure__ with ConsoleOptions, Segment…

sha256:75206e75eb65edfdd9fbe98e2b62195d6a7dc617a9ba2f998059c9f8cca05b73

Esta red ofrece una sola cosa: una muestra que compila. La ejecutó en un sandbox y guardó el recibo firmado. No califica ni garantiza nada: si el mismo código compila donde estás no es algo que haya medido. Cuántas claves de firma distintas presentaron un recibo de contrato aprobado. Una es solo el autor; más de una significa que alguien más también lo compiló. Una clave se genera sola y no tiene identidad registrada detrás, así que cuenta claves, no personas. MIT-0

Evidencia de ejecución

El entorno declarado y las ejecuciones firmadas se muestran por separado, para que veas exactamente qué ejecutó esta muestra y dónde.

Base de evidencia
Contrato firmado aprobado
Recibos de verificación
2
Claves de firma que lo compilaron
2
Entorno declarado python linux x64 python python pip

Entornos de las ejecuciones de verificación

Entorno Contrato Etapas Ejecución
python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-16
python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-18

Caso

HOW
Objetivo
Implement custom Rich renderables via __rich_console__ and __rich_measure__ with ConsoleOptions, Segment, Measurement, and RegexHighlighter while avoiding str character iteration, line-break injection, and unmeasured container expansion
Paquetes
Símbolos
  • rich.console.ConsoleOptions
  • rich.console.RenderResult
  • rich.segment.Segment
  • rich.measure.Measurement
  • rich.highlighter.RegexHighlighter
  • rich.errors.NotRenderableError
  • __rich_console__
  • __rich_measure__
Entorno
python
Creado
2026-08-16T06:22:38Z

Contrato

  1. assert returning a str from __rich_console__ iterates character-by-character emitting each char on its own line, while returning a list of str or yielding str emits whole lines
  2. assert yielding a non-renderable primitive like int or object inside __rich_console__ raises rich.errors.NotRenderableError (subclass of ConsoleError, not TypeError)
  3. assert every str yielded in __rich_console__ appends a newline, parses markup tags, and triggers console highlighting on numbers, while yielding Segment preserves exact text without newlines, markup parsing, or highlighting
  4. assert yielding a Text object in __rich_console__ renders text literally without markup parsing or number highlighting
  5. assert ConsoleOptions is immutable and options.update_width() returns a new instance setting both min_width and max_width without altering the original options
  6. assert child_options passed to console.render() in __rich_console__ cleanly scopes options like highlight=False to child renderables
  7. assert an unmeasured custom renderable defaults to Measurement(1, options.max_width) causing Table columns to blow up to the full console width, while __rich_measure__ allows snug content sizing
  8. assert console.render_lines() defaults to pad=True which space-pads every rendered line to options.max_width, requiring pad=False for tight borders and custom container wrapping
  9. assert calling a Highlighter instance on a Text object returns a new highlighted copy leaving the original unchanged, while highlighter.highlight() mutates in-place

Archivos

  • NOTES.md
  • csx.json
  • requirements.txt
  • src/__init__.py
  • src/custom_renderables.py
  • test/contract.py

Descargar el artefacto de código fuente (tar.gz)

Código fuente

NOTES.md
# Notes on rich Custom Renderables, ConsoleOptions, and Highlighters

## What search_known_solution answered
`search_known_solution` returned existing sample `sha256:3dae0c04df2ebeff413e9a4a6dc0213423def8b204f079fa3e738abce3780035` ("Render rich Console output to a string a test can assert on, and pin the escape codes, width, markup and highlighting that decide what the string holds"), which covers top-level `Console` string capturing, environment variables, Style caching, and basic object dispatch. We targeted a distinct trap: implementing custom `__rich_console__` and `__rich_measure__` renderables, `ConsoleOptions` scoping, `Segment` yielding, and `RegexHighlighter` semantics.

## What a model would have written instead
A model writes `def __rich_console__(self, console, options): return "READY"` to emit text from a custom widget, yields `str` prefixes inline, omits `__rich_measure__` expecting `Table` to measure rendered output, and expects `highlighter(text)` to mutate `text` in-place.

## How the wrong version fails
Fails silently with broken layouts: returning a `str` causes Rich to treat it as an iterable of single characters and emits each letter on a separate line (`R\nE\nA\nD\nY\n`), yielding `str` tokens forces unwanted newlines between inline prefix elements, missing `__rich_measure__` blows up `Table` column widths to the full console width (`max_width=50`), and calling `highlighter(text)` leaves `text.spans` empty because it returns a copy instead of mutating in-place.
csx.json
{"case":{"caseId":"case:sha256:c88debb68ff52decca4caa386de26e1b12bb0d5ecdd336dfa0326c6ab8f7e8f2","contract":["assert returning a str from __rich_console__ iterates character-by-character emitting each char on its own line, while returning a list of str or yielding str emits whole lines","assert yielding a non-renderable primitive like int or object inside __rich_console__ raises rich.errors.NotRenderableError (subclass of ConsoleError, not TypeError)","assert every str yielded in __rich_console__ appends a newline, parses markup tags, and triggers console highlighting on numbers, while yielding Segment preserves exact text without newlines, markup parsing, or highlighting","assert yielding a Text object in __rich_console__ renders text literally without markup parsing or number highlighting","assert ConsoleOptions is immutable and options.update_width() returns a new instance setting both min_width and max_width without altering the original options","assert child_options passed to console.render() in __rich_console__ cleanly scopes options like highlight=False to child renderables","assert an unmeasured custom renderable defaults to Measurement(1, options.max_width) causing Table columns to blow up to the full console width, while __rich_measure__ allows snug content sizing","assert console.render_lines() defaults to pad=True which space-pads every rendered line to options.max_width, requiring pad=False for tight borders and custom container wrapping","assert calling a Highlighter instance on a Text object returns a new highlighted copy leaving the original unchanged, while highlighter.highlight() mutates in-place"],"goal":"Implement custom Rich renderables via __rich_console__ and __rich_measure__ with ConsoleOptions, Segment, Measurement, and RegexHighlighter while avoiding str character iteration, line-break injection, and unmeasured container expansion","kind":"HOW","packages":["pkg:pypi/rich@14.3.1"],"schemaVersion":1,"symbols":["rich.console.ConsoleOptions","rich.console.RenderResult","rich.segment.Segment","rich.measure.Measurement","rich.highlighter.RegexHighlighter","rich.errors.NotRenderableError","__rich_console__","__rich_measure__"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/rich@14.3.1"],"schemaVersion":1,"symbols":["rich.console.ConsoleOptions","rich.console.RenderResult","rich.segment.Segment","rich.measure.Measurement","rich.highlighter.RegexHighlighter","rich.errors.NotRenderableError","__rich_console__","__rich_measure__"],"verifierAdapter":"python@1"}
requirements.txt
rich==14.3.1
markdown-it-py==4.0.0
mdurl==0.1.2
pygments==2.19.2
src/__init__.py
src/custom_renderables.py
"""Custom Rich renderables, measurement, and highlighting utilities.

Proves how custom Rich renderables interact with ConsoleOptions, RenderResult,
Segments, Measurements, and Highlighters.
"""

from __future__ import annotations

from typing import Iterable, Union
from rich.console import Console, ConsoleOptions, RenderResult
from rich.highlighter import RegexHighlighter
from rich.measure import Measurement
from rich.segment import Segment
from rich.style import Style
from rich.text import Text


class ReturnStrWidget:
    """Returns a string directly from __rich_console__.

    Trap: Rich expects __rich_console__ to return an Iterable[Renderable].
    In Python, str is an iterable of single characters, so Rich iterates
    over the string character-by-character and outputs each char on a new line.
    """

    def __init__(self, text: str) -> None:
        self.text = text

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        # Returning str directly triggers character-level iteration in Rich
        return self.text  # type: ignore[return-value]


class YieldStrWidget:
    """Yields a string from __rich_console__.

    Yielding a str treats the whole string as a line renderable.
    """

    def __init__(self, text: str) -> None:
        self.text = text

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        yield self.text


class InvalidNestedWidget:
    """Yields an unrenderable object from __rich_console__.

    Trap: While top-level console.print(obj) converts unknown objects via str(),
    yielding an unsupported type inside __rich_console__ raises
    rich.errors.NotRenderableError.
    """

    def __init__(self, value: object) -> None:
        self.value = value

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        yield self.value  # type: ignore[misc]


class InlinePrefixWidget:
    """Demonstrates yielding Segments vs yielding strings for inline tokens.

    Trap: Every str yielded in __rich_console__ is treated as a separate line
    and receives an automatic newline. To output inline styled tokens or
    prefixes without unwanted line breaks, Segments must be yielded.
    """

    def __init__(self, prefix: str, content: str, *, use_segments: bool = True) -> None:
        self.prefix = prefix
        self.content = content
        self.use_segments = use_segments

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        if self.use_segments:
            yield Segment(self.prefix, Style(bold=True))
            yield Segment(self.content + "\n")
        else:
            # Yielding strings introduces a newline between prefix and content
            yield self.prefix
            yield self.content


class CustomBorderBox:
    """A custom container that wraps child content in an ASCII border.

    Demonstrates:
      1. Updating options via options.update_width() to narrow available width.
      2. Using console.render_lines(..., pad=False) to avoid expanding lines.
      3. Yielding Segments to construct custom multi-line bordered layouts.
    """

    def __init__(self, content: object) -> None:
        self.content = content

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        border_width = 4  # '| ' and ' |'
        inner_width = max(1, options.max_width - border_width)
        inner_options = options.update_width(inner_width)

        # Render child lines without full-width padding
        lines = console.render_lines(self.content, inner_options, pad=False)

        top_border = "+" + "-" * (inner_width + 2) + "+\n"
        bot_border = "+" + "-" * (inner_width + 2) + "+\n"

        yield Segment(top_border)
        for line in lines:
            yield Segment("| ")
            yield from line
            # Fill remaining space if line was shorter than inner_width
            line_len = sum(len(seg.text) for seg in line)
            padding = " " * (inner_width - line_len)
            yield Segment(padding + " |\n")
        yield Segment(bot_border)


class UnmeasuredBadge:
    """A widget without __rich_measure__.

    Trap: When placed into auto-sizing containers like Table, Rich falls back
    to Measurement(1, options.max_width), expanding the column to full console width.
    """

    def __init__(self, label: str) -> None:
        self.label = label

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        yield Segment(f"[{self.label}]")


class MeasuredBadge:
    """A widget with __rich_measure__ defined.

    Allows Table and other measuring containers to snugly wrap the widget.
    """

    def __init__(self, label: str) -> None:
        self.label = label

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        yield Segment(f"[{self.label}]")

    def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
        badge_len = len(self.label) + 2  # '[' + label + ']'
        return Measurement(badge_len, badge_len)


class ScopedHighlightWrapper:
    """Wraps child renderables with scoped options.

    Demonstrates scoping highlight=False to child renderables via console.render().
    """

    def __init__(self, child: object, *, highlight: bool = False) -> None:
        self.child = child
        self.highlight = highlight

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        child_options = options.update(highlight=self.highlight)
        yield from console.render(self.child, child_options)


class StatusPatternHighlighter(RegexHighlighter):
    """Custom regex highlighter for status tokens.

    Uses named capture groups (?P<name>...) which map to 'status.<name>' styles.
    """

    base_style = "status."
    highlights = [
        r"(?P<ok>\bPASS\b)",
        r"(?P<fail>\bFAIL\b)",
        r"(?P<warn>\bWARN\b)",
    ]
test/contract.py
"""Contract test for custom Rich renderables, ConsoleOptions, Measurements, and Highlighters."""

from __future__ import annotations

import io
import os
import sys
from importlib.metadata import version
from pathlib import Path

# Ensure src is importable
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

import rich.errors
from rich.console import Console, ConsoleOptions, RenderResult
from rich.highlighter import RegexHighlighter, ReprHighlighter
from rich.measure import Measurement
from rich.segment import Segment
from rich.style import Style
from rich.table import Table
from rich.text import Text
from rich.theme import Theme

from src.custom_renderables import (
    CustomBorderBox,
    InlinePrefixWidget,
    InvalidNestedWidget,
    MeasuredBadge,
    ReturnStrWidget,
    ScopedHighlightWrapper,
    StatusPatternHighlighter,
    UnmeasuredBadge,
    YieldStrWidget,
)

# Neutralize environment variables that affect Rich console rendering
ENVIRONMENT_INPUTS = (
    "COLUMNS",
    "LINES",
    "TERM",
    "NO_COLOR",
    "FORCE_COLOR",
    "TTY_COMPATIBLE",
    "COLORTERM",
    "TTY_INTERACTIVE",
    "UNICODE_VERSION",
)
for key in ENVIRONMENT_INPUTS:
    os.environ.pop(key, None)


def make_console(**kwargs: object) -> tuple[Console, io.StringIO]:
    """Helper to create a reproducible test console writing to a StringIO buffer."""
    buf = io.StringIO()
    con = Console(file=buf, legacy_windows=False, **kwargs)
    return con, buf


# --- 1. str return vs yield in __rich_console__ -------------------------------
# In Python, str is an Iterable[str]. When a naive model writes `return "READY"`
# from __rich_console__, Rich iterates over the return value character by character,
# printing each character on its own newline instead of printing the whole word.
con, buf = make_console(color_system=None)
con.print(ReturnStrWidget("READY"))
assert buf.getvalue() == "R\nE\nA\nD\nY\n"

con, buf = make_console(color_system=None)
con.print(YieldStrWidget("READY"))
assert buf.getvalue() == "READY\n"


# --- 2. NotRenderableError on nested unrenderable objects --------------------
# Top-level console.print(12345) converts integers to strings, but yielding
# a non-renderable primitive from __rich_console__ raises NotRenderableError.
# NotRenderableError inherits from rich.errors.ConsoleError, not TypeError.
con, buf = make_console(color_system=None)
try:
    con.print(InvalidNestedWidget(12345))
except rich.errors.NotRenderableError as error:
    assert isinstance(error, rich.errors.ConsoleError)
    assert not isinstance(error, TypeError)
    assert "Unable to render 12345" in str(error)
else:
    raise AssertionError("expected NotRenderableError for unrenderable nested object")


# --- 3. Yielding str vs Segment in __rich_console__ --------------------------
# Yielding strings in __rich_console__ appends newlines between yields, parses
# markup, and triggers the highlighter on numbers. Yielding Segments preserves
# exact inline text, bypasses markup parsing, and avoids highlighter regexes.
con, buf = make_console(color_system="standard", force_terminal=True)
con.print(InlinePrefixWidget(">> ", "item 42", use_segments=False))
# Str yield produces 2 separate lines and highlights the number 42 with cyan ANSI codes
assert buf.getvalue() == ">> \nitem \x1b[1;36m42\x1b[0m\n"

con, buf = make_console(color_system="standard", force_terminal=True)
con.print(InlinePrefixWidget(">> ", "item 42", use_segments=True))
# Segment yield produces 1 single line with bold prefix and unhighlighted text
assert buf.getvalue() == "\x1b[1m>> \x1b[0mitem 42\n"


# --- 4. Yielding Text vs str in __rich_console__ -----------------------------
# Yielding a Text object treats markup tags literally and skips number highlighting,
# whereas yielding a str parses markup tags.
class TextVsStrWidget:
    def __init__(self, use_text: bool) -> None:
        self.use_text = use_text

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        if self.use_text:
            yield Text("[bold]literal[/bold] 100")
        else:
            yield "[bold]literal[/bold] 100"


con, buf = make_console(color_system="standard", force_terminal=True)
con.print(TextVsStrWidget(use_text=True))
assert buf.getvalue() == "[bold]literal[/bold] 100\n"

con, buf = make_console(color_system="standard", force_terminal=True)
con.print(TextVsStrWidget(use_text=False))
assert buf.getvalue() == "\x1b[1mliteral\x1b[0m \x1b[1;36m100\x1b[0m\n"


# --- 5. ConsoleOptions immutability and options.update_width -----------------
# ConsoleOptions instances are immutable copies. Calling update_width() returns
# a new instance setting both min_width and max_width without modifying the parent.
con, _ = make_console(width=80)
orig_options = con.options
narrow_options = orig_options.update_width(30)
assert orig_options.max_width == 80
assert orig_options.min_width == 1
assert narrow_options.max_width == 30
assert narrow_options.min_width == 30
assert narrow_options is not orig_options


# --- 6. Scoped child options in console.render() -----------------------------
# Child renderables can be rendered with modified child_options (e.g. highlight=False)
# via console.render(), without affecting sibling or parent renders.
class NumberWidget:
    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        yield "value: 42"


con, buf = make_console(color_system="standard", force_terminal=True)
con.print(NumberWidget())
assert buf.getvalue() == "value: \x1b[1;36m42\x1b[0m\n"

con, buf = make_console(color_system="standard", force_terminal=True)
con.print(ScopedHighlightWrapper(NumberWidget(), highlight=False))
assert buf.getvalue() == "value: 42\n"


# --- 7. Table column auto-sizing and __rich_measure__ -----------------------
# When a custom renderable lacks __rich_measure__, Measurement.get() falls back
# to Measurement(1, options.max_width). Inside a Table, this causes the column
# to expand across the full width of the console (50 cols).
# Implementing __rich_measure__ allows the Table to tightly fit the content (9 cols).
table_unmeasured = Table("Badge")
table_unmeasured.add_row(UnmeasuredBadge("OK"))
con, buf = make_console(width=50, color_system=None)
con.print(table_unmeasured)
first_line_unmeasured = buf.getvalue().splitlines()[0]
assert len(first_line_unmeasured) == 50

table_measured = Table("Badge")
table_measured.add_row(MeasuredBadge("OK"))
con, buf = make_console(width=50, color_system=None)
con.print(table_measured)
lines_measured = buf.getvalue().splitlines()
assert len(lines_measured[0]) == 9
assert lines_measured == [
    "┏━━━━━━━┓",
    "┃ Badge ┃",
    "┡━━━━━━━┩",
    "│ [OK]  │",
    "└───────┘",
]


# --- 8. console.render_lines pad=True vs pad=False ---------------------------
# console.render_lines() defaults to pad=True, which pads every line with spaces
# to options.max_width. Custom container widgets require pad=False to render tight
# content without trailing space inflation.
con, _ = make_console()
opts = con.options.update_width(25)
padded_lines = con.render_lines("test", opts, pad=True)
unpadded_lines = con.render_lines("test", opts, pad=False)
assert sum(len(seg.text) for seg in padded_lines[0]) == 25
assert sum(len(seg.text) for seg in unpadded_lines[0]) == 4

con, buf = make_console(width=20, color_system=None)
con.print(CustomBorderBox("Hello\nWorld"))
assert buf.getvalue().splitlines() == [
    "+------------------+",
    "| Hello            |",
    "| World            |",
    "+------------------+",
]


# --- 9. RegexHighlighter copy semantics vs in-place mutation -----------------
# Calling a Highlighter instance on a Text object returns a new highlighted copy
# leaving the input Text instance unchanged. In-place mutation requires calling
# highlighter.highlight(text) directly.
# Also, setting a custom highlighter on Console replaces ReprHighlighter entirely.
theme = Theme({"status.ok": "bold green", "status.fail": "bold red"})
sh = StatusPatternHighlighter()

raw_text = Text("job status: PASS")
highlighted_copy = sh(raw_text)
assert highlighted_copy is not raw_text
assert len(raw_text.spans) == 0
assert len(highlighted_copy.spans) == 1
assert highlighted_copy.spans[0].style == "status.ok"

# Direct highlight() mutates in place
sh.highlight(raw_text)
assert len(raw_text.spans) == 1
assert raw_text.spans[0].style == "status.ok"

# Console with custom theme and highlighter applies styles to status tokens,
# replacing ReprHighlighter (so numbers '1' and '2' remain plain text)
con, buf = make_console(
    theme=theme,
    highlighter=sh,
    color_system="standard",
    force_terminal=True,
)
con.print("server 1: PASS")
con.print("server 2: FAIL")
assert buf.getvalue() == (
    "server 1: \x1b[1;32mPASS\x1b[0m\n"
    "server 2: \x1b[1;31mFAIL\x1b[0m\n"
)

rich_ver = version("rich")
print(f"contract ok: rich {rich_ver} custom renderables and highlighters verified")

Seeder de origen

csx-seed