ai-station/.venv/lib/python3.12/site-packages/textual/compose.py

100 lines
3.1 KiB
Python
Raw Normal View History

2025-12-25 14:54:33 +00:00
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from textual.app import App, ComposeResult
from textual.widget import Widget
__all__ = ["compose"]
def compose(
node: App | Widget, compose_result: ComposeResult | None = None
) -> list[Widget]:
"""Compose child widgets from a generator in the same way as [compose][textual.widget.Widget.compose].
Example:
```python
def on_key(self, event:events.Key) -> None:
def add_key(key:str) -> ComposeResult:
with containers.HorizontalGroup():
yield Label("You pressed:")
yield Label(key)
self.mount_all(
compose(self, add_key(event.key)),
)
```
Args:
node: The parent node.
compose_result: A compose result, or `None` to call `node.compose()`.
Returns:
A list of widgets.
"""
_rich_traceback_omit = True
from textual.widget import MountError, Widget
app = node.app
nodes: list[Widget] = []
compose_stack: list[Widget] = []
composed: list[Widget] = []
app._compose_stacks.append(compose_stack)
app._composed.append(composed)
iter_compose = iter(
compose_result if compose_result is not None else node.compose()
)
is_generator = hasattr(iter_compose, "throw")
try:
while True:
try:
child = next(iter_compose)
except StopIteration:
break
if not isinstance(child, Widget):
mount_error = MountError(
f"Can't mount {type(child)}; expected a Widget instance."
)
if is_generator:
iter_compose.throw(mount_error) # type: ignore
else:
raise mount_error from None
try:
child.id
except AttributeError:
mount_error = MountError(
"Widget is missing an 'id' attribute; did you forget to call super().__init__()?"
)
if is_generator:
iter_compose.throw(mount_error) # type: ignore
else:
raise mount_error from None
if composed:
nodes.extend(composed)
composed.clear()
if compose_stack:
try:
compose_stack[-1].compose_add_child(child)
except Exception as error:
if is_generator:
# So the error is raised inside the generator
# This will generate a more sensible traceback for the dev
iter_compose.throw(error) # type: ignore
else:
raise
else:
nodes.append(child)
if composed:
nodes.extend(composed)
composed.clear()
finally:
app._compose_stacks.pop()
app._composed.pop()
return nodes