added textual.getters

This commit is contained in:
Will McGugan
2025-07-06 19:47:55 +01:00
parent 93709e91fd
commit e452077d3e
5 changed files with 116 additions and 5 deletions

View File

@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
### Added
- Added textual.getters
## [3.6.0] - 2025-07-06
### Fixed

5
docs/api/getters.md Normal file
View File

@@ -0,0 +1,5 @@
---
title: "textual.getters"
---
::: textual.getters

View File

@@ -6,7 +6,7 @@ except ImportError:
raise ImportError("Please install httpx with 'pip install httpx' ")
from textual import work
from textual import getters, work
from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.widgets import Input, Markdown
@@ -17,6 +17,9 @@ class DictionaryApp(App):
CSS_PATH = "dictionary.tcss"
results = getters.query_one("#results", Markdown)
input = getters.query_one(Input)
def compose(self) -> ComposeResult:
yield Input(placeholder="Search for a word", id="dictionary-search")
with VerticalScroll(id="results-container"):
@@ -28,7 +31,7 @@ class DictionaryApp(App):
self.lookup_word(message.value)
else:
# Clear the results
await self.query_one("#results", Markdown).update("")
await self.results.update("")
@work(exclusive=True)
async def lookup_word(self, word: str) -> None:
@@ -40,12 +43,12 @@ class DictionaryApp(App):
try:
results = response.json()
except Exception:
self.query_one("#results", Markdown).update(response.text)
self.results.update(response.text)
return
if word == self.query_one(Input).value:
if word == self.input.value:
markdown = self.make_word_markdown(results)
self.query_one("#results", Markdown).update(markdown)
self.results.update(markdown)
def make_word_markdown(self, results: object) -> str:
"""Convert the results into markdown."""

View File

@@ -202,6 +202,7 @@ nav:
- "api/filter.md"
- "api/fuzzy_matcher.md"
- "api/geometry.md"
- "api/getters.md"
- "api/layout.md"
- "api/lazy.md"
- "api/logger.md"

96
src/textual/getters.py Normal file
View File

@@ -0,0 +1,96 @@
"""
Descriptors to define properties on your widget, screen, or App.
"""
from typing import Generic, overload
from textual.css.query import QueryType
from textual.dom import DOMNode
from textual.widget import Widget
class query_one(Generic[QueryType]):
"""Create a query one property.
A query one property calls [query_one][textual.dom.DOMNode.query_one] when accessed, and returns
a widget.
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:
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")
```
"""
selector: str
expect_type: type[Widget]
@overload
def __init__(self, selector: str) -> None:
self.selector = selector
self.expect_type = Widget
@overload
def __init__(self, selector: type[QueryType]) -> None:
self.selector = selector.__name__
self.expect_type = selector
@overload
def __init__(self, selector: str, expect_type: type[QueryType]) -> None:
self.selector = selector
self.expect_type = expect_type
@overload
def __init__(self, selector: type[QueryType], expect_type: type[QueryType]) -> None:
self.selector = selector.__name__
self.expect_type = expect_type
def __init__(
self,
selector: str | type[QueryType],
expect_type: type[QueryType] | None = None,
) -> None:
if expect_type is None:
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