mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #1102 from davep/there-can-only-be-one
Change query_one so that it raises an error on multiple hits
This commit is contained in:
@@ -12,11 +12,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Dropped support for mounting "named" and "anonymous" widgets via
|
||||
`App.mount` and `Widget.mount`. Both methods now simply take one or more
|
||||
widgets as positional arguments.
|
||||
- `DOMNode.query_one` now raises a `TooManyMatches` exception if there is
|
||||
more than one matching node.
|
||||
https://github.com/Textualize/textual/issues/1096
|
||||
|
||||
### Added
|
||||
|
||||
- Added `init` param to reactive.watch
|
||||
- `CSS_PATH` can now be a list of CSS files https://github.com/Textualize/textual/pull/1079
|
||||
- Added `DOMQuery.only_one` https://github.com/Textualize/textual/issues/1096
|
||||
|
||||
## [0.3.0] - 2022-10-31
|
||||
|
||||
|
||||
@@ -42,6 +42,10 @@ class NoMatches(QueryError):
|
||||
"""No nodes matched the query."""
|
||||
|
||||
|
||||
class TooManyMatches(QueryError):
|
||||
"""Too many nodes matched the query."""
|
||||
|
||||
|
||||
class WrongType(QueryError):
|
||||
"""Query result was not of the correct type."""
|
||||
|
||||
@@ -208,6 +212,49 @@ class DOMQuery(Generic[QueryType]):
|
||||
else:
|
||||
raise NoMatches(f"No nodes match {self!r}")
|
||||
|
||||
@overload
|
||||
def only_one(self) -> Widget:
|
||||
...
|
||||
|
||||
@overload
|
||||
def only_one(self, expect_type: type[ExpectType]) -> ExpectType:
|
||||
...
|
||||
|
||||
def only_one(
|
||||
self, expect_type: type[ExpectType] | None = None
|
||||
) -> Widget | ExpectType:
|
||||
"""Get the *only* matching node.
|
||||
|
||||
Args:
|
||||
expect_type (type[ExpectType] | None, optional): Require matched node is of this type,
|
||||
or None for any type. Defaults to None.
|
||||
|
||||
Raises:
|
||||
WrongType: If the wrong type was found.
|
||||
TooManyMatches: If there is more than one matching node in the query.
|
||||
|
||||
Returns:
|
||||
Widget | ExpectType: The matching Widget.
|
||||
"""
|
||||
# Call on first to get the first item. Here we'll use all of the
|
||||
# testing and checking it provides.
|
||||
the_one = self.first(expect_type) if expect_type is not None else self.first()
|
||||
try:
|
||||
# Now see if we can access a subsequent item in the nodes. There
|
||||
# should *not* be anything there, so we *should* get an
|
||||
# IndexError. We *could* have just checked the length of the
|
||||
# query, but the idea here is to do the check as cheaply as
|
||||
# possible.
|
||||
_ = self.nodes[1]
|
||||
raise TooManyMatches(
|
||||
"Call to only_one resulted in more than one matched node"
|
||||
)
|
||||
except IndexError:
|
||||
# The IndexError was got, that's a good thing in this case. So
|
||||
# we return what we found.
|
||||
pass
|
||||
return the_one
|
||||
|
||||
@overload
|
||||
def last(self) -> Widget:
|
||||
...
|
||||
|
||||
@@ -783,10 +783,7 @@ class DOMNode(MessagePump):
|
||||
query_selector = selector.__name__
|
||||
query: DOMQuery[Widget] = DOMQuery(self, filter=query_selector)
|
||||
|
||||
if expect_type is None:
|
||||
return query.first()
|
||||
else:
|
||||
return query.first(expect_type)
|
||||
return query.only_one() if expect_type is None else query.only_one(expect_type)
|
||||
|
||||
def set_styles(self, css: str | None = None, **update_styles) -> None:
|
||||
"""Set custom styles on this object."""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from textual.widget import Widget
|
||||
from textual.css.query import InvalidQueryFormat, WrongType, NoMatches
|
||||
from textual.css.query import InvalidQueryFormat, WrongType, NoMatches, TooManyMatches
|
||||
|
||||
|
||||
def test_query():
|
||||
@@ -82,6 +82,10 @@ def test_query():
|
||||
helpbar,
|
||||
]
|
||||
assert list(app.query("Widget.float").results(View)) == []
|
||||
assert app.query_one("#widget1") == widget1
|
||||
assert app.query_one("#widget1", Widget) == widget1
|
||||
with pytest.raises(TooManyMatches):
|
||||
_ = app.query_one(Widget)
|
||||
|
||||
assert app.query("Widget.float")[0] == sidebar
|
||||
assert app.query("Widget.float")[0:2] == [sidebar, tooltip]
|
||||
|
||||
Reference in New Issue
Block a user