Skip to content

Rendering related RecursionError: maximum recursion depth exceeded while calling a Python object #5669

Closed as not planned
@mzebrak

Description

@mzebrak

Worth mentioning: this happens on the revision d9f7ffd, because it includes a fix for other crashes (#5641)

Sorry, but I do not yet have an MRE that can reproduce the problem. This happens when pushing/dismissing a screen quickly enough. I've included some code that presents a similar situation.

Maybe related: #5629

In a real scenario spamming LMB on "Show dialog" causes the dialog to be showed and closed again and again. After a while, it crashes with RecursionError.

from __future__ import annotations

from datetime import datetime

from textual import on, work
from textual.app import App, ComposeResult
from textual.containers import Horizontal, Vertical, VerticalScroll
from textual.events import Click, Mount
from textual.screen import ModalScreen
from textual.widgets import Button, Static


class Dialog(ModalScreen):
    DEFAULT_CSS = """
       Dialog {
        align: center middle;
        background: $background 85%;

        #dialog-content {
            border-title-style: bold;
            border-title-color: $text;
            border-title-background: $primary;
            border: $primary outer;
            background: $panel 80%;
            padding: 1;
            width: 50%;
            height: auto;
        }
    }
"""

    def __init__(self, item: str) -> None:
        super().__init__()
        self._item = item

    def compose(self) -> ComposeResult:
        with Vertical(id="dialog-content"):
            yield Static("This is a dialog")
            yield Static(f"You clicked on {self._item}")
            data = Static("", id="data")
            data.loading = True
            yield data
            yield Button("Close", name="close", id="close-dialog")

    @property
    def data_widget(self) -> Static:
        return self.query_exactly_one("#data", Static)

    @on(Mount)
    def schedule_data_updating(self) -> None:
        self.set_interval(1, self._update)

    @on(Button.Pressed, "#close-dialog")
    def close_by_button(self) -> None:
        self.dismiss()

    @on(Click)
    def close_by_clicking_outside(self, event: Click) -> None:
        """Close the Dialog if the user clicks outside the modal content."""
        if self.get_widget_at(event.screen_x, event.screen_y)[0] is self:
            self.dismiss()

    @work(name="update data worker")
    async def _update(self) -> None:
        data_widget = self.data_widget
        data_widget.loading = False
        data_widget.update(f"Data loaded - {datetime.now()}")


class ExampleApp(App):
    DEFAULT_CSS = """
    Button {
        border: none !important;

        &:hover {
            border: none !important;
        }
    }

    .item {
        height: auto;

        .item-name {
            width: auto;
        }
    }
"""

    def compose(self) -> ComposeResult:
        with VerticalScroll():
            for i in range(30):
                with Horizontal(classes="item"):
                    item_name = f"item {i}"
                    yield Static(item_name, classes="item-name")
                    yield Button("Show dialog", name=item_name, id="push-dialog")

    @on(Button.Pressed, "#push-dialog")
    def push_dialog(self, event: Button.Pressed) -> None:
        self.push_screen(Dialog(event.button.name))


if __name__ == "__main__":
    ExampleApp().run()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions