Skip to content

[BUG] @auto-generated __rich_repr__() does not escape tuples #4016

@finite-state-machine

Description

@finite-state-machine

When @auto is representing an attribute (__init__() arg) which is emitted as positional (rather than with the attribute name), the implementation yields the value directly. If that value is a tuple, it will be misinterpreted as a (name, value, ...) group, causing TypeErrors later when rendering.

Fixed by pull request #4014.
An example of #4015.

Reproduction code:

from __future__ import annotations
import rich
import rich.repr


class ClassA:
    "This class is not a `str` and doesn't have a `__len__()` method"


@rich.repr.auto
class ClassB:

    pair_of_a_instances: tuple[ClassA, ClassA]

    def __init__(self, pair_of_a_instances: tuple[ClassA, ClassA], /) -> None:
        self.pair_of_a_instances = pair_of_a_instances


def test_textualize_rich_4014() -> None:

    a1 = ClassA()
    a2 = ClassA()
    pair_of_a_instances = (a1, a2)
    b = ClassB(pair_of_a_instances)
    rich.print(b)  # XXX raises a `TypeError`!

Traceback:

    def test_textualize_rich_4014() -> None:

        a1 = ClassA()
        a2 = ClassA()
        pair_of_a_instances = (a1, a2)
        b = ClassB(pair_of_a_instances)
>       rich.print(b)  # XXX raises a `TypeError`!
        ^^^^^^^^^^^^^

rich_bug_example.py:25:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
……/python3.14/site-packages/rich/__init__.py:74: in print
    return write_console.print(*objects, sep=sep, end=end)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
……/python3.14/site-packages/rich/console.py:1724: in print
    extend(render(renderable, render_options))
……/python3.14/site-packages/rich/console.py:1345: in render
    for render_output in iter_render:
                         ^^^^^^^^^^^
……/python3.14/site-packages/rich/pretty.py:307: in __rich_console__
    pretty_str = pretty_repr(
……/python3.14/site-packages/rich/pretty.py:912: in pretty_repr
    repr_str: str = node.render(
……/python3.14/site-packages/rich/pretty.py:485: in render
    if expand_all or not line.check_length(max_width):
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
……/python3.14/site-packages/rich/pretty.py:517: in check_length
    return self.node.check_length(start_length, max_length)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
……/python3.14/site-packages/rich/pretty.py:458: in check_length
    total_length += cell_len(token)
                    ^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

text = <rich_bug_example.ClassA object at 0x10839b770>, unicode_version = 'auto'

    def cell_len(text: str, unicode_version: str = "auto") -> int:
        """Get the cell length of a string (length as it appears in the terminal).

        Args:
            text: String to measure.
            unicode_version: Unicode version, `"auto"` to auto detect, `"latest"` for the latest unicode version.

        Returns:
            Length of string in terminal cells.
        """
>       if len(text) < 512:
           ^^^^^^^^^
E       TypeError: object of type 'ClassA' has no len()

……/python3.14/site-packages/rich/cells.py:108: TypeError

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions