Skip to content

Mounting duplicate widget IDs can result in a NoScreen error #2863

Closed
@davep

Description

@davep

I'm writing this one up near the end of the day, at the start of a holiday, just so it's written down and I/we don't forget. Annoyingly I can't recreate the problem in isolation, only in the code I'm working on at the moment.

It is possible to get into a situation where an attempt to mount a widget with an ID that already exists amongst its would-be siblings results in a NoScreen error being thrown from somewhere deep in the compositor code rather than the DuplicateIds, which is what you'd expect.

The error I was seeing is follows at the end of here.

Quick testing suggests that NodeList._append and NodeList._insert are the issue here. Making a change akin to this:

@@ -74,12 +74,12 @@ class NodeList(Sequence["Widget"]):
             widget: A widget.
         """
         if widget not in self._nodes_set:
-            self._nodes.append(widget)
-            self._nodes_set.add(widget)
             widget_id = widget.id
             if widget_id is not None:
                 self._ensure_unique_id(widget_id)
                 self._nodes_by_id[widget_id] = widget
+            self._nodes.append(widget)
+            self._nodes_set.add(widget)
             self._updates += 1
 
     def _insert(self, index: int, widget: Widget) -> None:

seems to address the issue.

If I can work out an isolated reproduction of this I'll add it here, but the above change does seem to make some sense I think.

...
│                                                                                                                                                                       │
│ /Users/davep/develop/python/textual/src/textual/dom.py:477 in screen                                                                                                  │
│                                                                                                                                                                       │
│    474 │   │   while node is not None and not isinstance(node, Screen):                                                                                               │
│    475 │   │   │   node = node._parent                                                                                                                                │
│    476 │   │   if not isinstance(node, Screen):                                                                                                                       │
│ ❱  477 │   │   │   raise NoScreen("node has no screen")                                                                                                               │
│    478 │   │   return node                                                                                                                                            │
│    479 │                                                                                                                                                              │
│    480 │   @property                                                                                                                                                  │
│                                                                                                                                                                       │
│ ╭───────────────────────────────────── locals ──────────────────────────────────────╮                                                                                 │
│ │   node = None                                                                     │                                                                                 │
│ │ Screen = <class 'textual.screen.Screen'>                                          │                                                                                 │
│ │   self = RightAlignToast(id='textual-toast-91b49506-d61a-4698-ac8b-5bc491fdd5c9') │                                                                                 │
│ ╰───────────────────────────────────────────────────────────────────────────────────╯                                                                                 │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
NoScreen: node has no screen

Metadata

Metadata

Assignees

No one assigned

    Labels

    Can not reproduceThe issue couldn't be reproducedTaskbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions