From 7aa201e9679a20f6af991c615a431b8caa25f46c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:46:22 +0000 Subject: [PATCH 01/68] Helper file for error progress tracking. --- mypy.txt | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 mypy.txt diff --git a/mypy.txt b/mypy.txt new file mode 100644 index 0000000000..860057085c --- /dev/null +++ b/mypy.txt @@ -0,0 +1,96 @@ +poetry run mypy src/textual +src/textual/keys.py:16: error: Incompatible types in assignment (expression has type "str", base class "Enum" defined the type as "Callable[[Enum], Any]") [assignment] +src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] +src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] +src/textual/devtools/service.py:10: error: Skipping analyzing "msgpack": module is installed, but missing library stubs or py.typed marker [import] +src/textual/devtools/client.py:13: error: Skipping analyzing "msgpack": module is installed, but missing library stubs or py.typed marker [import] +src/textual/devtools/redirect_output.py:42: error: Item "None" of "Optional[FrameType]" has no attribute "f_back" [union-attr] +src/textual/devtools/redirect_output.py:43: error: Argument 1 to "getframeinfo" has incompatible type "Union[FrameType, None, Any]"; expected "Union[FrameType, TracebackType]" [arg-type] +src/textual/devtools/redirect_output.py:45: error: Argument "caller" to "DevtoolsLog" has incompatible type "Traceback"; expected "FrameInfo" [arg-type] +src/textual/__init__.py:27: error: Library stubs not installed for "pkg_resources" [import] +src/textual/__init__.py:27: note: Hint: "python3 -m pip install types-setuptools" +src/textual/__init__.py:27: note: (or run "mypy --install-types" to install all missing stub packages) +src/textual/__init__.py:27: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +src/textual/__init__.py:67: error: Item "None" of "Optional[FrameType]" has no attribute "f_back" [union-attr] +src/textual/__init__.py:68: error: Argument 1 to "getframeinfo" has incompatible type "Union[FrameType, None, Any]"; expected "Union[FrameType, TracebackType]" [arg-type] +src/textual/__init__.py:75: error: Argument 3 to "_log" of "App" has incompatible type "Traceback"; expected "FrameInfo" [arg-type] +src/textual/_compositor.py:435: error: Argument 4 to "add_widget" has incompatible type "Tuple[object, ...]"; expected "Tuple[Tuple[int, ...], ...]" [arg-type] +src/textual/_compositor.py:731: error: Name "Callable" is not defined [name-defined] +src/textual/_compositor.py:731: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Callable") +src/textual/_callback.py:61: error: Item "None" of "Optional[App[Any]]" has no attribute "log" [union-attr] +src/textual/reactive.py:211: error: Argument "callback" to "Callback" has incompatible type "partial[None]"; expected "Callable[[], Awaitable[None]]" [arg-type] +src/textual/reactive.py:211: note: "partial[None].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], None]" +src/textual/_xterm_parser.py:211: error: Incompatible types in assignment (expression has type "Optional[Event]", variable has type "Key") [assignment] +src/textual/css/scalar_animation.py:38: error: "DOMNode" has no attribute "outer_size" [attr-defined] +src/textual/message_pump.py:79: error: Name "CallbackType" is not defined [name-defined] +src/textual/css/_style_properties.py:546: error: Argument 1 to "len" has incompatible type "Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]"; expected "Sized" [arg-type] +src/textual/css/styles.py:584: error: Incompatible types in assignment (expression has type "Dict[_KT, _VT]", variable has type "RulesMap") [assignment] +src/textual/css/styles.py:651: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] +src/textual/css/styles.py:651: note: This violates the Liskov substitution principle +src/textual/css/styles.py:651: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides +src/textual/css/styles.py:1061: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] +src/textual/css/styles.py:1061: note: This violates the Liskov substitution principle +src/textual/css/styles.py:1061: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides +src/textual/css/parse.py:278: error: Item "None" of "Optional[Token]" has no attribute "name" [union-attr] +src/textual/css/parse.py:279: error: Incompatible types in "yield" (actual type "Optional[Token]", expected type "Token") [misc] +src/textual/dom.py:272: error: Need type annotation for "keys" (hint: "keys: Dict[, ] = ...") [var-annotated] +src/textual/dom.py:353: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "DOMNode") [assignment] +src/textual/dom.py:794: error: Incompatible return value type (got "DOMQuery[]", expected "Union[DOMQuery[Widget], DOMQuery[ExpectType]]") [return-value] +src/textual/widget.py:433: error: "DOMNode" has no attribute "get_child_by_id" [attr-defined] +src/textual/widget.py:706: error: Argument 1 to "_to_widget" has incompatible type "Union[int, Widget, None]"; expected "Union[int, Widget]" [arg-type] +src/textual/widget.py:2098: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] +src/textual/widget.py:2122: error: Argument 1 to "_Styled" has incompatible type "Union[ConsoleRenderable, RichCast]"; expected "ConsoleRenderable" [arg-type] +src/textual/widget.py:2328: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] +src/textual/scrollbar.py:249: error: Item "None" of "Optional[DOMNode]" has no attribute "styles" [union-attr] +src/textual/scrollbar.py:260: error: Invalid self argument "Color" to attribute function "rich_color" with type "Callable[[textual.color.Color], rich.color.Color]" [misc] +src/textual/scrollbar.py:282: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_down" incompatible with return type "None" in supertype "Widget" [override] +src/textual/scrollbar.py:287: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_up" incompatible with return type "None" in supertype "Widget" [override] +src/textual/css/stylesheet.py:81: error: Item "None" of "Optional[Path]" has no attribute "absolute" [union-attr] +src/textual/app.py:303: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] +src/textual/app.py:1566: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] +src/textual/app.py:1571: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] +src/textual/app.py:1572: error: Value of type variable "_T_io" of "redirect_stdout" cannot be "_NullFile" [type-var] +src/textual/app.py:1702: error: Incompatible types in assignment (expression has type "reversed[Widget]", variable has type "List[Widget]") [assignment] +src/textual/app.py:1770: error: Incompatible types in assignment (expression has type "Union[Screen, Callable[[], Screen]]", variable has type "Screen") [assignment] +src/textual/app.py:2032: error: Argument 1 to "action" of "App" has incompatible type "Union[str, Tuple[Any, ...]]"; expected "Union[str, Tuple[str, Tuple[str, ...]]]" [arg-type] +src/textual/app.py:2309: error: Cannot find implementation or library stub for module named "uvloop" [import] +src/textual/widgets/_placeholder.py:122: error: Return type "Iterable[Union[Union[ConsoleRenderable, RichCast, str], Segment]]" of "render" incompatible with return type "Union[ConsoleRenderable, RichCast, str]" in supertype "Widget" [override] +src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] +src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] +src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] +src/textual/_doc.py:18: error: Missing return statement [return] +src/textual/_doc.py:85: error: Argument 1 to "open" has incompatible type "Union[PurePath, str, None]"; expected "Union[Union[str, bytes, PathLike[str], PathLike[bytes]], int]" [arg-type] +src/textual/_doc.py:115: error: Argument 1 to "write_text" of "Path" has incompatible type "Optional[Any]"; expected "str" [arg-type] +src/textual/widgets/_list_view.py:90: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") [assignment] +src/textual/widgets/_list_view.py:93: error: Missing return statement [return] +src/textual/widgets/_list_view.py:98: error: Incompatible return value type (got "Widget", expected "Optional[ListItem]") [return-value] +src/textual/widgets/_list_view.py:128: error: "Widget" has no attribute "highlighted"; maybe "highlight_style"? [attr-defined] +src/textual/widgets/_list_view.py:131: error: "Widget" has no attribute "highlighted"; maybe "highlight_style"? [attr-defined] +src/textual/widgets/_list_view.py:136: error: Argument 2 to "Highlighted" has incompatible type "Optional[Widget]"; expected "Optional[ListItem]" [arg-type] +src/textual/widgets/_list_view.py:161: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] +src/textual/widgets/_list_view.py:167: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] +src/textual/widgets/_list_view.py:180: error: Argument 2 to "Selected" has incompatible type "MessageTarget"; expected "ListItem" [arg-type] +src/textual/widgets/_data_table.py:688: error: Unsupported operand types for in ("Union[RowKey, str]" and "TwoWayDict[RowKey, int]") [operator] +src/textual/widgets/_data_table.py:690: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:727: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "TwoWayDict[ColumnKey, int]") [operator] +src/textual/widgets/_data_table.py:733: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1409: error: Unsupported operand types for in ("Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" and "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]") [operator] +src/textual/widgets/_data_table.py:1410: error: Invalid index type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" for "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]"; expected type "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [index] +src/textual/widgets/_data_table.py:1453: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1455: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1481: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1482: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1487: error: Argument 1 has incompatible type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]"; expected "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1543: error: Unsupported operand types for in ("Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" and "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]") [operator] +src/textual/widgets/_data_table.py:1544: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] +src/textual/widgets/_data_table.py:1563: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1628: error: Argument "key" to "sorted" has incompatible type "Callable[[Tuple[RowKey, Dict[Union[ColumnKey, str], CellType]]], Any]"; expected "Callable[[Tuple[RowKey, Dict[ColumnKey, CellType]]], Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]]" [arg-type] +src/textual/widgets/_button.py:241: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] +src/textual/widgets/_button.py:241: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] +src/textual/widgets/_button.py:241: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] +src/textual/demo.py:206: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[bool], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] +src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-defined] +src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] +src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] +src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] +Found 85 errors in 29 files (checked 152 source files) From e93b7e2c0e31a2ac949642f422d7b38f0bfae97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 17 Feb 2023 23:12:35 +0000 Subject: [PATCH 02/68] Fix typing of Keys.???.value. --- mypy.txt | 3 +-- src/textual/keys.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index 860057085c..6cd28f27d8 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,5 +1,4 @@ poetry run mypy src/textual -src/textual/keys.py:16: error: Incompatible types in assignment (expression has type "str", base class "Enum" defined the type as "Callable[[Enum], Any]") [assignment] src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] src/textual/devtools/service.py:10: error: Skipping analyzing "msgpack": module is installed, but missing library stubs or py.typed marker [import] @@ -93,4 +92,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 85 errors in 29 files (checked 152 source files) +Found 84 errors in 28 files (checked 152 source files) diff --git a/src/textual/keys.py b/src/textual/keys.py index 81e0aca381..c2dead70e1 100644 --- a/src/textual/keys.py +++ b/src/textual/keys.py @@ -2,10 +2,11 @@ import unicodedata from enum import Enum +from typing import TYPE_CHECKING # Adapted from prompt toolkit https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/keys.py -class Keys(str, Enum): +class Keys(str, Enum): # type: ignore[no-redef] """ List of keys for use in key bindings. @@ -13,8 +14,6 @@ class Keys(str, Enum): strings. """ - value: str - Escape = "escape" # Also Control-[ ShiftEscape = "shift+escape" Return = "return" @@ -197,6 +196,18 @@ class Keys(str, Enum): ShiftControlEnd = ControlShiftEnd +# We want to make sure that mypy knows that the values of Keys will always be strings. +# Typing is verbose here because the attribute `Enum.value` was special-cased to have +# very different behaviours in `Keys.value` and `Keys.SomeKey.value`. +if TYPE_CHECKING: + from enum import property as enum_property + + class Keys(str, Enum): # type: ignore[no-redef] + @enum_property + def value(self) -> str: + ... + + # Unicode db contains some obscure names # This mapping replaces them with more common terms KEY_NAME_REPLACEMENTS = { From b70921965506871125c404330c60f4acfd044e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 17 Feb 2023 23:35:30 +0000 Subject: [PATCH 03/68] Assert that we have frame information. @willmcgugan I went with the asserts because the tone of the code tells me that we are kind of certain that the current and previous frames always exist. Should the function be refactored to handle None for the previous frame (or the current & previous frames) by writing to the buffer without caller information, for example? --- mypy.txt | 6 ++---- src/textual/devtools/redirect_output.py | 5 ++++- src/textual/devtools/service.py | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mypy.txt b/mypy.txt index 6cd28f27d8..7834b6fb6e 100644 --- a/mypy.txt +++ b/mypy.txt @@ -3,9 +3,7 @@ src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Opt src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] src/textual/devtools/service.py:10: error: Skipping analyzing "msgpack": module is installed, but missing library stubs or py.typed marker [import] src/textual/devtools/client.py:13: error: Skipping analyzing "msgpack": module is installed, but missing library stubs or py.typed marker [import] -src/textual/devtools/redirect_output.py:42: error: Item "None" of "Optional[FrameType]" has no attribute "f_back" [union-attr] -src/textual/devtools/redirect_output.py:43: error: Argument 1 to "getframeinfo" has incompatible type "Union[FrameType, None, Any]"; expected "Union[FrameType, TracebackType]" [arg-type] -src/textual/devtools/redirect_output.py:45: error: Argument "caller" to "DevtoolsLog" has incompatible type "Traceback"; expected "FrameInfo" [arg-type] +src/textual/devtools/redirect_output.py:48: error: Argument "caller" to "DevtoolsLog" has incompatible type "Traceback"; expected "FrameInfo" [arg-type] src/textual/__init__.py:27: error: Library stubs not installed for "pkg_resources" [import] src/textual/__init__.py:27: note: Hint: "python3 -m pip install types-setuptools" src/textual/__init__.py:27: note: (or run "mypy --install-types" to install all missing stub packages) @@ -92,4 +90,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 84 errors in 28 files (checked 152 source files) +Found 82 errors in 28 files (checked 152 source files) diff --git a/src/textual/devtools/redirect_output.py b/src/textual/devtools/redirect_output.py index b79f935805..0e1f934b2e 100644 --- a/src/textual/devtools/redirect_output.py +++ b/src/textual/devtools/redirect_output.py @@ -39,7 +39,10 @@ def write(self, string: str) -> None: if not self.devtools.is_connected: return - previous_frame = inspect.currentframe().f_back + current_frame = inspect.currentframe() + assert current_frame is not None + previous_frame = current_frame.f_back + assert previous_frame is not None caller = inspect.getframeinfo(previous_frame) self._buffer.append(DevtoolsLog(string, caller=caller)) diff --git a/src/textual/devtools/service.py b/src/textual/devtools/service.py index 5e85d2f895..f3c3f19f77 100644 --- a/src/textual/devtools/service.py +++ b/src/textual/devtools/service.py @@ -5,10 +5,10 @@ import json import pickle from json import JSONDecodeError -from typing import Any, cast +from typing import Any import msgpack -from aiohttp import WSMessage, WSMsgType +from aiohttp import WSMsgType from aiohttp.abc import Request from aiohttp.web_ws import WebSocketResponse from rich.console import Console From aad366082ff687636f1f10810d5b71e674f81ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 17 Feb 2023 23:44:18 +0000 Subject: [PATCH 04/68] Use inspect.Traceback instead of inspect.FrameInfo. Looks like the two shared a lot of attributes, so we were trying to use FrameInfo but inspect.getframeinfo returns a Traceback, so that felt like the correct type to use. --- mypy.txt | 4 +--- src/textual/app.py | 2 +- src/textual/devtools/client.py | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index 7834b6fb6e..ddc6e3239a 100644 --- a/mypy.txt +++ b/mypy.txt @@ -3,14 +3,12 @@ src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Opt src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] src/textual/devtools/service.py:10: error: Skipping analyzing "msgpack": module is installed, but missing library stubs or py.typed marker [import] src/textual/devtools/client.py:13: error: Skipping analyzing "msgpack": module is installed, but missing library stubs or py.typed marker [import] -src/textual/devtools/redirect_output.py:48: error: Argument "caller" to "DevtoolsLog" has incompatible type "Traceback"; expected "FrameInfo" [arg-type] src/textual/__init__.py:27: error: Library stubs not installed for "pkg_resources" [import] src/textual/__init__.py:27: note: Hint: "python3 -m pip install types-setuptools" src/textual/__init__.py:27: note: (or run "mypy --install-types" to install all missing stub packages) src/textual/__init__.py:27: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports src/textual/__init__.py:67: error: Item "None" of "Optional[FrameType]" has no attribute "f_back" [union-attr] src/textual/__init__.py:68: error: Argument 1 to "getframeinfo" has incompatible type "Union[FrameType, None, Any]"; expected "Union[FrameType, TracebackType]" [arg-type] -src/textual/__init__.py:75: error: Argument 3 to "_log" of "App" has incompatible type "Traceback"; expected "FrameInfo" [arg-type] src/textual/_compositor.py:435: error: Argument 4 to "add_widget" has incompatible type "Tuple[object, ...]"; expected "Tuple[Tuple[int, ...], ...]" [arg-type] src/textual/_compositor.py:731: error: Name "Callable" is not defined [name-defined] src/textual/_compositor.py:731: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Callable") @@ -90,4 +88,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 82 errors in 28 files (checked 152 source files) +Found 80 errors in 27 files (checked 152 source files) diff --git a/src/textual/app.py b/src/textual/app.py index bf33e07cd9..fc30ad5b0c 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -608,7 +608,7 @@ def _log( self, group: LogGroup, verbosity: LogVerbosity, - _textual_calling_frame: inspect.FrameInfo, + _textual_calling_frame: inspect.Traceback, *objects: Any, **kwargs, ) -> None: diff --git a/src/textual/devtools/client.py b/src/textual/devtools/client.py index 0ba5705529..3c8592ae61 100644 --- a/src/textual/devtools/client.py +++ b/src/textual/devtools/client.py @@ -35,7 +35,7 @@ class DevtoolsLog(NamedTuple): """ objects_or_string: tuple[Any, ...] | str - caller: inspect.FrameInfo + caller: inspect.Traceback class DevtoolsConsole(Console): From 148ce039765efe946cb5ad3e917d8f5842ab11c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 17 Feb 2023 23:50:14 +0000 Subject: [PATCH 05/68] Update after installing msgpack-types. --- mypy.txt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/mypy.txt b/mypy.txt index ddc6e3239a..93f21fb791 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,12 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/devtools/service.py:10: error: Skipping analyzing "msgpack": module is installed, but missing library stubs or py.typed marker [import] -src/textual/devtools/client.py:13: error: Skipping analyzing "msgpack": module is installed, but missing library stubs or py.typed marker [import] -src/textual/__init__.py:27: error: Library stubs not installed for "pkg_resources" [import] -src/textual/__init__.py:27: note: Hint: "python3 -m pip install types-setuptools" -src/textual/__init__.py:27: note: (or run "mypy --install-types" to install all missing stub packages) -src/textual/__init__.py:27: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports src/textual/__init__.py:67: error: Item "None" of "Optional[FrameType]" has no attribute "f_back" [union-attr] src/textual/__init__.py:68: error: Argument 1 to "getframeinfo" has incompatible type "Union[FrameType, None, Any]"; expected "Union[FrameType, TracebackType]" [arg-type] src/textual/_compositor.py:435: error: Argument 4 to "add_widget" has incompatible type "Tuple[object, ...]"; expected "Tuple[Tuple[int, ...], ...]" [arg-type] @@ -49,18 +43,19 @@ src/textual/app.py:1702: error: Incompatible types in assignment (expression has src/textual/app.py:1770: error: Incompatible types in assignment (expression has type "Union[Screen, Callable[[], Screen]]", variable has type "Screen") [assignment] src/textual/app.py:2032: error: Argument 1 to "action" of "App" has incompatible type "Union[str, Tuple[Any, ...]]"; expected "Union[str, Tuple[str, Tuple[str, ...]]]" [arg-type] src/textual/app.py:2309: error: Cannot find implementation or library stub for module named "uvloop" [import] +src/textual/app.py:2309: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports src/textual/widgets/_placeholder.py:122: error: Return type "Iterable[Union[Union[ConsoleRenderable, RichCast, str], Segment]]" of "render" incompatible with return type "Union[ConsoleRenderable, RichCast, str]" in supertype "Widget" [override] src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/_doc.py:18: error: Missing return statement [return] -src/textual/_doc.py:85: error: Argument 1 to "open" has incompatible type "Union[PurePath, str, None]"; expected "Union[Union[str, bytes, PathLike[str], PathLike[bytes]], int]" [arg-type] +src/textual/_doc.py:85: error: Argument 1 to "open" has incompatible type "Union[PurePath, str, None]"; expected "Union[int, Union[str, bytes, PathLike[str], PathLike[bytes]]]" [arg-type] src/textual/_doc.py:115: error: Argument 1 to "write_text" of "Path" has incompatible type "Optional[Any]"; expected "str" [arg-type] src/textual/widgets/_list_view.py:90: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") [assignment] src/textual/widgets/_list_view.py:93: error: Missing return statement [return] src/textual/widgets/_list_view.py:98: error: Incompatible return value type (got "Widget", expected "Optional[ListItem]") [return-value] -src/textual/widgets/_list_view.py:128: error: "Widget" has no attribute "highlighted"; maybe "highlight_style"? [attr-defined] -src/textual/widgets/_list_view.py:131: error: "Widget" has no attribute "highlighted"; maybe "highlight_style"? [attr-defined] +src/textual/widgets/_list_view.py:128: error: "Widget" has no attribute "highlighted" [attr-defined] +src/textual/widgets/_list_view.py:131: error: "Widget" has no attribute "highlighted" [attr-defined] src/textual/widgets/_list_view.py:136: error: Argument 2 to "Highlighted" has incompatible type "Optional[Widget]"; expected "Optional[ListItem]" [arg-type] src/textual/widgets/_list_view.py:161: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] src/textual/widgets/_list_view.py:167: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] @@ -88,4 +83,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 80 errors in 27 files (checked 152 source files) +Found 77 errors in 25 files (checked 152 source files) From a410afaf480817ef61f46e209b9b876299119272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 09:38:59 +0000 Subject: [PATCH 06/68] Assert we have frame info. Same fix as in b709219. --- mypy.txt | 4 +--- src/textual/__init__.py | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy.txt b/mypy.txt index 93f21fb791..9f14cf758d 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,8 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/__init__.py:67: error: Item "None" of "Optional[FrameType]" has no attribute "f_back" [union-attr] -src/textual/__init__.py:68: error: Argument 1 to "getframeinfo" has incompatible type "Union[FrameType, None, Any]"; expected "Union[FrameType, TracebackType]" [arg-type] src/textual/_compositor.py:435: error: Argument 4 to "add_widget" has incompatible type "Tuple[object, ...]"; expected "Tuple[Tuple[int, ...], ...]" [arg-type] src/textual/_compositor.py:731: error: Name "Callable" is not defined [name-defined] src/textual/_compositor.py:731: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Callable") @@ -83,4 +81,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 77 errors in 25 files (checked 152 source files) +Found 75 errors in 24 files (checked 152 source files) diff --git a/src/textual/__init__.py b/src/textual/__init__.py index 0ffb3533c1..bef888ae18 100644 --- a/src/textual/__init__.py +++ b/src/textual/__init__.py @@ -64,7 +64,10 @@ def __call__(self, *args: object, **kwargs) -> None: if app.devtools is None or not app.devtools.is_connected: return - previous_frame = inspect.currentframe().f_back + current_frame = inspect.currentframe() + assert current_frame is not None + previous_frame = current_frame.f_back + assert previous_frame is not None caller = inspect.getframeinfo(previous_frame) _log = self._log or app._log From 9f084b50226971c959843e793dda513c892c794f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 11:26:56 +0000 Subject: [PATCH 07/68] Fix MapGeometry order information typing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was another alternative solution, which was to just flatten everything entirely, so that the code actually obeyed the comments. After all, the comments for the attribute `order` in the definition of `MapGeometry` said that `order` was a tuple of integers defining the painting order, which was not true because the very first widget was having its order defined as `order = ((0,),)` instead of `order = (0,)`. Thus, another possible solution would be to retype `order` as `tuple[int, ...]` and make everything flat, but I opted for the “tuple of tuples” approach because the sub-tuples will highlight the nested structure of the DOM hierarchy. --- mypy.txt | 7 +++--- src/textual/_compositor.py | 46 +++++++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/mypy.txt b/mypy.txt index 9f14cf758d..0afe4050b6 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,9 +1,8 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/_compositor.py:435: error: Argument 4 to "add_widget" has incompatible type "Tuple[object, ...]"; expected "Tuple[Tuple[int, ...], ...]" [arg-type] -src/textual/_compositor.py:731: error: Name "Callable" is not defined [name-defined] -src/textual/_compositor.py:731: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Callable") +src/textual/_compositor.py:746: error: Name "Callable" is not defined [name-defined] +src/textual/_compositor.py:746: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Callable") src/textual/_callback.py:61: error: Item "None" of "Optional[App[Any]]" has no attribute "log" [union-attr] src/textual/reactive.py:211: error: Argument "callback" to "Callback" has incompatible type "partial[None]"; expected "Callable[[], Awaitable[None]]" [arg-type] src/textual/reactive.py:211: note: "partial[None].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], None]" @@ -81,4 +80,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 75 errors in 24 files (checked 152 source files) +Found 74 errors in 24 files (checked 152 source files) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 5489c86878..6d707bc64a 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -45,12 +45,23 @@ class ReflowResult(NamedTuple): class MapGeometry(NamedTuple): """Defines the absolute location of a Widget.""" - region: Region # The (screen) region occupied by the widget - order: tuple[tuple[int, ...], ...] # A tuple of ints defining the painting order - clip: Region # A region to clip the widget by (if a Widget is within a container) - virtual_size: Size # The virtual size (scrollable region) of a widget if it is a container - container_size: Size # The container size (area not occupied by scrollbars) - virtual_region: Region # The region relative to the container (but not necessarily visible) + region: Region + """The (screen) region occupied by the widget.""" + order: tuple[tuple[int, int, int], ...] + """Tuple of tuples defining the painting order of the widget. + + Each successive triple represents painting order information with regards to + ancestors in the DOM hierarchy and the last triple provides painting order + information for this specific widget. + """ + clip: Region + """A region to clip the widget by (if a Widget is within a container).""" + virtual_size: Size + """The virtual size (scrollable region) of a widget if it is a container.""" + container_size: Size + """The container size (area not occupied by scrollbars).""" + virtual_region: Region + """The region relative to the container (but not necessarily visible).""" @property def visible_region(self) -> Region: @@ -343,19 +354,23 @@ def add_widget( widget: Widget, virtual_region: Region, region: Region, - order: tuple[tuple[int, ...], ...], + order: tuple[tuple[int, int, int], ...], layer_order: int, clip: Region, visible: bool, - _MapGeometry=MapGeometry, + _MapGeometry: type[MapGeometry] = MapGeometry, ) -> None: """Called recursively to place a widget and its children in the map. Args: widget: The widget to add. + virtual_region: TODO. region: The region the widget will occupy. - order: A tuple of ints to define the order. + order: Painting order information. + layer_order: The order of the widget in its layer. clip: The clipping region (i.e. the viewport which contains it). + visible: Whether the widget should be visible by default. + This may be overriden by the CSS rule `visibility`. """ visibility = widget.styles.get_rule("visibility") if visibility is not None: @@ -421,11 +436,12 @@ def add_widget( ) widget_region = sub_region + placement_scroll_offset - widget_order = ( - *order, - get_layer_index(sub_widget.layer, 0), - z, - layer_order, + widget_order = order + ( + ( + get_layer_index(sub_widget.layer, 0), + z, + layer_order, + ), ) add_widget( @@ -478,7 +494,7 @@ def add_widget( root, size.region, size.region, - ((0,),), + ((0, 0, 0),), layer_order, size.region, True, From eeeac1af87b1b3ee1398a1153d37bbefbf51c2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 13:13:12 +0000 Subject: [PATCH 08/68] Fix import and typing for fromkeys. --- mypy.txt | 4 +--- src/textual/_compositor.py | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/mypy.txt b/mypy.txt index 0afe4050b6..b8f2efeed1 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,8 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/_compositor.py:746: error: Name "Callable" is not defined [name-defined] -src/textual/_compositor.py:746: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Callable") src/textual/_callback.py:61: error: Item "None" of "Optional[App[Any]]" has no attribute "log" [union-attr] src/textual/reactive.py:211: error: Argument "callback" to "Callback" has incompatible type "partial[None]"; expected "Callable[[], Awaitable[None]]" [arg-type] src/textual/reactive.py:211: note: "partial[None].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], None]" @@ -80,4 +78,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 74 errors in 24 files (checked 152 source files) +Found 73 errors in 23 files (checked 152 source files) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 6d707bc64a..c34ac1d399 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -14,7 +14,7 @@ from __future__ import annotations from operator import itemgetter -from typing import TYPE_CHECKING, Iterable, NamedTuple, cast +from typing import TYPE_CHECKING, Callable, Iterable, NamedTuple, cast import rich.repr from rich.console import Console, ConsoleOptions, RenderableType, RenderResult @@ -743,10 +743,7 @@ def render(self, full: bool = False) -> RenderableType | None: cuts = self.cuts # dict.fromkeys is a callable which takes a list of ints returns a dict which maps ints on to a list of Segments or None. - fromkeys = cast( - "Callable[[list[int]], dict[int, list[Segment] | None]]", dict.fromkeys - ) - # A mapping of cut index to a list of segments for each line + fromkeys = cast("Callable[[list[int]], dict[int, Strip | None]]", dict.fromkeys) chops: list[dict[int, Strip | None]] chops = [fromkeys(cut_set[:-1]) for cut_set in cuts] From cb091a3d88af5b9bfdda2296b3064c518b29b0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 13:26:20 +0000 Subject: [PATCH 09/68] Assert app is not None. [skip cli] --- mypy.txt | 3 +-- src/textual/_callback.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy.txt b/mypy.txt index b8f2efeed1..a4da5d87b1 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,7 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/_callback.py:61: error: Item "None" of "Optional[App[Any]]" has no attribute "log" [union-attr] src/textual/reactive.py:211: error: Argument "callback" to "Callback" has incompatible type "partial[None]"; expected "Callable[[], Awaitable[None]]" [arg-type] src/textual/reactive.py:211: note: "partial[None].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], None]" src/textual/_xterm_parser.py:211: error: Incompatible types in assignment (expression has type "Optional[Event]", variable has type "Key") [assignment] @@ -78,4 +77,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 73 errors in 23 files (checked 152 source files) +Found 72 errors in 22 files (checked 152 source files) diff --git a/src/textual/_callback.py b/src/textual/_callback.py index ee38014540..add75588be 100644 --- a/src/textual/_callback.py +++ b/src/textual/_callback.py @@ -58,6 +58,7 @@ async def invoke(callback: Callable, *params: object) -> Any: # In debug mode we will warn about callbacks that may be stuck def log_slow() -> None: """Log a message regarding a slow callback.""" + assert app is not None app.log.warning( f"Callback {callback} is still pending after {INVOKE_TIMEOUT_WARNING} seconds" ) From 2f9421d478c71d15c65ba61fe268183b7ae8290d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:54:41 +0000 Subject: [PATCH 10/68] Import missing type. --- mypy.txt | 3 +-- src/textual/message_pump.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy.txt b/mypy.txt index a4da5d87b1..2235c17410 100644 --- a/mypy.txt +++ b/mypy.txt @@ -5,7 +5,6 @@ src/textual/reactive.py:211: error: Argument "callback" to "Callback" has incomp src/textual/reactive.py:211: note: "partial[None].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], None]" src/textual/_xterm_parser.py:211: error: Incompatible types in assignment (expression has type "Optional[Event]", variable has type "Key") [assignment] src/textual/css/scalar_animation.py:38: error: "DOMNode" has no attribute "outer_size" [attr-defined] -src/textual/message_pump.py:79: error: Name "CallbackType" is not defined [name-defined] src/textual/css/_style_properties.py:546: error: Argument 1 to "len" has incompatible type "Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]"; expected "Sized" [arg-type] src/textual/css/styles.py:584: error: Incompatible types in assignment (expression has type "Dict[_KT, _VT]", variable has type "RulesMap") [assignment] src/textual/css/styles.py:651: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] @@ -77,4 +76,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 72 errors in 22 files (checked 152 source files) +Found 71 errors in 21 files (checked 152 source files) diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index d17bd82497..b7ac10ab32 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -19,6 +19,7 @@ from ._callback import invoke from ._context import NoActiveAppError, active_app, active_message_pump from ._time import time +from ._types import CallbackType from .case import camel_to_snake from .errors import DuplicateKeyHandlers from .events import Event From ef8f46b8f384997c06df796b794b1089994131d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:57:36 +0000 Subject: [PATCH 11/68] Use CallbackType for event Callback. --- mypy.txt | 4 +--- src/textual/events.py | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/mypy.txt b/mypy.txt index 2235c17410..d6d058c132 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,8 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/reactive.py:211: error: Argument "callback" to "Callback" has incompatible type "partial[None]"; expected "Callable[[], Awaitable[None]]" [arg-type] -src/textual/reactive.py:211: note: "partial[None].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], None]" src/textual/_xterm_parser.py:211: error: Incompatible types in assignment (expression has type "Optional[Event]", variable has type "Key") [assignment] src/textual/css/scalar_animation.py:38: error: "DOMNode" has no attribute "outer_size" [attr-defined] src/textual/css/_style_properties.py:546: error: Argument 1 to "len" has incompatible type "Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]"; expected "Sized" [arg-type] @@ -76,4 +74,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 71 errors in 21 files (checked 152 source files) +Found 70 errors in 20 files (checked 152 source files) diff --git a/src/textual/events.py b/src/textual/events.py index 9914c03d51..28887bec9e 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -5,7 +5,7 @@ import rich.repr from rich.style import Style -from ._types import MessageTarget +from ._types import CallbackType, MessageTarget from .geometry import Offset, Size from .keys import _get_key_aliases from .message import Message @@ -28,9 +28,7 @@ def __rich_repr__(self) -> rich.repr.Result: @rich.repr.auto class Callback(Event, bubble=False, verbose=True): - def __init__( - self, sender: MessageTarget, callback: Callable[[], Awaitable[None]] - ) -> None: + def __init__(self, sender: MessageTarget, callback: CallbackType) -> None: self.callback = callback super().__init__(sender) From 479591042ecd565cacdda16ac498d5931ba57df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:02:34 +0000 Subject: [PATCH 12/68] Remove variable name clash. --- mypy.txt | 3 +-- src/textual/_xterm_parser.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mypy.txt b/mypy.txt index d6d058c132..9ddae6c172 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,7 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/_xterm_parser.py:211: error: Incompatible types in assignment (expression has type "Optional[Event]", variable has type "Key") [assignment] src/textual/css/scalar_animation.py:38: error: "DOMNode" has no attribute "outer_size" [attr-defined] src/textual/css/_style_properties.py:546: error: Argument 1 to "len" has incompatible type "Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]"; expected "Sized" [arg-type] src/textual/css/styles.py:584: error: Incompatible types in assignment (expression has type "Dict[_KT, _VT]", variable has type "RulesMap") [assignment] @@ -74,4 +73,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 70 errors in 20 files (checked 152 source files) +Found 69 errors in 19 files (checked 152 source files) diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py index 336da9af0a..a68da302c7 100644 --- a/src/textual/_xterm_parser.py +++ b/src/textual/_xterm_parser.py @@ -200,8 +200,8 @@ def reissue_sequence_as_keys(reissue_sequence: str) -> None: if not bracketed_paste: # Was it a pressed key event that we received? key_events = list(sequence_to_key_events(sequence)) - for event in key_events: - on_token(event) + for key_event in key_events: + on_token(key_event) if key_events: break # Or a mouse event? From 4f6e5c819c4cd623e9f6ebd227ca2fb82b94004f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:53:48 +0000 Subject: [PATCH 13/68] Ensure ScalarAnimation only receives widgets. Two other alternatives would be: - leave typing of 'widget: DOMNode' and then assert 'widget' is actually of type 'Widget', which works just fine but looks weird in the sense that we type a parameter in one way but then only manage to do any work if it is of another type; - type it as 'widget: DOMNode | Widget' and set 'size' to something other than 'widget.outer_size' if 'widget' is a 'DOMNode'. --- mypy.txt | 17 ++++++++--------- src/textual/css/scalar_animation.py | 4 ++-- src/textual/css/styles.py | 3 +++ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/mypy.txt b/mypy.txt index 9ddae6c172..c9cd81676f 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,15 +1,14 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/css/scalar_animation.py:38: error: "DOMNode" has no attribute "outer_size" [attr-defined] src/textual/css/_style_properties.py:546: error: Argument 1 to "len" has incompatible type "Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]"; expected "Sized" [arg-type] -src/textual/css/styles.py:584: error: Incompatible types in assignment (expression has type "Dict[_KT, _VT]", variable has type "RulesMap") [assignment] -src/textual/css/styles.py:651: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] -src/textual/css/styles.py:651: note: This violates the Liskov substitution principle -src/textual/css/styles.py:651: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides -src/textual/css/styles.py:1061: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] -src/textual/css/styles.py:1061: note: This violates the Liskov substitution principle -src/textual/css/styles.py:1061: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides +src/textual/css/styles.py:587: error: Incompatible types in assignment (expression has type "Dict[_KT, _VT]", variable has type "RulesMap") [assignment] +src/textual/css/styles.py:654: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] +src/textual/css/styles.py:654: note: This violates the Liskov substitution principle +src/textual/css/styles.py:654: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides +src/textual/css/styles.py:1064: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] +src/textual/css/styles.py:1064: note: This violates the Liskov substitution principle +src/textual/css/styles.py:1064: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides src/textual/css/parse.py:278: error: Item "None" of "Optional[Token]" has no attribute "name" [union-attr] src/textual/css/parse.py:279: error: Incompatible types in "yield" (actual type "Optional[Token]", expected type "Token") [misc] src/textual/dom.py:272: error: Need type annotation for "keys" (hint: "keys: Dict[, ] = ...") [var-annotated] @@ -73,4 +72,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 69 errors in 19 files (checked 152 source files) +Found 68 errors in 18 files (checked 152 source files) diff --git a/src/textual/css/scalar_animation.py b/src/textual/css/scalar_animation.py index 935be134ac..cf541d298e 100644 --- a/src/textual/css/scalar_animation.py +++ b/src/textual/css/scalar_animation.py @@ -7,14 +7,14 @@ from .scalar import Scalar, ScalarOffset if TYPE_CHECKING: - from ..dom import DOMNode + from ..widget import Widget from .styles import StylesBase class ScalarAnimation(Animation): def __init__( self, - widget: DOMNode, + widget: Widget, styles: StylesBase, start_time: float, attribute: str, diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index b23bad3681..c797dfc15e 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -335,6 +335,9 @@ def __textual_animation__( if not isinstance(value, (Scalar, ScalarOffset)): return None + from ..widget import Widget + + assert isinstance(self.node, Widget) return ScalarAnimation( self.node, self, From d7f27c005341aa5cd88d76a4991208e2a5352172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:08:11 +0000 Subject: [PATCH 14/68] Count spacing values as 1 for int instance. Adding an 'assert not isinstance(spacing, int)' before raising the error sounds reasonable, because 'Spacing.unpack' seems to be able to handle a lone integer just fine, but I decided against it because I would not like to raise an assertion error from within an exception handling inside Textual. So, to keep it on the safer side, I went with the conditional expression that checks if we have an integer or a tuple. --- mypy.txt | 3 +-- src/textual/css/_style_properties.py | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index c9cd81676f..0c98258138 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,7 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/css/_style_properties.py:546: error: Argument 1 to "len" has incompatible type "Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]"; expected "Sized" [arg-type] src/textual/css/styles.py:587: error: Incompatible types in assignment (expression has type "Dict[_KT, _VT]", variable has type "RulesMap") [assignment] src/textual/css/styles.py:654: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] src/textual/css/styles.py:654: note: This violates the Liskov substitution principle @@ -72,4 +71,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 68 errors in 18 files (checked 152 source files) +Found 67 errors in 17 files (checked 152 source files) diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index f892ac4b9e..60389c3cae 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -528,8 +528,8 @@ def __set__(self, obj: StylesBase, spacing: SpacingDimensions | None): string (e.g. ``"blue on #f0f0f0"``). Raises: - ValueError: When the value is malformed, e.g. a ``tuple`` with a length that is - not 1, 2, or 4. + ValueError: When the value is malformed, + e.g. a ``tuple`` with a length that is not 1, 2, or 4. """ _rich_traceback_omit = True if spacing is None: @@ -543,7 +543,9 @@ def __set__(self, obj: StylesBase, spacing: SpacingDimensions | None): str(error), help_text=spacing_wrong_number_of_values_help_text( property_name=self.name, - num_values_supplied=len(spacing), + num_values_supplied=( + 1 if isinstance(spacing, int) else len(spacing) + ), context="inline", ), ) From 4d6090704f0999776ab6a1c8df8dcee8c11b4771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:31:18 +0000 Subject: [PATCH 15/68] Use correct default. The obvious fix would be to do 'default_factory=RulesMap' but mypy will infer that the type of 'RulesMap' is 'type[RulesMap]' and will not see it as a 'Callable[[], RulesMap]'. That could be fixed by using 'cast'. I decided to use 'RulesMap.__call__'. [skip ci] --- mypy.txt | 3 +-- src/textual/css/styles.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mypy.txt b/mypy.txt index 0c98258138..ac2676a5fc 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,7 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/css/styles.py:587: error: Incompatible types in assignment (expression has type "Dict[_KT, _VT]", variable has type "RulesMap") [assignment] src/textual/css/styles.py:654: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] src/textual/css/styles.py:654: note: This violates the Liskov substitution principle src/textual/css/styles.py:654: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides @@ -71,4 +70,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 67 errors in 17 files (checked 152 source files) +Found 66 errors in 17 files (checked 152 source files) diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index c797dfc15e..17752b5b82 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -584,7 +584,7 @@ def partial_rich_style(self) -> Style: @dataclass class Styles(StylesBase): node: DOMNode | None = None - _rules: RulesMap = field(default_factory=dict) + _rules: RulesMap = field(default_factory=RulesMap.__call__) _updates: int = 0 important: set[str] = field(default_factory=set) From 83356ed5e56b46affe147ce5c7255d1a664309e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:27:44 +0000 Subject: [PATCH 16/68] Use correct abstract methods. We fix the LSP violation by using the abstract methods that the ABC already provides... Which is a shame, because I thought I was about to write my first Protocol. --- mypy.txt | 8 +------- src/textual/css/styles.py | 6 +++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/mypy.txt b/mypy.txt index ac2676a5fc..ad71bed855 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,12 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/css/styles.py:654: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] -src/textual/css/styles.py:654: note: This violates the Liskov substitution principle -src/textual/css/styles.py:654: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides -src/textual/css/styles.py:1064: error: Argument 1 of "merge" is incompatible with supertype "StylesBase"; supertype defines the argument type as "StylesBase" [override] -src/textual/css/styles.py:1064: note: This violates the Liskov substitution principle -src/textual/css/styles.py:1064: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides src/textual/css/parse.py:278: error: Item "None" of "Optional[Token]" has no attribute "name" [union-attr] src/textual/css/parse.py:279: error: Incompatible types in "yield" (actual type "Optional[Token]", expected type "Token") [misc] src/textual/dom.py:272: error: Need type annotation for "keys" (hint: "keys: Dict[, ] = ...") [var-annotated] @@ -70,4 +64,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 66 errors in 17 files (checked 152 source files) +Found 64 errors in 16 files (checked 152 source files) diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 17752b5b82..5d0593afd7 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -651,14 +651,14 @@ def reset(self) -> None: self._updates += 1 self._rules.clear() # type: ignore - def merge(self, other: Styles) -> None: + def merge(self, other: StylesBase) -> None: """Merge values from another Styles. Args: other: A Styles object. """ self._updates += 1 - self._rules.update(other._rules) + self._rules.update(other.get_rules()) def merge_rules(self, rules: RulesMap) -> None: self._updates += 1 @@ -1061,7 +1061,7 @@ def __rich_repr__(self) -> rich.repr.Result: def refresh(self, *, layout: bool = False, children: bool = False) -> None: self._inline_styles.refresh(layout=layout, children=children) - def merge(self, other: Styles) -> None: + def merge(self, other: StylesBase) -> None: """Merge values from another Styles. Args: From e297c8c92de4f7dbb57b0a784ea0436c8a7eeead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:42:14 +0000 Subject: [PATCH 17/68] Add missing annotation. --- mypy.txt | 3 +-- src/textual/dom.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mypy.txt b/mypy.txt index ad71bed855..e88567b388 100644 --- a/mypy.txt +++ b/mypy.txt @@ -3,7 +3,6 @@ src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Opt src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] src/textual/css/parse.py:278: error: Item "None" of "Optional[Token]" has no attribute "name" [union-attr] src/textual/css/parse.py:279: error: Incompatible types in "yield" (actual type "Optional[Token]", expected type "Token") [misc] -src/textual/dom.py:272: error: Need type annotation for "keys" (hint: "keys: Dict[, ] = ...") [var-annotated] src/textual/dom.py:353: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "DOMNode") [assignment] src/textual/dom.py:794: error: Incompatible return value type (got "DOMQuery[]", expected "Union[DOMQuery[Widget], DOMQuery[ExpectType]]") [return-value] src/textual/widget.py:433: error: "DOMNode" has no attribute "get_child_by_id" [attr-defined] @@ -64,4 +63,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 64 errors in 16 files (checked 152 source files) +Found 63 errors in 16 files (checked 152 source files) diff --git a/src/textual/dom.py b/src/textual/dom.py index 77fc6c76d6..53436ef57a 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -24,7 +24,7 @@ from ._context import NoActiveAppError from ._node_list import NodeList from ._types import CallbackType -from .binding import Bindings, BindingType +from .binding import Binding, Bindings, BindingType from .color import BLACK, WHITE, Color from .css._error_tools import friendly_list from .css.constants import VALID_DISPLAY, VALID_VISIBILITY @@ -269,7 +269,7 @@ def _merge_bindings(cls) -> Bindings: base.__dict__.get("BINDINGS", []), ) ) - keys = {} + keys: dict[str, Binding] = {} for bindings_ in bindings: keys.update(bindings_.keys) return Bindings(keys.values()) From 0c52f704216232fff31dccd3ca12081e37dc9c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:43:19 +0000 Subject: [PATCH 18/68] Fix type inference. --- mypy.txt | 3 +-- src/textual/dom.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mypy.txt b/mypy.txt index e88567b388..34d35cedf3 100644 --- a/mypy.txt +++ b/mypy.txt @@ -3,7 +3,6 @@ src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Opt src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] src/textual/css/parse.py:278: error: Item "None" of "Optional[Token]" has no attribute "name" [union-attr] src/textual/css/parse.py:279: error: Incompatible types in "yield" (actual type "Optional[Token]", expected type "Token") [misc] -src/textual/dom.py:353: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "DOMNode") [assignment] src/textual/dom.py:794: error: Incompatible return value type (got "DOMQuery[]", expected "Union[DOMQuery[Widget], DOMQuery[ExpectType]]") [return-value] src/textual/widget.py:433: error: "DOMNode" has no attribute "get_child_by_id" [attr-defined] src/textual/widget.py:706: error: Argument 1 to "_to_widget" has incompatible type "Union[int, Widget, None]"; expected "Union[int, Widget]" [arg-type] @@ -63,4 +62,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 63 errors in 16 files (checked 152 source files) +Found 62 errors in 16 files (checked 152 source files) diff --git a/src/textual/dom.py b/src/textual/dom.py index 53436ef57a..172f3c683c 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -348,7 +348,7 @@ def screen(self) -> "Screen": # Note that self.screen may not be the same as self.app.screen from .screen import Screen - node = self + node: MessagePump | None = self while node is not None and not isinstance(node, Screen): node = node._parent if not isinstance(node, Screen): From 0ae463366e53403aa8cacf7559af889571f382e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:52:42 +0000 Subject: [PATCH 19/68] Check token is not None. --- mypy.txt | 4 +--- src/textual/css/parse.py | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index 34d35cedf3..ec6356dad9 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,8 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/css/parse.py:278: error: Item "None" of "Optional[Token]" has no attribute "name" [union-attr] -src/textual/css/parse.py:279: error: Incompatible types in "yield" (actual type "Optional[Token]", expected type "Token") [misc] src/textual/dom.py:794: error: Incompatible return value type (got "DOMQuery[]", expected "Union[DOMQuery[Widget], DOMQuery[ExpectType]]") [return-value] src/textual/widget.py:433: error: "DOMNode" has no attribute "get_child_by_id" [attr-defined] src/textual/widget.py:706: error: Argument 1 to "_to_widget" has incompatible type "Union[int, Widget, None]"; expected "Union[int, Widget]" [arg-type] @@ -62,4 +60,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 62 errors in 16 files (checked 152 source files) +Found 60 errors in 15 files (checked 152 source files) diff --git a/src/textual/css/parse.py b/src/textual/css/parse.py index d1c5f709b8..b4c0e0fe8d 100644 --- a/src/textual/css/parse.py +++ b/src/textual/css/parse.py @@ -274,8 +274,7 @@ def substitute_references( while True: token = next(iter_tokens, None) - # TODO: Mypy error looks legit - if token.name == "whitespace": + if token is not None and token.name == "whitespace": yield token else: break From c84a3c49dbb5dfb8ede01a913858c35c4a288dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 11:26:00 +0000 Subject: [PATCH 20/68] Revert "Check token is not None." This reverts commit 0ae463366e53403aa8cacf7559af889571f382e1. Upon closer look, this is not the correct fix. --- mypy.txt | 4 +++- src/textual/css/parse.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mypy.txt b/mypy.txt index ec6356dad9..34d35cedf3 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,6 +1,8 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] +src/textual/css/parse.py:278: error: Item "None" of "Optional[Token]" has no attribute "name" [union-attr] +src/textual/css/parse.py:279: error: Incompatible types in "yield" (actual type "Optional[Token]", expected type "Token") [misc] src/textual/dom.py:794: error: Incompatible return value type (got "DOMQuery[]", expected "Union[DOMQuery[Widget], DOMQuery[ExpectType]]") [return-value] src/textual/widget.py:433: error: "DOMNode" has no attribute "get_child_by_id" [attr-defined] src/textual/widget.py:706: error: Argument 1 to "_to_widget" has incompatible type "Union[int, Widget, None]"; expected "Union[int, Widget]" [arg-type] @@ -60,4 +62,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 60 errors in 15 files (checked 152 source files) +Found 62 errors in 16 files (checked 152 source files) diff --git a/src/textual/css/parse.py b/src/textual/css/parse.py index b4c0e0fe8d..d1c5f709b8 100644 --- a/src/textual/css/parse.py +++ b/src/textual/css/parse.py @@ -274,7 +274,8 @@ def substitute_references( while True: token = next(iter_tokens, None) - if token is not None and token.name == "whitespace": + # TODO: Mypy error looks legit + if token.name == "whitespace": yield token else: break From 4cea32df949d473f42e35340a48787397ab2f90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 14:03:57 +0000 Subject: [PATCH 21/68] Check that token is not None. Checking if the token is not 'None' brings us a tiny step closer to fixing #1849, which still needs to ensure the variable definition is complete and valid, even if empty. --- mypy.txt | 4 +--- src/textual/css/parse.py | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/mypy.txt b/mypy.txt index 34d35cedf3..ec6356dad9 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,8 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/css/parse.py:278: error: Item "None" of "Optional[Token]" has no attribute "name" [union-attr] -src/textual/css/parse.py:279: error: Incompatible types in "yield" (actual type "Optional[Token]", expected type "Token") [misc] src/textual/dom.py:794: error: Incompatible return value type (got "DOMQuery[]", expected "Union[DOMQuery[Widget], DOMQuery[ExpectType]]") [return-value] src/textual/widget.py:433: error: "DOMNode" has no attribute "get_child_by_id" [attr-defined] src/textual/widget.py:706: error: Argument 1 to "_to_widget" has incompatible type "Union[int, Widget, None]"; expected "Union[int, Widget]" [arg-type] @@ -62,4 +60,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 62 errors in 16 files (checked 152 source files) +Found 60 errors in 15 files (checked 152 source files) diff --git a/src/textual/css/parse.py b/src/textual/css/parse.py index d1c5f709b8..92dd6eacff 100644 --- a/src/textual/css/parse.py +++ b/src/textual/css/parse.py @@ -264,7 +264,7 @@ def substitute_references( iter_tokens = iter(tokens) - while tokens: + while True: token = next(iter_tokens, None) if token is None: break @@ -274,8 +274,7 @@ def substitute_references( while True: token = next(iter_tokens, None) - # TODO: Mypy error looks legit - if token.name == "whitespace": + if token is not None and token.name == "whitespace": yield token else: break From b5fe26c4f76c239df30929e901044780a6db9f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:14:02 +0000 Subject: [PATCH 22/68] Type DOMQuery instantiation correctly. After some fiddling, some crying, and talking to Dave and Will, we got to a partial solution. I cried a bit more and came up with the fix that entailed lifting 'ExpectType' to outside of 'DOMNode'. Then, I realised 'ExpectType' and 'QueryType' from 'query.py' were essentially the same thing so I decided to only make use of 'QueryType'. --- mypy.txt | 3 +-- src/textual/dom.py | 30 +++++++++++++----------------- src/textual/screen.py | 12 ++++-------- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/mypy.txt b/mypy.txt index ec6356dad9..9feecc59f3 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,7 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/dom.py:794: error: Incompatible return value type (got "DOMQuery[]", expected "Union[DOMQuery[Widget], DOMQuery[ExpectType]]") [return-value] src/textual/widget.py:433: error: "DOMNode" has no attribute "get_child_by_id" [attr-defined] src/textual/widget.py:706: error: Argument 1 to "_to_widget" has incompatible type "Union[int, Widget, None]"; expected "Union[int, Widget]" [arg-type] src/textual/widget.py:2098: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] @@ -60,4 +59,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 60 errors in 15 files (checked 152 source files) +Found 59 errors in 14 files (checked 152 source files) diff --git a/src/textual/dom.py b/src/textual/dom.py index 172f3c683c..ed0604f153 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -39,7 +39,7 @@ if TYPE_CHECKING: from .app import App - from .css.query import DOMQuery + from .css.query import DOMQuery, QueryType from .screen import Screen from .widget import Widget from typing_extensions import TypeAlias @@ -762,19 +762,17 @@ def walk_children( nodes.reverse() return cast("list[DOMNode]", nodes) - ExpectType = TypeVar("ExpectType", bound="Widget") - @overload def query(self, selector: str | None) -> DOMQuery[Widget]: ... @overload - def query(self, selector: type[ExpectType]) -> DOMQuery[ExpectType]: + def query(self, selector: type[QueryType]) -> DOMQuery[QueryType]: ... def query( - self, selector: str | type[ExpectType] | None = None - ) -> DOMQuery[Widget] | DOMQuery[ExpectType]: + self, selector: str | type[QueryType] | None = None + ) -> DOMQuery[Widget] | DOMQuery[QueryType]: """Get a DOM query matching a selector. Args: @@ -783,33 +781,31 @@ def query( Returns: A query object. """ - from .css.query import DOMQuery + from .css.query import DOMQuery, QueryType + from .widget import Widget - query: str | None if isinstance(selector, str) or selector is None: - query = selector + return DOMQuery[Widget](self, filter=selector) else: - query = selector.__name__ - - return DOMQuery(self, filter=query) + return DOMQuery[QueryType](self, filter=selector.__name__) @overload def query_one(self, selector: str) -> Widget: ... @overload - def query_one(self, selector: type[ExpectType]) -> ExpectType: + def query_one(self, selector: type[QueryType]) -> QueryType: ... @overload - def query_one(self, selector: str, expect_type: type[ExpectType]) -> ExpectType: + def query_one(self, selector: str, expect_type: type[QueryType]) -> QueryType: ... def query_one( self, - selector: str | type[ExpectType], - expect_type: type[ExpectType] | None = None, - ) -> ExpectType | Widget: + selector: str | type[QueryType], + expect_type: type[QueryType] | None = None, + ) -> QueryType | Widget: """Get a single Widget matching the given selector or selector type. Args: diff --git a/src/textual/screen.py b/src/textual/screen.py index d1354a106f..b7687c7441 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -12,7 +12,7 @@ from ._types import CallbackType from .css.match import match from .css.parse import parse_selectors -from .dom import DOMNode +from .css.query import QueryType from .geometry import Offset, Region, Size from .reactive import Reactive from .renderables.blank import Blank @@ -183,7 +183,7 @@ def focus_chain(self) -> list[Widget]: return widgets def _move_focus( - self, direction: int = 0, selector: str | type[DOMNode.ExpectType] = "*" + self, direction: int = 0, selector: str | type[QueryType] = "*" ) -> Widget | None: """Move the focus in the given direction. @@ -244,9 +244,7 @@ def _move_focus( return self.focused - def focus_next( - self, selector: str | type[DOMNode.ExpectType] = "*" - ) -> Widget | None: + def focus_next(self, selector: str | type[QueryType] = "*") -> Widget | None: """Focus the next widget, optionally filtered by a CSS selector. If no widget is currently focused, this will focus the first focusable widget. @@ -263,9 +261,7 @@ def focus_next( """ return self._move_focus(1, selector) - def focus_previous( - self, selector: str | type[DOMNode.ExpectType] = "*" - ) -> Widget | None: + def focus_previous(self, selector: str | type[QueryType] = "*") -> Widget | None: """Focus the previous widget, optionally filtered by a CSS selector. If no widget is currently focused, this will focus the first focusable widget. From 53d49f4f7e255c4ee7e2c548442a213b18cac92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:47:58 +0000 Subject: [PATCH 23/68] Infer correct type while walking. --- mypy.txt | 11 +++++------ src/textual/widget.py | 14 +++++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/mypy.txt b/mypy.txt index 9feecc59f3..218764f4eb 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,11 +1,10 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/widget.py:433: error: "DOMNode" has no attribute "get_child_by_id" [attr-defined] -src/textual/widget.py:706: error: Argument 1 to "_to_widget" has incompatible type "Union[int, Widget, None]"; expected "Union[int, Widget]" [arg-type] -src/textual/widget.py:2098: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] -src/textual/widget.py:2122: error: Argument 1 to "_Styled" has incompatible type "Union[ConsoleRenderable, RichCast]"; expected "ConsoleRenderable" [arg-type] -src/textual/widget.py:2328: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] +src/textual/widget.py:710: error: Argument 1 to "_to_widget" has incompatible type "Union[int, Widget, None]"; expected "Union[int, Widget]" [arg-type] +src/textual/widget.py:2102: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] +src/textual/widget.py:2126: error: Argument 1 to "_Styled" has incompatible type "Union[ConsoleRenderable, RichCast]"; expected "ConsoleRenderable" [arg-type] +src/textual/widget.py:2332: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] src/textual/scrollbar.py:249: error: Item "None" of "Optional[DOMNode]" has no attribute "styles" [union-attr] src/textual/scrollbar.py:260: error: Invalid self argument "Color" to attribute function "rich_color" with type "Callable[[textual.color.Color], rich.color.Color]" [misc] src/textual/scrollbar.py:282: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_down" incompatible with return type "None" in supertype "Widget" [override] @@ -59,4 +58,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 59 errors in 14 files (checked 152 source files) +Found 58 errors in 14 files (checked 152 source files) diff --git a/src/textual/widget.py b/src/textual/widget.py index 0842aff932..a1fecc5c78 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -414,23 +414,27 @@ 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: The ID to search for in the subtree + id: The ID to search for in the subtree. expect_type: Require the object be of the supplied type, or None for any type. - Defaults to None. Returns: The first descendant encountered with this ID. Raises: - NoMatches: if no children could be found for this ID + NoMatches: if no children could be found for this ID. WrongType: if the wrong type was found. """ - for child in walk_depth_first(self): + # We use Widget as a filter_type so that the inferred type of child is Widget. + for child in walk_depth_first(self, filter_type=Widget): try: - return child.get_child_by_id(id, expect_type=expect_type) + if expect_type is None: + return child.get_child_by_id(id) + else: + return child.get_child_by_id(id, expect_type=expect_type) except NoMatches: pass except WrongType as exc: From 870e71bacf10858a2ca2997ac0194124b37ca201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:59:20 +0000 Subject: [PATCH 24/68] Cast to remove doubt about None. mypy can't infer that if after is None, then before won't. --- mypy.txt | 9 ++++----- src/textual/widget.py | 4 +++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/mypy.txt b/mypy.txt index 218764f4eb..501f2a0f56 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,10 +1,9 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/widget.py:710: error: Argument 1 to "_to_widget" has incompatible type "Union[int, Widget, None]"; expected "Union[int, Widget]" [arg-type] -src/textual/widget.py:2102: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] -src/textual/widget.py:2126: error: Argument 1 to "_Styled" has incompatible type "Union[ConsoleRenderable, RichCast]"; expected "ConsoleRenderable" [arg-type] -src/textual/widget.py:2332: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] +src/textual/widget.py:2104: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] +src/textual/widget.py:2128: error: Argument 1 to "_Styled" has incompatible type "Union[ConsoleRenderable, RichCast]"; expected "ConsoleRenderable" [arg-type] +src/textual/widget.py:2334: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] src/textual/scrollbar.py:249: error: Item "None" of "Optional[DOMNode]" has no attribute "styles" [union-attr] src/textual/scrollbar.py:260: error: Invalid self argument "Color" to attribute function "rich_color" with type "Callable[[textual.color.Color], rich.color.Color]" [misc] src/textual/scrollbar.py:282: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_down" incompatible with return type "None" in supertype "Widget" [override] @@ -58,4 +57,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 58 errors in 14 files (checked 152 source files) +Found 57 errors in 14 files (checked 152 source files) diff --git a/src/textual/widget.py b/src/textual/widget.py index a1fecc5c78..141356dae2 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -707,7 +707,9 @@ def _to_widget(child: int | Widget, called: str) -> Widget: # Ensure the child and target are widgets. child = _to_widget(child, "move") - target = _to_widget(before if after is None else after, "move towards") + target = _to_widget( + cast("int | Widget", before if after is None else after), "move towards" + ) # At this point we should know what we're moving, and it should be a # child; where we're moving it to, which should be within the child From 54f92268339e21669cbfcf826c072e7e3ae13cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:01:44 +0000 Subject: [PATCH 25/68] Explicitly type variable. --- mypy.txt | 7 +++---- src/textual/widget.py | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index 501f2a0f56..985211d9b8 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,9 +1,8 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/widget.py:2104: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] -src/textual/widget.py:2128: error: Argument 1 to "_Styled" has incompatible type "Union[ConsoleRenderable, RichCast]"; expected "ConsoleRenderable" [arg-type] -src/textual/widget.py:2334: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] +src/textual/widget.py:2129: error: Argument 1 to "_Styled" has incompatible type "Union[ConsoleRenderable, RichCast]"; expected "ConsoleRenderable" [arg-type] +src/textual/widget.py:2335: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] src/textual/scrollbar.py:249: error: Item "None" of "Optional[DOMNode]" has no attribute "styles" [union-attr] src/textual/scrollbar.py:260: error: Invalid self argument "Color" to attribute function "rich_color" with type "Callable[[textual.color.Color], rich.color.Color]" [misc] src/textual/scrollbar.py:282: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_down" incompatible with return type "None" in supertype "Widget" [override] @@ -57,4 +56,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 57 errors in 14 files (checked 152 source files) +Found 56 errors in 14 files (checked 152 source files) diff --git a/src/textual/widget.py b/src/textual/widget.py index 141356dae2..940f6a6178 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -61,6 +61,7 @@ if TYPE_CHECKING: from .app import App, ComposeResult + from .message_pump import MessagePump from .scrollbar import ( ScrollBar, ScrollBarCorner, @@ -2096,7 +2097,7 @@ def get_pseudo_classes(self) -> Iterable[str]: pass else: if focused: - node = focused + node: MessagePump | None = focused while node is not None: if node is self: yield "focus-within" From 8ae1665bfbfb3ecf8a6cde61e06b0fc4bb634a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:18:23 +0000 Subject: [PATCH 26/68] Cast to console renderable. @willmcgugan did I understand correctly that this 'cast' is exactly what renderable being possibly a 'RichCast' asks me to do..? To be honest, I was not 100% sure after reading rich's documentation for 'RichCast' and after reading the source of 'rich_cast'. --- mypy.txt | 5 ++--- src/textual/widget.py | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy.txt b/mypy.txt index 985211d9b8..c4864c3dfa 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,8 +1,7 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/widget.py:2129: error: Argument 1 to "_Styled" has incompatible type "Union[ConsoleRenderable, RichCast]"; expected "ConsoleRenderable" [arg-type] -src/textual/widget.py:2335: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] +src/textual/widget.py:2337: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] src/textual/scrollbar.py:249: error: Item "None" of "Optional[DOMNode]" has no attribute "styles" [union-attr] src/textual/scrollbar.py:260: error: Invalid self argument "Color" to attribute function "rich_color" with type "Callable[[textual.color.Color], rich.color.Color]" [misc] src/textual/scrollbar.py:282: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_down" incompatible with return type "None" in supertype "Widget" [override] @@ -56,4 +55,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 56 errors in 14 files (checked 152 source files) +Found 55 errors in 14 files (checked 152 source files) diff --git a/src/textual/widget.py b/src/textual/widget.py index 940f6a6178..42378624bc 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -2126,7 +2126,9 @@ def post_render(self, renderable: RenderableType) -> ConsoleRenderable: renderable.justify = text_justify renderable = _Styled( - renderable, self.rich_style, self.link_style if self.auto_links else None + cast(ConsoleRenderable, renderable), + self.rich_style, + self.link_style if self.auto_links else None, ) return renderable From a1a059b31d0602517cdcf62cc74f9418a48c1b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:20:25 +0000 Subject: [PATCH 27/68] Type variable to remove literal ambiguity. mypy was inferring the type of the empty string as a literal and thus was not type checking the fact that 'render' would either be a string or Text. --- mypy.txt | 3 +-- src/textual/widget.py | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mypy.txt b/mypy.txt index c4864c3dfa..512a7e3bb2 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,7 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/widget.py:2337: error: Incompatible return value type (got "object", expected "Union[ConsoleRenderable, RichCast, str]") [return-value] src/textual/scrollbar.py:249: error: Item "None" of "Optional[DOMNode]" has no attribute "styles" [union-attr] src/textual/scrollbar.py:260: error: Invalid self argument "Color" to attribute function "rich_color" with type "Callable[[textual.color.Color], rich.color.Color]" [misc] src/textual/scrollbar.py:282: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_down" incompatible with return type "None" in supertype "Widget" [override] @@ -55,4 +54,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 55 errors in 14 files (checked 152 source files) +Found 54 errors in 13 files (checked 152 source files) diff --git a/src/textual/widget.py b/src/textual/widget.py index 42378624bc..4639266841 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -2318,7 +2318,7 @@ def refresh( self.check_idle() def remove(self) -> AwaitRemove: - """Remove the Widget from the DOM (effectively deleting it) + """Remove the Widget from the DOM (effectively deleting it). Returns: An awaitable object that waits for the widget to be removed. @@ -2331,16 +2331,16 @@ def render(self) -> RenderableType: """Get renderable for widget. Returns: - Any renderable + Any renderable. """ - render = "" if self.is_container else self.css_identifier_styled + render: Text | str = "" if self.is_container else self.css_identifier_styled return render def _render(self) -> ConsoleRenderable | RichCast: """Get renderable, promoting str to text as required. Returns: - A renderable + A renderable. """ renderable = self.render() if isinstance(renderable, str): From 83a94b265d2c6cc235172719256a61ebdea79911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:22:17 +0000 Subject: [PATCH 28/68] Assert scrollbars always have parents. --- mypy.txt | 8 +++----- src/textual/scrollbar.py | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index 512a7e3bb2..cc5ff71bd1 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,10 +1,8 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/scrollbar.py:249: error: Item "None" of "Optional[DOMNode]" has no attribute "styles" [union-attr] -src/textual/scrollbar.py:260: error: Invalid self argument "Color" to attribute function "rich_color" with type "Callable[[textual.color.Color], rich.color.Color]" [misc] -src/textual/scrollbar.py:282: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_down" incompatible with return type "None" in supertype "Widget" [override] -src/textual/scrollbar.py:287: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_up" incompatible with return type "None" in supertype "Widget" [override] +src/textual/scrollbar.py:283: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_down" incompatible with return type "None" in supertype "Widget" [override] +src/textual/scrollbar.py:288: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_up" incompatible with return type "None" in supertype "Widget" [override] src/textual/css/stylesheet.py:81: error: Item "None" of "Optional[Path]" has no attribute "absolute" [union-attr] src/textual/app.py:303: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] src/textual/app.py:1566: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] @@ -54,4 +52,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 54 errors in 13 files (checked 152 source files) +Found 52 errors in 13 files (checked 152 source files) diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index 12e1fd65d3..e3a31715fc 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -246,6 +246,7 @@ def __rich_repr__(self) -> rich.repr.Result: yield "thickness", self.thickness def render(self) -> RenderableType: + assert self.parent is not None styles = self.parent.styles if self.grabbed: background = styles.scrollbar_background_active From 9d9c8cc635ef8e7cab234a49679d2b67aea36821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:27:41 +0000 Subject: [PATCH 29/68] Make scrollbar scroll actions synchronous. The scrollbars were posting the messages and awaiting for them but that's not what Widget does, which handles scrolling synchronously. Thus, I made them synchronous methods by using 'post_message_no_wait'. --- mypy.txt | 4 +--- src/textual/scrollbar.py | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/mypy.txt b/mypy.txt index cc5ff71bd1..c265c37970 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,8 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/scrollbar.py:283: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_down" incompatible with return type "None" in supertype "Widget" [override] -src/textual/scrollbar.py:288: error: Return type "Coroutine[Any, Any, None]" of "action_scroll_up" incompatible with return type "None" in supertype "Widget" [override] src/textual/css/stylesheet.py:81: error: Item "None" of "Optional[Path]" has no attribute "absolute" [union-attr] src/textual/app.py:303: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] src/textual/app.py:1566: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] @@ -52,4 +50,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 52 errors in 13 files (checked 152 source files) +Found 50 errors in 12 files (checked 152 source files) diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index e3a31715fc..48b36927df 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -280,13 +280,13 @@ def _on_enter(self, event: events.Enter) -> None: def _on_leave(self, event: events.Leave) -> None: self.mouse_over = False - async def action_scroll_down(self) -> None: - await self.post_message( + def action_scroll_down(self) -> None: + self.post_message_no_wait( ScrollDown(self) if self.vertical else ScrollRight(self) ) - async def action_scroll_up(self) -> None: - await self.post_message(ScrollUp(self) if self.vertical else ScrollLeft(self)) + def action_scroll_up(self) -> None: + self.post_message_no_wait(ScrollUp(self) if self.vertical else ScrollLeft(self)) def action_grab(self) -> None: self.capture_mouse() From 5ca0763daea003211a1a784e5738b5df4b0d7343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:31:03 +0000 Subject: [PATCH 30/68] Use link only when available. --- mypy.txt | 3 +-- src/textual/css/stylesheet.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mypy.txt b/mypy.txt index c265c37970..86415a688f 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,7 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/css/stylesheet.py:81: error: Item "None" of "Optional[Path]" has no attribute "absolute" [union-attr] src/textual/app.py:303: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] src/textual/app.py:1566: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] src/textual/app.py:1571: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] @@ -50,4 +49,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 50 errors in 12 files (checked 152 source files) +Found 49 errors in 11 files (checked 152 source files) diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 975326202a..139a692ad1 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -78,7 +78,7 @@ def __rich_console__( f"{path.absolute() if path else filename}:{line_no}:{col_no}" ) link_style = Style( - link=f"file://{path.absolute()}", + link=f"file://{path.absolute()}" if path else None, color="red", bold=True, italic=True, From 6fbf2833a11b608978093a8aab129f852e93910a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:09:41 +0000 Subject: [PATCH 31/68] Update errors. --- mypy.txt | 80 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/mypy.txt b/mypy.txt index 86415a688f..94122cd231 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,52 +1,54 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/app.py:303: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] -src/textual/app.py:1566: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] -src/textual/app.py:1571: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] -src/textual/app.py:1572: error: Value of type variable "_T_io" of "redirect_stdout" cannot be "_NullFile" [type-var] -src/textual/app.py:1702: error: Incompatible types in assignment (expression has type "reversed[Widget]", variable has type "List[Widget]") [assignment] -src/textual/app.py:1770: error: Incompatible types in assignment (expression has type "Union[Screen, Callable[[], Screen]]", variable has type "Screen") [assignment] -src/textual/app.py:2032: error: Argument 1 to "action" of "App" has incompatible type "Union[str, Tuple[Any, ...]]"; expected "Union[str, Tuple[str, Tuple[str, ...]]]" [arg-type] -src/textual/app.py:2309: error: Cannot find implementation or library stub for module named "uvloop" [import] -src/textual/app.py:2309: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -src/textual/widgets/_placeholder.py:122: error: Return type "Iterable[Union[Union[ConsoleRenderable, RichCast, str], Segment]]" of "render" incompatible with return type "Union[ConsoleRenderable, RichCast, str]" in supertype "Widget" [override] +src/textual/widget.py:2139: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] +src/textual/widget.py:2152: error: Name "node" already defined on line 2134 [no-redef] +src/textual/widget.py:2157: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] +src/textual/app.py:315: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] +src/textual/app.py:1607: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] +src/textual/app.py:1612: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] +src/textual/app.py:1613: error: Value of type variable "_T_io" of "redirect_stdout" cannot be "_NullFile" [type-var] +src/textual/app.py:1744: error: Incompatible types in assignment (expression has type "reversed[Widget]", variable has type "List[Widget]") [assignment] +src/textual/app.py:1812: error: Incompatible types in assignment (expression has type "Union[Screen, Callable[[], Screen]]", variable has type "Screen") [assignment] +src/textual/app.py:2075: error: Argument 1 to "action" of "App" has incompatible type "Union[str, Tuple[Any, ...]]"; expected "Union[str, Tuple[str, Tuple[str, ...]]]" [arg-type] +src/textual/app.py:2352: error: Cannot find implementation or library stub for module named "uvloop" [import] +src/textual/app.py:2352: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/_doc.py:18: error: Missing return statement [return] src/textual/_doc.py:85: error: Argument 1 to "open" has incompatible type "Union[PurePath, str, None]"; expected "Union[int, Union[str, bytes, PathLike[str], PathLike[bytes]]]" [arg-type] src/textual/_doc.py:115: error: Argument 1 to "write_text" of "Path" has incompatible type "Optional[Any]"; expected "str" [arg-type] -src/textual/widgets/_list_view.py:90: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") [assignment] -src/textual/widgets/_list_view.py:93: error: Missing return statement [return] -src/textual/widgets/_list_view.py:98: error: Incompatible return value type (got "Widget", expected "Optional[ListItem]") [return-value] -src/textual/widgets/_list_view.py:128: error: "Widget" has no attribute "highlighted" [attr-defined] -src/textual/widgets/_list_view.py:131: error: "Widget" has no attribute "highlighted" [attr-defined] -src/textual/widgets/_list_view.py:136: error: Argument 2 to "Highlighted" has incompatible type "Optional[Widget]"; expected "Optional[ListItem]" [arg-type] -src/textual/widgets/_list_view.py:161: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] -src/textual/widgets/_list_view.py:167: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] -src/textual/widgets/_list_view.py:180: error: Argument 2 to "Selected" has incompatible type "MessageTarget"; expected "ListItem" [arg-type] -src/textual/widgets/_data_table.py:688: error: Unsupported operand types for in ("Union[RowKey, str]" and "TwoWayDict[RowKey, int]") [operator] -src/textual/widgets/_data_table.py:690: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:727: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "TwoWayDict[ColumnKey, int]") [operator] -src/textual/widgets/_data_table.py:733: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:1409: error: Unsupported operand types for in ("Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" and "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]") [operator] -src/textual/widgets/_data_table.py:1410: error: Invalid index type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" for "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]"; expected type "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [index] -src/textual/widgets/_data_table.py:1453: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1455: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1481: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_list_view.py:94: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") [assignment] +src/textual/widgets/_list_view.py:97: error: Missing return statement [return] +src/textual/widgets/_list_view.py:102: error: Incompatible return value type (got "Widget", expected "Optional[ListItem]") [return-value] +src/textual/widgets/_list_view.py:132: error: "Widget" has no attribute "highlighted" [attr-defined] +src/textual/widgets/_list_view.py:135: error: "Widget" has no attribute "highlighted" [attr-defined] +src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has incompatible type "Optional[Widget]"; expected "Optional[ListItem]" [arg-type] +src/textual/widgets/_list_view.py:165: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] +src/textual/widgets/_list_view.py:171: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] +src/textual/widgets/_list_view.py:184: error: Argument 2 to "Selected" has incompatible type "MessageTarget"; expected "ListItem" [arg-type] +src/textual/widgets/_data_table.py:689: error: Unsupported operand types for in ("Union[RowKey, str]" and "TwoWayDict[RowKey, int]") [operator] +src/textual/widgets/_data_table.py:691: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:728: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "TwoWayDict[ColumnKey, int]") [operator] +src/textual/widgets/_data_table.py:734: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1410: error: Unsupported operand types for in ("Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" and "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]") [operator] +src/textual/widgets/_data_table.py:1411: error: Invalid index type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" for "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]"; expected type "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [index] +src/textual/widgets/_data_table.py:1454: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1456: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] src/textual/widgets/_data_table.py:1482: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1487: error: Argument 1 has incompatible type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]"; expected "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1543: error: Unsupported operand types for in ("Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" and "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]") [operator] -src/textual/widgets/_data_table.py:1544: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] -src/textual/widgets/_data_table.py:1563: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1628: error: Argument "key" to "sorted" has incompatible type "Callable[[Tuple[RowKey, Dict[Union[ColumnKey, str], CellType]]], Any]"; expected "Callable[[Tuple[RowKey, Dict[ColumnKey, CellType]]], Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]]" [arg-type] -src/textual/widgets/_button.py:241: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -src/textual/widgets/_button.py:241: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -src/textual/widgets/_button.py:241: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] +src/textual/widgets/_data_table.py:1483: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1488: error: Argument 1 has incompatible type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]"; expected "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1544: error: Unsupported operand types for in ("Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" and "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]") [operator] +src/textual/widgets/_data_table.py:1545: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] +src/textual/widgets/_data_table.py:1564: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1629: error: Argument "key" to "sorted" has incompatible type "Callable[[Tuple[RowKey, Dict[Union[ColumnKey, str], CellType]]], Any]"; expected "Callable[[Tuple[RowKey, Dict[ColumnKey, CellType]]], Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]]" [arg-type] +src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] +src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] +src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/demo.py:206: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[bool], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] -src/textual/cli/previews/easing.py:102: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] -src/textual/cli/previews/easing.py:111: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 49 errors in 11 files (checked 152 source files) +src/textual/cli/previews/easing.py:100: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] +src/textual/cli/previews/easing.py:109: error: "MessageTarget" has no attribute "id" [attr-defined] +Found 51 errors in 11 files (checked 154 source files) From 79c274994a8fff6d3a2e94180c7e272e1381fac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:11:40 +0000 Subject: [PATCH 32/68] Relax type inference. --- mypy.txt | 5 +---- src/textual/widget.py | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/mypy.txt b/mypy.txt index 94122cd231..878f45dfb6 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,9 +1,6 @@ poetry run mypy src/textual src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] -src/textual/widget.py:2139: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] -src/textual/widget.py:2152: error: Name "node" already defined on line 2134 [no-redef] -src/textual/widget.py:2157: error: Incompatible types in assignment (expression has type "Optional[MessagePump]", variable has type "Widget") [assignment] src/textual/app.py:315: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] src/textual/app.py:1607: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] src/textual/app.py:1612: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] @@ -51,4 +48,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:100: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:109: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 51 errors in 11 files (checked 154 source files) +Found 48 errors in 10 files (checked 154 source files) diff --git a/src/textual/widget.py b/src/textual/widget.py index 6091ea375b..35ff6103d7 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -2131,7 +2131,7 @@ def get_pseudo_classes(self) -> Iterable[str]: Names of the pseudo classes. """ - node = self + node: MessagePump | None = self while isinstance(node, Widget): if node.disabled: yield "disabled" @@ -2149,7 +2149,7 @@ def get_pseudo_classes(self) -> Iterable[str]: pass else: if focused: - node: MessagePump | None = focused + node = focused while node is not None: if node is self: yield "focus-within" From 232b70c6f7e645736926eb6b6f391066ac255442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:15:25 +0000 Subject: [PATCH 33/68] Ignore missing imports for uvloop. 'uvloop' is completely optional and this code only exists to cater for the case where the user _already_ has uvloop installed. With that in mind, it makes sense to silence errors about uvloop not being available because we don't need to know about that. We only care if uvloop happens to be installed. --- mypy.ini | 4 ++++ mypy.txt | 4 +--- src/textual/app.py | 11 +++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mypy.ini b/mypy.ini index c61bcf0ace..37236b0c3d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -14,3 +14,7 @@ ignore_missing_imports = True [mypy-ipywidgets.*] ignore_missing_imports = True + +[mypy-uvloop.*] +# Ignore missing imports for optional library that isn't listed as a dependency. +ignore_missing_imports = True diff --git a/mypy.txt b/mypy.txt index 878f45dfb6..3d5526e293 100644 --- a/mypy.txt +++ b/mypy.txt @@ -8,8 +8,6 @@ src/textual/app.py:1613: error: Value of type variable "_T_io" of "redirect_stdo src/textual/app.py:1744: error: Incompatible types in assignment (expression has type "reversed[Widget]", variable has type "List[Widget]") [assignment] src/textual/app.py:1812: error: Incompatible types in assignment (expression has type "Union[Screen, Callable[[], Screen]]", variable has type "Screen") [assignment] src/textual/app.py:2075: error: Argument 1 to "action" of "App" has incompatible type "Union[str, Tuple[Any, ...]]"; expected "Union[str, Tuple[str, Tuple[str, ...]]]" [arg-type] -src/textual/app.py:2352: error: Cannot find implementation or library stub for module named "uvloop" [import] -src/textual/app.py:2352: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] @@ -48,4 +46,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:100: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:109: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 48 errors in 10 files (checked 154 source files) +Found 47 errors in 10 files (checked 154 source files) diff --git a/src/textual/app.py b/src/textual/app.py index 5a7e065357..81cc443b18 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -2339,9 +2339,12 @@ def _end_update(self) -> None: def _init_uvloop() -> None: - """ - Import and install the `uvloop` asyncio policy, if available. + """Import and install the `uvloop` asyncio policy, if available. + This is done only once, even if the function is called multiple times. + + This is provided as a nicety for users that have `uvloop` installed independently + of Textual, as `uvloop` is not listed as a Textual dependency. """ global _uvloop_init_done @@ -2349,10 +2352,10 @@ def _init_uvloop() -> None: return try: - import uvloop + import uvloop # type: ignore[reportMissingImports] except ImportError: pass else: - uvloop.install() + uvloop.install() # type: ignore[reportUnknownMemberType] _uvloop_init_done = True From 0f48929f009c9041433f5c256ebbd325da25d14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:37:21 +0000 Subject: [PATCH 34/68] Enable variable reuse. --- mypy.txt | 5 ++--- src/textual/app.py | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy.txt b/mypy.txt index 3d5526e293..41fcd1d390 100644 --- a/mypy.txt +++ b/mypy.txt @@ -6,8 +6,7 @@ src/textual/app.py:1607: error: Value of type variable "_T_io" of "redirect_stde src/textual/app.py:1612: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] src/textual/app.py:1613: error: Value of type variable "_T_io" of "redirect_stdout" cannot be "_NullFile" [type-var] src/textual/app.py:1744: error: Incompatible types in assignment (expression has type "reversed[Widget]", variable has type "List[Widget]") [assignment] -src/textual/app.py:1812: error: Incompatible types in assignment (expression has type "Union[Screen, Callable[[], Screen]]", variable has type "Screen") [assignment] -src/textual/app.py:2075: error: Argument 1 to "action" of "App" has incompatible type "Union[str, Tuple[Any, ...]]"; expected "Union[str, Tuple[str, Tuple[str, ...]]]" [arg-type] +src/textual/app.py:2076: error: Argument 1 to "action" of "App" has incompatible type "Union[str, Tuple[Any, ...]]"; expected "Union[str, Tuple[str, Tuple[str, ...]]]" [arg-type] src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] @@ -46,4 +45,4 @@ src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-de src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/cli/previews/easing.py:100: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] src/textual/cli/previews/easing.py:109: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 47 errors in 10 files (checked 154 source files) +Found 46 errors in 10 files (checked 154 source files) diff --git a/src/textual/app.py b/src/textual/app.py index 81cc443b18..6252232142 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1801,6 +1801,7 @@ def is_mounted(self, widget: Widget) -> bool: async def _close_all(self) -> None: """Close all message pumps.""" + screen: Screen | Callable[[], Screen] # Explicit type to reuse variable below. # Close all screens on the stack for screen in reversed(self._screen_stack): if screen._running: From 759b04a1dfcec38c8b3feeacf21c9c6ee1a77093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 11:53:13 +0000 Subject: [PATCH 35/68] Fix type issues in easing.py. --- mypy.txt | 4 +--- src/textual/cli/previews/easing.py | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mypy.txt b/mypy.txt index 41fcd1d390..d2f7bace63 100644 --- a/mypy.txt +++ b/mypy.txt @@ -43,6 +43,4 @@ src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderabl src/textual/demo.py:206: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[bool], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-defined] src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] -src/textual/cli/previews/easing.py:100: error: Argument "easing" to "animate" of "App" has incompatible type "Optional[str]"; expected "Union[Callable[[float], float], str]" [arg-type] -src/textual/cli/previews/easing.py:109: error: "MessageTarget" has no attribute "id" [attr-defined] -Found 46 errors in 10 files (checked 154 source files) +Found 44 errors in 9 files (checked 154 source files) diff --git a/src/textual/cli/previews/easing.py b/src/textual/cli/previews/easing.py index 204cd62463..70dc45758a 100644 --- a/src/textual/cli/previews/easing.py +++ b/src/textual/cli/previews/easing.py @@ -92,6 +92,7 @@ def _animation_complete(): target_position = ( END_POSITION if self.position == START_POSITION else START_POSITION ) + assert event.button.id is not None # Should be set to an easing function str. self.animate( "position", value=target_position, @@ -106,7 +107,7 @@ def watch_position(self, value: int): self.opacity_widget.styles.opacity = 1 - value / END_POSITION def on_input_changed(self, event: Input.Changed): - if event.sender.id == "duration-input": + if event.input.id == "duration-input": new_duration = _try_float(event.value) if new_duration is not None: self.duration = new_duration From f36678392046ae6ffcf1e3e5f8354e91ec5f50f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:24:52 +0000 Subject: [PATCH 36/68] Fix typing issues in demo. Fixing the typing issues here involved making use of the messaging features to replace a method (wasn't exactly wrong, but this is more in line with the Textual way of doing things, which should be prevalent in the demo of the framework). We also dealt with a typing issue in DarkSwitch.on_dark_change by deleting an unnecessary parameter, but the underlying problem is unsolved and was logged as issue #1865. Related issues: #1865. --- mypy.txt | 5 +- src/textual/demo.css | 522 +++++++++++++++++++++---------------------- src/textual/demo.py | 27 ++- 3 files changed, 280 insertions(+), 274 deletions(-) diff --git a/mypy.txt b/mypy.txt index d2f7bace63..82decd25ef 100644 --- a/mypy.txt +++ b/mypy.txt @@ -40,7 +40,4 @@ src/textual/widgets/_data_table.py:1629: error: Argument "key" to "sorted" has i src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -src/textual/demo.py:206: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[bool], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] -src/textual/demo.py:221: error: "App[Any]" has no attribute "add_note" [attr-defined] -src/textual/demo.py:276: error: "App[Any]" has no attribute "add_note" [attr-defined] -Found 44 errors in 9 files (checked 154 source files) +Found 41 errors in 8 files (checked 154 source files) diff --git a/src/textual/demo.css b/src/textual/demo.css index 3fb8c7d719..083be4e288 100644 --- a/src/textual/demo.css +++ b/src/textual/demo.css @@ -1,263 +1,263 @@ - * { - transition: background 500ms in_out_cubic, color 500ms in_out_cubic; -} - -Screen { - layers: base overlay notes notifications; - overflow: hidden; -} - - -Notification { - dock: bottom; - layer: notification; - width: auto; - margin: 2 4; - padding: 1 2; - background: $background; - color: $text; - height: auto; - -} - -Sidebar { - width: 40; - background: $panel; - transition: offset 500ms in_out_cubic; - layer: overlay; - -} - -Sidebar:focus-within { - offset: 0 0 !important; -} - -Sidebar.-hidden { - offset-x: -100%; -} - -Sidebar Title { - background: $boost; - color: $secondary; - padding: 2 4; - border-right: vkey $background; - dock: top; - text-align: center; - text-style: bold; -} - - -OptionGroup { - background: $boost; - color: $text; - height: 1fr; - border-right: vkey $background; -} - -Option { - margin: 1 0 0 1; - height: 3; - padding: 1 2; - background: $boost; - border: tall $panel; - text-align: center; -} - -Option:hover { - background: $primary 20%; - color: $text; -} - -Body { - height: 100%; - overflow-y: scroll; - width: 100%; - background: $surface; - -} - -AboveFold { - width: 100%; - height: 100%; - align: center middle; -} - -Welcome { - background: $boost; - height: auto; - max-width: 100; - min-width: 40; - border: wide $primary; - padding: 1 2; - margin: 1 2; - box-sizing: border-box; -} - -Welcome Button { - width: 100%; - margin-top: 1; -} - -Column { - height: auto; - min-height: 100vh; - align: center top; -} - - -DarkSwitch { - background: $panel; - padding: 1; - dock: bottom; - height: auto; - border-right: vkey $background; -} - -DarkSwitch .label { - width: 1fr; - padding: 1 2; - color: $text-muted; -} - -DarkSwitch Switch { - background: $boost; - dock: left; -} - - -Screen > Container { - height: 100%; - overflow: hidden; -} - -TextLog { - background: $surface; - color: $text; - height: 50vh; - dock: bottom; - layer: notes; - border-top: hkey $primary; - offset-y: 0; - transition: offset 400ms in_out_cubic; - padding: 0 1 1 1; -} - - -TextLog:focus { - offset: 0 0 !important; -} - -TextLog.-hidden { - offset-y: 100%; -} - - - -Section { - height: auto; - min-width: 40; - margin: 1 2 4 2; - -} - -SectionTitle { - padding: 1 2; - background: $boost; - text-align: center; - text-style: bold; -} - -SubTitle { - padding-top: 1; - border-bottom: heavy $panel; - color: $text; - text-style: bold; -} - -TextContent { - margin: 1 0; -} - -QuickAccess { - width: 30; - dock: left; - -} - -LocationLink { - margin: 1 0 0 1; - height: 1; - padding: 1 2; - background: $boost; - color: $text; - box-sizing: content-box; - content-align: center middle; -} - -LocationLink:hover { - background: $accent; - color: $text; - text-style: bold; -} - - -.pad { - margin: 1 0; -} - -DataTable { - height: 16; -} - - -LoginForm { - height: auto; - margin: 1 0; - padding: 1 2; - layout: grid; - grid-size: 2; - grid-rows: 4; - grid-columns: 12 1fr; - background: $boost; - border: wide $background; -} - -LoginForm Button{ - margin: 0 1; - width: 100%; -} - -LoginForm .label { - padding: 1 2; - text-align: right; -} - -Message { - margin: 0 1; - -} - - -Tree { - margin: 1 0; -} + * { + transition: background 500ms in_out_cubic, color 500ms in_out_cubic; + } + + Screen { + layers: base overlay notes notifications; + overflow: hidden; + } + + + Notification { + dock: bottom; + layer: notification; + width: auto; + margin: 2 4; + padding: 1 2; + background: $background; + color: $text; + height: auto; + + } + + Sidebar { + width: 40; + background: $panel; + transition: offset 500ms in_out_cubic; + layer: overlay; + + } + + Sidebar:focus-within { + offset: 0 0 !important; + } + + Sidebar.-hidden { + offset-x: -100%; + } + + Sidebar Title { + background: $boost; + color: $secondary; + padding: 2 4; + border-right: vkey $background; + dock: top; + text-align: center; + text-style: bold; + } + + + OptionGroup { + background: $boost; + color: $text; + height: 1fr; + border-right: vkey $background; + } + + Option { + margin: 1 0 0 1; + height: 3; + padding: 1 2; + background: $boost; + border: tall $panel; + text-align: center; + } + + Option:hover { + background: $primary 20%; + color: $text; + } + + Body { + height: 100%; + overflow-y: scroll; + width: 100%; + background: $surface; + + } + + AboveFold { + width: 100%; + height: 100%; + align: center middle; + } + + Welcome { + background: $boost; + height: auto; + max-width: 100; + min-width: 40; + border: wide $primary; + padding: 1 2; + margin: 1 2; + box-sizing: border-box; + } + + Welcome Button { + width: 100%; + margin-top: 1; + } + + Column { + height: auto; + min-height: 100vh; + align: center top; + } + + + DarkSwitch { + background: $panel; + padding: 1; + dock: bottom; + height: auto; + border-right: vkey $background; + } + + DarkSwitch .label { + width: 1fr; + padding: 1 2; + color: $text-muted; + } + + DarkSwitch Switch { + background: $boost; + dock: left; + } + + + Screen>Container { + height: 100%; + overflow: hidden; + } + + TextLog { + background: $surface; + color: $text; + height: 50vh; + dock: bottom; + layer: notes; + border-top: hkey $primary; + offset-y: 0; + transition: offset 400ms in_out_cubic; + padding: 0 1 1 1; + } + + + TextLog:focus { + offset: 0 0 !important; + } + + TextLog.-hidden { + offset-y: 100%; + } + + + + Section { + height: auto; + min-width: 40; + margin: 1 2 4 2; + + } + + SectionTitle { + padding: 1 2; + background: $boost; + text-align: center; + text-style: bold; + } + + SubTitle { + padding-top: 1; + border-bottom: heavy $panel; + color: $text; + text-style: bold; + } + + TextContent { + margin: 1 0; + } + + QuickAccess { + width: 30; + dock: left; + + } + + LocationLink { + margin: 1 0 0 1; + height: 1; + padding: 1 2; + background: $boost; + color: $text; + box-sizing: content-box; + content-align: center middle; + } + + LocationLink:hover { + background: $accent; + color: $text; + text-style: bold; + } + + + .pad { + margin: 1 0; + } + + DataTable { + height: 16; + } + + + LoginForm { + height: auto; + margin: 1 0; + padding: 1 2; + layout: grid; + grid-size: 2; + grid-rows: 4; + grid-columns: 12 1fr; + background: $boost; + border: wide $background; + } + + LoginForm Button { + margin: 0 1; + width: 100%; + } + + LoginForm .label { + padding: 1 2; + text-align: right; + } + + SidebarMessage { + margin: 0 1; + + } + + + Tree { + margin: 1 0; + } -Window { - background: $boost; - overflow: auto; - height: auto; - max-height: 16; -} - -Window > Static { - width: auto; -} - - -Version { - color: $text-disabled; - dock: bottom; - text-align: center; - padding: 1; -} + Window { + background: $boost; + overflow: auto; + height: auto; + max-height: 16; + } + + Window>Static { + width: auto; + } + + + Version { + color: $text-disabled; + dock: bottom; + text-align: center; + padding: 1; + } diff --git a/src/textual/demo.py b/src/textual/demo.py index c80c662396..7d6e7c29fe 100644 --- a/src/textual/demo.py +++ b/src/textual/demo.py @@ -15,6 +15,7 @@ from textual.app import App, ComposeResult from textual.binding import Binding from textual.containers import Container, Horizontal +from textual.message import Message, MessageTarget from textual.reactive import reactive from textual.widgets import ( Button, @@ -189,6 +190,14 @@ """ +class Note(Message): + """Sent by some demo widgets with noteworthy information.""" + + def __init__(self, sender: MessageTarget, note: str | Text) -> None: + super().__init__(sender) + self.note = note + + class Body(Container): pass @@ -205,7 +214,7 @@ def compose(self) -> ComposeResult: def on_mount(self) -> None: self.watch(self.app, "dark", self.on_dark_change, init=False) - def on_dark_change(self, dark: bool) -> None: + def on_dark_change(self) -> None: self.query_one(Switch).value = self.app.dark def on_switch_changed(self, event: Switch.Changed) -> None: @@ -218,7 +227,7 @@ def compose(self) -> ComposeResult: yield Button("Start", variant="success") def on_button_pressed(self, event: Button.Pressed) -> None: - self.app.add_note("[b magenta]Start!") + self.post_message_no_wait(Note(self, "[b magenta]Start!")) self.app.query_one(".location-first").scroll_visible(duration=0.5, top=True) @@ -230,7 +239,7 @@ class SectionTitle(Static): pass -class Message(Static): +class SidebarMessage(Static): pass @@ -242,7 +251,7 @@ def render(self) -> RenderableType: class Sidebar(Container): def compose(self) -> ComposeResult: yield Title("Textual Demo") - yield OptionGroup(Message(MESSAGE), Version()) + yield OptionGroup(SidebarMessage(MESSAGE), Version()) yield DarkSwitch() @@ -273,7 +282,7 @@ def __init__(self, label: str, reveal: str) -> None: def on_click(self) -> None: self.app.query_one(self.reveal).scroll_visible(top=True, duration=0.5) - self.app.add_note(f"Scrolling to [b]{self.reveal}[/b]") + self.post_message_no_wait(Note(self, f"Scrolling to [b]{self.reveal}[/b]")) class LoginForm(Container): @@ -315,8 +324,8 @@ class DemoApp(App): show_sidebar = reactive(False) - def add_note(self, renderable: RenderableType) -> None: - self.query_one(TextLog).write(renderable) + def on_note(self, event: Note) -> None: + self.query_one(TextLog).write(event.note) def compose(self) -> ComposeResult: example_css = "\n".join(Path(self.css_path[0]).read_text().splitlines()[:50]) @@ -393,7 +402,7 @@ def action_toggle_sidebar(self) -> None: sidebar.add_class("-hidden") def on_mount(self) -> None: - self.add_note("Textual Demo app is running") + self.post_message_no_wait(Note(self, "Textual Demo app is running")) table = self.query_one(DataTable) table.add_column("Foo", width=20) table.add_column("Bar", width=20) @@ -416,7 +425,7 @@ def action_screenshot(self, filename: str | None = None, path: str = "./") -> No self.bell() path = self.save_screenshot(filename, path) message = Text.assemble("Screenshot saved to ", (f"'{path}'", "bold green")) - self.add_note(message) + self.post_message_no_wait(Note(self, message)) self.screen.mount(Notification(message)) From 8b91a82aead6ab8ac81345186379ca707e5309dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 14:02:34 +0000 Subject: [PATCH 37/68] Fix typing issues in _doc.py. --- mypy.txt | 5 +---- src/textual/_doc.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/mypy.txt b/mypy.txt index 82decd25ef..eabacb7355 100644 --- a/mypy.txt +++ b/mypy.txt @@ -10,9 +10,6 @@ src/textual/app.py:2076: error: Argument 1 to "action" of "App" has incompatible src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] -src/textual/_doc.py:18: error: Missing return statement [return] -src/textual/_doc.py:85: error: Argument 1 to "open" has incompatible type "Union[PurePath, str, None]"; expected "Union[int, Union[str, bytes, PathLike[str], PathLike[bytes]]]" [arg-type] -src/textual/_doc.py:115: error: Argument 1 to "write_text" of "Path" has incompatible type "Optional[Any]"; expected "str" [arg-type] src/textual/widgets/_list_view.py:94: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") [assignment] src/textual/widgets/_list_view.py:97: error: Missing return statement [return] src/textual/widgets/_list_view.py:102: error: Incompatible return value type (got "Widget", expected "Optional[ListItem]") [return-value] @@ -40,4 +37,4 @@ src/textual/widgets/_data_table.py:1629: error: Argument "key" to "sorted" has i src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 41 errors in 8 files (checked 154 source files) +Found 38 errors in 7 files (checked 154 source files) diff --git a/src/textual/_doc.py b/src/textual/_doc.py index 3b4c9d08b5..391c85d128 100644 --- a/src/textual/_doc.py +++ b/src/textual/_doc.py @@ -4,7 +4,7 @@ import os import shlex from pathlib import Path -from typing import Iterable +from typing import Iterable, cast from textual._import_app import import_app from textual.app import App @@ -45,6 +45,7 @@ def format_svg(source, language, css_class, options, md, attrs, **kwargs) -> str import traceback traceback.print_exception(error) + return "" def take_svg_screenshot( @@ -82,6 +83,7 @@ def get_cache_key(app: App) -> str: hash = hashlib.md5() file_paths = [app_path] + app.css_path for path in file_paths: + assert path is not None with open(path, "rb") as source_file: hash.update(source_file.read()) hash.update(f"{press}-{title}-{terminal_size}".encode("utf-8")) @@ -105,10 +107,13 @@ async def auto_pilot(pilot: Pilot) -> None: app.exit(svg) - svg = app.run( - headless=True, - auto_pilot=auto_pilot, - size=terminal_size, + svg = cast( + str, + app.run( + headless=True, + auto_pilot=auto_pilot, + size=terminal_size, + ), ) if app_path is not None: From 12a88fa192814136c76cfa65a299b582e1145e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 14:44:46 +0000 Subject: [PATCH 38/68] Type actions with a type alias. --- mypy.txt | 3 +-- src/textual/actions.py | 6 +++++- src/textual/app.py | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mypy.txt b/mypy.txt index eabacb7355..dea4d304ff 100644 --- a/mypy.txt +++ b/mypy.txt @@ -6,7 +6,6 @@ src/textual/app.py:1607: error: Value of type variable "_T_io" of "redirect_stde src/textual/app.py:1612: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] src/textual/app.py:1613: error: Value of type variable "_T_io" of "redirect_stdout" cannot be "_NullFile" [type-var] src/textual/app.py:1744: error: Incompatible types in assignment (expression has type "reversed[Widget]", variable has type "List[Widget]") [assignment] -src/textual/app.py:2076: error: Argument 1 to "action" of "App" has incompatible type "Union[str, Tuple[Any, ...]]"; expected "Union[str, Tuple[str, Tuple[str, ...]]]" [arg-type] src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] @@ -37,4 +36,4 @@ src/textual/widgets/_data_table.py:1629: error: Argument "key" to "sorted" has i src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 38 errors in 7 files (checked 154 source files) +Found 37 errors in 7 files (checked 154 source files) diff --git a/src/textual/actions.py b/src/textual/actions.py index dcb5f16e99..8a863582d7 100644 --- a/src/textual/actions.py +++ b/src/textual/actions.py @@ -2,6 +2,10 @@ import ast import re +from typing import Any, TypeAlias + +Action: TypeAlias = tuple[str, tuple[Any, ...]] +"""An action is its name and the arbitrary tuple of its parameters.""" class SkipAction(Exception): @@ -15,7 +19,7 @@ class ActionError(Exception): re_action_params = re.compile(r"([\w\.]+)(\(.*?\))") -def parse(action: str) -> tuple[str, tuple[object, ...]]: +def parse(action: str) -> Action: """Parses an action string. Args: diff --git a/src/textual/app.py b/src/textual/app.py index 6252232142..e1c2b82caa 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -57,7 +57,7 @@ from ._event_broker import NoHandler, extract_handler_actions from ._path import _make_path_object_relative from ._wait import wait_for_idle -from .actions import SkipAction +from .actions import Action, SkipAction from .await_remove import AwaitRemove from .binding import Binding, Bindings from .css.query import NoMatches @@ -1975,7 +1975,7 @@ async def on_event(self, event: events.Event) -> None: async def action( self, - action: str | tuple[str, tuple[str, ...]], + action: str | Action, default_namespace: object | None = None, ) -> bool: """Perform an action. @@ -2073,7 +2073,7 @@ async def _broker_event( else: event.stop() if isinstance(action, (str, tuple)): - await self.action(action, default_namespace=default_namespace) + await self.action(action, default_namespace=default_namespace) # type: ignore[arg-type] elif callable(action): await action() else: From e7c0367b31066710604b14bbb84af1f23dcd26d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 14:46:01 +0000 Subject: [PATCH 39/68] Make return values optional. As per discussion with @darrenburns. --- mypy.txt | 27 ++++++++++++++++++++++++--- src/textual/_two_way_dict.py | 4 ++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index dea4d304ff..c7c8e1adc6 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,6 +1,4 @@ poetry run mypy src/textual -src/textual/_two_way_dict.py:41: error: Incompatible return value type (got "Optional[Value]", expected "Value") [return-value] -src/textual/_two_way_dict.py:52: error: Incompatible return value type (got "Optional[Key]", expected "Key") [return-value] src/textual/app.py:315: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] src/textual/app.py:1607: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] src/textual/app.py:1612: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] @@ -20,20 +18,43 @@ src/textual/widgets/_list_view.py:171: error: Argument 2 to "Selected" has incom src/textual/widgets/_list_view.py:184: error: Argument 2 to "Selected" has incompatible type "MessageTarget"; expected "ListItem" [arg-type] src/textual/widgets/_data_table.py:689: error: Unsupported operand types for in ("Union[RowKey, str]" and "TwoWayDict[RowKey, int]") [operator] src/textual/widgets/_data_table.py:691: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:714: error: Argument 1 to "get_row" of "DataTable" has incompatible type "Optional[RowKey]"; expected "Union[RowKey, str]" [arg-type] src/textual/widgets/_data_table.py:728: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "TwoWayDict[ColumnKey, int]") [operator] src/textual/widgets/_data_table.py:734: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:752: error: Argument 1 to "get_column" of "DataTable" has incompatible type "Optional[ColumnKey]"; expected "Union[ColumnKey, str]" [arg-type] +src/textual/widgets/_data_table.py:859: error: Argument 1 to "CellKey" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:859: error: Argument 2 to "CellKey" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] +src/textual/widgets/_data_table.py:868: error: Argument 3 to "RowHighlighted" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:877: error: Argument 3 to "ColumnHighlighted" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] +src/textual/widgets/_data_table.py:972: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] +src/textual/widgets/_data_table.py:977: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:992: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] +src/textual/widgets/_data_table.py:1008: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1270: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1286: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] +src/textual/widgets/_data_table.py:1358: error: Incompatible types in assignment (expression has type "Optional[RowKey]", variable has type "RowKey") [assignment] +src/textual/widgets/_data_table.py:1362: error: Unsupported operand types for in ("Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" and "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]") [operator] +src/textual/widgets/_data_table.py:1371: error: Argument 1 has incompatible type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]"; expected "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1372: error: Invalid index type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" for "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]"; expected type "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [index] src/textual/widgets/_data_table.py:1410: error: Unsupported operand types for in ("Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" and "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]") [operator] src/textual/widgets/_data_table.py:1411: error: Invalid index type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" for "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]"; expected type "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [index] +src/textual/widgets/_data_table.py:1447: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1449: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] src/textual/widgets/_data_table.py:1454: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] src/textual/widgets/_data_table.py:1456: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1468: error: Unsupported operand types for % ("None" and "int") [operator] +src/textual/widgets/_data_table.py:1468: note: Left operand is of type "Optional[int]" +src/textual/widgets/_data_table.py:1476: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1478: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] src/textual/widgets/_data_table.py:1482: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] src/textual/widgets/_data_table.py:1483: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] src/textual/widgets/_data_table.py:1488: error: Argument 1 has incompatible type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]"; expected "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [arg-type] src/textual/widgets/_data_table.py:1544: error: Unsupported operand types for in ("Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" and "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]") [operator] src/textual/widgets/_data_table.py:1545: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] src/textual/widgets/_data_table.py:1564: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1572: error: List comprehension has incompatible type List[Optional[RowKey]]; expected List[RowKey] [misc] src/textual/widgets/_data_table.py:1629: error: Argument "key" to "sorted" has incompatible type "Callable[[Tuple[RowKey, Dict[Union[ColumnKey, str], CellType]]], Any]"; expected "Callable[[Tuple[RowKey, Dict[ColumnKey, CellType]]], Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]]" [arg-type] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 37 errors in 7 files (checked 154 source files) +Found 57 errors in 6 files (checked 154 source files) diff --git a/src/textual/_two_way_dict.py b/src/textual/_two_way_dict.py index d733edcdcc..910ff1b56b 100644 --- a/src/textual/_two_way_dict.py +++ b/src/textual/_two_way_dict.py @@ -29,7 +29,7 @@ def __delitem__(self, key: Key) -> None: self._forward.__delitem__(key) self._reverse.__delitem__(value) - def get(self, key: Key) -> Value: + def get(self, key: Key) -> Value | None: """Given a key, efficiently lookup and return the associated value. Args: @@ -40,7 +40,7 @@ def get(self, key: Key) -> Value: """ return self._forward.get(key) - def get_key(self, value: Value) -> Key: + def get_key(self, value: Value) -> Key | None: """Given a value, efficiently lookup and return the associated key. Args: From 208ddd125b66de8ed3e6c54d5ab4b7334d56ca98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:17:04 +0000 Subject: [PATCH 40/68] Make StringKey idempotent. This set up is so that we can easily make sure that a variable of the type str | SK (where SK is a subclass of StringKey) becomes of the type SK. Instead of having to write a conditional expression to only convert in case of a string, we make SK idempotent so that we can just call it on the value that may still be a string. --- mypy.txt | 76 +++++++++++++++--------------- src/textual/widgets/_data_table.py | 20 ++++++-- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/mypy.txt b/mypy.txt index c7c8e1adc6..80cb594aa7 100644 --- a/mypy.txt +++ b/mypy.txt @@ -16,44 +16,44 @@ src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has in src/textual/widgets/_list_view.py:165: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] src/textual/widgets/_list_view.py:171: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] src/textual/widgets/_list_view.py:184: error: Argument 2 to "Selected" has incompatible type "MessageTarget"; expected "ListItem" [arg-type] -src/textual/widgets/_data_table.py:689: error: Unsupported operand types for in ("Union[RowKey, str]" and "TwoWayDict[RowKey, int]") [operator] -src/textual/widgets/_data_table.py:691: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:714: error: Argument 1 to "get_row" of "DataTable" has incompatible type "Optional[RowKey]"; expected "Union[RowKey, str]" [arg-type] -src/textual/widgets/_data_table.py:728: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "TwoWayDict[ColumnKey, int]") [operator] -src/textual/widgets/_data_table.py:734: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:752: error: Argument 1 to "get_column" of "DataTable" has incompatible type "Optional[ColumnKey]"; expected "Union[ColumnKey, str]" [arg-type] -src/textual/widgets/_data_table.py:859: error: Argument 1 to "CellKey" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:859: error: Argument 2 to "CellKey" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] -src/textual/widgets/_data_table.py:868: error: Argument 3 to "RowHighlighted" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:877: error: Argument 3 to "ColumnHighlighted" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] -src/textual/widgets/_data_table.py:972: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] -src/textual/widgets/_data_table.py:977: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:992: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] -src/textual/widgets/_data_table.py:1008: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:1270: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:1286: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] -src/textual/widgets/_data_table.py:1358: error: Incompatible types in assignment (expression has type "Optional[RowKey]", variable has type "RowKey") [assignment] -src/textual/widgets/_data_table.py:1362: error: Unsupported operand types for in ("Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" and "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]") [operator] -src/textual/widgets/_data_table.py:1371: error: Argument 1 has incompatible type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]"; expected "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1372: error: Invalid index type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" for "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]"; expected type "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [index] -src/textual/widgets/_data_table.py:1410: error: Unsupported operand types for in ("Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" and "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]") [operator] -src/textual/widgets/_data_table.py:1411: error: Invalid index type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" for "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]"; expected type "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [index] -src/textual/widgets/_data_table.py:1447: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1449: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1454: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1456: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1468: error: Unsupported operand types for % ("None" and "int") [operator] -src/textual/widgets/_data_table.py:1468: note: Left operand is of type "Optional[int]" -src/textual/widgets/_data_table.py:1476: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1478: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1482: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1483: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1488: error: Argument 1 has incompatible type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]"; expected "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1544: error: Unsupported operand types for in ("Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" and "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]") [operator] -src/textual/widgets/_data_table.py:1545: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] -src/textual/widgets/_data_table.py:1564: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1572: error: List comprehension has incompatible type List[Optional[RowKey]]; expected List[RowKey] [misc] -src/textual/widgets/_data_table.py:1629: error: Argument "key" to "sorted" has incompatible type "Callable[[Tuple[RowKey, Dict[Union[ColumnKey, str], CellType]]], Any]"; expected "Callable[[Tuple[RowKey, Dict[ColumnKey, CellType]]], Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]]" [arg-type] +src/textual/widgets/_data_table.py:703: error: Unsupported operand types for in ("Union[RowKey, str]" and "TwoWayDict[RowKey, int]") [operator] +src/textual/widgets/_data_table.py:705: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:728: error: Argument 1 to "get_row" of "DataTable" has incompatible type "Optional[RowKey]"; expected "Union[RowKey, str]" [arg-type] +src/textual/widgets/_data_table.py:742: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "TwoWayDict[ColumnKey, int]") [operator] +src/textual/widgets/_data_table.py:748: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:766: error: Argument 1 to "get_column" of "DataTable" has incompatible type "Optional[ColumnKey]"; expected "Union[ColumnKey, str]" [arg-type] +src/textual/widgets/_data_table.py:873: error: Argument 1 to "CellKey" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:873: error: Argument 2 to "CellKey" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] +src/textual/widgets/_data_table.py:882: error: Argument 3 to "RowHighlighted" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:891: error: Argument 3 to "ColumnHighlighted" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] +src/textual/widgets/_data_table.py:986: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] +src/textual/widgets/_data_table.py:991: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1006: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] +src/textual/widgets/_data_table.py:1022: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1284: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1300: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] +src/textual/widgets/_data_table.py:1372: error: Incompatible types in assignment (expression has type "Optional[RowKey]", variable has type "RowKey") [assignment] +src/textual/widgets/_data_table.py:1376: error: Unsupported operand types for in ("Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" and "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]") [operator] +src/textual/widgets/_data_table.py:1385: error: Argument 1 has incompatible type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]"; expected "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1386: error: Invalid index type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" for "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]"; expected type "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [index] +src/textual/widgets/_data_table.py:1424: error: Unsupported operand types for in ("Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" and "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]") [operator] +src/textual/widgets/_data_table.py:1425: error: Invalid index type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" for "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]"; expected type "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [index] +src/textual/widgets/_data_table.py:1461: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1463: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1468: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1470: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1482: error: Unsupported operand types for % ("None" and "int") [operator] +src/textual/widgets/_data_table.py:1482: note: Left operand is of type "Optional[int]" +src/textual/widgets/_data_table.py:1490: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1492: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1496: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1497: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1502: error: Argument 1 has incompatible type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]"; expected "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1558: error: Unsupported operand types for in ("Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" and "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]") [operator] +src/textual/widgets/_data_table.py:1559: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] +src/textual/widgets/_data_table.py:1578: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1586: error: List comprehension has incompatible type List[Optional[RowKey]]; expected List[RowKey] [misc] +src/textual/widgets/_data_table.py:1643: error: Argument "key" to "sorted" has incompatible type "Callable[[Tuple[RowKey, Dict[Union[ColumnKey, str], CellType]]], Any]"; expected "Callable[[Tuple[RowKey, Dict[ColumnKey, CellType]]], Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]]" [arg-type] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index fe04dfa07b..8522028f8d 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -70,12 +70,26 @@ class StringKey: It can optionally wrap a string, and lookups into a map using the object behave the same as lookups using - the string itself.""" + the string itself. + + This class and subclasses are idempotent, which means that if a value + `s` is `str | StringKey`, then `StringKey(s)` will safely convert it to + a `StringKey` instance if `s` is a string and it will leave `s` unchanged + if it was already of the correct type. + """ value: str | None - def __init__(self, value: str | None = None): - self.value = value + def __new__(cls, value: StringKey | str | None = None) -> StringKey: + """Creates a new `StringKey` object if necessary.""" + if isinstance(value, cls): + return value + + string_key = super().__new__(cls) + if isinstance(value, StringKey): # This should never be true. + value = value.value + string_key.value = value + return string_key def __hash__(self): # If a string is supplied, we use the hash of the string. If no string was From 9277ed5b0089bb31d2d27f65c247afd6497c5fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:18:36 +0000 Subject: [PATCH 41/68] Make sorting type safe. --- mypy.txt | 3 +-- src/textual/widgets/_data_table.py | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/mypy.txt b/mypy.txt index 80cb594aa7..34009a1c75 100644 --- a/mypy.txt +++ b/mypy.txt @@ -53,8 +53,7 @@ src/textual/widgets/_data_table.py:1558: error: Unsupported operand types for in src/textual/widgets/_data_table.py:1559: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] src/textual/widgets/_data_table.py:1578: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] src/textual/widgets/_data_table.py:1586: error: List comprehension has incompatible type List[Optional[RowKey]]; expected List[RowKey] [misc] -src/textual/widgets/_data_table.py:1643: error: Argument "key" to "sorted" has incompatible type "Callable[[Tuple[RowKey, Dict[Union[ColumnKey, str], CellType]]], Any]"; expected "Callable[[Tuple[RowKey, Dict[ColumnKey, CellType]]], Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]]" [arg-type] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 57 errors in 6 files (checked 154 source files) +Found 56 errors in 6 files (checked 154 source files) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 8522028f8d..c8a5711fb0 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -1632,13 +1632,12 @@ def sort( reverse: If True, the sort order will be reversed. """ - def sort_by_column_keys( - row: tuple[RowKey, dict[ColumnKey | str, CellType]] - ) -> Any: + def sort_by_column_keys(row: tuple[RowKey, dict[ColumnKey, CellType]]) -> Any: _, row_data = row - result = itemgetter(*columns)(row_data) + result = itemgetter(*column_keys)(row_data) return result + column_keys = [ColumnKey(column) for column in columns] ordered_rows = sorted( self._data.items(), key=sort_by_column_keys, reverse=reverse ) From 21a098524f97dcd2ea27570a2a1dde24c092b26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:33:30 +0000 Subject: [PATCH 42/68] Fix typing of StringKey.__new__. This is needed to ensure the type checkers know the exact type of the instances returned when instantiating subclasses of StringKey. --- mypy.txt | 74 +++++++++++++++--------------- src/textual/widgets/_data_table.py | 7 ++- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/mypy.txt b/mypy.txt index 34009a1c75..4c8a3602b2 100644 --- a/mypy.txt +++ b/mypy.txt @@ -16,43 +16,43 @@ src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has in src/textual/widgets/_list_view.py:165: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] src/textual/widgets/_list_view.py:171: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] src/textual/widgets/_list_view.py:184: error: Argument 2 to "Selected" has incompatible type "MessageTarget"; expected "ListItem" [arg-type] -src/textual/widgets/_data_table.py:703: error: Unsupported operand types for in ("Union[RowKey, str]" and "TwoWayDict[RowKey, int]") [operator] -src/textual/widgets/_data_table.py:705: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:728: error: Argument 1 to "get_row" of "DataTable" has incompatible type "Optional[RowKey]"; expected "Union[RowKey, str]" [arg-type] -src/textual/widgets/_data_table.py:742: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "TwoWayDict[ColumnKey, int]") [operator] -src/textual/widgets/_data_table.py:748: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:766: error: Argument 1 to "get_column" of "DataTable" has incompatible type "Optional[ColumnKey]"; expected "Union[ColumnKey, str]" [arg-type] -src/textual/widgets/_data_table.py:873: error: Argument 1 to "CellKey" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:873: error: Argument 2 to "CellKey" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] -src/textual/widgets/_data_table.py:882: error: Argument 3 to "RowHighlighted" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:891: error: Argument 3 to "ColumnHighlighted" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] -src/textual/widgets/_data_table.py:986: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] -src/textual/widgets/_data_table.py:991: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:1006: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] -src/textual/widgets/_data_table.py:1022: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:1284: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:1300: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] -src/textual/widgets/_data_table.py:1372: error: Incompatible types in assignment (expression has type "Optional[RowKey]", variable has type "RowKey") [assignment] -src/textual/widgets/_data_table.py:1376: error: Unsupported operand types for in ("Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" and "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]") [operator] -src/textual/widgets/_data_table.py:1385: error: Argument 1 has incompatible type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]"; expected "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1386: error: Invalid index type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" for "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]"; expected type "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [index] -src/textual/widgets/_data_table.py:1424: error: Unsupported operand types for in ("Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" and "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]") [operator] -src/textual/widgets/_data_table.py:1425: error: Invalid index type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" for "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]"; expected type "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [index] -src/textual/widgets/_data_table.py:1461: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1463: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1468: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1470: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1482: error: Unsupported operand types for % ("None" and "int") [operator] -src/textual/widgets/_data_table.py:1482: note: Left operand is of type "Optional[int]" -src/textual/widgets/_data_table.py:1490: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1492: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1496: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1497: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1502: error: Argument 1 has incompatible type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]"; expected "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1558: error: Unsupported operand types for in ("Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" and "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]") [operator] -src/textual/widgets/_data_table.py:1559: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] -src/textual/widgets/_data_table.py:1578: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1586: error: List comprehension has incompatible type List[Optional[RowKey]]; expected List[RowKey] [misc] +src/textual/widgets/_data_table.py:708: error: Unsupported operand types for in ("Union[RowKey, str]" and "TwoWayDict[RowKey, int]") [operator] +src/textual/widgets/_data_table.py:710: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:733: error: Argument 1 to "get_row" of "DataTable" has incompatible type "Optional[RowKey]"; expected "Union[RowKey, str]" [arg-type] +src/textual/widgets/_data_table.py:747: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "TwoWayDict[ColumnKey, int]") [operator] +src/textual/widgets/_data_table.py:753: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:771: error: Argument 1 to "get_column" of "DataTable" has incompatible type "Optional[ColumnKey]"; expected "Union[ColumnKey, str]" [arg-type] +src/textual/widgets/_data_table.py:878: error: Argument 1 to "CellKey" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:878: error: Argument 2 to "CellKey" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] +src/textual/widgets/_data_table.py:887: error: Argument 3 to "RowHighlighted" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:896: error: Argument 3 to "ColumnHighlighted" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] +src/textual/widgets/_data_table.py:991: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] +src/textual/widgets/_data_table.py:996: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1011: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] +src/textual/widgets/_data_table.py:1027: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1289: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:1305: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] +src/textual/widgets/_data_table.py:1377: error: Incompatible types in assignment (expression has type "Optional[RowKey]", variable has type "RowKey") [assignment] +src/textual/widgets/_data_table.py:1381: error: Unsupported operand types for in ("Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" and "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]") [operator] +src/textual/widgets/_data_table.py:1390: error: Argument 1 has incompatible type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]"; expected "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1391: error: Invalid index type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" for "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]"; expected type "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [index] +src/textual/widgets/_data_table.py:1429: error: Unsupported operand types for in ("Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" and "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]") [operator] +src/textual/widgets/_data_table.py:1430: error: Invalid index type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" for "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]"; expected type "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [index] +src/textual/widgets/_data_table.py:1466: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1468: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1473: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1475: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1487: error: Unsupported operand types for % ("None" and "int") [operator] +src/textual/widgets/_data_table.py:1487: note: Left operand is of type "Optional[int]" +src/textual/widgets/_data_table.py:1495: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1497: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] +src/textual/widgets/_data_table.py:1501: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1502: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] +src/textual/widgets/_data_table.py:1507: error: Argument 1 has incompatible type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]"; expected "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1563: error: Unsupported operand types for in ("Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" and "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]") [operator] +src/textual/widgets/_data_table.py:1564: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] +src/textual/widgets/_data_table.py:1583: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] +src/textual/widgets/_data_table.py:1591: error: List comprehension has incompatible type List[Optional[RowKey]]; expected List[RowKey] [misc] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index c8a5711fb0..6b2ce18831 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -64,6 +64,9 @@ class DuplicateKey(Exception): an existing row or column in the DataTable. Keys must be unique.""" +StringKeySub = TypeVar("StringKeySub", bound="StringKey") + + @functools.total_ordering class StringKey: """An object used as a key in a mapping. @@ -80,7 +83,9 @@ class StringKey: value: str | None - def __new__(cls, value: StringKey | str | None = None) -> StringKey: + def __new__( + cls: type[StringKeySub], value: StringKey | str | None = None + ) -> StringKeySub: """Creates a new `StringKey` object if necessary.""" if isinstance(value, cls): return value From 8013641fbc425393df62a1efc80fd019b8cdcf5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 17:20:38 +0000 Subject: [PATCH 43/68] Add explicit type variables. --- mypy.txt | 12 +----------- src/textual/widgets/_data_table.py | 4 ++-- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/mypy.txt b/mypy.txt index 4c8a3602b2..49563e397a 100644 --- a/mypy.txt +++ b/mypy.txt @@ -36,24 +36,14 @@ src/textual/widgets/_data_table.py:1377: error: Incompatible types in assignment src/textual/widgets/_data_table.py:1381: error: Unsupported operand types for in ("Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" and "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]") [operator] src/textual/widgets/_data_table.py:1390: error: Argument 1 has incompatible type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]"; expected "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [arg-type] src/textual/widgets/_data_table.py:1391: error: Invalid index type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" for "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]"; expected type "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [index] -src/textual/widgets/_data_table.py:1429: error: Unsupported operand types for in ("Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" and "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]") [operator] -src/textual/widgets/_data_table.py:1430: error: Invalid index type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]" for "LRUCache[Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int], Tuple[List[List[Segment]], List[List[Segment]]]]"; expected type "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [index] src/textual/widgets/_data_table.py:1466: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] src/textual/widgets/_data_table.py:1468: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1473: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1475: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] src/textual/widgets/_data_table.py:1487: error: Unsupported operand types for % ("None" and "int") [operator] src/textual/widgets/_data_table.py:1487: note: Left operand is of type "Optional[int]" src/textual/widgets/_data_table.py:1495: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] src/textual/widgets/_data_table.py:1497: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1501: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1502: error: Argument 3 to "_should_highlight" has incompatible type "str"; expected "Literal['cell', 'row', 'column', 'none']" [arg-type] -src/textual/widgets/_data_table.py:1507: error: Argument 1 has incompatible type "Tuple[RowKey, int, Style, Coordinate, Coordinate, str, bool, bool, int]"; expected "Tuple[RowKey, int, Style, Coordinate, Coordinate, Literal['cell', 'row', 'column', 'none'], bool, bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1563: error: Unsupported operand types for in ("Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" and "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]") [operator] -src/textual/widgets/_data_table.py:1564: error: Invalid index type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]" for "LRUCache[Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int], Strip]"; expected type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [index] -src/textual/widgets/_data_table.py:1583: error: Argument 1 has incompatible type "Tuple[int, int, int, int, Coordinate, Coordinate, Style, str, bool, int]"; expected "Tuple[int, int, int, int, Coordinate, Coordinate, Style, Literal['cell', 'row', 'column', 'none'], bool, int]" [arg-type] src/textual/widgets/_data_table.py:1591: error: List comprehension has incompatible type List[Optional[RowKey]]; expected List[RowKey] [misc] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 56 errors in 6 files (checked 154 source files) +Found 46 errors in 6 files (checked 154 source files) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 6b2ce18831..338092af15 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -302,7 +302,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): zebra_stripes = Reactive(False) header_height = Reactive(1) show_cursor = Reactive(True) - cursor_type = Reactive("cell") + cursor_type = Reactive[CursorType]("cell") cursor_coordinate: Reactive[Coordinate] = Reactive( Coordinate(0, 0), repaint=False, always_update=True @@ -1548,7 +1548,7 @@ def _render_line(self, y: int, x1: int, x2: int, base_style: Style) -> Strip: except LookupError: return Strip.blank(width, base_style) - cache_key = ( + cache_key: LineCacheKey = ( y, x1, x2, From 02b19c67451695aaedde778b624c2cf141c6de1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 17:51:04 +0000 Subject: [PATCH 44/68] Type-safe line rendering. --- mypy.txt | 3 +-- src/textual/widgets/_data_table.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mypy.txt b/mypy.txt index 49563e397a..37efbd3e6e 100644 --- a/mypy.txt +++ b/mypy.txt @@ -42,8 +42,7 @@ src/textual/widgets/_data_table.py:1487: error: Unsupported operand types for % src/textual/widgets/_data_table.py:1487: note: Left operand is of type "Optional[int]" src/textual/widgets/_data_table.py:1495: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] src/textual/widgets/_data_table.py:1497: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1591: error: List comprehension has incompatible type List[Optional[RowKey]]; expected List[RowKey] [misc] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 46 errors in 6 files (checked 154 source files) +Found 45 errors in 6 files (checked 154 source files) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 338092af15..82b5bf99da 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -1584,13 +1584,16 @@ def _render_line(self, y: int, x1: int, x2: int, base_style: Style) -> Strip: return strip def render_line(self, y: int) -> Strip: - width, height = self.size + width, _ = self.size scroll_x, scroll_y = self.scroll_offset - fixed_row_keys: list[RowKey] = [ - self._row_locations.get_key(row_index) - for row_index in range(self.fixed_rows) - ] + fixed_row_keys: list[RowKey] = [] + append = fixed_row_keys.append + for row_index in range(self.fixed_rows): + row_key = self._row_locations.get_key(row_index) + if row_key is None: + raise RowDoesNotExist(f"Row index {row_index!r} is not valid.") + append(row_key) fixed_rows_height = sum( self.get_row_height(row_key) for row_key in fixed_row_keys From f22f257afcca24c65bd67dd7721bca20e6700505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 17:54:59 +0000 Subject: [PATCH 45/68] Type safe _render_line_in_row. --- mypy.txt | 8 +------- src/textual/widgets/_data_table.py | 5 +++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/mypy.txt b/mypy.txt index 37efbd3e6e..f91583d954 100644 --- a/mypy.txt +++ b/mypy.txt @@ -36,13 +36,7 @@ src/textual/widgets/_data_table.py:1377: error: Incompatible types in assignment src/textual/widgets/_data_table.py:1381: error: Unsupported operand types for in ("Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" and "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]") [operator] src/textual/widgets/_data_table.py:1390: error: Argument 1 has incompatible type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]"; expected "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [arg-type] src/textual/widgets/_data_table.py:1391: error: Invalid index type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" for "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]"; expected type "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [index] -src/textual/widgets/_data_table.py:1466: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1468: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1487: error: Unsupported operand types for % ("None" and "int") [operator] -src/textual/widgets/_data_table.py:1487: note: Left operand is of type "Optional[int]" -src/textual/widgets/_data_table.py:1495: error: Argument 1 to "Coordinate" has incompatible type "Optional[int]"; expected "int" [arg-type] -src/textual/widgets/_data_table.py:1497: error: Argument 1 has incompatible type "Optional[int]"; expected "int" [arg-type] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 45 errors in 6 files (checked 154 source files) +Found 40 errors in 6 files (checked 154 source files) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 82b5bf99da..78bb268fde 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -1452,6 +1452,7 @@ def _should_highlight( if row_key in self._row_locations: row_index = self._row_locations.get(row_key) + assert row_index is not None else: row_index = -1 @@ -1459,7 +1460,7 @@ def _should_highlight( if self.fixed_columns: fixed_style = self.get_component_styles("datatable--fixed").rich_style fixed_style += Style.from_meta({"fixed": True}) - fixed_row = [] + fixed_row: list[list[Segment]] = [] for column_index, column in enumerate( self.ordered_columns[: self.fixed_columns] ): @@ -1490,7 +1491,7 @@ def _should_highlight( else: row_style = base_style - scrollable_row = [] + scrollable_row: list[list[Segment]] = [] for column_index, column in enumerate(self.ordered_columns): cell_location = Coordinate(row_index, column_index) cell_lines = render_cell( From 64db52e946055d3081a22865987afa805ed94128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 17:57:54 +0000 Subject: [PATCH 46/68] Type safe _render_cell. --- mypy.txt | 6 +----- src/textual/widgets/_data_table.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/mypy.txt b/mypy.txt index f91583d954..6db01296cc 100644 --- a/mypy.txt +++ b/mypy.txt @@ -32,11 +32,7 @@ src/textual/widgets/_data_table.py:1011: error: Invalid index type "Optional[Row src/textual/widgets/_data_table.py:1027: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] src/textual/widgets/_data_table.py:1289: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] src/textual/widgets/_data_table.py:1305: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] -src/textual/widgets/_data_table.py:1377: error: Incompatible types in assignment (expression has type "Optional[RowKey]", variable has type "RowKey") [assignment] -src/textual/widgets/_data_table.py:1381: error: Unsupported operand types for in ("Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" and "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]") [operator] -src/textual/widgets/_data_table.py:1390: error: Argument 1 has incompatible type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]"; expected "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [arg-type] -src/textual/widgets/_data_table.py:1391: error: Invalid index type "Tuple[RowKey, Optional[ColumnKey], Style, bool, bool, int]" for "LRUCache[Tuple[RowKey, ColumnKey, Style, bool, bool, int], List[List[Segment]]]"; expected type "Tuple[RowKey, ColumnKey, Style, bool, bool, int]" [index] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 40 errors in 6 files (checked 154 source files) +Found 36 errors in 6 files (checked 154 source files) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 78bb268fde..243354cdd7 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -1371,13 +1371,25 @@ def _render_cell( if is_fixed_style: style += self.get_component_styles("datatable--cursor-fixed").rich_style + row_key: RowKey | None if is_header_row: row_key = self._header_row_key else: row_key = self._row_locations.get_key(row_index) + if row_key is None: + raise RowDoesNotExist(f"Row index {row_index!r} is not valid.") column_key = self._column_locations.get_key(column_index) - cell_cache_key = (row_key, column_key, style, cursor, hover, self._update_count) + if column_key is None: + raise ColumnDoesNotExist(f"Column index {column_index!r} is not valid.") + cell_cache_key: CellCacheKey = ( + row_key, + column_key, + style, + cursor, + hover, + self._update_count, + ) if cell_cache_key not in self._cell_render_cache: style += Style.from_meta({"row": row_index, "column": column_index}) height = self.header_height if is_header_row else self.rows[row_key].height From 6ee618932df0862d5b440b88fc2df285f24ce92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 18:02:00 +0000 Subject: [PATCH 47/68] Type safe ordered_rows property. --- mypy.txt | 3 +-- src/textual/widgets/_data_table.py | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy.txt b/mypy.txt index 6db01296cc..d72e5feaa2 100644 --- a/mypy.txt +++ b/mypy.txt @@ -31,8 +31,7 @@ src/textual/widgets/_data_table.py:996: error: Invalid index type "Optional[Colu src/textual/widgets/_data_table.py:1011: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] src/textual/widgets/_data_table.py:1027: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] src/textual/widgets/_data_table.py:1289: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:1305: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 36 errors in 6 files (checked 154 source files) +Found 35 errors in 6 files (checked 154 source files) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 243354cdd7..acf96af65b 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -1295,6 +1295,7 @@ def ordered_rows(self) -> list[Row]: num_rows = self.row_count update_count = self._update_count cache_key = (num_rows, update_count) + ordered_rows: list[Row] if cache_key in self._ordered_row_cache: ordered_rows = self._ordered_row_cache[cache_key] else: @@ -1302,6 +1303,8 @@ def ordered_rows(self) -> list[Row]: ordered_rows = [] for row_index in row_indices: row_key = self._row_locations.get_key(row_index) + if row_key is None: + raise RowDoesNotExist(f"Row index {row_index!r} is not valid.") row = self.rows[row_key] ordered_rows.append(row) self._ordered_row_cache[cache_key] = ordered_rows From 7844c37c44ccab84db85f751d790772f2e3e0ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 18:05:57 +0000 Subject: [PATCH 48/68] Type safe ordered_columns property. --- mypy.txt | 3 +-- src/textual/widgets/_data_table.py | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/mypy.txt b/mypy.txt index d72e5feaa2..817645fcea 100644 --- a/mypy.txt +++ b/mypy.txt @@ -30,8 +30,7 @@ src/textual/widgets/_data_table.py:991: error: Invalid index type "Optional[RowK src/textual/widgets/_data_table.py:996: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] src/textual/widgets/_data_table.py:1011: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] src/textual/widgets/_data_table.py:1027: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:1289: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 35 errors in 6 files (checked 154 source files) +Found 34 errors in 6 files (checked 154 source files) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index acf96af65b..eccb9b8be9 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -1283,10 +1283,12 @@ def is_valid_coordinate(self, coordinate: Coordinate) -> bool: def ordered_columns(self) -> list[Column]: """The list of Columns in the DataTable, ordered as they appear on screen.""" column_indices = range(len(self.columns)) - column_keys = [ - self._column_locations.get_key(index) for index in column_indices - ] - ordered_columns = [self.columns[key] for key in column_keys] + ordered_columns: list[Column] = [] + for index in column_indices: + column_key = self._column_locations.get_key(index) + if column_key is None: + raise ColumnDoesNotExist(f"Column index {index!r} is not valid.") + ordered_columns.append(self.columns[column_key]) return ordered_columns @property From 95e007b23aeb4b70892c9fb1dbba3213aa9b9bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 18:32:31 +0000 Subject: [PATCH 49/68] Simplify handling of Nones in TwoWayDict. In the beginning of the work on this PR, mypy flagged two issues in the implementation of TwoWayDict, which would return None when the keys/values were not available but the signatures of get/get_key did not have the missing '| None'. When I added the '| None' to the return values of TwoWayDict.get and TwoWayDict.get_key, many new errors popped up because the implementation of DataTable assumes, in many different places, that those methods return the exact thing that we asked for, and not None. To fix this, I started going over the call sites for get/get_key and started adding checks for 'None' and raising the appropriate RowDoesNotExist/ColumnDoesNotExist error. At some point, I realised that went a little bit against the semantics of the code, which pretty much assumes those accesses WILL work. So, I subclassed TwoWayDict to specialise it even further to this use case. The subclass is meant to work with RowKey/ColumnKey as the underlying keys and it will automatically raise an error if the access fails. CC: @darrenburns. --- mypy.txt | 20 ++---- src/textual/widgets/_data_table.py | 107 +++++++++++++++++++---------- 2 files changed, 76 insertions(+), 51 deletions(-) diff --git a/mypy.txt b/mypy.txt index 817645fcea..792d5918c3 100644 --- a/mypy.txt +++ b/mypy.txt @@ -16,21 +16,11 @@ src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has in src/textual/widgets/_list_view.py:165: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] src/textual/widgets/_list_view.py:171: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] src/textual/widgets/_list_view.py:184: error: Argument 2 to "Selected" has incompatible type "MessageTarget"; expected "ListItem" [arg-type] -src/textual/widgets/_data_table.py:708: error: Unsupported operand types for in ("Union[RowKey, str]" and "TwoWayDict[RowKey, int]") [operator] -src/textual/widgets/_data_table.py:710: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:733: error: Argument 1 to "get_row" of "DataTable" has incompatible type "Optional[RowKey]"; expected "Union[RowKey, str]" [arg-type] -src/textual/widgets/_data_table.py:747: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "TwoWayDict[ColumnKey, int]") [operator] -src/textual/widgets/_data_table.py:753: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:771: error: Argument 1 to "get_column" of "DataTable" has incompatible type "Optional[ColumnKey]"; expected "Union[ColumnKey, str]" [arg-type] -src/textual/widgets/_data_table.py:878: error: Argument 1 to "CellKey" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:878: error: Argument 2 to "CellKey" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] -src/textual/widgets/_data_table.py:887: error: Argument 3 to "RowHighlighted" has incompatible type "Optional[RowKey]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:896: error: Argument 3 to "ColumnHighlighted" has incompatible type "Optional[ColumnKey]"; expected "ColumnKey" [arg-type] -src/textual/widgets/_data_table.py:991: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] -src/textual/widgets/_data_table.py:996: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] -src/textual/widgets/_data_table.py:1011: error: Invalid index type "Optional[RowKey]" for "Dict[RowKey, Row]"; expected type "RowKey" [index] -src/textual/widgets/_data_table.py:1027: error: Invalid index type "Optional[ColumnKey]" for "Dict[ColumnKey, Column]"; expected type "ColumnKey" [index] +src/textual/widgets/_data_table.py:757: error: Unsupported operand types for in ("Union[RowKey, str]" and "KeysToIndices[RowKey]") [operator] +src/textual/widgets/_data_table.py:759: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] +src/textual/widgets/_data_table.py:796: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "KeysToIndices[ColumnKey]") [operator] +src/textual/widgets/_data_table.py:802: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 34 errors in 6 files (checked 154 source files) +Found 24 errors in 6 files (checked 154 source files) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index eccb9b8be9..c6652964e6 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -64,7 +64,7 @@ class DuplicateKey(Exception): an existing row or column in the DataTable. Keys must be unique.""" -StringKeySub = TypeVar("StringKeySub", bound="StringKey") +StringKeySubclass = TypeVar("StringKeySubclass", bound="StringKey") @functools.total_ordering @@ -84,8 +84,8 @@ class StringKey: value: str | None def __new__( - cls: type[StringKeySub], value: StringKey | str | None = None - ) -> StringKeySub: + cls: type[StringKeySubclass], value: StringKey | str | None = None + ) -> StringKeySubclass: """Creates a new `StringKey` object if necessary.""" if isinstance(value, cls): return value @@ -157,6 +157,53 @@ def __rich_repr__(self): yield "column_key", self.column_key +class KeysToIndices(TwoWayDict[StringKeySubclass, int]): + """Bidirectional dictionary to turn StringKey keys into their indices.""" + + _exception: type[Exception] + """The exception to raise when access in either direction fails.""" + + def __init__( + self, initial: dict[StringKeySubclass, int], exception: type[Exception] + ) -> None: + super().__init__(initial) + self._exception = exception + + def get(self, key: StringKeySubclass) -> int: + """Given a key, efficiently lookup and return the associated index. + + This will raise the exception passed in at instantiation time if the key + is not valid. + + Args: + key: The key. + + Returns: + The corresponding index. + """ + index = super().get(key) + if index is None: + raise self._exception(f"Key {key!r} is not valid.") + return index + + def get_key(self, value: int) -> StringKeySubclass: + """Given a index, efficiently lookup and return the associated key. + + This will raise the exception passed in at instantiation time if the idnex + is not valid. + + Args: + value: The index. + + Returns: + The corresponding key. + """ + key = super().get_key(value) + if key is None: + raise self._exception(f"Index {key!r} is not valid.") + return key + + def default_cell_formatter(obj: object) -> RenderableType: """Convert a cell into a Rich renderable for display. @@ -510,9 +557,11 @@ def __init__( # given a row or column key, the index that row or column is currently # present at, and mean that rows and columns are location independent - they # can move around without requiring us to modify the underlying data. - self._row_locations: TwoWayDict[RowKey, int] = TwoWayDict({}) + self._row_locations: KeysToIndices[RowKey] = KeysToIndices({}, RowDoesNotExist) """Maps row keys to row indices which represent row order.""" - self._column_locations: TwoWayDict[ColumnKey, int] = TwoWayDict({}) + self._column_locations: KeysToIndices[ColumnKey] = KeysToIndices( + {}, ColumnDoesNotExist + ) """Maps column keys to column indices which represent column order.""" self._row_render_cache: LRUCache[ @@ -1040,10 +1089,10 @@ def clear(self, columns: bool = False) -> None: self._y_offsets.clear() self._data.clear() self.rows.clear() - self._row_locations = TwoWayDict({}) + self._row_locations = KeysToIndices({}, RowDoesNotExist) if columns: self.columns.clear() - self._column_locations = TwoWayDict({}) + self._column_locations = KeysToIndices({}, ColumnDoesNotExist) self._require_update_dimensions = True self.cursor_coordinate = Coordinate(0, 0) self.hover_coordinate = Coordinate(0, 0) @@ -1282,13 +1331,10 @@ def is_valid_coordinate(self, coordinate: Coordinate) -> bool: @property def ordered_columns(self) -> list[Column]: """The list of Columns in the DataTable, ordered as they appear on screen.""" - column_indices = range(len(self.columns)) - ordered_columns: list[Column] = [] - for index in column_indices: - column_key = self._column_locations.get_key(index) - if column_key is None: - raise ColumnDoesNotExist(f"Column index {index!r} is not valid.") - ordered_columns.append(self.columns[column_key]) + ordered_columns = [ + self.columns[self._column_locations.get_key(index)] + for index in range(len(self.columns)) + ] return ordered_columns @property @@ -1301,14 +1347,10 @@ def ordered_rows(self) -> list[Row]: if cache_key in self._ordered_row_cache: ordered_rows = self._ordered_row_cache[cache_key] else: - row_indices = range(num_rows) - ordered_rows = [] - for row_index in row_indices: - row_key = self._row_locations.get_key(row_index) - if row_key is None: - raise RowDoesNotExist(f"Row index {row_index!r} is not valid.") - row = self.rows[row_key] - ordered_rows.append(row) + ordered_rows = [ + self.rows[self._row_locations.get_key(row_index)] + for row_index in range(num_rows) + ] self._ordered_row_cache[cache_key] = ordered_rows return ordered_rows @@ -1376,17 +1418,12 @@ def _render_cell( if is_fixed_style: style += self.get_component_styles("datatable--cursor-fixed").rich_style - row_key: RowKey | None if is_header_row: row_key = self._header_row_key else: row_key = self._row_locations.get_key(row_index) - if row_key is None: - raise RowDoesNotExist(f"Row index {row_index!r} is not valid.") column_key = self._column_locations.get_key(column_index) - if column_key is None: - raise ColumnDoesNotExist(f"Column index {column_index!r} is not valid.") cell_cache_key: CellCacheKey = ( row_key, column_key, @@ -1605,13 +1642,10 @@ def render_line(self, y: int) -> Strip: width, _ = self.size scroll_x, scroll_y = self.scroll_offset - fixed_row_keys: list[RowKey] = [] - append = fixed_row_keys.append - for row_index in range(self.fixed_rows): - row_key = self._row_locations.get_key(row_index) - if row_key is None: - raise RowDoesNotExist(f"Row index {row_index!r} is not valid.") - append(row_key) + fixed_row_keys = [ + self._row_locations.get_key(row_index) + for row_index in range(self.fixed_rows) + ] fixed_rows_height = sum( self.get_row_height(row_key) for row_key in fixed_row_keys @@ -1667,8 +1701,9 @@ def sort_by_column_keys(row: tuple[RowKey, dict[ColumnKey, CellType]]) -> Any: ordered_rows = sorted( self._data.items(), key=sort_by_column_keys, reverse=reverse ) - self._row_locations = TwoWayDict( - {key: new_index for new_index, (key, _) in enumerate(ordered_rows)} + self._row_locations = KeysToIndices( + {key: new_index for new_index, (key, _) in enumerate(ordered_rows)}, + RowDoesNotExist, ) self._update_count += 1 self.refresh() From beb1906f2a95c1cde42fd26b301a6e4d19fcc77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 18:35:36 +0000 Subject: [PATCH 50/68] Make use of idempotency of StringKey subclasses. --- mypy.txt | 6 +----- src/textual/widgets/_data_table.py | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index 792d5918c3..9934db1ee7 100644 --- a/mypy.txt +++ b/mypy.txt @@ -16,11 +16,7 @@ src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has in src/textual/widgets/_list_view.py:165: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] src/textual/widgets/_list_view.py:171: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] src/textual/widgets/_list_view.py:184: error: Argument 2 to "Selected" has incompatible type "MessageTarget"; expected "ListItem" [arg-type] -src/textual/widgets/_data_table.py:757: error: Unsupported operand types for in ("Union[RowKey, str]" and "KeysToIndices[RowKey]") [operator] -src/textual/widgets/_data_table.py:759: error: Argument 1 to "get" of "dict" has incompatible type "Union[RowKey, str]"; expected "RowKey" [arg-type] -src/textual/widgets/_data_table.py:796: error: Unsupported operand types for in ("Union[ColumnKey, str]" and "KeysToIndices[ColumnKey]") [operator] -src/textual/widgets/_data_table.py:802: error: Invalid index type "Union[ColumnKey, str]" for "Dict[ColumnKey, CellType]"; expected type "ColumnKey" [index] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 24 errors in 6 files (checked 154 source files) +Found 20 errors in 5 files (checked 154 source files) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index c6652964e6..9007d905cf 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -754,6 +754,7 @@ def get_row(self, row_key: RowKey | str) -> list[CellType]: Raises: RowDoesNotExist: When there is no row corresponding to the key. """ + row_key = RowKey(row_key) if row_key not in self._row_locations: raise RowDoesNotExist(f"Row key {row_key!r} is not valid.") cell_mapping: dict[ColumnKey, CellType] = self._data.get(row_key, {}) @@ -793,6 +794,7 @@ def get_column(self, column_key: ColumnKey | str) -> Iterable[CellType]: Raises: ColumnDoesNotExist: If there is no column corresponding to the key. """ + column_key = ColumnKey(column_key) if column_key not in self._column_locations: raise ColumnDoesNotExist(f"Column key {column_key!r} is not valid.") From 99491d180e3beb3945c7fba691fe9d6cf9944c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:03:20 +0000 Subject: [PATCH 51/68] Make message aware of type of sender. --- mypy.txt | 3 +-- src/textual/widgets/_list_item.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mypy.txt b/mypy.txt index 9934db1ee7..d6045dc7f6 100644 --- a/mypy.txt +++ b/mypy.txt @@ -15,8 +15,7 @@ src/textual/widgets/_list_view.py:135: error: "Widget" has no attribute "highlig src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has incompatible type "Optional[Widget]"; expected "Optional[ListItem]" [arg-type] src/textual/widgets/_list_view.py:165: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] src/textual/widgets/_list_view.py:171: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] -src/textual/widgets/_list_view.py:184: error: Argument 2 to "Selected" has incompatible type "MessageTarget"; expected "ListItem" [arg-type] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 20 errors in 5 files (checked 154 source files) +Found 19 errors in 5 files (checked 154 source files) diff --git a/src/textual/widgets/_list_item.py b/src/textual/widgets/_list_item.py index e9222a7aac..522305e009 100644 --- a/src/textual/widgets/_list_item.py +++ b/src/textual/widgets/_list_item.py @@ -31,7 +31,7 @@ class ListItem(Widget, can_focus=False): class _ChildClicked(Message): """For informing with the parent ListView that we were clicked""" - pass + sender: "ListItem" def on_click(self, event: events.Click) -> None: self.post_message_no_wait(self._ChildClicked(self)) From b4418c535eadad54df8780db98ed1d828d8a4125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:04:33 +0000 Subject: [PATCH 52/68] Only select when possible. --- mypy.txt | 3 +-- src/textual/widgets/_list_view.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy.txt b/mypy.txt index d6045dc7f6..4206a780e8 100644 --- a/mypy.txt +++ b/mypy.txt @@ -14,8 +14,7 @@ src/textual/widgets/_list_view.py:132: error: "Widget" has no attribute "highlig src/textual/widgets/_list_view.py:135: error: "Widget" has no attribute "highlighted" [attr-defined] src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has incompatible type "Optional[Widget]"; expected "Optional[ListItem]" [arg-type] src/textual/widgets/_list_view.py:165: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] -src/textual/widgets/_list_view.py:171: error: Argument 2 to "Selected" has incompatible type "Optional[ListItem]"; expected "ListItem" [arg-type] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 19 errors in 5 files (checked 154 source files) +Found 18 errors in 5 files (checked 154 source files) diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index af41589dbd..7fa4b32b07 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -168,6 +168,8 @@ def clear(self) -> AwaitRemove: def action_select_cursor(self) -> None: """Select the current item in the list.""" selected_child = self.highlighted_child + if selected_child is None: + return self.post_message_no_wait(self.Selected(self, selected_child)) def action_cursor_down(self) -> None: From da8db5e812b31b7e8b9e6700217d13d0f6c7b671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:09:07 +0000 Subject: [PATCH 53/68] Fix typing of reactive attribute. --- mypy.txt | 6 ++++-- src/textual/widgets/_list_view.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy.txt b/mypy.txt index 4206a780e8..aa9a3ba65a 100644 --- a/mypy.txt +++ b/mypy.txt @@ -7,13 +7,15 @@ src/textual/app.py:1744: error: Incompatible types in assignment (expression has src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] -src/textual/widgets/_list_view.py:94: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") [assignment] src/textual/widgets/_list_view.py:97: error: Missing return statement [return] src/textual/widgets/_list_view.py:102: error: Incompatible return value type (got "Widget", expected "Optional[ListItem]") [return-value] src/textual/widgets/_list_view.py:132: error: "Widget" has no attribute "highlighted" [attr-defined] src/textual/widgets/_list_view.py:135: error: "Widget" has no attribute "highlighted" [attr-defined] src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has incompatible type "Optional[Widget]"; expected "Optional[ListItem]" [arg-type] -src/textual/widgets/_list_view.py:165: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] +src/textual/widgets/_list_view.py:177: error: Unsupported operand types for + ("None" and "int") [operator] +src/textual/widgets/_list_view.py:177: note: Left operand is of type "Optional[int]" +src/textual/widgets/_list_view.py:181: error: Unsupported operand types for - ("None" and "int") [operator] +src/textual/widgets/_list_view.py:181: note: Left operand is of type "Optional[int]" src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index 7fa4b32b07..8de2639f6d 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -35,7 +35,7 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): | down | Move the cursor down. | """ - index = reactive(0, always_update=True) + index = reactive[int | None](0, always_update=True) class Highlighted(Message, bubble=True): """Posted when the highlighted item changes. From 4c605f4feac56456e0ee9264c6e188eae9d64ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:09:35 +0000 Subject: [PATCH 54/68] Reset cursor upon moving action if needed. --- mypy.txt | 6 +----- src/textual/widgets/_list_view.py | 6 ++++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index aa9a3ba65a..990726c49b 100644 --- a/mypy.txt +++ b/mypy.txt @@ -12,11 +12,7 @@ src/textual/widgets/_list_view.py:102: error: Incompatible return value type (go src/textual/widgets/_list_view.py:132: error: "Widget" has no attribute "highlighted" [attr-defined] src/textual/widgets/_list_view.py:135: error: "Widget" has no attribute "highlighted" [attr-defined] src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has incompatible type "Optional[Widget]"; expected "Optional[ListItem]" [arg-type] -src/textual/widgets/_list_view.py:177: error: Unsupported operand types for + ("None" and "int") [operator] -src/textual/widgets/_list_view.py:177: note: Left operand is of type "Optional[int]" -src/textual/widgets/_list_view.py:181: error: Unsupported operand types for - ("None" and "int") [operator] -src/textual/widgets/_list_view.py:181: note: Left operand is of type "Optional[int]" src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 18 errors in 5 files (checked 154 source files) +Found 16 errors in 5 files (checked 154 source files) diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index 8de2639f6d..0da083e909 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -174,10 +174,16 @@ def action_select_cursor(self) -> None: def action_cursor_down(self) -> None: """Highlight the next item in the list.""" + if self.index is None: + self.index = 0 + return self.index += 1 def action_cursor_up(self) -> None: """Highlight the previous item in the list.""" + if self.index is None: + self.index = 0 + return self.index -= 1 def on_list_item__child_clicked(self, event: ListItem._ChildClicked) -> None: From 96403b22b87bac52f35db394ba70c501e6b07b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:22:37 +0000 Subject: [PATCH 55/68] Assure mypy we have ListItems. This could be improved (as in, cast wouldn't be needed) if #1871 is resolved favourably. --- mypy.txt | 6 +----- src/textual/widgets/_list_view.py | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/mypy.txt b/mypy.txt index 990726c49b..e773cd427a 100644 --- a/mypy.txt +++ b/mypy.txt @@ -8,11 +8,7 @@ src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" ha src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_list_view.py:97: error: Missing return statement [return] -src/textual/widgets/_list_view.py:102: error: Incompatible return value type (got "Widget", expected "Optional[ListItem]") [return-value] -src/textual/widgets/_list_view.py:132: error: "Widget" has no attribute "highlighted" [attr-defined] -src/textual/widgets/_list_view.py:135: error: "Widget" has no attribute "highlighted" [attr-defined] -src/textual/widgets/_list_view.py:140: error: Argument 2 to "Highlighted" has incompatible type "Optional[Widget]"; expected "Optional[ListItem]" [arg-type] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 16 errors in 5 files (checked 154 source files) +Found 12 errors in 5 files (checked 154 source files) diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index 0da083e909..bf00d36e56 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import ClassVar +from typing import ClassVar, cast from textual.await_remove import AwaitRemove from textual.binding import Binding, BindingType @@ -99,7 +99,7 @@ def highlighted_child(self) -> ListItem | None: if self.index is None: return None elif 0 <= self.index < len(self._nodes): - return self._nodes[self.index] + return cast(ListItem, self._nodes[self.index]) def validate_index(self, index: int | None) -> int | None: """Clamp the index to the valid range, or set to None if there's nothing to highlight. @@ -128,10 +128,10 @@ def _is_valid_index(self, index: int | None) -> bool: def watch_index(self, old_index: int, new_index: int) -> None: """Updates the highlighting when the index changes.""" if self._is_valid_index(old_index): - old_child = self._nodes[old_index] + old_child = cast(ListItem, self._nodes[old_index]) old_child.highlighted = False if self._is_valid_index(new_index): - new_child = self._nodes[new_index] + new_child = cast(ListItem, self._nodes[new_index]) new_child.highlighted = True else: new_child = None From 2f808c38db6beecfc9424650197f13f4884df919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:26:51 +0000 Subject: [PATCH 56/68] Add explicit return. --- mypy.txt | 3 +-- src/textual/widgets/_list_view.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mypy.txt b/mypy.txt index e773cd427a..87875f24ab 100644 --- a/mypy.txt +++ b/mypy.txt @@ -7,8 +7,7 @@ src/textual/app.py:1744: error: Incompatible types in assignment (expression has src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] -src/textual/widgets/_list_view.py:97: error: Missing return statement [return] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 12 errors in 5 files (checked 154 source files) +Found 11 errors in 4 files (checked 154 source files) diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index bf00d36e56..84eceaf84e 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -96,10 +96,10 @@ def on_mount(self) -> None: @property def highlighted_child(self) -> ListItem | None: """The currently highlighted ListItem, or None if nothing is highlighted.""" - if self.index is None: - return None - elif 0 <= self.index < len(self._nodes): + if self.index is not None and 0 <= self.index < len(self._nodes): return cast(ListItem, self._nodes[self.index]) + else: + return None def validate_index(self, index: int | None) -> int | None: """Clamp the index to the valid range, or set to None if there's nothing to highlight. From bb7f58abfbf6f3a2bdf91638de988535c794b864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:30:32 +0000 Subject: [PATCH 57/68] Ignore argument types to watch. Related issues: #1865. --- mypy.txt | 5 +---- src/textual/widgets/_footer.py | 2 +- src/textual/widgets/_header.py | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/mypy.txt b/mypy.txt index 87875f24ab..3e44e11c61 100644 --- a/mypy.txt +++ b/mypy.txt @@ -4,10 +4,7 @@ src/textual/app.py:1607: error: Value of type variable "_T_io" of "redirect_stde src/textual/app.py:1612: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] src/textual/app.py:1613: error: Value of type variable "_T_io" of "redirect_stdout" cannot be "_NullFile" [type-var] src/textual/app.py:1744: error: Incompatible types in assignment (expression has type "reversed[Widget]", variable has type "List[Widget]") [assignment] -src/textual/widgets/_header.py:136: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] -src/textual/widgets/_header.py:137: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[str], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] -src/textual/widgets/_footer.py:69: error: Argument 3 to "watch" of "DOMNode" has incompatible type "Callable[[Optional[Widget]], None]"; expected "Union[Callable[[], Awaitable[None]], Callable[[], None]]" [arg-type] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 11 errors in 4 files (checked 154 source files) +Found 8 errors in 2 files (checked 154 source files) diff --git a/src/textual/widgets/_footer.py b/src/textual/widgets/_footer.py index 00ccf9e1ff..7c53fab8b0 100644 --- a/src/textual/widgets/_footer.py +++ b/src/textual/widgets/_footer.py @@ -66,7 +66,7 @@ async def watch_highlight_key(self, value) -> None: self.refresh() def on_mount(self) -> None: - self.watch(self.screen, "focused", self._focus_changed) + self.watch(self.screen, "focused", self._focus_changed) # type: ignore[arg-type] def _focus_changed(self, focused: Widget | None) -> None: self._key_text = None diff --git a/src/textual/widgets/_header.py b/src/textual/widgets/_header.py index 12e07e4c1b..43de8d4acb 100644 --- a/src/textual/widgets/_header.py +++ b/src/textual/widgets/_header.py @@ -133,5 +133,5 @@ def set_title(title: str) -> None: def set_sub_title(sub_title: str) -> None: self.query_one(HeaderTitle).sub_text = sub_title - self.watch(self.app, "title", set_title) - self.watch(self.app, "sub_title", set_sub_title) + self.watch(self.app, "title", set_title) # type: ignore[arg-type] + self.watch(self.app, "sub_title", set_sub_title) # type: ignore[arg-type] From 84bc80e1fb448f95753d2cca9c3b464b8c535749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:35:45 +0000 Subject: [PATCH 58/68] Type safe App._register. --- mypy.txt | 3 +-- src/textual/app.py | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mypy.txt b/mypy.txt index 3e44e11c61..3133f25df6 100644 --- a/mypy.txt +++ b/mypy.txt @@ -3,8 +3,7 @@ src/textual/app.py:315: error: Argument "file" to "Console" has incompatible typ src/textual/app.py:1607: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] src/textual/app.py:1612: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] src/textual/app.py:1613: error: Value of type variable "_T_io" of "redirect_stdout" cannot be "_NullFile" [type-var] -src/textual/app.py:1744: error: Incompatible types in assignment (expression has type "reversed[Widget]", variable has type "List[Widget]") [assignment] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 8 errors in 2 files (checked 154 source files) +Found 7 errors in 2 files (checked 154 source files) diff --git a/src/textual/app.py b/src/textual/app.py index e1c2b82caa..9bd882df94 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1735,13 +1735,16 @@ def _register( if not widgets: return [] - new_widgets = list(widgets) + widget_list = list(widgets) + new_widgets: Iterable[Widget] if before is not None or after is not None: # There's a before or after, which means there's going to be an # insertion, so make it easier to get the new things in the # correct order. - new_widgets = reversed(new_widgets) + new_widgets = reversed(widget_list) + else: + new_widgets = widget_list apply_stylesheet = self.stylesheet.apply for widget in new_widgets: From 26a8bb09c0db9a653297b86936404c633a9607b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:46:45 +0000 Subject: [PATCH 59/68] Redirect output to void. --- mypy.txt | 8 +++----- src/textual/app.py | 5 ++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/mypy.txt b/mypy.txt index 3133f25df6..50b67c577f 100644 --- a/mypy.txt +++ b/mypy.txt @@ -1,9 +1,7 @@ poetry run mypy src/textual -src/textual/app.py:315: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] -src/textual/app.py:1607: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] -src/textual/app.py:1612: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "_NullFile" [type-var] -src/textual/app.py:1613: error: Value of type variable "_T_io" of "redirect_stdout" cannot be "_NullFile" [type-var] +src/textual/app.py:316: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] +src/textual/app.py:1608: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 7 errors in 2 files (checked 154 source files) +Found 5 errors in 2 files (checked 154 source files) diff --git a/src/textual/app.py b/src/textual/app.py index 9bd882df94..7b2a79ea88 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1608,9 +1608,8 @@ async def invoke_ready_callback() -> None: with redirect_stdout(redirector): # type: ignore await run_process_messages() else: - null_file = _NullFile() - with redirect_stderr(null_file): - with redirect_stdout(null_file): + with redirect_stderr(None): + with redirect_stdout(None): await run_process_messages() finally: From b99283ee4e00b7a536256b7cd7600ce3f23e16d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:48:11 +0000 Subject: [PATCH 60/68] Remove helper file. --- mypy.txt | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 mypy.txt diff --git a/mypy.txt b/mypy.txt deleted file mode 100644 index 50b67c577f..0000000000 --- a/mypy.txt +++ /dev/null @@ -1,7 +0,0 @@ -poetry run mypy src/textual -src/textual/app.py:316: error: Argument "file" to "Console" has incompatible type "_WriterThread"; expected "Optional[IO[str]]" [arg-type] -src/textual/app.py:1608: error: Value of type variable "_T_io" of "redirect_stderr" cannot be "StdoutRedirector" [type-var] -src/textual/widgets/_button.py:219: error: Item "ConsoleRenderable" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -src/textual/widgets/_button.py:219: error: Item "RichCast" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -src/textual/widgets/_button.py:219: error: Item "str" of "Union[ConsoleRenderable, RichCast, str]" has no attribute "copy" [union-attr] -Found 5 errors in 2 files (checked 154 source files) From 336f0002100ad77914eeb25f24f7e21ec39c6c4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 24 Feb 2023 11:14:50 +0000 Subject: [PATCH 61/68] Fix Python compat. issues. --- src/textual/_styles_cache.py | 4 ++-- src/textual/actions.py | 5 +++-- src/textual/widgets/_list_view.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/textual/_styles_cache.py b/src/textual/_styles_cache.py index c760fcfe3f..e7685eaa47 100644 --- a/src/textual/_styles_cache.py +++ b/src/textual/_styles_cache.py @@ -8,17 +8,17 @@ from rich.style import Style from ._border import get_box, render_row -from .filter import LineFilter from ._opacity import _apply_opacity from ._segment_tools import line_pad, line_trim from .color import Color +from .filter import LineFilter from .geometry import Region, Size, Spacing from .renderables.text_opacity import TextOpacity from .renderables.tint import Tint from .strip import Strip if TYPE_CHECKING: - from typing import TypeAlias + from typing_extensions import TypeAlias from .css.styles import StylesBase from .widget import Widget diff --git a/src/textual/actions.py b/src/textual/actions.py index 8a863582d7..7f33ec0212 100644 --- a/src/textual/actions.py +++ b/src/textual/actions.py @@ -2,9 +2,10 @@ import ast import re -from typing import Any, TypeAlias -Action: TypeAlias = tuple[str, tuple[Any, ...]] +from typing_extensions import Any, TypeAlias + +Action: TypeAlias = "tuple[str, tuple[Any, ...]]" """An action is its name and the arbitrary tuple of its parameters.""" diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index 84eceaf84e..c5b1af8c56 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import ClassVar, cast +from typing import ClassVar, Optional, cast from textual.await_remove import AwaitRemove from textual.binding import Binding, BindingType @@ -35,7 +35,7 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): | down | Move the cursor down. | """ - index = reactive[int | None](0, always_update=True) + index = reactive[Optional[int]](0, always_update=True) class Highlighted(Message, bubble=True): """Posted when the highlighted item changes. From a4072c4f13630dc381633177c620b6f088c4157c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 24 Feb 2023 12:19:16 +0000 Subject: [PATCH 62/68] Button can only accept str/Text as label. Fixes: #1870. --- CHANGELOG.md | 1 + src/textual/widgets/_button.py | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d3de1f8fe..2175f30dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Scrolling by page now adds to current position. - Markdown lists have been polished: a selection of bullets, better alignment of numbers, style tweaks https://github.com/Textualize/textual/pull/1832 - Added alternative method of composing Widgets https://github.com/Textualize/textual/pull/1847 +- Buttons no longer accept arbitrary renderables https://github.com/Textualize/textual/issues/1870 ### Removed diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 156ba3f194..f8b22a9694 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -4,7 +4,6 @@ from typing import cast import rich.repr -from rich.console import RenderableType from rich.text import Text, TextType from typing_extensions import Literal @@ -145,7 +144,7 @@ class Button(Static, can_focus=True): ACTIVE_EFFECT_DURATION = 0.3 """When buttons are clicked they get the `-active` class for this duration (in seconds)""" - label: reactive[RenderableType] = reactive[RenderableType]("") + label: reactive[TextType] = reactive[TextType]("") """The text label that appears within the button.""" variant = reactive("default") @@ -209,15 +208,14 @@ def watch_variant(self, old_variant: str, variant: str): self.remove_class(f"-{old_variant}") self.add_class(f"-{variant}") - def validate_label(self, label: RenderableType) -> RenderableType: + def validate_label(self, label: TextType) -> TextType: """Parse markup for self.label""" if isinstance(label, str): return Text.from_markup(label) return label - def render(self) -> RenderableType: - label = self.label.copy() - label = Text.assemble(" ", label, " ") + def render(self) -> TextType: + label = Text.assemble(" ", self.label, " ") label.stylize(self.text_style) return label From aadfd0d7915d42289345388c4da82353c5d250d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 24 Feb 2023 18:14:54 +0000 Subject: [PATCH 63/68] Add runtime type check for list items. This change follows from a discussion in issue #1871. --- src/textual/widgets/_list_view.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index c5b1af8c56..e24ecf31b5 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import ClassVar, Optional, cast +from typing import ClassVar, Optional from textual.await_remove import AwaitRemove from textual.binding import Binding, BindingType @@ -8,7 +8,7 @@ from textual.geometry import clamp from textual.message import Message from textual.reactive import reactive -from textual.widget import AwaitMount +from textual.widget import AwaitMount, Widget from textual.widgets._list_item import ListItem @@ -97,7 +97,9 @@ def on_mount(self) -> None: def highlighted_child(self) -> ListItem | None: """The currently highlighted ListItem, or None if nothing is highlighted.""" if self.index is not None and 0 <= self.index < len(self._nodes): - return cast(ListItem, self._nodes[self.index]) + list_item = self._nodes[self.index] + assert isinstance(list_item, ListItem) + return list_item else: return None @@ -128,10 +130,14 @@ def _is_valid_index(self, index: int | None) -> bool: def watch_index(self, old_index: int, new_index: int) -> None: """Updates the highlighting when the index changes.""" if self._is_valid_index(old_index): - old_child = cast(ListItem, self._nodes[old_index]) + old_child = self._nodes[old_index] + assert isinstance(old_child, ListItem) old_child.highlighted = False + + new_child: Widget | None if self._is_valid_index(new_index): - new_child = cast(ListItem, self._nodes[new_index]) + new_child = self._nodes[new_index] + assert isinstance(new_child, ListItem) new_child.highlighted = True else: new_child = None From 86756391216e21aebd08697ec229d9877a2a0c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 27 Feb 2023 12:18:19 +0000 Subject: [PATCH 64/68] Address review comments. --- src/textual/_compositor.py | 4 ++-- src/textual/actions.py | 4 ++-- src/textual/app.py | 25 +++++++++++-------------- src/textual/keys.py | 17 ++++------------- 4 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index a822051ad2..5a87777a73 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -440,7 +440,7 @@ def add_widget( Args: widget: The widget to add. - virtual_region: TODO. + virtual_region: The Widget region relative to it's container. region: The region the widget will occupy. order: Painting order information. layer_order: The order of the widget in its layer. @@ -834,7 +834,7 @@ def render(self, full: bool = False) -> RenderableType | None: # Maps each cut on to a list of segments cuts = self.cuts - # dict.fromkeys is a callable which takes a list of ints returns a dict which maps ints on to a list of Segments or None. + # dict.fromkeys is a callable which takes a list of ints returns a dict which maps ints onto a list of Segments or None. fromkeys = cast("Callable[[list[int]], dict[int, Strip | None]]", dict.fromkeys) chops: list[dict[int, Strip | None]] chops = [fromkeys(cut_set[:-1]) for cut_set in cuts] diff --git a/src/textual/actions.py b/src/textual/actions.py index 7f33ec0212..d85a0cacc2 100644 --- a/src/textual/actions.py +++ b/src/textual/actions.py @@ -5,7 +5,7 @@ from typing_extensions import Any, TypeAlias -Action: TypeAlias = "tuple[str, tuple[Any, ...]]" +ActionParseResult: TypeAlias = "tuple[str, tuple[Any, ...]]" """An action is its name and the arbitrary tuple of its parameters.""" @@ -20,7 +20,7 @@ class ActionError(Exception): re_action_params = re.compile(r"([\w\.]+)(\(.*?\))") -def parse(action: str) -> Action: +def parse(action: str) -> ActionParseResult: """Parses an action string. Args: diff --git a/src/textual/app.py b/src/textual/app.py index 7b2a79ea88..77ee759dac 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -57,7 +57,7 @@ from ._event_broker import NoHandler, extract_handler_actions from ._path import _make_path_object_relative from ._wait import wait_for_idle -from .actions import Action, SkipAction +from .actions import ActionParseResult, SkipAction from .await_remove import AwaitRemove from .binding import Binding, Bindings from .css.query import NoMatches @@ -1734,19 +1734,17 @@ def _register( if not widgets: return [] - widget_list = list(widgets) - - new_widgets: Iterable[Widget] + widget_list: Iterable[Widget] if before is not None or after is not None: # There's a before or after, which means there's going to be an # insertion, so make it easier to get the new things in the # correct order. - new_widgets = reversed(widget_list) + widget_list = reversed(widgets) else: - new_widgets = widget_list + widget_list = widgets apply_stylesheet = self.stylesheet.apply - for widget in new_widgets: + for widget in widget_list: if not isinstance(widget, Widget): raise AppError(f"Can't register {widget!r}; expected a Widget instance") if widget not in self._registry: @@ -1803,15 +1801,14 @@ def is_mounted(self, widget: Widget) -> bool: async def _close_all(self) -> None: """Close all message pumps.""" - screen: Screen | Callable[[], Screen] # Explicit type to reuse variable below. - # Close all screens on the stack - for screen in reversed(self._screen_stack): - if screen._running: - await self._prune_node(screen) + # Close all screens on the stack. + for stack_screen in reversed(self._screen_stack): + if stack_screen._running: + await self._prune_node(stack_screen) self._screen_stack.clear() - # Close pre-defined screens + # Close pre-defined screens. for screen in self.SCREENS.values(): if isinstance(screen, Screen) and screen._running: await self._prune_node(screen) @@ -1977,7 +1974,7 @@ async def on_event(self, event: events.Event) -> None: async def action( self, - action: str | Action, + action: str | ActionParseResult, default_namespace: object | None = None, ) -> bool: """Perform an action. diff --git a/src/textual/keys.py b/src/textual/keys.py index c2dead70e1..1c5fe219d1 100644 --- a/src/textual/keys.py +++ b/src/textual/keys.py @@ -2,7 +2,6 @@ import unicodedata from enum import Enum -from typing import TYPE_CHECKING # Adapted from prompt toolkit https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/keys.py @@ -14,6 +13,10 @@ class Keys(str, Enum): # type: ignore[no-redef] strings. """ + @property + def value(self) -> str: + return super().value + Escape = "escape" # Also Control-[ ShiftEscape = "shift+escape" Return = "return" @@ -196,18 +199,6 @@ class Keys(str, Enum): # type: ignore[no-redef] ShiftControlEnd = ControlShiftEnd -# We want to make sure that mypy knows that the values of Keys will always be strings. -# Typing is verbose here because the attribute `Enum.value` was special-cased to have -# very different behaviours in `Keys.value` and `Keys.SomeKey.value`. -if TYPE_CHECKING: - from enum import property as enum_property - - class Keys(str, Enum): # type: ignore[no-redef] - @enum_property - def value(self) -> str: - ... - - # Unicode db contains some obscure names # This mapping replaces them with more common terms KEY_NAME_REPLACEMENTS = { From dccf167bbfa61e219c9d207040f422aa60734019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:09:32 +0000 Subject: [PATCH 65/68] Revert "Fix typing issues in demo." This reverts commit f36678392046ae6ffcf1e3e5f8354e91ec5f50f2. --- src/textual/demo.css | 522 +++++++++++++++++++++---------------------- src/textual/demo.py | 27 +-- 2 files changed, 270 insertions(+), 279 deletions(-) diff --git a/src/textual/demo.css b/src/textual/demo.css index 083be4e288..3fb8c7d719 100644 --- a/src/textual/demo.css +++ b/src/textual/demo.css @@ -1,263 +1,263 @@ - * { - transition: background 500ms in_out_cubic, color 500ms in_out_cubic; - } - - Screen { - layers: base overlay notes notifications; - overflow: hidden; - } - - - Notification { - dock: bottom; - layer: notification; - width: auto; - margin: 2 4; - padding: 1 2; - background: $background; - color: $text; - height: auto; - - } - - Sidebar { - width: 40; - background: $panel; - transition: offset 500ms in_out_cubic; - layer: overlay; - - } - - Sidebar:focus-within { - offset: 0 0 !important; - } - - Sidebar.-hidden { - offset-x: -100%; - } - - Sidebar Title { - background: $boost; - color: $secondary; - padding: 2 4; - border-right: vkey $background; - dock: top; - text-align: center; - text-style: bold; - } - - - OptionGroup { - background: $boost; - color: $text; - height: 1fr; - border-right: vkey $background; - } - - Option { - margin: 1 0 0 1; - height: 3; - padding: 1 2; - background: $boost; - border: tall $panel; - text-align: center; - } - - Option:hover { - background: $primary 20%; - color: $text; - } - - Body { - height: 100%; - overflow-y: scroll; - width: 100%; - background: $surface; - - } - - AboveFold { - width: 100%; - height: 100%; - align: center middle; - } - - Welcome { - background: $boost; - height: auto; - max-width: 100; - min-width: 40; - border: wide $primary; - padding: 1 2; - margin: 1 2; - box-sizing: border-box; - } - - Welcome Button { - width: 100%; - margin-top: 1; - } - - Column { - height: auto; - min-height: 100vh; - align: center top; - } - - - DarkSwitch { - background: $panel; - padding: 1; - dock: bottom; - height: auto; - border-right: vkey $background; - } - - DarkSwitch .label { - width: 1fr; - padding: 1 2; - color: $text-muted; - } - - DarkSwitch Switch { - background: $boost; - dock: left; - } - - - Screen>Container { - height: 100%; - overflow: hidden; - } - - TextLog { - background: $surface; - color: $text; - height: 50vh; - dock: bottom; - layer: notes; - border-top: hkey $primary; - offset-y: 0; - transition: offset 400ms in_out_cubic; - padding: 0 1 1 1; - } - - - TextLog:focus { - offset: 0 0 !important; - } - - TextLog.-hidden { - offset-y: 100%; - } - - - - Section { - height: auto; - min-width: 40; - margin: 1 2 4 2; - - } - - SectionTitle { - padding: 1 2; - background: $boost; - text-align: center; - text-style: bold; - } - - SubTitle { - padding-top: 1; - border-bottom: heavy $panel; - color: $text; - text-style: bold; - } - - TextContent { - margin: 1 0; - } - - QuickAccess { - width: 30; - dock: left; - - } - - LocationLink { - margin: 1 0 0 1; - height: 1; - padding: 1 2; - background: $boost; - color: $text; - box-sizing: content-box; - content-align: center middle; - } - - LocationLink:hover { - background: $accent; - color: $text; - text-style: bold; - } - - - .pad { - margin: 1 0; - } - - DataTable { - height: 16; - } - - - LoginForm { - height: auto; - margin: 1 0; - padding: 1 2; - layout: grid; - grid-size: 2; - grid-rows: 4; - grid-columns: 12 1fr; - background: $boost; - border: wide $background; - } - - LoginForm Button { - margin: 0 1; - width: 100%; - } - - LoginForm .label { - padding: 1 2; - text-align: right; - } - - SidebarMessage { - margin: 0 1; - - } - - - Tree { - margin: 1 0; - } + * { + transition: background 500ms in_out_cubic, color 500ms in_out_cubic; +} + +Screen { + layers: base overlay notes notifications; + overflow: hidden; +} + + +Notification { + dock: bottom; + layer: notification; + width: auto; + margin: 2 4; + padding: 1 2; + background: $background; + color: $text; + height: auto; + +} + +Sidebar { + width: 40; + background: $panel; + transition: offset 500ms in_out_cubic; + layer: overlay; + +} + +Sidebar:focus-within { + offset: 0 0 !important; +} + +Sidebar.-hidden { + offset-x: -100%; +} + +Sidebar Title { + background: $boost; + color: $secondary; + padding: 2 4; + border-right: vkey $background; + dock: top; + text-align: center; + text-style: bold; +} + + +OptionGroup { + background: $boost; + color: $text; + height: 1fr; + border-right: vkey $background; +} + +Option { + margin: 1 0 0 1; + height: 3; + padding: 1 2; + background: $boost; + border: tall $panel; + text-align: center; +} + +Option:hover { + background: $primary 20%; + color: $text; +} + +Body { + height: 100%; + overflow-y: scroll; + width: 100%; + background: $surface; + +} + +AboveFold { + width: 100%; + height: 100%; + align: center middle; +} + +Welcome { + background: $boost; + height: auto; + max-width: 100; + min-width: 40; + border: wide $primary; + padding: 1 2; + margin: 1 2; + box-sizing: border-box; +} + +Welcome Button { + width: 100%; + margin-top: 1; +} + +Column { + height: auto; + min-height: 100vh; + align: center top; +} + + +DarkSwitch { + background: $panel; + padding: 1; + dock: bottom; + height: auto; + border-right: vkey $background; +} + +DarkSwitch .label { + width: 1fr; + padding: 1 2; + color: $text-muted; +} + +DarkSwitch Switch { + background: $boost; + dock: left; +} + + +Screen > Container { + height: 100%; + overflow: hidden; +} + +TextLog { + background: $surface; + color: $text; + height: 50vh; + dock: bottom; + layer: notes; + border-top: hkey $primary; + offset-y: 0; + transition: offset 400ms in_out_cubic; + padding: 0 1 1 1; +} + + +TextLog:focus { + offset: 0 0 !important; +} + +TextLog.-hidden { + offset-y: 100%; +} + + + +Section { + height: auto; + min-width: 40; + margin: 1 2 4 2; + +} + +SectionTitle { + padding: 1 2; + background: $boost; + text-align: center; + text-style: bold; +} + +SubTitle { + padding-top: 1; + border-bottom: heavy $panel; + color: $text; + text-style: bold; +} + +TextContent { + margin: 1 0; +} + +QuickAccess { + width: 30; + dock: left; + +} + +LocationLink { + margin: 1 0 0 1; + height: 1; + padding: 1 2; + background: $boost; + color: $text; + box-sizing: content-box; + content-align: center middle; +} + +LocationLink:hover { + background: $accent; + color: $text; + text-style: bold; +} + + +.pad { + margin: 1 0; +} + +DataTable { + height: 16; +} + + +LoginForm { + height: auto; + margin: 1 0; + padding: 1 2; + layout: grid; + grid-size: 2; + grid-rows: 4; + grid-columns: 12 1fr; + background: $boost; + border: wide $background; +} + +LoginForm Button{ + margin: 0 1; + width: 100%; +} + +LoginForm .label { + padding: 1 2; + text-align: right; +} + +Message { + margin: 0 1; + +} + + +Tree { + margin: 1 0; +} - Window { - background: $boost; - overflow: auto; - height: auto; - max-height: 16; - } - - Window>Static { - width: auto; - } - - - Version { - color: $text-disabled; - dock: bottom; - text-align: center; - padding: 1; - } +Window { + background: $boost; + overflow: auto; + height: auto; + max-height: 16; +} + +Window > Static { + width: auto; +} + + +Version { + color: $text-disabled; + dock: bottom; + text-align: center; + padding: 1; +} diff --git a/src/textual/demo.py b/src/textual/demo.py index 7d6e7c29fe..c80c662396 100644 --- a/src/textual/demo.py +++ b/src/textual/demo.py @@ -15,7 +15,6 @@ from textual.app import App, ComposeResult from textual.binding import Binding from textual.containers import Container, Horizontal -from textual.message import Message, MessageTarget from textual.reactive import reactive from textual.widgets import ( Button, @@ -190,14 +189,6 @@ """ -class Note(Message): - """Sent by some demo widgets with noteworthy information.""" - - def __init__(self, sender: MessageTarget, note: str | Text) -> None: - super().__init__(sender) - self.note = note - - class Body(Container): pass @@ -214,7 +205,7 @@ def compose(self) -> ComposeResult: def on_mount(self) -> None: self.watch(self.app, "dark", self.on_dark_change, init=False) - def on_dark_change(self) -> None: + def on_dark_change(self, dark: bool) -> None: self.query_one(Switch).value = self.app.dark def on_switch_changed(self, event: Switch.Changed) -> None: @@ -227,7 +218,7 @@ def compose(self) -> ComposeResult: yield Button("Start", variant="success") def on_button_pressed(self, event: Button.Pressed) -> None: - self.post_message_no_wait(Note(self, "[b magenta]Start!")) + self.app.add_note("[b magenta]Start!") self.app.query_one(".location-first").scroll_visible(duration=0.5, top=True) @@ -239,7 +230,7 @@ class SectionTitle(Static): pass -class SidebarMessage(Static): +class Message(Static): pass @@ -251,7 +242,7 @@ def render(self) -> RenderableType: class Sidebar(Container): def compose(self) -> ComposeResult: yield Title("Textual Demo") - yield OptionGroup(SidebarMessage(MESSAGE), Version()) + yield OptionGroup(Message(MESSAGE), Version()) yield DarkSwitch() @@ -282,7 +273,7 @@ def __init__(self, label: str, reveal: str) -> None: def on_click(self) -> None: self.app.query_one(self.reveal).scroll_visible(top=True, duration=0.5) - self.post_message_no_wait(Note(self, f"Scrolling to [b]{self.reveal}[/b]")) + self.app.add_note(f"Scrolling to [b]{self.reveal}[/b]") class LoginForm(Container): @@ -324,8 +315,8 @@ class DemoApp(App): show_sidebar = reactive(False) - def on_note(self, event: Note) -> None: - self.query_one(TextLog).write(event.note) + def add_note(self, renderable: RenderableType) -> None: + self.query_one(TextLog).write(renderable) def compose(self) -> ComposeResult: example_css = "\n".join(Path(self.css_path[0]).read_text().splitlines()[:50]) @@ -402,7 +393,7 @@ def action_toggle_sidebar(self) -> None: sidebar.add_class("-hidden") def on_mount(self) -> None: - self.post_message_no_wait(Note(self, "Textual Demo app is running")) + self.add_note("Textual Demo app is running") table = self.query_one(DataTable) table.add_column("Foo", width=20) table.add_column("Bar", width=20) @@ -425,7 +416,7 @@ def action_screenshot(self, filename: str | None = None, path: str = "./") -> No self.bell() path = self.save_screenshot(filename, path) message = Text.assemble("Screenshot saved to ", (f"'{path}'", "bold green")) - self.post_message_no_wait(Note(self, message)) + self.add_note(message) self.screen.mount(Notification(message)) From fa5d6cf81ec8531c68ee3ba8355c207dc34a229d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:28:29 +0000 Subject: [PATCH 66/68] Address review comments. Related comments: https://github.com/Textualize/textual/pull/1831\#discussion_r1118155296 --- src/textual/_compositor.py | 2 +- src/textual/css/styles.py | 2 +- src/textual/demo.css | 8 ++++---- src/textual/demo.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 5a87777a73..79080f59f5 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -834,7 +834,7 @@ def render(self, full: bool = False) -> RenderableType | None: # Maps each cut on to a list of segments cuts = self.cuts - # dict.fromkeys is a callable which takes a list of ints returns a dict which maps ints onto a list of Segments or None. + # dict.fromkeys is a callable which takes a list of ints returns a dict which maps ints onto a Segment or None. fromkeys = cast("Callable[[list[int]], dict[int, Strip | None]]", dict.fromkeys) chops: list[dict[int, Strip | None]] chops = [fromkeys(cut_set[:-1]) for cut_set in cuts] diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 5d0593afd7..eab27ee39a 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -584,7 +584,7 @@ def partial_rich_style(self) -> Style: @dataclass class Styles(StylesBase): node: DOMNode | None = None - _rules: RulesMap = field(default_factory=RulesMap.__call__) + _rules: RulesMap = field(default_factory=lambda: RulesMap()) _updates: int = 0 important: set[str] = field(default_factory=set) diff --git a/src/textual/demo.css b/src/textual/demo.css index 3fb8c7d719..9941d8ba51 100644 --- a/src/textual/demo.css +++ b/src/textual/demo.css @@ -1,4 +1,4 @@ - * { +* { transition: background 500ms in_out_cubic, color 500ms in_out_cubic; } @@ -125,7 +125,7 @@ DarkSwitch Switch { } -Screen > Container { +Screen>Container { height: 100%; overflow: hidden; } @@ -222,7 +222,7 @@ LoginForm { border: wide $background; } -LoginForm Button{ +LoginForm Button { margin: 0 1; width: 100%; } @@ -250,7 +250,7 @@ Window { max-height: 16; } -Window > Static { +Window>Static { width: auto; } diff --git a/src/textual/demo.py b/src/textual/demo.py index c80c662396..4fa0f32d60 100644 --- a/src/textual/demo.py +++ b/src/textual/demo.py @@ -205,7 +205,7 @@ def compose(self) -> ComposeResult: def on_mount(self) -> None: self.watch(self.app, "dark", self.on_dark_change, init=False) - def on_dark_change(self, dark: bool) -> None: + def on_dark_change(self) -> None: self.query_one(Switch).value = self.app.dark def on_switch_changed(self, event: Switch.Changed) -> None: @@ -302,7 +302,7 @@ def on_click(self) -> None: self.remove() -class DemoApp(App): +class DemoApp(App[None]): CSS_PATH = "demo.css" TITLE = "Textual Demo" BINDINGS = [ From 865ef1434d0a0bf4e88af176a2d80656e3ea7fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:30:32 +0000 Subject: [PATCH 67/68] Add clarifying comment. See: https://github.com/Textualize/textual/pull/1831\#discussion_r1118154777 --- src/textual/css/styles.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index eab27ee39a..bad4bccd60 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -584,7 +584,9 @@ def partial_rich_style(self) -> Style: @dataclass class Styles(StylesBase): node: DOMNode | None = None - _rules: RulesMap = field(default_factory=lambda: RulesMap()) + _rules: RulesMap = field( + default_factory=lambda: RulesMap() + ) # mypy won't be happy with `default_factory=RulesMap` _updates: int = 0 important: set[str] = field(default_factory=set) From db3372d3033281132dbde1745868ce959e535bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 1 Mar 2023 15:08:04 +0000 Subject: [PATCH 68/68] Revert changes to data table. --- src/textual/_two_way_dict.py | 4 +- src/textual/widgets/_data_table.py | 135 +++++++---------------------- 2 files changed, 32 insertions(+), 107 deletions(-) diff --git a/src/textual/_two_way_dict.py b/src/textual/_two_way_dict.py index 910ff1b56b..d733edcdcc 100644 --- a/src/textual/_two_way_dict.py +++ b/src/textual/_two_way_dict.py @@ -29,7 +29,7 @@ def __delitem__(self, key: Key) -> None: self._forward.__delitem__(key) self._reverse.__delitem__(value) - def get(self, key: Key) -> Value | None: + def get(self, key: Key) -> Value: """Given a key, efficiently lookup and return the associated value. Args: @@ -40,7 +40,7 @@ def get(self, key: Key) -> Value | None: """ return self._forward.get(key) - def get_key(self, value: Value) -> Key | None: + def get_key(self, value: Value) -> Key: """Given a value, efficiently lookup and return the associated key. Args: diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index ccd3766e6c..3ae85ca767 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -66,37 +66,18 @@ class DuplicateKey(Exception): an existing row or column in the DataTable. Keys must be unique.""" -StringKeySubclass = TypeVar("StringKeySubclass", bound="StringKey") - - @functools.total_ordering class StringKey: """An object used as a key in a mapping. It can optionally wrap a string, and lookups into a map using the object behave the same as lookups using - the string itself. - - This class and subclasses are idempotent, which means that if a value - `s` is `str | StringKey`, then `StringKey(s)` will safely convert it to - a `StringKey` instance if `s` is a string and it will leave `s` unchanged - if it was already of the correct type. - """ + the string itself.""" value: str | None - def __new__( - cls: type[StringKeySubclass], value: StringKey | str | None = None - ) -> StringKeySubclass: - """Creates a new `StringKey` object if necessary.""" - if isinstance(value, cls): - return value - - string_key = super().__new__(cls) - if isinstance(value, StringKey): # This should never be true. - value = value.value - string_key.value = value - return string_key + def __init__(self, value: str | None = None): + self.value = value def __hash__(self): # If a string is supplied, we use the hash of the string. If no string was @@ -159,53 +140,6 @@ def __rich_repr__(self): yield "column_key", self.column_key -class KeysToIndices(TwoWayDict[StringKeySubclass, int]): - """Bidirectional dictionary to turn StringKey keys into their indices.""" - - _exception: type[Exception] - """The exception to raise when access in either direction fails.""" - - def __init__( - self, initial: dict[StringKeySubclass, int], exception: type[Exception] - ) -> None: - super().__init__(initial) - self._exception = exception - - def get(self, key: StringKeySubclass) -> int: - """Given a key, efficiently lookup and return the associated index. - - This will raise the exception passed in at instantiation time if the key - is not valid. - - Args: - key: The key. - - Returns: - The corresponding index. - """ - index = super().get(key) - if index is None: - raise self._exception(f"Key {key!r} is not valid.") - return index - - def get_key(self, value: int) -> StringKeySubclass: - """Given a index, efficiently lookup and return the associated key. - - This will raise the exception passed in at instantiation time if the idnex - is not valid. - - Args: - value: The index. - - Returns: - The corresponding key. - """ - key = super().get_key(value) - if key is None: - raise self._exception(f"Index {key!r} is not valid.") - return key - - def default_cell_formatter(obj: object) -> RenderableType: """Convert a cell into a Rich renderable for display. @@ -366,7 +300,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): zebra_stripes = Reactive(False) header_height = Reactive(1) show_cursor = Reactive(True) - cursor_type = Reactive[CursorType]("cell") + cursor_type = Reactive("cell") cursor_coordinate: Reactive[Coordinate] = Reactive( Coordinate(0, 0), repaint=False, always_update=True @@ -600,11 +534,9 @@ def __init__( # given a row or column key, the index that row or column is currently # present at, and mean that rows and columns are location independent - they # can move around without requiring us to modify the underlying data. - self._row_locations: KeysToIndices[RowKey] = KeysToIndices({}, RowDoesNotExist) + self._row_locations: TwoWayDict[RowKey, int] = TwoWayDict({}) """Maps row keys to row indices which represent row order.""" - self._column_locations: KeysToIndices[ColumnKey] = KeysToIndices( - {}, ColumnDoesNotExist - ) + self._column_locations: TwoWayDict[ColumnKey, int] = TwoWayDict({}) """Maps column keys to column indices which represent column order.""" self._row_render_cache: LRUCache[ @@ -806,7 +738,6 @@ def get_row(self, row_key: RowKey | str) -> list[CellType]: Raises: RowDoesNotExist: When there is no row corresponding to the key. """ - row_key = RowKey(row_key) if row_key not in self._row_locations: raise RowDoesNotExist(f"Row key {row_key!r} is not valid.") cell_mapping: dict[ColumnKey, CellType] = self._data.get(row_key, {}) @@ -846,7 +777,6 @@ def get_column(self, column_key: ColumnKey | str) -> Iterable[CellType]: Raises: ColumnDoesNotExist: If there is no column corresponding to the key. """ - column_key = ColumnKey(column_key) if column_key not in self._column_locations: raise ColumnDoesNotExist(f"Column key {column_key!r} is not valid.") @@ -1179,10 +1109,10 @@ def clear(self, columns: bool = False) -> None: self._y_offsets.clear() self._data.clear() self.rows.clear() - self._row_locations = KeysToIndices({}, RowDoesNotExist) + self._row_locations = TwoWayDict({}) if columns: self.columns.clear() - self._column_locations = KeysToIndices({}, ColumnDoesNotExist) + self._column_locations = TwoWayDict({}) self._require_update_dimensions = True self.cursor_coordinate = Coordinate(0, 0) self.hover_coordinate = Coordinate(0, 0) @@ -1429,10 +1359,11 @@ def is_valid_coordinate(self, coordinate: Coordinate) -> bool: @property def ordered_columns(self) -> list[Column]: """The list of Columns in the DataTable, ordered as they appear on screen.""" - ordered_columns = [ - self.columns[self._column_locations.get_key(index)] - for index in range(len(self.columns)) + column_indices = range(len(self.columns)) + column_keys = [ + self._column_locations.get_key(index) for index in column_indices ] + ordered_columns = [self.columns[key] for key in column_keys] return ordered_columns @property @@ -1441,14 +1372,15 @@ def ordered_rows(self) -> list[Row]: num_rows = self.row_count update_count = self._update_count cache_key = (num_rows, update_count) - ordered_rows: list[Row] if cache_key in self._ordered_row_cache: ordered_rows = self._ordered_row_cache[cache_key] else: - ordered_rows = [ - self.rows[self._row_locations.get_key(row_index)] - for row_index in range(num_rows) - ] + row_indices = range(num_rows) + ordered_rows = [] + for row_index in row_indices: + row_key = self._row_locations.get_key(row_index) + row = self.rows[row_key] + ordered_rows.append(row) self._ordered_row_cache[cache_key] = ordered_rows return ordered_rows @@ -1547,14 +1479,8 @@ def _render_cell( row_key = self._row_locations.get_key(row_index) column_key = self._column_locations.get_key(column_index) - cell_cache_key: CellCacheKey = ( - row_key, - column_key, - style, - cursor, - hover, - self._update_count, - ) + cell_cache_key = (row_key, column_key, style, cursor, hover, self._update_count) + if cell_cache_key not in self._cell_render_cache: style += Style.from_meta({"row": row_index, "column": column_index}) height = self.header_height if is_header_cell else self.rows[row_key].height @@ -1640,7 +1566,6 @@ def _should_highlight( if row_key in self._row_locations: row_index = self._row_locations.get(row_key) - assert row_index is not None else: row_index = -1 @@ -1694,7 +1619,7 @@ def _should_highlight( else: row_style = base_style - scrollable_row: list[list[Segment]] = [] + scrollable_row = [] for column_index, column in enumerate(self.ordered_columns): cell_location = Coordinate(row_index, column_index) cell_lines = render_cell( @@ -1752,7 +1677,7 @@ def _render_line(self, y: int, x1: int, x2: int, base_style: Style) -> Strip: except LookupError: return Strip.blank(width, base_style) - cache_key: LineCacheKey = ( + cache_key = ( y, x1, x2, @@ -1788,10 +1713,10 @@ def _render_line(self, y: int, x1: int, x2: int, base_style: Style) -> Strip: return strip def render_line(self, y: int) -> Strip: - width, _ = self.size + width, height = self.size scroll_x, scroll_y = self.scroll_offset - fixed_row_keys = [ + fixed_row_keys: list[RowKey] = [ self._row_locations.get_key(row_index) for row_index in range(self.fixed_rows) ] @@ -1845,18 +1770,18 @@ def sort( reverse: If True, the sort order will be reversed. """ - def sort_by_column_keys(row: tuple[RowKey, dict[ColumnKey, CellType]]) -> Any: + def sort_by_column_keys( + row: tuple[RowKey, dict[ColumnKey | str, CellType]] + ) -> Any: _, row_data = row - result = itemgetter(*column_keys)(row_data) + result = itemgetter(*columns)(row_data) return result - column_keys = [ColumnKey(column) for column in columns] ordered_rows = sorted( self._data.items(), key=sort_by_column_keys, reverse=reverse ) - self._row_locations = KeysToIndices( - {key: new_index for new_index, (key, _) in enumerate(ordered_rows)}, - RowDoesNotExist, + self._row_locations = TwoWayDict( + {key: new_index for new_index, (key, _) in enumerate(ordered_rows)} ) self._update_count += 1 self.refresh()