feat: get_*_by_id expect_type

This commit is contained in:
Aaron Stephens
2022-11-20 16:35:30 -08:00
parent f5db48211f
commit e18fac9033
3 changed files with 104 additions and 23 deletions

View File

@@ -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:

View File

@@ -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):

View File

@@ -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