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

226 lines
6.7 KiB
Python

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