Skip to content

Commit 896aa9f

Browse files
Merge pull request #3718 from Textualize/compositor-ignore-unmounted
Compositor ignores non-mounted widgets.
2 parents 67dd75a + a000994 commit 896aa9f

File tree

6 files changed

+42
-1
lines changed

6 files changed

+42
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2121
- Off-by-one in CSS error reporting https://github.com/Textualize/textual/issues/3625
2222
- Loading indicators and app notifications overlapped in the wrong order https://github.com/Textualize/textual/issues/3677
2323
- Widgets being loaded are disabled and have their scrolling explicitly disabled too https://github.com/Textualize/textual/issues/3677
24+
- Method render on a widget could be called before mounting said widget https://github.com/Textualize/textual/issues/2914
2425

2526
### Added
2627

src/textual/_compositor.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,9 @@ def add_widget(
573573
visible: Whether the widget should be visible by default.
574574
This may be overridden by the CSS rule `visibility`.
575575
"""
576+
if not widget._is_mounted:
577+
return
578+
576579
styles = widget.styles
577580
visibility = styles.get_rule("visibility")
578581
if visibility is not None:

src/textual/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,6 +2202,7 @@ async def invoke_ready_callback() -> None:
22022202
self.check_idle()
22032203
finally:
22042204
self._mounted_event.set()
2205+
self._is_mounted = True
22052206

22062207
Reactive._initialize_object(self)
22072208

src/textual/message_pump.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ def __init__(self, parent: MessagePump | None = None) -> None:
122122
self._last_idle: float = time()
123123
self._max_idle: float | None = None
124124
self._mounted_event = asyncio.Event()
125+
self._is_mounted = False
126+
"""Having this explicit Boolean is an optimization.
127+
128+
The same information could be retrieved from `self._mounted_event.is_set()`, but
129+
we need to access this frequently in the compositor and the attribute with the
130+
explicit Boolean value is faster than the two lookups and the function call.
131+
"""
125132
self._next_callbacks: list[events.Callback] = []
126133
self._thread_id: int = threading.get_ident()
127134

@@ -508,6 +515,7 @@ async def _pre_process(self) -> bool:
508515
finally:
509516
# This is critical, mount may be waiting
510517
self._mounted_event.set()
518+
self._is_mounted = True
511519
return True
512520

513521
def _post_mount(self):
@@ -547,6 +555,7 @@ async def _process_messages_loop(self) -> None:
547555
raise
548556
except Exception as error:
549557
self._mounted_event.set()
558+
self._is_mounted = True
550559
self.app._handle_exception(error)
551560
break
552561
finally:

src/textual/widget.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ def __init__(
396396
@property
397397
def is_mounted(self) -> bool:
398398
"""Check if this widget is mounted."""
399-
return self._mounted_event.is_set()
399+
return self._is_mounted
400400

401401
@property
402402
def siblings(self) -> list[Widget]:

tests/test_mount.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Regression test for https://github.com/Textualize/textual/issues/2914
2+
3+
Make sure that calls to render only happen after a widget being mounted.
4+
"""
5+
6+
import asyncio
7+
8+
from textual.app import App
9+
from textual.widget import Widget
10+
11+
12+
class W(Widget):
13+
def render(self):
14+
return self.renderable
15+
16+
async def on_mount(self):
17+
await asyncio.sleep(0.1)
18+
self.renderable = "1234"
19+
20+
21+
async def test_render_only_after_mount():
22+
"""Regression test for https://github.com/Textualize/textual/issues/2914"""
23+
app = App()
24+
async with app.run_test() as pilot:
25+
app.mount(W())
26+
app.mount(W())
27+
await pilot.pause()

0 commit comments

Comments
 (0)