Skip to content

Commit

Permalink
Merge pull request #4547 from Textualize/check-mount
Browse files Browse the repository at this point in the history
enforce mounting
  • Loading branch information
willmcgugan authored May 23, 2024
2 parents 5bd94b7 + c7c9a6e commit 6034065
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Fixed `Footer` grid size https://github.com/Textualize/textual/pull/4545

### Changed

- Attempting to mount on a non-mounted widget now raises a MountError https://github.com/Textualize/textual/pull/4547

## [0.63.2] - 2024-05-23

### Fixed
Expand Down
5 changes: 5 additions & 0 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,11 @@ async def stop_animation(self, attribute: str, complete: bool = True) -> None:
"""
await self._animator.stop_animation(self, attribute, complete)

@property
def is_dom_root(self) -> bool:
"""Is this a root node (i.e. the App)?"""
return True

@property
def debug(self) -> bool:
"""Is debug mode enabled?"""
Expand Down
15 changes: 15 additions & 0 deletions src/textual/message_pump.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ def message_queue_size(self) -> int:
"""The current size of the message queue."""
return self._message_queue.qsize()

@property
def is_dom_root(self):
"""Is this a root node (i.e. the App)?"""
return False

@property
def app(self) -> "App[object]":
"""
Expand All @@ -239,6 +244,16 @@ def app(self) -> "App[object]":
active_app.set(node)
return node

@property
def _is_linked_to_app(self) -> bool:
"""Is this node linked to the app through the DOM?"""
node: MessagePump | None = self

while (node := node._parent) is not None:
if node.is_dom_root:
return True
return False

@property
def is_parent_active(self) -> bool:
"""Is the parent active?"""
Expand Down
5 changes: 4 additions & 1 deletion src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,8 @@ def mount(
Only one of ``before`` or ``after`` can be provided. If both are
provided a ``MountError`` will be raised.
"""
if not self._is_linked_to_app:
raise MountError(f"Can't mount widget(s) before {self!r} is mounted")
# Check for duplicate IDs in the incoming widgets
ids_to_mount = [widget.id for widget in widgets if widget.id is not None]
unique_ids = set(ids_to_mount)
Expand Down Expand Up @@ -1124,7 +1126,8 @@ async def recompose(self) -> None:
if self._parent is not None:
async with self.batch():
await self.query("*").exclude(".-textual-system").remove()
await self.mount_all(compose(self))
if self._is_linked_to_app:
await self.mount_all(compose(self))

def _post_register(self, app: App) -> None:
"""Called when the instance is registered.
Expand Down
7 changes: 7 additions & 0 deletions tests/test_widget_mounting.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,10 @@ async def test_mount_via_app() -> None:
await pilot.app.mount_all(widgets)
with pytest.raises(TooManyMatches):
await pilot.app.mount(Static(), before="Static")


def test_mount_error() -> None:
"""Mounting a widget on an un-mounted widget should raise an error."""
with pytest.raises(MountError):
widget = Widget()
widget.mount(Static())

0 comments on commit 6034065

Please sign in to comment.