""" Descriptors to define properties on your widget, screen, or App. """ from __future__ import annotations from inspect import isclass from typing import TYPE_CHECKING, Callable, Generic, TypeVar, overload from textual._context import NoActiveAppError, active_app from textual.css.query import NoMatches, QueryType, WrongType from textual.widget import Widget if TYPE_CHECKING: from textual.app import App from textual.dom import DOMNode from textual.message_pump import MessagePump AppType = TypeVar("AppType", bound="App") class app(Generic[AppType]): """Create a property to return the active app. All widgets have a default `app` property which returns an App instance. Type checkers will complain if you try to access attributes defined on your App class, which aren't present in the base class. To keep the type checker happy you can add this property to get your specific App subclass. Example: ```python class MyWidget(Widget): app = getters.app(MyApp) ``` Args: app_type: The App subclass, or a callable which returns an App subclass. """ def __init__(self, app_type: type[AppType] | Callable[[], type[AppType]]) -> None: self._app_type = app_type if isclass(app_type) else app_type() def __get__(self, obj: MessagePump, obj_type: type[MessagePump]) -> AppType: try: app = active_app.get() except LookupError: from textual.app import App node: MessagePump | None = obj while not isinstance(node, App): if node is None: raise NoActiveAppError() node = node._parent app = node assert isinstance(app, self._app_type) return app class query_one(Generic[QueryType]): """Create a query one property. A query one property calls [Widget.query_one][textual.dom.DOMNode.query_one] when accessed, and returns a widget. If the widget doesn't exist, then the property will raise the same exceptions as `Widget.query_one`. Example: ```python from textual import getters class MyScreen(screen): # Note this is at the class level output_log = getters.query_one("#output", RichLog) def compose(self) -> ComposeResult: with containers.Vertical(): yield RichLog(id="output") def on_mount(self) -> None: self.output_log.write("Screen started") # Equivalent to the following line: # self.query_one("#output", RichLog).write("Screen started") ``` Args: selector: A TCSS selector, e.g. "#mywidget". Or a widget type, i.e. `Input`. expect_type: The type of the expected widget, e.g. `Input`, if the first argument is a selector. """ selector: str expect_type: type["Widget"] @overload def __init__(self, selector: str) -> None: """ Args: selector: A TCSS selector, e.g. "#mywidget" """ @overload def __init__(self, selector: type[QueryType]) -> None: ... @overload def __init__(self, selector: str, expect_type: type[QueryType]) -> None: ... @overload def __init__( self, selector: type[QueryType], expect_type: type[QueryType] ) -> None: ... def __init__( self, selector: str | type[QueryType], expect_type: type[QueryType] | None = None, ) -> None: if expect_type is None: from textual.widget import Widget self.expect_type = Widget else: self.expect_type = expect_type if isinstance(selector, str): self.selector = selector else: self.selector = selector.__name__ self.expect_type = selector @overload def __get__( self: "query_one[QueryType]", obj: DOMNode, obj_type: type[DOMNode] ) -> QueryType: ... @overload def __get__( self: "query_one[QueryType]", obj: None, obj_type: type[DOMNode] ) -> "query_one[QueryType]": ... def __get__( self: "query_one[QueryType]", obj: DOMNode | None, obj_type: type[DOMNode] ) -> QueryType | Widget | "query_one": """Get the widget matching the selector and/or type.""" if obj is None: return self query_node = obj.query_one(self.selector, self.expect_type) return query_node class child_by_id(Generic[QueryType]): """Create a child_by_id property, which returns the child with the given ID. This is similar using [query_one][textual.getters.query_one] with an id selector, except that only the immediate children are considered. It is also more efficient as it doesn't need to search the DOM. Example: ```python from textual import getters class MyScreen(screen): # Note this is at the class level output_log = getters.child_by_id("output", RichLog) def compose(self) -> ComposeResult: yield RichLog(id="output") def on_mount(self) -> None: self.output_log.write("Screen started") ``` Args: child_id: The `id` of the widget to get (not a selector). expect_type: The type of the expected widget, e.g. `Input`. """ child_id: str expect_type: type[Widget] @overload def __init__(self, child_id: str) -> None: ... @overload def __init__(self, child_id: str, expect_type: type[QueryType]) -> None: ... def __init__( self, child_id: str, expect_type: type[QueryType] | None = None, ) -> None: if expect_type is None: self.expect_type = Widget else: self.expect_type = expect_type self.child_id = child_id @overload def __get__( self: "child_by_id[QueryType]", obj: DOMNode, obj_type: type[DOMNode] ) -> QueryType: ... @overload def __get__( self: "child_by_id[QueryType]", obj: None, obj_type: type[DOMNode] ) -> "child_by_id[QueryType]": ... def __get__( self: "child_by_id[QueryType]", obj: DOMNode | None, obj_type: type[DOMNode] ) -> QueryType | Widget | "child_by_id": """Get the widget matching the selector and/or type.""" if obj is None: return self child = obj._get_dom_base()._nodes._get_by_id(self.child_id) if child is None: raise NoMatches(f"No child found with id={self.child_id!r}") if not isinstance(child, self.expect_type): raise WrongType( f"Child with id={self.child_id!r} is the wrong type; expected type {self.expect_type.__name__!r}, found {child}" ) return child