mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
feat: get_*_by_id expect_type
This commit is contained in:
@@ -63,8 +63,9 @@ class NodeList(Sequence):
|
||||
"""
|
||||
return self._nodes.index(widget)
|
||||
|
||||
def _get_by_id(self, widget_id: str) -> Widget | None:
|
||||
"""Get the widget for the given widget_id, or None if there's no matches in this list"""
|
||||
def get_by_id(self, widget_id: str) -> Widget | None:
|
||||
"""Get the widget for the given widget_id, or None if there's no matches in this list
|
||||
"""
|
||||
return self._nodes_by_id.get(widget_id)
|
||||
|
||||
def _append(self, widget: Widget) -> None:
|
||||
@@ -100,9 +101,9 @@ class NodeList(Sequence):
|
||||
def _ensure_unique_id(self, widget_id: str) -> None:
|
||||
if widget_id in self._nodes_by_id:
|
||||
raise DuplicateIds(
|
||||
f"Tried to insert a widget with ID {widget_id!r}, but a widget {self._nodes_by_id[widget_id]!r} "
|
||||
f"already exists with that ID in this list of children. "
|
||||
f"The children of a widget must have unique IDs."
|
||||
f"Tried to insert a widget with ID {widget_id!r}, but a widget"
|
||||
f" {self._nodes_by_id[widget_id]!r} already exists with that ID in this"
|
||||
" list of children. The children of a widget must have unique IDs."
|
||||
)
|
||||
|
||||
def _remove(self, widget: Widget) -> None:
|
||||
|
||||
@@ -26,6 +26,7 @@ from typing import (
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
overload,
|
||||
)
|
||||
from weakref import WeakSet, WeakValueDictionary
|
||||
|
||||
@@ -877,7 +878,8 @@ class App(Generic[ReturnType], DOMNode):
|
||||
stylesheet.parse()
|
||||
elapsed = (perf_counter() - time) * 1000
|
||||
self.log.system(
|
||||
f"<stylesheet> loaded {len(css_paths)} CSS files in {elapsed:.0f} ms"
|
||||
f"<stylesheet> loaded {len(css_paths)} CSS files in"
|
||||
f" {elapsed:.0f} ms"
|
||||
)
|
||||
except Exception as error:
|
||||
# TODO: Catch specific exceptions
|
||||
@@ -892,23 +894,52 @@ class App(Generic[ReturnType], DOMNode):
|
||||
def render(self) -> RenderableType:
|
||||
return Blank(self.styles.background)
|
||||
|
||||
ExpectType = TypeVar("ExpectType", bound=Widget)
|
||||
|
||||
@overload
|
||||
def get_child_by_id(self, id: str) -> Widget:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_child_by_id(self, id: str, expect_type: type[ExpectType]) -> ExpectType:
|
||||
...
|
||||
|
||||
def get_child_by_id(
|
||||
self, id: str, expect_type: type[ExpectType] | None = None
|
||||
) -> ExpectType | Widget:
|
||||
"""Shorthand for self.screen.get_child(id: str)
|
||||
Returns the first child (immediate descendent) of this DOMNode
|
||||
with the given ID.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the node to search for.
|
||||
expect_type (type | None, optional): Require the object be of the supplied type, or None for any type.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
DOMNode: The first child of this node with the specified ID.
|
||||
ExpectType | Widget: The first child of this node with the specified ID.
|
||||
|
||||
Raises:
|
||||
NoMatches: if no children could be found for this ID
|
||||
WrongType: if the wrong type was found.
|
||||
"""
|
||||
return self.screen.get_child_by_id(id)
|
||||
return (
|
||||
self.screen.get_child_by_id(id)
|
||||
if expect_type is None
|
||||
else self.screen.get_child_by_id(id, expect_type)
|
||||
)
|
||||
|
||||
@overload
|
||||
def get_widget_by_id(self, id: str) -> Widget:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_widget_by_id(self, id: str, expect_type: type[ExpectType]) -> ExpectType:
|
||||
...
|
||||
|
||||
def get_widget_by_id(
|
||||
self, id: str, expect_type: type[ExpectType] | None = None
|
||||
) -> ExpectType | Widget:
|
||||
"""Shorthand for self.screen.get_widget_by_id(id)
|
||||
Return the first descendant widget with the given ID.
|
||||
|
||||
@@ -918,14 +949,21 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Args:
|
||||
id (str): The ID to search for in the subtree
|
||||
expect_type (type | None, optional): Require the object be of the supplied type, or None for any type.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
DOMNode: The first descendant encountered with this ID.
|
||||
ExpectType | Widget: The first descendant encountered with this ID.
|
||||
|
||||
Raises:
|
||||
NoMatches: if no children could be found for this ID
|
||||
WrongType: if the wrong type was found.
|
||||
"""
|
||||
return self.screen.get_widget_by_id(id)
|
||||
return (
|
||||
self.screen.get_widget_by_id(id)
|
||||
if expect_type is None
|
||||
else self.screen.get_widget_by_id(id, expect_type)
|
||||
)
|
||||
|
||||
def update_styles(self, node: DOMNode | None = None) -> None:
|
||||
"""Request update of styles.
|
||||
@@ -1463,7 +1501,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
# If we don't already know about this widget...
|
||||
if child not in self._registry:
|
||||
|
||||
# Now to figure out where to place it. If we've got a `before`...
|
||||
if before is not None:
|
||||
# ...it's safe to NodeList._insert before that location.
|
||||
@@ -1792,7 +1829,8 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
if private_method is None and public_method is None:
|
||||
log(
|
||||
f"<action> {action_name!r} has no target. Couldn't find methods {public_method_name!r} or {private_method_name!r}"
|
||||
f"<action> {action_name!r} has no target. Couldn't find methods"
|
||||
f" {public_method_name!r} or {private_method_name!r}"
|
||||
)
|
||||
|
||||
if callable(private_method):
|
||||
|
||||
@@ -13,7 +13,9 @@ from typing import (
|
||||
Iterable,
|
||||
NamedTuple,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
cast,
|
||||
overload,
|
||||
)
|
||||
|
||||
import rich.repr
|
||||
@@ -42,7 +44,7 @@ from ._styles_cache import StylesCache
|
||||
from ._types import Lines
|
||||
from .binding import NoBinding
|
||||
from .box_model import BoxModel, get_box_model
|
||||
from .css.query import NoMatches
|
||||
from .css.query import NoMatches, WrongType
|
||||
from .css.scalar import ScalarOffset
|
||||
from .dom import DOMNode, NoScreen
|
||||
from .geometry import Offset, Region, Size, Spacing, clamp
|
||||
@@ -209,7 +211,6 @@ class Widget(DOMNode):
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
) -> None:
|
||||
|
||||
self._size = Size(0, 0)
|
||||
self._container_size = Size(0, 0)
|
||||
self._layout_required = False
|
||||
@@ -337,41 +338,81 @@ class Widget(DOMNode):
|
||||
def offset(self, offset: Offset) -> None:
|
||||
self.styles.offset = ScalarOffset.from_offset(offset)
|
||||
|
||||
ExpectType = TypeVar("ExpectType", bound="Widget")
|
||||
|
||||
@overload
|
||||
def get_child_by_id(self, id: str) -> Widget:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_child_by_id(self, id: str, expect_type: type[ExpectType]) -> ExpectType:
|
||||
...
|
||||
|
||||
def get_child_by_id(
|
||||
self, id: str, expect_type: type[ExpectType] | None = None
|
||||
) -> ExpectType | Widget:
|
||||
"""Return the first child (immediate descendent) of this node with the given ID.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the child.
|
||||
expect_type (type | None, optional): Require the object be of the supplied type, or None for any type.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
DOMNode: The first child of this node with the ID.
|
||||
ExpectType | Widget: The first child of this node with the ID.
|
||||
|
||||
Raises:
|
||||
NoMatches: if no children could be found for this ID
|
||||
WrongType: if the wrong type was found.
|
||||
"""
|
||||
child = self.children._get_by_id(id)
|
||||
if child is not None:
|
||||
child = self.children.get_by_id(id)
|
||||
if child is None:
|
||||
raise NoMatches(f"No child found with id={id!r}")
|
||||
if expect_type is None:
|
||||
return child
|
||||
raise NoMatches(f"No child found with id={id!r}")
|
||||
if not isinstance(child, expect_type):
|
||||
raise WrongType(
|
||||
f"Child with id={id!r} is wrong type; expected {expect_type}, got"
|
||||
f" {type(child)}"
|
||||
)
|
||||
return child
|
||||
|
||||
@overload
|
||||
def get_widget_by_id(self, id: str) -> Widget:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_widget_by_id(self, id: str, expect_type: type[ExpectType]) -> ExpectType:
|
||||
...
|
||||
|
||||
def get_widget_by_id(
|
||||
self, id: str, expect_type: type[ExpectType] | None = None
|
||||
) -> ExpectType | Widget:
|
||||
"""Return the first descendant widget with the given ID.
|
||||
Performs a depth-first search rooted at this widget.
|
||||
|
||||
Args:
|
||||
id (str): The ID to search for in the subtree
|
||||
expect_type (type | None, optional): Require the object be of the supplied type, or None for any type.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
DOMNode: The first descendant encountered with this ID.
|
||||
ExpectType | Widget: The first descendant encountered with this ID.
|
||||
|
||||
Raises:
|
||||
NoMatches: if no children could be found for this ID
|
||||
WrongType: if the wrong type was found.
|
||||
"""
|
||||
for child in walk_depth_first(self):
|
||||
try:
|
||||
return child.get_child_by_id(id)
|
||||
return child.get_child_by_id(id, expect_type=expect_type)
|
||||
except NoMatches:
|
||||
pass
|
||||
except WrongType as exc:
|
||||
raise WrongType(
|
||||
f"Descendant with id={id!r} is wrong type; expected {expect_type},"
|
||||
f" got {type(child)}"
|
||||
) from exc
|
||||
raise NoMatches(f"No descendant found with id={id!r}")
|
||||
|
||||
def get_component_rich_style(self, name: str) -> Style:
|
||||
@@ -511,8 +552,8 @@ class Widget(DOMNode):
|
||||
for widget_id, count in counter.items():
|
||||
if count > 1:
|
||||
raise MountError(
|
||||
f"Tried to insert {count!r} widgets with the same ID {widget_id!r}. "
|
||||
f"Widget IDs must be unique."
|
||||
f"Tried to insert {count!r} widgets with the same ID"
|
||||
f" {widget_id!r}. Widget IDs must be unique."
|
||||
)
|
||||
|
||||
# Saying you want to mount before *and* after something is an error.
|
||||
@@ -573,7 +614,8 @@ class Widget(DOMNode):
|
||||
child = self.children[child]
|
||||
except IndexError:
|
||||
raise WidgetError(
|
||||
f"An index of {child} for the child to {called} is out of bounds"
|
||||
f"An index of {child} for the child to {called} is out of"
|
||||
" bounds"
|
||||
) from None
|
||||
else:
|
||||
# We got an actual widget, so let's be sure it really is one of
|
||||
|
||||
Reference in New Issue
Block a user